Skip to main content

fast_mesh_store/
channel.rs

1use crate::column::Column;
2
3/// Name of the UV channel: a source channel of 2 values per vertex slot, in
4/// the file's own convention (glTF's has `v` pointing down), interpolated for
5/// new vertices. Read it with [`crate::Mesh::uvs`].
6///
7/// A mesh without it pays nothing. The name lives in the channel namespace;
8/// the plane `(u, v)` coordinates of a section are plain result fields and do
9/// not meet it.
10pub const UV_CHANNEL: &str = "uv";
11
12/// Whether a channel's values are undoable input or recomputable output.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ChannelKind {
15    /// Values provided by the user (e.g. a painted mask). Journaled and undoable.
16    Source,
17    /// Values computed from other data (e.g. distances, colors). Not journaled;
18    /// recomputed after edits, undo and redo.
19    Derived,
20}
21
22/// How a source channel gets values for vertices created by an edit.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum TopologyPolicy {
25    /// Weighted average of the creation parents' values.
26    Interpolate,
27    /// The channel's default value.
28    Default,
29    /// The value of the creation parent with the largest weight.
30    Nearest,
31}
32
33/// Describes a per-vertex attribute channel.
34#[derive(Debug, Clone, PartialEq)]
35pub struct ChannelDesc {
36    pub name: String,
37    /// Values per vertex, 1 to 4.
38    pub width: usize,
39    pub kind: ChannelKind,
40    pub policy: TopologyPolicy,
41    /// Value for new vertices without usable parents, and for derived channels.
42    pub default: f32,
43    /// The values describe the mesh as it was when they were written, so they
44    /// stop being true when it changes in ways they cannot follow. A commit that
45    /// changes topology drops the channel, as
46    /// [`Transaction::drop_channel`](crate::Transaction::drop_channel) would (its
47    /// undo brings it back), unless the writes ran under
48    /// [`Transaction::keeping_bound_channels`](crate::Transaction::keeping_bound_channels).
49    /// Undoing or redoing a patch recorded before the channel was added removes
50    /// the channel for good: that takes the mesh to a state it never described.
51    pub topology_bound: bool,
52}
53
54impl ChannelDesc {
55    /// A source channel that interpolates values for new vertices and defaults to 0.
56    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    /// A derived channel whose new values are NaN until computed.
68    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    /// The UV channel: [`UV_CHANNEL`], a source channel of 2 interpolated values.
80    pub fn uv() -> Self {
81        Self::source(UV_CHANNEL, 2)
82    }
83
84    /// Makes the channel [`topology_bound`](Self::topology_bound).
85    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    /// Unique within its mesh, never reused. Patches refer to channels by this id, so
103    /// a channel removed and re-added under the same name is a different channel.
104    pub(crate) id: u64,
105    pub(crate) desc: ChannelDesc,
106    /// `width` values per vertex slot.
107    pub(crate) data: Column<f32>,
108    /// Dropped by a committed edit whose patch can still bring it back: name
109    /// lookups skip it, while its column keeps following the slots so that
110    /// undo restores it exactly.
111    pub(crate) hidden: bool,
112}
113
114impl Channel {
115    /// Value for a new vertex created from `parents` (vertex id, weight) for component `k`.
116    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}