1use crate::column::Column;
2
3pub const UV_CHANNEL: &str = "uv";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ChannelKind {
15 Source,
17 Derived,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum TopologyPolicy {
25 Interpolate,
27 Default,
29 Nearest,
31}
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct ChannelDesc {
36 pub name: String,
37 pub width: usize,
39 pub kind: ChannelKind,
40 pub policy: TopologyPolicy,
41 pub default: f32,
43 pub topology_bound: bool,
52}
53
54impl ChannelDesc {
55 pub fn source(name: impl Into<String>, width: usize) -> Self {
57 Self {
58 name: name.into(),
59 width,
60 kind: ChannelKind::Source,
61 policy: TopologyPolicy::Interpolate,
62 default: 0.0,
63 topology_bound: false,
64 }
65 }
66
67 pub fn derived(name: impl Into<String>, width: usize) -> Self {
69 Self {
70 name: name.into(),
71 width,
72 kind: ChannelKind::Derived,
73 policy: TopologyPolicy::Default,
74 default: f32::NAN,
75 topology_bound: false,
76 }
77 }
78
79 pub fn uv() -> Self {
81 Self::source(UV_CHANNEL, 2)
82 }
83
84 pub fn bound_to_topology(mut self) -> Self {
86 self.topology_bound = true;
87 self
88 }
89
90 pub fn with_policy(mut self, policy: TopologyPolicy) -> Self {
91 self.policy = policy;
92 self
93 }
94
95 pub fn with_default(mut self, default: f32) -> Self {
96 self.default = default;
97 self
98 }
99}
100
101pub(crate) struct Channel {
102 pub(crate) id: u64,
105 pub(crate) desc: ChannelDesc,
106 pub(crate) data: Column<f32>,
108 pub(crate) hidden: bool,
112}
113
114impl Channel {
115 pub(crate) fn initial_value(&self, parents: &[(u32, f32)], k: usize) -> f32 {
117 let d = &self.desc;
118 if d.kind == ChannelKind::Derived || parents.is_empty() {
119 return d.default;
120 }
121 let at = |v: u32| self.data.get(v as usize * d.width + k);
122 match d.policy {
123 TopologyPolicy::Default => d.default,
124 TopologyPolicy::Nearest => {
125 let (v, _) = parents
126 .iter()
127 .copied()
128 .max_by(|a, b| a.1.total_cmp(&b.1))
129 .expect("parents is not empty");
130 at(v)
131 }
132 TopologyPolicy::Interpolate => {
133 let total: f32 = parents.iter().map(|p| p.1).sum();
134 if total == 0.0 {
135 d.default
136 } else {
137 parents.iter().map(|&(v, w)| at(v) * w).sum::<f32>() / total
138 }
139 }
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn channel(desc: ChannelDesc, values: &[f32]) -> Channel {
149 Channel {
150 id: 0,
151 desc,
152 data: Column::from_vec(values.to_vec()),
153 hidden: false,
154 }
155 }
156
157 #[test]
158 fn interpolate_uses_normalized_weights() {
159 let c = channel(ChannelDesc::source("m", 1), &[10.0, 20.0]);
160 assert_eq!(c.initial_value(&[(0, 1.0), (1, 3.0)], 0), 17.5);
161 }
162
163 #[test]
164 fn nearest_takes_the_heaviest_parent() {
165 let c = channel(
166 ChannelDesc::source("m", 1).with_policy(TopologyPolicy::Nearest),
167 &[10.0, 20.0],
168 );
169 assert_eq!(c.initial_value(&[(0, 0.9), (1, 0.1)], 0), 10.0);
170 }
171
172 #[test]
173 fn no_parents_or_zero_weights_use_the_default() {
174 let c = channel(ChannelDesc::source("m", 1).with_default(-1.0), &[10.0]);
175 assert_eq!(c.initial_value(&[], 0), -1.0);
176 assert_eq!(c.initial_value(&[(0, 0.0)], 0), -1.0);
177 }
178
179 #[test]
180 fn derived_channels_always_start_at_the_default() {
181 let c = channel(ChannelDesc::derived("d", 1), &[10.0]);
182 assert!(c.initial_value(&[(0, 1.0)], 0).is_nan());
183 }
184
185 #[test]
186 fn components_are_read_per_width() {
187 let c = channel(
188 ChannelDesc::source("rgb", 3),
189 &[1.0, 2.0, 3.0, 5.0, 6.0, 7.0],
190 );
191 let parents = [(0, 1.0), (1, 1.0)];
192 assert_eq!(
193 [0, 1, 2].map(|k| c.initial_value(&parents, k)),
194 [3.0, 4.0, 5.0]
195 );
196 }
197}