Skip to main content

ifc_lite_geometry/instancing/
wire.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use super::collate::{collate_refs, Collated, InstanceMeshRef};
6use crate::mesh::Mesh;
7
8// ----------------------------------------------------------------------------
9// Instanced wire format
10// ----------------------------------------------------------------------------
11//
12// Little-endian, mirroring the packed-shard conventions (header + tables +
13// pooled data) but carrying UNIQUE template geometry once + a per-occurrence
14// instance table, so the renderer uploads each template once and
15// `drawIndexed(.., instanceCount)`. This Rust encoder/decoder is the spec the TS
16// decoder mirrors. Flat (non-instanced) meshes are emitted as singleton
17// templates (one identity instance) so every input mesh is represented uniformly.
18//
19// Layout:
20//   Header (8 u32): magic, version, templateCount, instanceCount,
21//                   positionsLen, normalsLen, indicesLen, reserved
22//   Template table (templateCount × 48 bytes): posOff,posLen,nrmOff,nrmLen,
23//                   idxOff,idxLen (6× u32) then originX,originY,originZ (3× f64)
24//   Instance table (instanceCount × 88 bytes): templateIndex(u32), entityId(u32),
25//                   color(4× f32), transform(16× f32, row-major rel_k)
26//   Data: positions (f32 × positionsLen), normals (f32 × normalsLen),
27//         indices (u32 × indicesLen). Offsets/lengths are ELEMENT counts; indices
28//         stay local to each template's vertex range (0-based).
29
30/// `"IFNS"` little-endian — the instanced-shard magic the TS decoder validates.
31pub const INSTANCED_MAGIC: u32 = 0x4946_4E53;
32/// Instanced format version. Bump in lockstep with the TS decoder.
33pub const INSTANCED_VERSION: u32 = 1;
34
35const INST_IDENTITY_F32: [f32; 16] = [
36    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
37];
38
39/// A unique geometry decoded from an instanced shard.
40#[derive(Debug, Clone)]
41pub struct DecodedTemplate {
42    pub positions: Vec<f32>,
43    pub normals: Vec<f32>,
44    pub indices: Vec<u32>,
45    /// Per-template local origin (f64); world vertex = transform · (origin + position).
46    pub origin: [f64; 3],
47}
48
49/// One occurrence of a decoded template.
50#[derive(Debug, Clone)]
51pub struct DecodedInstance {
52    pub template_index: u32,
53    pub entity_id: u32,
54    pub color: [f32; 4],
55    /// Row-major mat4 mapping the template's world geometry onto this occurrence.
56    pub transform: [f32; 16],
57}
58
59/// A decoded instanced shard.
60#[derive(Debug, Clone, Default)]
61pub struct DecodedInstanced {
62    pub templates: Vec<DecodedTemplate>,
63    pub instances: Vec<DecodedInstance>,
64}
65
66/// Encode a [`Collated`] result + its source mesh views into an instanced shard.
67/// Per-occurrence entity id + colour come from each `InstanceMeshRef`.
68pub fn encode_refs(meshes: &[InstanceMeshRef], collated: &Collated) -> Vec<u8> {
69    // (template mesh index, [(occurrence mesh index, rel transform)]).
70    struct TSpec {
71        mesh_idx: usize,
72        instances: Vec<(usize, [f32; 16])>,
73    }
74    let mut tspecs: Vec<TSpec> = Vec::with_capacity(collated.templates.len() + collated.flat_indices.len());
75    for t in &collated.templates {
76        tspecs.push(TSpec {
77            mesh_idx: t.template_index,
78            instances: t.occurrences.iter().map(|o| (o.mesh_index, o.transform)).collect(),
79        });
80    }
81    for &f in &collated.flat_indices {
82        tspecs.push(TSpec {
83            mesh_idx: f,
84            instances: vec![(f, INST_IDENTITY_F32)],
85        });
86    }
87
88    let template_count = tspecs.len();
89    let instance_count: usize = tspecs.iter().map(|t| t.instances.len()).sum();
90    let positions_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].positions.len()).sum();
91    let normals_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].normals.len()).sum();
92    let indices_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].indices.len()).sum();
93
94    // Wire offsets/lengths are u32 (header + template records). A pool exceeding
95    // u32::MAX elements (>16GB of positions in ONE shard) would wrap SILENTLY and
96    // corrupt template lookups. Fail loudly instead — the caller must chunk shards
97    // below this (real instanced shards are <<1GB; this is an impossible-scale
98    // backstop, not a normal limit).
99    assert!(
100        positions_len <= u32::MAX as usize
101            && normals_len <= u32::MAX as usize
102            && indices_len <= u32::MAX as usize
103            && template_count <= u32::MAX as usize
104            && instance_count <= u32::MAX as usize,
105        "instanced shard exceeds u32 wire limits (pos={positions_len} idx={indices_len}); chunk it"
106    );
107
108    let mut buf: Vec<u8> = Vec::with_capacity(
109        32 + template_count * 48 + instance_count * 88 + (positions_len + normals_len + indices_len) * 4,
110    );
111    let pu32 = |b: &mut Vec<u8>, v: u32| b.extend_from_slice(&v.to_le_bytes());
112    let pf32 = |b: &mut Vec<u8>, v: f32| b.extend_from_slice(&v.to_le_bytes());
113    let pf64 = |b: &mut Vec<u8>, v: f64| b.extend_from_slice(&v.to_le_bytes());
114
115    // Header.
116    pu32(&mut buf, INSTANCED_MAGIC);
117    pu32(&mut buf, INSTANCED_VERSION);
118    pu32(&mut buf, template_count as u32);
119    pu32(&mut buf, instance_count as u32);
120    pu32(&mut buf, positions_len as u32);
121    pu32(&mut buf, normals_len as u32);
122    pu32(&mut buf, indices_len as u32);
123    pu32(&mut buf, 0);
124
125    // Template table (running element offsets into the pooled data arrays).
126    let (mut pos_off, mut nrm_off, mut idx_off) = (0u32, 0u32, 0u32);
127    for t in &tspecs {
128        let m = &meshes[t.mesh_idx];
129        pu32(&mut buf, pos_off);
130        pu32(&mut buf, m.positions.len() as u32);
131        pu32(&mut buf, nrm_off);
132        pu32(&mut buf, m.normals.len() as u32);
133        pu32(&mut buf, idx_off);
134        pu32(&mut buf, m.indices.len() as u32);
135        pf64(&mut buf, m.origin[0]);
136        pf64(&mut buf, m.origin[1]);
137        pf64(&mut buf, m.origin[2]);
138        pos_off += m.positions.len() as u32;
139        nrm_off += m.normals.len() as u32;
140        idx_off += m.indices.len() as u32;
141    }
142
143    // Instance table.
144    for (ti, t) in tspecs.iter().enumerate() {
145        for (occ_idx, transform) in &t.instances {
146            pu32(&mut buf, ti as u32);
147            pu32(&mut buf, meshes[*occ_idx].entity_id);
148            for c in meshes[*occ_idx].color {
149                pf32(&mut buf, c);
150            }
151            for v in transform {
152                pf32(&mut buf, *v);
153            }
154        }
155    }
156
157    // Data pools.
158    for t in &tspecs {
159        for &p in meshes[t.mesh_idx].positions {
160            pf32(&mut buf, p);
161        }
162    }
163    for t in &tspecs {
164        for &n in meshes[t.mesh_idx].normals {
165            pf32(&mut buf, n);
166        }
167    }
168    for t in &tspecs {
169        for &i in meshes[t.mesh_idx].indices {
170            pu32(&mut buf, i);
171        }
172    }
173    buf
174}
175
176/// `encode_refs` over geometry `Mesh` values, with id/colour accessor closures
177/// (thin wrapper, no geometry clone).
178pub fn encode_instanced(
179    meshes: &[Mesh],
180    collated: &Collated,
181    entity_id: impl Fn(usize) -> u32,
182    color: impl Fn(usize) -> [f32; 4],
183) -> Vec<u8> {
184    let refs: Vec<InstanceMeshRef> = meshes
185        .iter()
186        .enumerate()
187        .map(|(i, m)| {
188            let mut r = InstanceMeshRef::from_mesh(m);
189            r.entity_id = entity_id(i);
190            r.color = color(i);
191            r
192        })
193        .collect();
194    encode_refs(&refs, collated)
195}
196
197/// One-shot producer: collate the mesh views into templates + instances and
198/// encode them as an instanced shard. The caller (e.g. the native helper) builds
199/// `InstanceMeshRef`s borrowing its own mesh storage — no geometry is cloned.
200pub fn collate_and_encode(meshes: &[InstanceMeshRef], min_group: usize, rtc: [f64; 3]) -> Vec<u8> {
201    let collated = collate_refs(meshes, min_group, rtc);
202    encode_refs(meshes, &collated)
203}
204
205/// Decode an instanced shard. Returns None on a bad magic/version or truncation.
206pub fn decode_instanced(bytes: &[u8]) -> Option<DecodedInstanced> {
207    let ru32 = |o: usize| -> Option<u32> {
208        bytes.get(o..o + 4).map(|s| u32::from_le_bytes(s.try_into().unwrap()))
209    };
210    let rf32 = |o: usize| -> Option<f32> {
211        bytes.get(o..o + 4).map(|s| f32::from_le_bytes(s.try_into().unwrap()))
212    };
213    let rf64 = |o: usize| -> Option<f64> {
214        bytes.get(o..o + 8).map(|s| f64::from_le_bytes(s.try_into().unwrap()))
215    };
216    if ru32(0)? != INSTANCED_MAGIC || ru32(4)? != INSTANCED_VERSION {
217        return None;
218    }
219    let template_count = ru32(8)? as usize;
220    let instance_count = ru32(12)? as usize;
221    let positions_len = ru32(16)? as usize;
222    let normals_len = ru32(20)? as usize;
223    let _indices_len = ru32(24)? as usize;
224
225    let tt_off = 32;
226    let it_off = tt_off + template_count * 48;
227    let data_off = it_off + instance_count * 88;
228    let nrm_data = data_off + positions_len * 4;
229    let idx_data = nrm_data + normals_len * 4;
230
231    // A corrupt/hostile header can claim an arbitrary template_count or
232    // instance_count. Bound both against the buffer we actually have BEFORE
233    // sizing `Vec::with_capacity` below — otherwise a bogus huge count tries
234    // to reserve gigabytes (or aborts the process via the allocator's OOM
235    // handler) long before the per-field `ru32`/`rf32` reads below would ever
236    // get a chance to fail gracefully and return `None`.
237    if bytes.len() < data_off {
238        return None;
239    }
240
241    let mut templates = Vec::with_capacity(template_count);
242    for t in 0..template_count {
243        let r = tt_off + t * 48;
244        let pos_off = ru32(r)? as usize;
245        let pos_len = ru32(r + 4)? as usize;
246        let nrm_off = ru32(r + 8)? as usize;
247        let nrm_len = ru32(r + 12)? as usize;
248        let i_off = ru32(r + 16)? as usize;
249        let i_len = ru32(r + 20)? as usize;
250        let origin = [rf64(r + 24)?, rf64(r + 32)?, rf64(r + 40)?];
251        let positions = (0..pos_len)
252            .map(|k| rf32(data_off + (pos_off + k) * 4))
253            .collect::<Option<Vec<f32>>>()?;
254        let normals = (0..nrm_len)
255            .map(|k| rf32(nrm_data + (nrm_off + k) * 4))
256            .collect::<Option<Vec<f32>>>()?;
257        let indices = (0..i_len)
258            .map(|k| ru32(idx_data + (i_off + k) * 4))
259            .collect::<Option<Vec<u32>>>()?;
260        templates.push(DecodedTemplate { positions, normals, indices, origin });
261    }
262
263    let mut instances = Vec::with_capacity(instance_count);
264    for i in 0..instance_count {
265        let r = it_off + i * 88;
266        let template_index = ru32(r)?;
267        let entity_id = ru32(r + 4)?;
268        let mut color = [0.0f32; 4];
269        for (k, c) in color.iter_mut().enumerate() {
270            *c = rf32(r + 8 + k * 4)?;
271        }
272        let mut transform = [0.0f32; 16];
273        for (k, v) in transform.iter_mut().enumerate() {
274            *v = rf32(r + 24 + k * 4)?;
275        }
276        instances.push(DecodedInstance { template_index, entity_id, color, transform });
277    }
278    Some(DecodedInstanced { templates, instances })
279}