git.lucas.co / cce-designer
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 }