polytope_playground/
color.rs1use loam::runtime::{EdgeShading, PaletteId};
2use loam::shape::polytope::{vertex_color_by_position, Polytope4Topology};
3
4pub(crate) const W_DEPTH_BACK: [f32; 4] = [0.30, 0.42, 0.58, 1.0];
6
7pub(crate) const W_DEPTH_FRONT: [f32; 4] = [1.00, 0.78, 0.45, 1.0];
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub(crate) enum ColorMode {
11 #[default]
12 VertexGradient,
13 UniqueEdge,
14 WDepth,
15}
16
17impl ColorMode {
18 pub(crate) const ALL: [ColorMode; 3] = [
19 ColorMode::VertexGradient,
20 ColorMode::UniqueEdge,
21 ColorMode::WDepth,
22 ];
23
24 pub(crate) fn name(self) -> &'static str {
25 match self {
26 ColorMode::VertexGradient => "vertex",
27 ColorMode::UniqueEdge => "edge",
28 ColorMode::WDepth => "w-depth",
29 }
30 }
31
32 pub(crate) fn from_token(token: &str) -> Option<Self> {
33 ColorMode::ALL.into_iter().find(|mode| mode.name() == token)
34 }
35}
36
37#[derive(Clone, Copy)]
38pub(crate) struct Shades {
39 pub(crate) gradient: PaletteId,
40 pub(crate) unique: PaletteId,
41 pub(crate) extent: f32,
42}
43
44impl Shades {
45 pub(crate) fn of(self, mode: ColorMode) -> EdgeShading {
46 match mode {
47 ColorMode::VertexGradient => EdgeShading::Palette(self.gradient),
48 ColorMode::UniqueEdge => EdgeShading::Palette(self.unique),
49 ColorMode::WDepth => EdgeShading::Depth {
50 back: W_DEPTH_BACK,
51 front: W_DEPTH_FRONT,
52 extent: self.extent,
53 },
54 }
55 }
56}
57
58fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> [f32; 3] {
59 let sixth = hue.fract() * 6.0;
60 let chroma = value * saturation;
61 let second = chroma * (1.0 - (sixth % 2.0 - 1.0).abs());
62 let base = value - chroma;
63 let (r, g, b) = match sixth.floor() as i32 % 6 {
64 0 => (chroma, second, 0.0),
65 1 => (second, chroma, 0.0),
66 2 => (0.0, chroma, second),
67 3 => (0.0, second, chroma),
68 4 => (second, 0.0, chroma),
69 _ => (chroma, 0.0, second),
70 };
71 [r + base, g + base, b + base]
72}
73
74fn palette_color(index: usize) -> [f32; 4] {
75 const PHI_INV: f32 = 0.618_034;
76 let hue = ((index as f32) * PHI_INV).fract();
77 let saturation = 0.78 + 0.18 * ((index % 3) as f32 / 2.0);
78 let value = 0.92 - 0.18 * (((index / 3) % 2) as f32);
79 let [r, g, b] = hsv_to_rgb(hue, saturation, value);
80 [r, g, b, 1.0]
81}
82
83pub(crate) fn unique_edge_colors(edges: &[[u32; 2]]) -> Vec<[f32; 4]> {
84 let mut chosen = Vec::with_capacity(edges.len());
85 let mut used = vec![usize::MAX; edges.len()];
86 for (index, &[a, b]) in edges.iter().enumerate() {
87 for (other, &[c, d]) in edges[..index].iter().enumerate() {
88 if a == c || a == d || b == c || b == d {
89 used[chosen[other]] = index;
90 }
91 }
92 let color = used
93 .iter()
94 .position(|generation| *generation != index)
95 .unwrap_or(index);
96 chosen.push(color);
97 }
98 chosen
99 .into_iter()
100 .flat_map(|index| [palette_color(index); 2])
101 .collect()
102}
103
104pub(crate) fn vertex_gradient_colors(topology: &Polytope4Topology) -> Vec<[f32; 4]> {
105 topology
106 .edges
107 .iter()
108 .flat_map(|&[a, b]| {
109 [
110 vertex_color_by_position(topology.vertices[a as usize]),
111 vertex_color_by_position(topology.vertices[b as usize]),
112 ]
113 })
114 .collect()
115}
116
117pub(crate) fn w_extent(topology: &Polytope4Topology, scale: f32) -> f32 {
118 topology
119 .vertices
120 .iter()
121 .map(|vertex| vertex.w.abs())
122 .fold(0.0_f32, f32::max)
123 .max(1e-6)
124 * scale
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn the_greedy_palette_separates_edges_that_share_a_vertex() {
133 let edges: &[[u32; 2]] = &[[0, 1], [0, 2], [0, 3]];
134 let colors = unique_edge_colors(edges);
135 assert_eq!(colors.len(), 6, "one color per endpoint");
136 for edge in 0..3 {
137 assert_eq!(
138 colors[edge * 2],
139 colors[edge * 2 + 1],
140 "an edge is one color"
141 );
142 for other in (edge + 1)..3 {
143 assert_ne!(
144 colors[edge * 2],
145 colors[other * 2],
146 "edges {edge} and {other} share vertex 0 and got one color"
147 );
148 }
149 }
150 }
151}