concinnity_core/render/transparent.rs
1//! Backend-agnostic helpers for the transparent (translucent) pass. The pass
2//! itself is encoded per backend; this module owns only the CPU-side ordering
3//! policy so it can be unit-tested without a GPU and reused as the Vulkan /
4//! DirectX transparent ports land.
5//!
6//! Transparent fragments use SRC_ALPHA / ONE_MINUS_SRC_ALPHA blending, which is
7//! order-dependent: a draw must be composited after everything behind it.
8//! `back_to_front_order` returns the draw indices sorted farthest-first by
9//! camera distance so the blend resolves correctly. This is a single fixed
10//! sorted draw list, not order-independent transparency.
11
12use alloc::vec::Vec;
13
14/// Return the indices `0..distances.len()` ordered farthest camera distance
15/// first (back-to-front). The sort is stable, so draws at equal distance keep
16/// their original (declaration) order. Non-finite distances (NaN) are treated
17/// as nearest so a degenerate value never pushes a draw behind valid geometry.
18pub fn back_to_front_order(distances: &[f32]) -> Vec<usize> {
19 let mut order: Vec<usize> = (0..distances.len()).collect();
20 order.sort_by(|&a, &b| {
21 // Farther (larger distance) sorts first. Map NaN to -inf so it lands
22 // last (nearest), keeping a total order for `sort_by`.
23 let da = if distances[a].is_finite() {
24 distances[a]
25 } else {
26 f32::NEG_INFINITY
27 };
28 let db = if distances[b].is_finite() {
29 distances[b]
30 } else {
31 f32::NEG_INFINITY
32 };
33 db.partial_cmp(&da).unwrap_or(core::cmp::Ordering::Equal)
34 });
35 order
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 use alloc::vec;
43 #[test]
44 fn orders_farthest_first() {
45 let d = [1.0, 5.0, 3.0];
46 assert_eq!(back_to_front_order(&d), vec![1, 2, 0]);
47 }
48
49 #[test]
50 fn empty_is_empty() {
51 assert!(back_to_front_order(&[]).is_empty());
52 }
53
54 #[test]
55 fn equal_distances_keep_declaration_order() {
56 let d = [2.0, 2.0, 2.0];
57 assert_eq!(back_to_front_order(&d), vec![0, 1, 2]);
58 }
59
60 #[test]
61 fn nan_sorts_last() {
62 let d = [4.0, f32::NAN, 2.0];
63 // 4.0 (farthest) → index 0, then 2.0 → index 2, then NaN → index 1.
64 assert_eq!(back_to_front_order(&d), vec![0, 2, 1]);
65 }
66}