graphic design tool
git clone https://git.lucas.co/cce-designer.git
src/detail.rs (64.9K)
1 //! The geometry container: points, vertices, primitives and detail.
2 //!
3 //! This is the Phase 0 replacement for `geometry::Geometry`, the triangle soup
4 //! (`Vec<GVertex>`, attributes stored per triangle corner). See
5 //! `shapeshifter.md` for why: every attribute operator worth having is a
6 //! statement about a point *and its neighbours*, and a soup has no points, no
7 //! edges, and no identity that survives a frame.
8 //!
9 //! Four element classes, exactly Houdini's:
10 //!
11 //! - **Points** carry position and the attributes that describe a place on the
12 //! surface. A point is shared by every primitive that uses it — moving one
13 //! moves them all, which is the whole difference from a soup.
14 //! - **Vertices** are a primitive's references to points, in winding order. One
15 //! per corner. They exist so a point can carry different per-corner data (a
16 //! UV seam, a hard normal) without splitting the point itself.
17 //! - **Primitives** are runs of vertices. Polygons of any size, not just
18 //! triangles; triangulation is a render concern (see [`Detail::triangulate`]).
19 //! - **Detail** is the single-element class: one row holding whole-geometry
20 //! values, which is where `Analysis` writes a range rather than inventing a
21 //! dictionary type.
22 //!
23 //! Three properties the soup could not have, all load-bearing for later phases:
24 //!
25 //! **Columnar attributes.** One array per named attribute, not a `HashMap` per
26 //! element. This is the layout a GPU buffer already wants, so the Phase 1 kernel
27 //! ABI binds a slice instead of marshalling a million little maps.
28 //!
29 //! **Stable point ids.** [`PointId`] is allocated once per point and preserved by
30 //! every operator that does not create points. A solver needs to know that the
31 //! point it is looking at is the one it wrote to last step; a positional weld
32 //! cannot answer that, because points move.
33 //!
34 //! **Real groups.** A named membership set per class, not the `group:<name>`
35 //! key convention the soup used in its attribute map.
36 //!
37 //! Topology (point→prim, point→point, the edge list) is *derived*, built lazily
38 //! on first ask and dropped on any structural edit — so a chain of ten attribute
39 //! nodes builds it once rather than welding from scratch in every node, which is
40 //! what `resolve_relax_geometry_with_errors` has to do today.
41
42 use glam::Vec3;
43 use std::collections::HashMap;
44 use std::sync::OnceLock;
45
46 /// A point's identity, stable across the operators that preserve points and
47 /// across simulation steps. Allocated by [`Detail::add_point`]; never reused
48 /// within one `Detail`.
49 pub type PointId = u64;
50
51 /// The conventional color attribute. Read by [`Detail::triangulate`] when
52 /// present; geometry without it renders at [`DEFAULT_COLOR`].
53 pub const CD: &str = "Cd";
54
55 /// Point attributes under this prefix are MARKER REQUESTS, not data: the
56 /// Visualize node copies a vector attribute into `vis_<name>`, already scaled,
57 /// and the viewport draws a segment from each point along it.
58 ///
59 /// A naming convention rather than a side-channel on the container, so the
60 /// request travels with the geometry through every operator that already knows
61 /// how to carry an attribute, and shows up in the spreadsheet where you can
62 /// see what is being drawn and why.
63 pub const VIS_PREFIX: &str = "vis_";
64
65 /// What a point renders as when it carries no `Cd`.
66 pub const DEFAULT_COLOR: [f32; 3] = [0.8, 0.8, 0.8];
67
68 /// Which element class an attribute or group belongs to.
69 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
70 pub enum Class {
71 Point,
72 Vertex,
73 Prim,
74 Detail,
75 }
76
77 impl Class {
78 pub fn name(self) -> &'static str {
79 match self {
80 Class::Point => "point",
81 Class::Vertex => "vertex",
82 Class::Prim => "prim",
83 Class::Detail => "detail",
84 }
85 }
86 }
87
88 /// An attribute's element type. Integers are here because the Developer set
89 /// needs counters and ages that stay whole — `Vitality` writes `_age0..2`, and
90 /// rounding a float age is how off-by-one frames happen.
91 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
92 pub enum AttribType {
93 Float,
94 Float2,
95 Float3,
96 Float4,
97 Int,
98 }
99
100 impl AttribType {
101 /// Component count. `Int` is one component, like `Float`.
102 pub fn components(self) -> usize {
103 match self {
104 AttribType::Float | AttribType::Int => 1,
105 AttribType::Float2 => 2,
106 AttribType::Float3 => 3,
107 AttribType::Float4 => 4,
108 }
109 }
110
111 pub fn name(self) -> &'static str {
112 match self {
113 AttribType::Float => "float",
114 AttribType::Float2 => "float2",
115 AttribType::Float3 => "float3",
116 AttribType::Float4 => "float4",
117 AttribType::Int => "int",
118 }
119 }
120 }
121
122 /// One element's value, for the get/set paths that do not care about layout.
123 #[derive(Clone, Copy, PartialEq, Debug)]
124 pub enum AttribValue {
125 Float(f32),
126 Float2([f32; 2]),
127 Float3([f32; 3]),
128 Float4([f32; 4]),
129 Int(i32),
130 }
131
132 impl AttribValue {
133 pub fn ty(self) -> AttribType {
134 match self {
135 AttribValue::Float(_) => AttribType::Float,
136 AttribValue::Float2(_) => AttribType::Float2,
137 AttribValue::Float3(_) => AttribType::Float3,
138 AttribValue::Float4(_) => AttribType::Float4,
139 AttribValue::Int(_) => AttribType::Int,
140 }
141 }
142
143 /// The value as a float, for the readers that treat every scalar alike.
144 /// Wider types yield their first component.
145 pub fn as_f32(self) -> f32 {
146 match self {
147 AttribValue::Float(v) => v,
148 AttribValue::Float2(v) => v[0],
149 AttribValue::Float3(v) => v[0],
150 AttribValue::Float4(v) => v[0],
151 AttribValue::Int(v) => v as f32,
152 }
153 }
154
155 /// The value as a vector, for the readers that treat every vector alike.
156 /// Scalars broadcast across all three components, which is what a scalar
157 /// used as a multiplier means.
158 pub fn as_vec3(self) -> Vec3 {
159 match self {
160 AttribValue::Float(v) => Vec3::splat(v),
161 AttribValue::Float2(v) => Vec3::new(v[0], v[1], 0.0),
162 AttribValue::Float3(v) => Vec3::from(v),
163 AttribValue::Float4(v) => Vec3::new(v[0], v[1], v[2]),
164 AttribValue::Int(v) => Vec3::splat(v as f32),
165 }
166 }
167 }
168
169 /// Whether an attribute survives a simulation step.
170 ///
171 /// The distinction `developer.md` draws between **live data**, which "runs
172 /// like a stream through the simulation", and **derivative data**, "calculated
173 /// anew every frame based on live data". Houdini has no such notion — there,
174 /// every attribute simply persists, and a chain that forgets to reset its
175 /// scratch values accumulates them silently until the sim goes wrong in a way
176 /// that looks like a physics bug.
177 ///
178 /// Making it a property of the ATTRIBUTE rather than a list of names on the
179 /// solver means the node that creates a value declares its nature at the point
180 /// of creation, where the author knows the answer, instead of somewhere else
181 /// that has to be kept in step.
182 ///
183 /// [`AttribKind::Live`] is the default, because it is the one whose failure
184 /// mode is visible: a value that should have been cleared and was not shows up
185 /// as a drift you can watch, where a value that should have persisted and was
186 /// cleared just quietly reads zero.
187 #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
188 pub enum AttribKind {
189 /// Carried across the step boundary, by point identity where the geometry
190 /// was rebuilt underneath it.
191 #[default]
192 Live,
193 /// Zeroed at the start of every step; the chain is expected to rebuild it.
194 Derivative,
195 }
196
197 /// One attribute's storage: a single array covering every element of the
198 /// owning class, in element order.
199 #[derive(Clone, Debug, PartialEq)]
200 pub enum AttribData {
201 Float(Vec<f32>),
202 Float2(Vec<[f32; 2]>),
203 Float3(Vec<[f32; 3]>),
204 Float4(Vec<[f32; 4]>),
205 Int(Vec<i32>),
206 }
207
208 impl AttribData {
209 /// An array of `len` elements, every one the type's zero.
210 pub fn zeroed(ty: AttribType, len: usize) -> Self {
211 match ty {
212 AttribType::Float => AttribData::Float(vec![0.0; len]),
213 AttribType::Float2 => AttribData::Float2(vec![[0.0; 2]; len]),
214 AttribType::Float3 => AttribData::Float3(vec![[0.0; 3]; len]),
215 AttribType::Float4 => AttribData::Float4(vec![[0.0; 4]; len]),
216 AttribType::Int => AttribData::Int(vec![0; len]),
217 }
218 }
219
220 /// An array of `len` elements, every one `value`.
221 pub fn filled(value: AttribValue, len: usize) -> Self {
222 match value {
223 AttribValue::Float(v) => AttribData::Float(vec![v; len]),
224 AttribValue::Float2(v) => AttribData::Float2(vec![v; len]),
225 AttribValue::Float3(v) => AttribData::Float3(vec![v; len]),
226 AttribValue::Float4(v) => AttribData::Float4(vec![v; len]),
227 AttribValue::Int(v) => AttribData::Int(vec![v; len]),
228 }
229 }
230
231 pub fn ty(&self) -> AttribType {
232 match self {
233 AttribData::Float(_) => AttribType::Float,
234 AttribData::Float2(_) => AttribType::Float2,
235 AttribData::Float3(_) => AttribType::Float3,
236 AttribData::Float4(_) => AttribType::Float4,
237 AttribData::Int(_) => AttribType::Int,
238 }
239 }
240
241 pub fn len(&self) -> usize {
242 match self {
243 AttribData::Float(v) => v.len(),
244 AttribData::Float2(v) => v.len(),
245 AttribData::Float3(v) => v.len(),
246 AttribData::Float4(v) => v.len(),
247 AttribData::Int(v) => v.len(),
248 }
249 }
250
251 pub fn is_empty(&self) -> bool {
252 self.len() == 0
253 }
254
255 pub fn get(&self, i: usize) -> Option<AttribValue> {
256 match self {
257 AttribData::Float(v) => v.get(i).copied().map(AttribValue::Float),
258 AttribData::Float2(v) => v.get(i).copied().map(AttribValue::Float2),
259 AttribData::Float3(v) => v.get(i).copied().map(AttribValue::Float3),
260 AttribData::Float4(v) => v.get(i).copied().map(AttribValue::Float4),
261 AttribData::Int(v) => v.get(i).copied().map(AttribValue::Int),
262 }
263 }
264
265 /// Write one element. The value's type must match the array's — a caller
266 /// wanting to change an attribute's type replaces the whole array, so that
267 /// a half-converted attribute is unrepresentable.
268 pub fn set(&mut self, i: usize, value: AttribValue) -> Result<(), String> {
269 macro_rules! put {
270 ($arr:expr, $v:expr) => {{
271 let len = $arr.len();
272 let slot = $arr
273 .get_mut(i)
274 .ok_or_else(|| format!("element {} is out of range ({})", i, len))?;
275 *slot = $v;
276 Ok(())
277 }};
278 }
279 match (self, value) {
280 (AttribData::Float(a), AttribValue::Float(v)) => put!(a, v),
281 (AttribData::Float2(a), AttribValue::Float2(v)) => put!(a, v),
282 (AttribData::Float3(a), AttribValue::Float3(v)) => put!(a, v),
283 (AttribData::Float4(a), AttribValue::Float4(v)) => put!(a, v),
284 (AttribData::Int(a), AttribValue::Int(v)) => put!(a, v),
285 (data, value) => Err(format!(
286 "type mismatch: attribute is {}, value is {}",
287 data.ty().name(),
288 value.ty().name()
289 )),
290 }
291 }
292
293 /// Append one zero element, keeping the array in step with a class that
294 /// just grew.
295 pub fn push_zero(&mut self) {
296 match self {
297 AttribData::Float(v) => v.push(0.0),
298 AttribData::Float2(v) => v.push([0.0; 2]),
299 AttribData::Float3(v) => v.push([0.0; 3]),
300 AttribData::Float4(v) => v.push([0.0; 4]),
301 AttribData::Int(v) => v.push(0),
302 }
303 }
304
305 /// Grow or shrink to `len`, zero-filling any new elements.
306 pub fn resize(&mut self, len: usize) {
307 match self {
308 AttribData::Float(v) => v.resize(len, 0.0),
309 AttribData::Float2(v) => v.resize(len, [0.0; 2]),
310 AttribData::Float3(v) => v.resize(len, [0.0; 3]),
311 AttribData::Float4(v) => v.resize(len, [0.0; 4]),
312 AttribData::Int(v) => v.resize(len, 0),
313 }
314 }
315
316 /// A new array holding this one's elements at `idx`, in that order.
317 ///
318 /// The single primitive every topology-changing operator needs: deleting
319 /// elements, reordering them, and duplicating them are all a gather, so
320 /// there is one place where "what happens to the attributes" is answered.
321 /// Indices out of range contribute a zero rather than panicking — a caller
322 /// building an index map should not be able to corrupt memory with an
323 /// arithmetic slip.
324 pub fn gather(&self, idx: &[u32]) -> AttribData {
325 macro_rules! pick {
326 ($arr:expr, $zero:expr, $wrap:path) => {{
327 let mut out = Vec::with_capacity(idx.len());
328 for &i in idx {
329 out.push($arr.get(i as usize).copied().unwrap_or($zero));
330 }
331 $wrap(out)
332 }};
333 }
334 match self {
335 AttribData::Float(a) => pick!(a, 0.0, AttribData::Float),
336 AttribData::Float2(a) => pick!(a, [0.0; 2], AttribData::Float2),
337 AttribData::Float3(a) => pick!(a, [0.0; 3], AttribData::Float3),
338 AttribData::Float4(a) => pick!(a, [0.0; 4], AttribData::Float4),
339 AttribData::Int(a) => pick!(a, 0, AttribData::Int),
340 }
341 }
342
343 /// The raw floats behind the array, for a GPU upload or a bulk read.
344 /// `Int` has no float view and yields `None`.
345 pub fn as_f32_slice(&self) -> Option<&[f32]> {
346 match self {
347 AttribData::Float(v) => Some(v.as_slice()),
348 AttribData::Float2(v) => Some(bytemuck::cast_slice(v.as_slice())),
349 AttribData::Float3(v) => Some(bytemuck::cast_slice(v.as_slice())),
350 AttribData::Float4(v) => Some(bytemuck::cast_slice(v.as_slice())),
351 AttribData::Int(_) => None,
352 }
353 }
354 }
355
356 /// Every attribute and group belonging to one element class, plus the element
357 /// count they are all kept in step with.
358 #[derive(Clone, Debug, Default)]
359 pub struct AttribStore {
360 len: usize,
361 attribs: HashMap<String, AttribData>,
362 /// Only the attributes that are NOT the default kind appear here, so an
363 /// absent entry reads as [`AttribKind::Live`] and nothing has to remember
364 /// to register an ordinary attribute.
365 kinds: HashMap<String, AttribKind>,
366 groups: HashMap<String, Vec<bool>>,
367 }
368
369 impl AttribStore {
370 pub fn with_len(len: usize) -> Self {
371 Self { len, attribs: HashMap::new(), kinds: HashMap::new(), groups: HashMap::new() }
372 }
373
374 pub fn len(&self) -> usize {
375 self.len
376 }
377
378 pub fn is_empty(&self) -> bool {
379 self.len == 0
380 }
381
382 /// Attribute names, sorted, so a spreadsheet's columns do not reshuffle
383 /// between frames on `HashMap` iteration order.
384 pub fn names(&self) -> Vec<&str> {
385 let mut names: Vec<&str> = self.attribs.keys().map(|s| s.as_str()).collect();
386 names.sort_unstable();
387 names
388 }
389
390 /// Group names, sorted, for the same reason.
391 pub fn group_names(&self) -> Vec<&str> {
392 let mut names: Vec<&str> = self.groups.keys().map(|s| s.as_str()).collect();
393 names.sort_unstable();
394 names
395 }
396
397 pub fn has(&self, name: &str) -> bool {
398 self.attribs.contains_key(name)
399 }
400
401 pub fn get(&self, name: &str) -> Option<&AttribData> {
402 self.attribs.get(name)
403 }
404
405 pub fn get_mut(&mut self, name: &str) -> Option<&mut AttribData> {
406 self.attribs.get_mut(name)
407 }
408
409 /// Create (or replace) an attribute, every element set to `default`.
410 ///
411 /// The attribute is [`AttribKind::Live`]; use [`AttribStore::create_kind`]
412 /// for one the solver should clear each step. Replacing an attribute
413 /// replaces its kind too — a name reused for a different purpose is a
414 /// different attribute.
415 pub fn create(&mut self, name: &str, default: AttribValue) -> &mut AttribData {
416 self.create_kind(name, default, AttribKind::Live)
417 }
418
419 /// Create (or replace) an attribute, declaring whether it survives a step.
420 pub fn create_kind(
421 &mut self,
422 name: &str,
423 default: AttribValue,
424 kind: AttribKind,
425 ) -> &mut AttribData {
426 self.attribs
427 .insert(name.to_string(), AttribData::filled(default, self.len));
428 match kind {
429 AttribKind::Live => self.kinds.remove(name),
430 other => self.kinds.insert(name.to_string(), other),
431 };
432 self.attribs.get_mut(name).expect("just inserted")
433 }
434
435 /// Whether an attribute survives a simulation step. An attribute nobody
436 /// declared is Live.
437 pub fn kind(&self, name: &str) -> AttribKind {
438 self.kinds.get(name).copied().unwrap_or_default()
439 }
440
441 /// Declare an existing attribute's kind without disturbing its values.
442 pub fn set_kind(&mut self, name: &str, kind: AttribKind) {
443 if !self.attribs.contains_key(name) {
444 return;
445 }
446 match kind {
447 AttribKind::Live => self.kinds.remove(name),
448 other => self.kinds.insert(name.to_string(), other),
449 };
450 }
451
452 /// The names of every attribute of one kind, sorted.
453 pub fn names_of_kind(&self, kind: AttribKind) -> Vec<&str> {
454 let mut names: Vec<&str> = self
455 .attribs
456 .keys()
457 .filter(|n| self.kind(n) == kind)
458 .map(|s| s.as_str())
459 .collect();
460 names.sort_unstable();
461 names
462 }
463
464 /// Zero every Derivative attribute, keeping the columns themselves — the
465 /// step that follows is expected to rebuild the values, and a reader
466 /// between the two should find the attribute present and empty rather than
467 /// missing.
468 pub fn clear_derivatives(&mut self) {
469 let names: Vec<String> = self.kinds
470 .iter()
471 .filter(|(_, &k)| k == AttribKind::Derivative)
472 .map(|(n, _)| n.clone())
473 .collect();
474 for name in names {
475 if let Some(data) = self.attribs.get_mut(&name) {
476 *data = AttribData::zeroed(data.ty(), self.len);
477 }
478 }
479 }
480
481 /// Create the attribute if it is absent, leaving an existing one — and its
482 /// values — alone. The read path for an operator that wants to write into
483 /// an attribute it does not own.
484 pub fn get_or_create(&mut self, name: &str, default: AttribValue) -> &mut AttribData {
485 if !self.attribs.contains_key(name) {
486 self.create(name, default);
487 }
488 self.attribs.get_mut(name).expect("present or just created")
489 }
490
491 pub fn remove(&mut self, name: &str) -> Option<AttribData> {
492 self.kinds.remove(name);
493 self.attribs.remove(name)
494 }
495
496 /// Install a whole array as an attribute, which is how a generator that
497 /// computed every value in one pass writes them — one move instead of an
498 /// element-at-a-time walk.
499 ///
500 /// A length mismatch is refused rather than padded: an array that does not
501 /// line up with its class is a caller bug, and silently zero-filling it
502 /// would put the wrong value on every element after the first mistake.
503 pub fn insert(&mut self, name: &str, data: AttribData) -> Result<(), String> {
504 if data.len() != self.len {
505 return Err(format!(
506 "attribute {:?} has {} entries, the class has {}",
507 name,
508 data.len(),
509 self.len
510 ));
511 }
512 self.attribs.insert(name.to_string(), data);
513 Ok(())
514 }
515
516 pub fn value(&self, name: &str, i: usize) -> Option<AttribValue> {
517 self.attribs.get(name).and_then(|a| a.get(i))
518 }
519
520 pub fn set_value(&mut self, name: &str, i: usize, v: AttribValue) -> Result<(), String> {
521 self.attribs
522 .get_mut(name)
523 .ok_or_else(|| format!("no attribute named {:?}", name))?
524 .set(i, v)
525 }
526
527 /// Create an empty group, or empty an existing one.
528 pub fn create_group(&mut self, name: &str) {
529 self.groups.insert(name.to_string(), vec![false; self.len]);
530 }
531
532 pub fn has_group(&self, name: &str) -> bool {
533 self.groups.contains_key(name)
534 }
535
536 pub fn remove_group(&mut self, name: &str) -> bool {
537 self.groups.remove(name).is_some()
538 }
539
540 /// Put one element in a group, creating the group if needed. Out-of-range
541 /// indices are ignored.
542 pub fn add_to_group(&mut self, name: &str, i: usize) {
543 let len = self.len;
544 let members = self
545 .groups
546 .entry(name.to_string())
547 .or_insert_with(|| vec![false; len]);
548 if let Some(slot) = members.get_mut(i) {
549 *slot = true;
550 }
551 }
552
553 pub fn in_group(&self, name: &str, i: usize) -> bool {
554 self.groups.get(name).and_then(|m| m.get(i)).copied().unwrap_or(false)
555 }
556
557 /// The members of a group, in element order. An absent group has no
558 /// members — asking about a group nobody created is not an error, because
559 /// a node's Group parameter is routinely left blank.
560 pub fn group_members(&self, name: &str) -> Vec<u32> {
561 match self.groups.get(name) {
562 Some(m) => m
563 .iter()
564 .enumerate()
565 .filter(|(_, &v)| v)
566 .map(|(i, _)| i as u32)
567 .collect(),
568 None => Vec::new(),
569 }
570 }
571
572 pub fn group_len(&self, name: &str) -> usize {
573 self.groups
574 .get(name)
575 .map(|m| m.iter().filter(|&&v| v).count())
576 .unwrap_or(0)
577 }
578
579 /// Append one element's worth of room to every attribute and group.
580 fn push_element(&mut self) {
581 self.len += 1;
582 for a in self.attribs.values_mut() {
583 a.push_zero();
584 }
585 for g in self.groups.values_mut() {
586 g.push(false);
587 }
588 }
589
590 /// Set the element count, resizing every attribute and group to match.
591 fn set_len(&mut self, len: usize) {
592 self.len = len;
593 for a in self.attribs.values_mut() {
594 a.resize(len);
595 }
596 for g in self.groups.values_mut() {
597 g.resize(len, false);
598 }
599 }
600
601 /// Rebuild the store around a new element order: element `n` of the result
602 /// is element `idx[n]` of this one. See [`AttribData::gather`].
603 fn gather(&self, idx: &[u32]) -> AttribStore {
604 let attribs = self
605 .attribs
606 .iter()
607 .map(|(k, v)| (k.clone(), v.gather(idx)))
608 .collect();
609 let groups = self
610 .groups
611 .iter()
612 .map(|(k, v)| {
613 let picked = idx
614 .iter()
615 .map(|&i| v.get(i as usize).copied().unwrap_or(false))
616 .collect();
617 (k.clone(), picked)
618 })
619 .collect();
620 AttribStore { len: idx.len(), attribs, kinds: self.kinds.clone(), groups }
621 }
622
623 /// Append `other`'s elements. Attributes present on only one side are
624 /// created on the other and zero-filled there, so a merge never silently
625 /// drops a column.
626 fn append(&mut self, other: &AttribStore) {
627 let (lhs_len, rhs_len) = (self.len, other.len);
628
629 for (name, rhs) in &other.attribs {
630 match self.attribs.get_mut(name) {
631 Some(lhs) if lhs.ty() == rhs.ty() => append_data(lhs, rhs),
632 // A type clash keeps the left side and zero-fills: the
633 // alternative is dropping one side's values entirely, and a
634 // merge is not the place to decide which side is right.
635 Some(lhs) => lhs.resize(lhs_len + rhs_len),
636 None => {
637 let mut fresh = AttribData::zeroed(rhs.ty(), lhs_len);
638 append_data(&mut fresh, rhs);
639 self.attribs.insert(name.clone(), fresh);
640 }
641 }
642 }
643 for (_, lhs) in self.attribs.iter_mut().filter(|(n, _)| !other.attribs.contains_key(*n)) {
644 lhs.resize(lhs_len + rhs_len);
645 }
646
647 // A kind declared on either side sticks: the left side wins a
648 // disagreement, the same way its values do.
649 for (name, kind) in &other.kinds {
650 self.kinds.entry(name.clone()).or_insert(*kind);
651 }
652
653 for (name, rhs) in &other.groups {
654 let lhs = self
655 .groups
656 .entry(name.clone())
657 .or_insert_with(|| vec![false; lhs_len]);
658 lhs.extend_from_slice(rhs);
659 }
660 for (_, lhs) in self.groups.iter_mut().filter(|(n, _)| !other.groups.contains_key(*n)) {
661 lhs.resize(lhs_len + rhs_len, false);
662 }
663
664 self.len = lhs_len + rhs_len;
665 }
666 }
667
668 const DETAIL_MAGIC: &[u8; 8] = b"CCEDTL01";
669
670 fn put_u32(out: &mut Vec<u8>, v: u32) {
671 out.extend_from_slice(&v.to_le_bytes());
672 }
673
674 fn put_u64(out: &mut Vec<u8>, v: u64) {
675 out.extend_from_slice(&v.to_le_bytes());
676 }
677
678 fn put_str(out: &mut Vec<u8>, s: &str) {
679 put_u32(out, s.len() as u32);
680 out.extend_from_slice(s.as_bytes());
681 }
682
683 /// A bounds-checked cursor over a blob. Every read either yields the bytes it
684 /// promised or fails; nothing here can index past the buffer.
685 struct Reader<'a> {
686 b: &'a [u8],
687 at: usize,
688 }
689
690 impl<'a> Reader<'a> {
691 fn take(&mut self, n: usize) -> Result<&'a [u8], String> {
692 let end = self.at.checked_add(n).ok_or("length overflow")?;
693 let slice = self.b.get(self.at..end).ok_or("unexpected end of blob")?;
694 self.at = end;
695 Ok(slice)
696 }
697 fn u32(&mut self) -> Result<u32, String> {
698 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
699 }
700 fn u64(&mut self) -> Result<u64, String> {
701 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
702 }
703 fn f32(&mut self) -> Result<f32, String> {
704 Ok(f32::from_le_bytes(self.take(4)?.try_into().unwrap()))
705 }
706 fn i32(&mut self) -> Result<i32, String> {
707 Ok(i32::from_le_bytes(self.take(4)?.try_into().unwrap()))
708 }
709 fn str(&mut self) -> Result<String, String> {
710 let n = self.u32()? as usize;
711 let bytes = self.take(n)?;
712 String::from_utf8(bytes.to_vec()).map_err(|_| "attribute name is not UTF-8".to_string())
713 }
714 }
715
716 impl AttribStore {
717 fn write_into(&self, out: &mut Vec<u8>) {
718 put_u32(out, self.len as u32);
719 let names = self.names();
720 put_u32(out, names.len() as u32);
721 for name in names {
722 let data = &self.attribs[name];
723 put_str(out, name);
724 out.push(match data.ty() {
725 AttribType::Float => 0,
726 AttribType::Float2 => 1,
727 AttribType::Float3 => 2,
728 AttribType::Float4 => 3,
729 AttribType::Int => 4,
730 });
731 out.push(match self.kind(name) {
732 AttribKind::Live => 0,
733 AttribKind::Derivative => 1,
734 });
735 match data {
736 AttribData::Float(v) => out.extend(v.iter().flat_map(|x| x.to_le_bytes())),
737 AttribData::Float2(v) => {
738 out.extend(v.iter().flatten().flat_map(|x| x.to_le_bytes()))
739 }
740 AttribData::Float3(v) => {
741 out.extend(v.iter().flatten().flat_map(|x| x.to_le_bytes()))
742 }
743 AttribData::Float4(v) => {
744 out.extend(v.iter().flatten().flat_map(|x| x.to_le_bytes()))
745 }
746 AttribData::Int(v) => out.extend(v.iter().flat_map(|x| x.to_le_bytes())),
747 }
748 }
749 let groups = self.group_names();
750 put_u32(out, groups.len() as u32);
751 for name in groups {
752 put_str(out, name);
753 out.extend(self.groups[name].iter().map(|&m| m as u8));
754 }
755 }
756
757 fn read_from(r: &mut Reader) -> Result<AttribStore, String> {
758 let len = r.u32()? as usize;
759 let mut store = AttribStore::with_len(len);
760 let n_attrs = r.u32()? as usize;
761 for _ in 0..n_attrs {
762 let name = r.str()?;
763 let ty = match r.take(1)?[0] {
764 0 => AttribType::Float,
765 1 => AttribType::Float2,
766 2 => AttribType::Float3,
767 3 => AttribType::Float4,
768 4 => AttribType::Int,
769 other => return Err(format!("unknown attribute type {other}")),
770 };
771 let kind = match r.take(1)?[0] {
772 0 => AttribKind::Live,
773 1 => AttribKind::Derivative,
774 other => return Err(format!("unknown attribute kind {other}")),
775 };
776 let data = match ty {
777 AttribType::Int => {
778 let mut v = Vec::with_capacity(len.min(1 << 20));
779 for _ in 0..len {
780 v.push(r.i32()?);
781 }
782 AttribData::Int(v)
783 }
784 _ => {
785 let k = ty.components();
786 let mut flat = Vec::with_capacity((len * k).min(1 << 22));
787 for _ in 0..len * k {
788 flat.push(r.f32()?);
789 }
790 match ty {
791 AttribType::Float => AttribData::Float(flat),
792 AttribType::Float2 => {
793 AttribData::Float2(flat.chunks_exact(2).map(|c| [c[0], c[1]]).collect())
794 }
795 AttribType::Float3 => AttribData::Float3(
796 flat.chunks_exact(3).map(|c| [c[0], c[1], c[2]]).collect(),
797 ),
798 _ => AttribData::Float4(
799 flat.chunks_exact(4).map(|c| [c[0], c[1], c[2], c[3]]).collect(),
800 ),
801 }
802 }
803 };
804 store.attribs.insert(name.clone(), data);
805 if kind != AttribKind::Live {
806 store.kinds.insert(name, kind);
807 }
808 }
809 let n_groups = r.u32()? as usize;
810 for _ in 0..n_groups {
811 let name = r.str()?;
812 let bits = r.take(len)?;
813 store.groups.insert(name, bits.iter().map(|&b| b != 0).collect());
814 }
815 Ok(store)
816 }
817 }
818
819 fn append_data(lhs: &mut AttribData, rhs: &AttribData) {
820 match (lhs, rhs) {
821 (AttribData::Float(a), AttribData::Float(b)) => a.extend_from_slice(b),
822 (AttribData::Float2(a), AttribData::Float2(b)) => a.extend_from_slice(b),
823 (AttribData::Float3(a), AttribData::Float3(b)) => a.extend_from_slice(b),
824 (AttribData::Float4(a), AttribData::Float4(b)) => a.extend_from_slice(b),
825 (AttribData::Int(a), AttribData::Int(b)) => a.extend_from_slice(b),
826 (lhs, rhs) => lhs.resize(lhs.len() + rhs.len()),
827 }
828 }
829
830 /// Derived connectivity: which primitives use a point, which points share an
831 /// edge with it, and the unique edge list.
832 ///
833 /// Built on demand by [`Detail::topology`] and dropped by any structural edit.
834 /// Everything is CSR — a start-offset array indexed by point, plus one flat
835 /// array of contents — so a neighbour walk is a slice, not an allocation, and
836 /// the whole thing uploads to a GPU buffer unchanged in Phase 1.
837 #[derive(Clone, Debug, Default)]
838 pub struct Topology {
839 point_prim_start: Vec<u32>,
840 point_prim: Vec<u32>,
841 point_nbr_start: Vec<u32>,
842 point_nbr: Vec<u32>,
843 edges: Vec<[u32; 2]>,
844 }
845
846 impl Topology {
847 /// The primitives using point `p`, ascending.
848 pub fn point_prims(&self, p: usize) -> &[u32] {
849 Self::span(&self.point_prim_start, &self.point_prim, p)
850 }
851
852 /// The points sharing an edge with point `p`, ascending and deduplicated.
853 pub fn point_neighbours(&self, p: usize) -> &[u32] {
854 Self::span(&self.point_nbr_start, &self.point_nbr, p)
855 }
856
857 /// Every unique undirected edge, each as `[low, high]`.
858 pub fn edges(&self) -> &[[u32; 2]] {
859 &self.edges
860 }
861
862 /// How many edges meet at point `p` — Houdini's valence, and the number
863 /// incremental remeshing steers toward 6.
864 pub fn valence(&self, p: usize) -> usize {
865 self.point_neighbours(p).len()
866 }
867
868 fn span<'a>(start: &[u32], flat: &'a [u32], i: usize) -> &'a [u32] {
869 if i + 1 >= start.len() {
870 return &[];
871 }
872 let (a, b) = (start[i] as usize, start[i + 1] as usize);
873 flat.get(a..b).unwrap_or(&[])
874 }
875
876 fn build(num_points: usize, vert_point: &[u32], prim_start: &[u32]) -> Topology {
877 let num_prims = prim_start.len().saturating_sub(1);
878
879 // point -> prims, by counting sort: one pass to count, a prefix sum,
880 // then one pass to place. A point appearing twice in one primitive
881 // (a degenerate fan) is counted once.
882 let mut counts = vec![0u32; num_points + 1];
883 let mut seen: Vec<u32> = Vec::new();
884 for prim in 0..num_prims {
885 seen.clear();
886 for &pt in &vert_point[prim_start[prim] as usize..prim_start[prim + 1] as usize] {
887 if !seen.contains(&pt) {
888 seen.push(pt);
889 if (pt as usize) < num_points {
890 counts[pt as usize] += 1;
891 }
892 }
893 }
894 }
895 let mut point_prim_start = vec![0u32; num_points + 1];
896 let mut acc = 0u32;
897 for p in 0..num_points {
898 point_prim_start[p] = acc;
899 acc += counts[p];
900 }
901 point_prim_start[num_points] = acc;
902
903 let mut cursor = point_prim_start.clone();
904 let mut point_prim = vec![0u32; acc as usize];
905 for prim in 0..num_prims {
906 seen.clear();
907 for &pt in &vert_point[prim_start[prim] as usize..prim_start[prim + 1] as usize] {
908 if !seen.contains(&pt) {
909 seen.push(pt);
910 if (pt as usize) < num_points {
911 point_prim[cursor[pt as usize] as usize] = prim as u32;
912 cursor[pt as usize] += 1;
913 }
914 }
915 }
916 }
917
918 // Edges: every consecutive pair around each primitive, closing the
919 // loop. A two-point primitive (an open line segment) contributes one
920 // edge, not two — closing it would invent a neighbour.
921 let mut edges: Vec<[u32; 2]> = Vec::new();
922 for prim in 0..num_prims {
923 let pts = &vert_point[prim_start[prim] as usize..prim_start[prim + 1] as usize];
924 let n = pts.len();
925 if n < 2 {
926 continue;
927 }
928 let span = if n == 2 { 1 } else { n };
929 for i in 0..span {
930 let (a, b) = (pts[i], pts[(i + 1) % n]);
931 if a == b {
932 continue;
933 }
934 edges.push([a.min(b), a.max(b)]);
935 }
936 }
937 edges.sort_unstable();
938 edges.dedup();
939
940 // point -> neighbours, from the deduplicated edge list. Both endpoints
941 // of every edge, counting-sorted the same way.
942 let mut counts = vec![0u32; num_points];
943 for e in &edges {
944 for &p in e {
945 if (p as usize) < num_points {
946 counts[p as usize] += 1;
947 }
948 }
949 }
950 let mut point_nbr_start = vec![0u32; num_points + 1];
951 let mut acc = 0u32;
952 for p in 0..num_points {
953 point_nbr_start[p] = acc;
954 acc += counts[p];
955 }
956 point_nbr_start[num_points] = acc;
957
958 let mut cursor = point_nbr_start.clone();
959 let mut point_nbr = vec![0u32; acc as usize];
960 for e in &edges {
961 let (a, b) = (e[0], e[1]);
962 if (a as usize) < num_points {
963 point_nbr[cursor[a as usize] as usize] = b;
964 cursor[a as usize] += 1;
965 }
966 if (b as usize) < num_points {
967 point_nbr[cursor[b as usize] as usize] = a;
968 cursor[b as usize] += 1;
969 }
970 }
971 for p in 0..num_points {
972 let (a, b) = (point_nbr_start[p] as usize, point_nbr_start[p + 1] as usize);
973 point_nbr[a..b].sort_unstable();
974 }
975
976 Topology { point_prim_start, point_prim, point_nbr_start, point_nbr, edges }
977 }
978 }
979
980 /// Points, vertices, primitives and detail — one piece of geometry.
981 ///
982 /// See the module docs. Position and [`PointId`] get dedicated fields rather
983 /// than living in the point attribute store: every operator touches both, and
984 /// neither should cost a name lookup or be removable.
985 #[derive(Debug)]
986 pub struct Detail {
987 pos: Vec<[f32; 3]>,
988 ids: Vec<PointId>,
989 next_id: PointId,
990 points: AttribStore,
991 /// One entry per vertex: the point it references. Primitives index into
992 /// this array through `prim_start`.
993 vert_point: Vec<u32>,
994 verts: AttribStore,
995 /// CSR offsets into `vert_point`, one per primitive plus a trailing total.
996 /// Always non-empty: a geometry with no primitives still has `[0]`.
997 prim_start: Vec<u32>,
998 prims: AttribStore,
999 detail: AttribStore,
1000 topo: OnceLock<Topology>,
1001 }
1002
1003 impl Default for Detail {
1004 fn default() -> Self {
1005 Self::new()
1006 }
1007 }
1008
1009 impl Clone for Detail {
1010 /// The topology cache is deliberately *not* cloned. It is derived, the
1011 /// clone exists to be modified, and rebuilding is cheaper than reasoning
1012 /// about whether a stale cache came along.
1013 fn clone(&self) -> Self {
1014 Self {
1015 pos: self.pos.clone(),
1016 ids: self.ids.clone(),
1017 next_id: self.next_id,
1018 points: self.points.clone(),
1019 vert_point: self.vert_point.clone(),
1020 verts: self.verts.clone(),
1021 prim_start: self.prim_start.clone(),
1022 prims: self.prims.clone(),
1023 detail: self.detail.clone(),
1024 topo: OnceLock::new(),
1025 }
1026 }
1027 }
1028
1029 impl Detail {
1030 pub fn new() -> Self {
1031 Self {
1032 pos: Vec::new(),
1033 ids: Vec::new(),
1034 next_id: 0,
1035 points: AttribStore::default(),
1036 vert_point: Vec::new(),
1037 verts: AttribStore::default(),
1038 prim_start: vec![0],
1039 prims: AttribStore::default(),
1040 detail: AttribStore::with_len(1),
1041 topo: OnceLock::new(),
1042 }
1043 }
1044
1045 // ---- counts ----
1046
1047 pub fn num_points(&self) -> usize {
1048 self.pos.len()
1049 }
1050
1051 pub fn num_verts(&self) -> usize {
1052 self.vert_point.len()
1053 }
1054
1055 pub fn num_prims(&self) -> usize {
1056 self.prim_start.len() - 1
1057 }
1058
1059 pub fn is_empty(&self) -> bool {
1060 self.pos.is_empty()
1061 }
1062
1063 // ---- attribute stores ----
1064
1065 pub fn points(&self) -> &AttribStore {
1066 &self.points
1067 }
1068
1069 pub fn points_mut(&mut self) -> &mut AttribStore {
1070 &mut self.points
1071 }
1072
1073 pub fn verts(&self) -> &AttribStore {
1074 &self.verts
1075 }
1076
1077 pub fn verts_mut(&mut self) -> &mut AttribStore {
1078 &mut self.verts
1079 }
1080
1081 pub fn prims(&self) -> &AttribStore {
1082 &self.prims
1083 }
1084
1085 pub fn prims_mut(&mut self) -> &mut AttribStore {
1086 &mut self.prims
1087 }
1088
1089 /// The single-row detail store — where whole-geometry values live.
1090 pub fn detail(&self) -> &AttribStore {
1091 &self.detail
1092 }
1093
1094 pub fn detail_mut(&mut self) -> &mut AttribStore {
1095 &mut self.detail
1096 }
1097
1098 pub fn store(&self, class: Class) -> &AttribStore {
1099 match class {
1100 Class::Point => &self.points,
1101 Class::Vertex => &self.verts,
1102 Class::Prim => &self.prims,
1103 Class::Detail => &self.detail,
1104 }
1105 }
1106
1107 pub fn store_mut(&mut self, class: Class) -> &mut AttribStore {
1108 match class {
1109 Class::Point => &mut self.points,
1110 Class::Vertex => &mut self.verts,
1111 Class::Prim => &mut self.prims,
1112 Class::Detail => &mut self.detail,
1113 }
1114 }
1115
1116 // ---- points ----
1117
1118 pub fn pos(&self, p: usize) -> Vec3 {
1119 self.pos.get(p).map(|v| Vec3::from(*v)).unwrap_or(Vec3::ZERO)
1120 }
1121
1122 pub fn set_pos(&mut self, p: usize, v: Vec3) {
1123 if let Some(slot) = self.pos.get_mut(p) {
1124 *slot = v.to_array();
1125 }
1126 }
1127
1128 /// Every position, flat — the slice a GPU buffer takes directly.
1129 pub fn positions(&self) -> &[[f32; 3]] {
1130 &self.pos
1131 }
1132
1133 /// Positions for in-place editing. Moving points does not change topology,
1134 /// so the cache survives; a caller that adds or removes points must go
1135 /// through [`Detail::add_point`] or [`Detail::gather_points`] instead.
1136 pub fn positions_mut(&mut self) -> &mut [[f32; 3]] {
1137 &mut self.pos
1138 }
1139
1140 /// The stable identity of point `p`.
1141 pub fn id(&self, p: usize) -> Option<PointId> {
1142 self.ids.get(p).copied()
1143 }
1144
1145 pub fn ids(&self) -> &[PointId] {
1146 &self.ids
1147 }
1148
1149 /// Where the point carrying `id` currently sits, or `None` if it is gone.
1150 /// Linear; a solver resolving many ids at once should build a map with
1151 /// [`Detail::id_map`] instead.
1152 pub fn index_of_id(&self, id: PointId) -> Option<usize> {
1153 self.ids.iter().position(|&i| i == id)
1154 }
1155
1156 /// Identity to index, for the solver path that reconciles two frames.
1157 pub fn id_map(&self) -> HashMap<PointId, u32> {
1158 self.ids
1159 .iter()
1160 .enumerate()
1161 .map(|(i, &id)| (id, i as u32))
1162 .collect()
1163 }
1164
1165 /// Replace every point's identity, and the counter new points draw from.
1166 ///
1167 /// For a rebuild that KNOWS which points it preserved — the remesher,
1168 /// which tears a mesh apart and puts it back, and needs the survivors to
1169 /// come out as themselves. Everything else must let [`Detail::add_point`]
1170 /// allocate, or two points end up answering to one identity.
1171 ///
1172 /// A mismatched length is refused rather than padded: a partial identity
1173 /// map is worse than none, because the points it does map look right.
1174 pub fn set_ids(&mut self, ids: Vec<PointId>, next_id: PointId) -> Result<(), String> {
1175 if ids.len() != self.pos.len() {
1176 return Err(format!(
1177 "{} identities for {} points",
1178 ids.len(),
1179 self.pos.len()
1180 ));
1181 }
1182 self.next_id = next_id.max(ids.iter().copied().max().map(|m| m + 1).unwrap_or(0));
1183 self.ids = ids;
1184 Ok(())
1185 }
1186
1187 /// Add a point at `pos`, assigning it a fresh identity. Returns its index.
1188 pub fn add_point(&mut self, pos: Vec3) -> u32 {
1189 let idx = self.pos.len() as u32;
1190 self.pos.push(pos.to_array());
1191 self.ids.push(self.next_id);
1192 self.next_id += 1;
1193 self.points.push_element();
1194 self.invalidate();
1195 idx
1196 }
1197
1198 /// Add `n` points at once, which is what a generator does. Returns the
1199 /// index of the first.
1200 pub fn add_points(&mut self, positions: &[[f32; 3]]) -> u32 {
1201 let first = self.pos.len() as u32;
1202 self.pos.extend_from_slice(positions);
1203 for _ in 0..positions.len() {
1204 self.ids.push(self.next_id);
1205 self.next_id += 1;
1206 }
1207 self.points.set_len(self.pos.len());
1208 self.invalidate();
1209 first
1210 }
1211
1212 // ---- primitives ----
1213
1214 /// Add a primitive over the given points, in winding order. One vertex per
1215 /// entry. Returns the primitive index.
1216 pub fn add_prim(&mut self, points: &[u32]) -> u32 {
1217 let idx = self.num_prims() as u32;
1218 self.vert_point.extend_from_slice(points);
1219 self.prim_start.push(self.vert_point.len() as u32);
1220 self.verts.set_len(self.vert_point.len());
1221 self.prims.push_element();
1222 self.invalidate();
1223 idx
1224 }
1225
1226 /// The vertex indices of primitive `p`.
1227 pub fn prim_verts(&self, p: usize) -> std::ops::Range<usize> {
1228 if p + 1 >= self.prim_start.len() {
1229 return 0..0;
1230 }
1231 self.prim_start[p] as usize..self.prim_start[p + 1] as usize
1232 }
1233
1234 /// The points of primitive `p`, in winding order.
1235 pub fn prim_points(&self, p: usize) -> &[u32] {
1236 let r = self.prim_verts(p);
1237 self.vert_point.get(r).unwrap_or(&[])
1238 }
1239
1240 /// The point a vertex references.
1241 pub fn vert_point(&self, v: usize) -> Option<u32> {
1242 self.vert_point.get(v).copied()
1243 }
1244
1245 pub fn vert_points(&self) -> &[u32] {
1246 &self.vert_point
1247 }
1248
1249 // ---- topology ----
1250
1251 /// Connectivity, built on first ask and reused until a structural edit
1252 /// drops it.
1253 pub fn topology(&self) -> &Topology {
1254 self.topo
1255 .get_or_init(|| Topology::build(self.num_points(), &self.vert_point, &self.prim_start))
1256 }
1257
1258 /// The points sharing an edge with point `p`.
1259 pub fn point_neighbours(&self, p: usize) -> &[u32] {
1260 self.topology().point_neighbours(p)
1261 }
1262
1263 /// The primitives using point `p`.
1264 pub fn point_prims(&self, p: usize) -> &[u32] {
1265 self.topology().point_prims(p)
1266 }
1267
1268 /// Every unique undirected edge.
1269 pub fn edges(&self) -> &[[u32; 2]] {
1270 self.topology().edges()
1271 }
1272
1273 /// Drop the derived topology. Called by every structural edit; public
1274 /// because an operator writing `vert_point` through a future bulk path
1275 /// must be able to say so.
1276 pub fn invalidate(&mut self) {
1277 self.topo.take();
1278 }
1279
1280 // ---- bulk edits ----
1281
1282 /// Rebuild around a new point order: point `n` of the result is point
1283 /// `idx[n]` of this one. Identities, positions, point attributes and point
1284 /// groups all follow. Primitives are rewired through the inverse map, and
1285 /// any primitive referencing a dropped point is dropped with it — a
1286 /// half-referenced polygon is not geometry.
1287 pub fn gather_points(&mut self, idx: &[u32]) {
1288 let mut inverse = vec![u32::MAX; self.num_points()];
1289 for (new, &old) in idx.iter().enumerate() {
1290 if let Some(slot) = inverse.get_mut(old as usize) {
1291 // A point appearing twice keeps its first landing place; the
1292 // duplicate still exists, it is simply not what primitives
1293 // point at.
1294 if *slot == u32::MAX {
1295 *slot = new as u32;
1296 }
1297 }
1298 }
1299
1300 self.pos = idx
1301 .iter()
1302 .map(|&i| self.pos.get(i as usize).copied().unwrap_or([0.0; 3]))
1303 .collect();
1304 self.ids = idx
1305 .iter()
1306 .map(|&i| self.ids.get(i as usize).copied().unwrap_or(0))
1307 .collect();
1308 self.points = self.points.gather(idx);
1309
1310 let mut vert_point = Vec::with_capacity(self.vert_point.len());
1311 let mut prim_start = vec![0u32];
1312 let mut kept_prims: Vec<u32> = Vec::new();
1313 let mut kept_verts: Vec<u32> = Vec::new();
1314 for prim in 0..self.num_prims() {
1315 let range = self.prim_verts(prim);
1316 let survives = self.vert_point[range.clone()]
1317 .iter()
1318 .all(|&pt| inverse.get(pt as usize).copied().unwrap_or(u32::MAX) != u32::MAX);
1319 if !survives {
1320 continue;
1321 }
1322 for v in range {
1323 kept_verts.push(v as u32);
1324 vert_point.push(inverse[self.vert_point[v] as usize]);
1325 }
1326 prim_start.push(vert_point.len() as u32);
1327 kept_prims.push(prim as u32);
1328 }
1329
1330 self.verts = self.verts.gather(&kept_verts);
1331 self.prims = self.prims.gather(&kept_prims);
1332 self.vert_point = vert_point;
1333 self.prim_start = prim_start;
1334 self.invalidate();
1335 }
1336
1337 /// Merge points onto representatives: point `p` becomes `rep[p]`.
1338 ///
1339 /// A representative keeps its identity and its values — the same choice
1340 /// the remesher's collapse makes, and for the same reason: one of the two
1341 /// is a point the solver has been writing to, and the merge should cost
1342 /// the simulation as little memory as it can. Primitives are rewired, and
1343 /// one left with a repeated corner is dropped, because a triangle with two
1344 /// corners in the same place is not a triangle.
1345 ///
1346 /// Chains are followed, so `rep` need not already be flat: a fuse that
1347 /// pointed a at b and b at c leaves everything at c.
1348 pub fn fuse_points(&mut self, rep: &[u32]) {
1349 let n = self.num_points();
1350 let root = |mut p: u32| {
1351 // Bounded rather than trusting the map to be acyclic: a cycle in a
1352 // caller's representative map would otherwise hang the app.
1353 for _ in 0..n {
1354 let next = rep.get(p as usize).copied().unwrap_or(p);
1355 if next == p {
1356 break;
1357 }
1358 p = next;
1359 }
1360 p
1361 };
1362 for v in self.vert_point.iter_mut() {
1363 *v = root(*v);
1364 }
1365
1366 // A primitive whose corners collapsed onto each other is not a
1367 // primitive any more. Dropped here rather than left for the point
1368 // compaction, which only knows about points that went away — these
1369 // ones all still exist, they have just stopped being distinct.
1370 let mut vert_point = Vec::with_capacity(self.vert_point.len());
1371 let mut prim_start = vec![0u32];
1372 let mut kept_prims: Vec<u32> = Vec::new();
1373 let mut kept_verts: Vec<u32> = Vec::new();
1374 for prim in 0..self.num_prims() {
1375 let range = self.prim_verts(prim);
1376 let pts = &self.vert_point[range.clone()];
1377 let mut uniq = pts.to_vec();
1378 uniq.sort_unstable();
1379 uniq.dedup();
1380 if uniq.len() < pts.len() || uniq.len() < 3 {
1381 continue;
1382 }
1383 for v in range {
1384 kept_verts.push(v as u32);
1385 vert_point.push(self.vert_point[v]);
1386 }
1387 prim_start.push(vert_point.len() as u32);
1388 kept_prims.push(prim as u32);
1389 }
1390 self.verts = self.verts.gather(&kept_verts);
1391 self.prims = self.prims.gather(&kept_prims);
1392 self.vert_point = vert_point;
1393 self.prim_start = prim_start;
1394
1395 let keep: Vec<bool> = (0..n).map(|p| root(p as u32) as usize == p).collect();
1396 self.invalidate();
1397 self.keep_points(&keep);
1398 }
1399
1400 /// Keep the points `keep` marks true, dropping the rest.
1401 pub fn keep_points(&mut self, keep: &[bool]) {
1402 let idx: Vec<u32> = (0..self.num_points() as u32)
1403 .filter(|&i| keep.get(i as usize).copied().unwrap_or(false))
1404 .collect();
1405 self.gather_points(&idx);
1406 }
1407
1408 /// Append `other`. Identities are reallocated on the way in, so two pieces
1409 /// of geometry that were generated independently — and therefore both
1410 /// number their points from zero — do not collide.
1411 pub fn merge(&mut self, other: &Detail) {
1412 let point_offset = self.num_points() as u32;
1413 let vert_offset = self.vert_point.len() as u32;
1414
1415 self.pos.extend_from_slice(&other.pos);
1416 for _ in 0..other.num_points() {
1417 self.ids.push(self.next_id);
1418 self.next_id += 1;
1419 }
1420 self.points.append(&other.points);
1421
1422 self.vert_point
1423 .extend(other.vert_point.iter().map(|&p| p + point_offset));
1424 self.verts.append(&other.verts);
1425
1426 for w in other.prim_start.iter().skip(1) {
1427 self.prim_start.push(w + vert_offset);
1428 }
1429 self.prims.append(&other.prims);
1430
1431 self.invalidate();
1432 }
1433
1434 // ---- convenience ----
1435
1436 /// Point color, from `Cd` where it exists.
1437 pub fn color(&self, p: usize) -> [f32; 3] {
1438 match self.points.value(CD, p) {
1439 Some(AttribValue::Float3(c)) => c,
1440 Some(other) => other.as_vec3().to_array(),
1441 None => DEFAULT_COLOR,
1442 }
1443 }
1444
1445 /// Set point color, creating `Cd` if this is the first writer.
1446 pub fn set_color(&mut self, p: usize, c: [f32; 3]) {
1447 self.points
1448 .get_or_create(CD, AttribValue::Float3(DEFAULT_COLOR));
1449 let _ = self.points.set_value(CD, p, AttribValue::Float3(c));
1450 }
1451
1452 /// Whether the surface is closed: every directed edge has exactly one
1453 /// opposite.
1454 ///
1455 /// The question "what is inside this?" only has an answer for a closed
1456 /// surface. A flat disc, a torn mesh or a single polygon has no inside, and
1457 /// anything that signs a distance field has to know the difference — sign
1458 /// an open surface and you get whichever side its normals happen to face,
1459 /// which is not a solid, just a preference.
1460 ///
1461 /// Directed, not undirected, because the property that matters is "no
1462 /// boundary, consistently wound", and those are the same test: every edge
1463 /// walked one way by one face and the other way by its neighbour. Counting
1464 /// undirected edges instead would ask for exactly two faces per edge, which
1465 /// is [`is_manifold`](Self::is_manifold) — a stricter thing that a boolean
1466 /// legitimately fails where two sheets pinch together along a knife edge
1467 /// thinner than a voxel. Such a surface is still watertight, still has an
1468 /// inside, and still voxelizes correctly.
1469 pub fn is_closed(&self) -> bool {
1470 if self.num_prims() == 0 {
1471 return false;
1472 }
1473 let mut counts: HashMap<[u32; 2], i32> = HashMap::new();
1474 for prim in 0..self.num_prims() {
1475 let pts = self.prim_points(prim);
1476 if pts.len() < 3 {
1477 return false;
1478 }
1479 for i in 0..pts.len() {
1480 let (a, b) = (pts[i], pts[(i + 1) % pts.len()]);
1481 // One counter per undirected edge, incremented one way and
1482 // decremented the other: it lands on zero exactly when every
1483 // traversal is matched by an opposite one.
1484 let (key, step) = if a < b { ([a, b], 1) } else { ([b, a], -1) };
1485 *counts.entry(key).or_default() += step;
1486 }
1487 }
1488 counts.values().all(|&c| c == 0)
1489 }
1490
1491 /// Whether every edge is shared by exactly two primitives.
1492 ///
1493 /// Stricter than [`is_closed`](Self::is_closed): it also rules out the
1494 /// place where more than two faces meet along one edge. Remeshing wants
1495 /// this — an edge with four faces has no single pair to flip or collapse
1496 /// between — while voxelizing does not.
1497 pub fn is_manifold(&self) -> bool {
1498 if self.num_prims() == 0 {
1499 return false;
1500 }
1501 let mut counts: HashMap<[u32; 2], usize> = HashMap::new();
1502 for prim in 0..self.num_prims() {
1503 let pts = self.prim_points(prim);
1504 if pts.len() < 3 {
1505 return false;
1506 }
1507 for i in 0..pts.len() {
1508 let (a, b) = (pts[i], pts[(i + 1) % pts.len()]);
1509 *counts.entry([a.min(b), a.max(b)]).or_default() += 1;
1510 }
1511 }
1512 counts.values().all(|&c| c == 2)
1513 }
1514
1515 /// The axis-aligned bounds, or `None` when there are no points.
1516 pub fn bounds(&self) -> Option<(Vec3, Vec3)> {
1517 let first = *self.pos.first()?;
1518 let (mut lo, mut hi) = (Vec3::from(first), Vec3::from(first));
1519 for p in &self.pos[1..] {
1520 let v = Vec3::from(*p);
1521 lo = lo.min(v);
1522 hi = hi.max(v);
1523 }
1524 Some((lo, hi))
1525 }
1526
1527 /// Zero every Derivative attribute on every class — the step boundary's
1528 /// first act. See [`AttribKind`].
1529 pub fn clear_derivatives(&mut self) {
1530 self.points.clear_derivatives();
1531 self.verts.clear_derivatives();
1532 self.prims.clear_derivatives();
1533 self.detail.clear_derivatives();
1534 }
1535
1536 /// Restore this geometry's Live point attributes from `prev`, matching
1537 /// points by identity.
1538 ///
1539 /// The other half of the contract, and the one that makes a rebuild safe
1540 /// to put in the middle of a solve. When a step's chain hands back geometry
1541 /// that has lost an attribute — a kernel generator that rebuilt its points,
1542 /// and in Phase 3 a remesh — the values are not gone, they are in the
1543 /// previous state, attached to identities. A point that survived gets its
1544 /// value back; a point that is genuinely new gets the type's zero, which is
1545 /// the only honest answer for a place that did not exist last step.
1546 ///
1547 /// Attributes the new geometry DOES carry are left alone: the chain
1548 /// computed them this step and that is the whole point of running it.
1549 /// Derivative attributes are not restored at all — they are meant to be
1550 /// rebuilt, and carrying one across would be exactly the silent
1551 /// accumulation the kind exists to prevent.
1552 pub fn restore_live_from(&mut self, prev: &Detail) {
1553 // Restoration bridges a REBUILD, not a deletion. If the chain handed
1554 // back the same identities in the same order, it kept the geometry it
1555 // was given — so an attribute that is gone was taken out on purpose,
1556 // and putting it back would override the author. Only when the point
1557 // set itself changed underneath is a missing attribute evidence of
1558 // loss rather than intent.
1559 if self.ids == prev.ids {
1560 return;
1561 }
1562 let missing: Vec<&str> = prev
1563 .points
1564 .names_of_kind(AttribKind::Live)
1565 .into_iter()
1566 .filter(|n| !self.points.has(n))
1567 .collect();
1568 if missing.is_empty() {
1569 return;
1570 }
1571 let was: HashMap<PointId, u32> = prev.id_map();
1572 for name in missing {
1573 let Some(src) = prev.points.get(name) else { continue };
1574 let ty = src.ty();
1575 let mut data = AttribData::zeroed(ty, self.num_points());
1576 for p in 0..self.num_points() {
1577 let Some(id) = self.id(p) else { continue };
1578 let Some(&old) = was.get(&id) else { continue };
1579 if let Some(v) = src.get(old as usize) {
1580 let _ = data.set(p, v);
1581 }
1582 }
1583 let _ = self.points.insert(name, data);
1584 self.points.set_kind(name, AttribKind::Live);
1585 }
1586 }
1587
1588 /// Serialize to a compact binary blob.
1589 ///
1590 /// Hand-rolled rather than derived, because the one thing a cache is for is
1591 /// being cheaper than recomputing: a hundred thousand points of JSON text
1592 /// is not. Positions and attribute arrays go out as raw little-endian
1593 /// floats, which is also how they sit in memory.
1594 ///
1595 /// The derived topology is NOT written — it is rebuilt from the primitives
1596 /// on read, and storing it would mean a file that can disagree with itself.
1597 pub fn to_bytes(&self) -> Vec<u8> {
1598 let mut out = Vec::new();
1599 out.extend_from_slice(DETAIL_MAGIC);
1600 put_u32(&mut out, self.pos.len() as u32);
1601 put_u64(&mut out, self.next_id);
1602 for p in &self.pos {
1603 for c in p {
1604 out.extend_from_slice(&c.to_le_bytes());
1605 }
1606 }
1607 for id in &self.ids {
1608 put_u64(&mut out, *id);
1609 }
1610 put_u32(&mut out, self.vert_point.len() as u32);
1611 for v in &self.vert_point {
1612 put_u32(&mut out, *v);
1613 }
1614 put_u32(&mut out, self.prim_start.len() as u32);
1615 for v in &self.prim_start {
1616 put_u32(&mut out, *v);
1617 }
1618 for store in [&self.points, &self.verts, &self.prims, &self.detail] {
1619 store.write_into(&mut out);
1620 }
1621 out
1622 }
1623
1624 /// Read back a blob written by [`Detail::to_bytes`].
1625 ///
1626 /// Every length is checked against what is actually left in the buffer, so
1627 /// a truncated or corrupt cache file is an error rather than a huge
1628 /// allocation or a panic. A cache lives in a directory anything can write
1629 /// to, and must never be trusted the way a value from memory is.
1630 pub fn from_bytes(bytes: &[u8]) -> Result<Detail, String> {
1631 let mut r = Reader { b: bytes, at: 0 };
1632 if r.take(DETAIL_MAGIC.len())? != DETAIL_MAGIC {
1633 return Err("not a Detail blob".into());
1634 }
1635 let num_points = r.u32()? as usize;
1636 let next_id = r.u64()?;
1637 let mut pos = Vec::with_capacity(num_points.min(1 << 20));
1638 for _ in 0..num_points {
1639 pos.push([r.f32()?, r.f32()?, r.f32()?]);
1640 }
1641 let mut ids = Vec::with_capacity(pos.len());
1642 for _ in 0..num_points {
1643 ids.push(r.u64()?);
1644 }
1645 let nv = r.u32()? as usize;
1646 let mut vert_point = Vec::with_capacity(nv.min(1 << 20));
1647 for _ in 0..nv {
1648 vert_point.push(r.u32()?);
1649 }
1650 let ns = r.u32()? as usize;
1651 let mut prim_start = Vec::with_capacity(ns.min(1 << 20));
1652 for _ in 0..ns {
1653 prim_start.push(r.u32()?);
1654 }
1655 if prim_start.is_empty() {
1656 return Err("primitive offsets are missing their terminator".into());
1657 }
1658 let points = AttribStore::read_from(&mut r)?;
1659 let verts = AttribStore::read_from(&mut r)?;
1660 let prims = AttribStore::read_from(&mut r)?;
1661 let detail = AttribStore::read_from(&mut r)?;
1662
1663 // Cross-checks, because every reader below indexes on these being
1664 // consistent and a corrupt file must not reach that code.
1665 if points.len() != num_points || verts.len() != nv || prims.len() != ns - 1 {
1666 return Err("element counts disagree with their attribute stores".into());
1667 }
1668 if vert_point.iter().any(|&p| p as usize >= num_points.max(1)) && num_points > 0 {
1669 return Err("a vertex references a point that is not there".into());
1670 }
1671 Ok(Detail {
1672 pos,
1673 ids,
1674 next_id,
1675 points,
1676 vert_point,
1677 verts,
1678 prim_start,
1679 prims,
1680 detail,
1681 topo: OnceLock::new(),
1682 })
1683 }
1684
1685 /// Weld a triangle soup into points and triangles: coincident positions
1686 /// become one point, every three positions become one primitive.
1687 ///
1688 /// The migration path for generators that still emit soup, and a faithful
1689 /// port of `geometry::weld_points` — including its 1e-4 quantization, so
1690 /// that vertices a kernel emitted from the same formula weld reliably.
1691 /// Color is carried onto `Cd`, taking the first copy of each welded point.
1692 pub fn from_triangle_soup(positions: &[[f32; 3]], colors: &[[f32; 3]]) -> Detail {
1693 Self::from_triangle_soup_with_map(positions, colors).0
1694 }
1695
1696 /// [`Detail::from_triangle_soup`], plus the point each input corner welded
1697 /// onto — so a caller holding per-corner data can carry it across.
1698 pub fn from_triangle_soup_with_map(
1699 positions: &[[f32; 3]],
1700 colors: &[[f32; 3]],
1701 ) -> (Detail, Vec<u32>) {
1702 let mut detail = Detail::new();
1703 let mut key_to_point: HashMap<(i64, i64, i64), u32> = HashMap::new();
1704 let mut point_of: Vec<u32> = Vec::with_capacity(positions.len());
1705 let mut cd: Vec<[f32; 3]> = Vec::new();
1706
1707 for (i, p) in positions.iter().enumerate() {
1708 let key = (
1709 (p[0] as f64 * 1e4).round() as i64,
1710 (p[1] as f64 * 1e4).round() as i64,
1711 (p[2] as f64 * 1e4).round() as i64,
1712 );
1713 let idx = match key_to_point.get(&key) {
1714 Some(&idx) => idx,
1715 None => {
1716 let idx = detail.add_point(Vec3::from(*p));
1717 key_to_point.insert(key, idx);
1718 cd.push(colors.get(i).copied().unwrap_or(DEFAULT_COLOR));
1719 idx
1720 }
1721 };
1722 point_of.push(idx);
1723 }
1724
1725 for tri in point_of.chunks_exact(3) {
1726 detail.add_prim(tri);
1727 }
1728
1729 if !cd.is_empty() {
1730 detail
1731 .points
1732 .attribs
1733 .insert(CD.to_string(), AttribData::Float3(cd));
1734 }
1735 (detail, point_of)
1736 }
1737
1738 /// The point behind every corner [`Detail::triangulate`] emits, in the
1739 /// same order.
1740 ///
1741 /// Lets a caller that had to flatten to triangles — the OpenCL launcher once,
1742 /// until the Phase 1 ABI binds attributes directly — put results back on
1743 /// the points they came from instead of welding the output and losing
1744 /// every identity.
1745 pub fn triangulate_points(&self) -> Vec<u32> {
1746 let mut out = Vec::new();
1747 for prim in 0..self.num_prims() {
1748 let pts = self.prim_points(prim);
1749 if pts.len() < 3 {
1750 continue;
1751 }
1752 for i in 1..pts.len() - 1 {
1753 out.extend_from_slice(&[pts[0], pts[i], pts[i + 1]]);
1754 }
1755 }
1756 out
1757 }
1758
1759 /// Fan-triangulate every primitive, handing each corner to `make` as
1760 /// (position, color).
1761 ///
1762 /// The render boundary. It takes a closure rather than returning the
1763 /// renderer's vertex type so that this module stays free of anything that
1764 /// draws — the caller in `geometry.rs` supplies `Vertex3D`.
1765 pub fn triangulate<V>(&self, mut make: impl FnMut([f32; 3], [f32; 3]) -> V) -> Vec<V> {
1766 let mut out = Vec::new();
1767 for prim in 0..self.num_prims() {
1768 let pts = self.prim_points(prim);
1769 if pts.len() < 3 {
1770 continue;
1771 }
1772 for i in 1..pts.len() - 1 {
1773 for &p in &[pts[0], pts[i], pts[i + 1]] {
1774 let p = p as usize;
1775 out.push(make(
1776 self.pos.get(p).copied().unwrap_or([0.0; 3]),
1777 self.color(p),
1778 ));
1779 }
1780 }
1781 }
1782 out
1783 }
1784 }