Skip to main content

unity_formats/
mesh.rs

1//! Unity meshes: vertex channels in interleaved streams (decoded through checked vertex layouts), or a compressed
2//! mesh (bit-packed quantised floats); an index buffer of 16- or 32-bit indices; submeshes.
3//!
4//! Vertex data layout (Unity 5 and later): the channels name a stream, an offset in it, a format and a dimension; a
5//! stream's stride is the sum of its channels' sizes, and the streams follow each other, each starting 16-byte
6//! aligned. The bytes are in the mesh, or streamed from a resource file (`m_StreamData`).
7//!
8//! A compressed mesh's floats are `PackedBitVector`s: `n` items of `bits` bits each, little-endian bit order, each
9//! `start + item * (range / (2^bits - 1))` in f32; normals and tangents keep x and y, z is rebuilt from the unit
10//! length with a stored sign; its indices are packed integers (`m_Triangles`).
11
12use half::f16;
13use primase_foundations::vertex::{
14    Element, Format, Stream as VertexStream, decode_stream_raw, resolve_layout,
15};
16
17use crate::player::{ObjRef, Player, SkinWeights};
18use crate::resource::StreamingInfo;
19use crate::value::{Array, Value};
20use crate::{Error, Result, invalid};
21
22/// Unity's shader channels, in the order of `m_Channels`.
23pub const CHANNELS: [&str; 14] = [
24    "Vertex",
25    "Normal",
26    "Tangent",
27    "Color",
28    "TexCoord0",
29    "TexCoord1",
30    "TexCoord2",
31    "TexCoord3",
32    "TexCoord4",
33    "TexCoord5",
34    "TexCoord6",
35    "TexCoord7",
36    "BlendWeight",
37    "BlendIndices",
38];
39
40/// One decoded channel: `dim` components per vertex.
41#[derive(Clone, Debug, PartialEq)]
42pub struct Channel {
43    /// Components per vertex.
44    pub dim: usize,
45    /// `dim` floats for each vertex, vertex after vertex.
46    pub values: Vec<f32>,
47}
48
49impl Channel {
50    /// Vertex `i`'s components; `None` when `i` is not a vertex of the channel.
51    pub fn vertex(&self, i: usize) -> Option<&[f32]> {
52        let start = i.checked_mul(self.dim)?;
53        self.values.get(start..start.checked_add(self.dim)?)
54    }
55}
56
57/// A submesh: a run of the index buffer.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct SubMesh {
60    /// Into the index buffer, in bytes.
61    pub first_byte: u32,
62    /// How many indices the run holds.
63    pub index_count: u32,
64    /// Unity's MeshTopology (0: triangles).
65    pub topology: i64,
66    /// `baseVertex` as stored: the vertex the run's indices count from.
67    pub base_vertex: u32,
68}
69
70/// Bone influences per vertex, as a mesh stores them: vertex `v`'s weights and bone slots are
71/// `weights[starts[v]..starts[v + 1]]` and `bones[..]`, heaviest first.
72#[derive(Clone, Debug, Default, PartialEq)]
73pub struct Blend {
74    /// Where each vertex's influences start in `weights` and `bones`: one entry more than there are vertices, the
75    /// last the total.
76    pub starts: Vec<u32>,
77    /// The influences' weights, vertex after vertex.
78    pub weights: Vec<f32>,
79    /// The influences' bone slots, parallel to `weights`.
80    pub bones: Vec<u32>,
81}
82
83impl Blend {
84    /// `influences` (at most four) to a vertex from lists padded to four with zero weights.
85    pub fn from_padded(influences: usize, weights: &[[f32; 4]], bones: &[[u32; 4]]) -> Blend {
86        let influences = influences.min(4);
87        Blend {
88            starts: (0..=weights.len())
89                .map(|v| u32::try_from(v.saturating_mul(influences)).unwrap_or(u32::MAX))
90                .collect(),
91            weights: weights
92                .iter()
93                .flat_map(|w| w.iter().take(influences).copied())
94                .collect(),
95            bones: bones
96                .iter()
97                .flat_map(|b| b.iter().take(influences).copied())
98                .collect(),
99        }
100    }
101
102    /// How many vertices the blend covers.
103    pub fn vertices(&self) -> usize {
104        self.starts.len().saturating_sub(1)
105    }
106
107    /// Vertex `v`'s weights and bone slots; `None` when `v` is not a vertex of the blend or its range is not inside
108    /// the lists.
109    pub fn vertex(&self, v: usize) -> Option<(&[f32], &[u32])> {
110        let start = usize::try_from(*self.starts.get(v)?).ok()?;
111        let end = usize::try_from(*self.starts.get(v.checked_add(1)?)?).ok()?;
112        Some((self.weights.get(start..end)?, self.bones.get(start..end)?))
113    }
114
115    /// The most influences any vertex has.
116    pub fn max_influences(&self) -> usize {
117        self.starts
118            .iter()
119            .zip(self.starts.iter().skip(1))
120            .map(|(&a, &b)| usize::try_from(b.saturating_sub(a)).unwrap_or(usize::MAX))
121            .max()
122            .unwrap_or(0)
123    }
124}
125
126/// A skin's influences as sets of four per vertex (`[set][vertex]`): the weights, the bone slots and the number of
127/// vertices cut to a limit ([`MeshSkin::padded`]).
128pub type PaddedSets = (Vec<Vec<[f32; 4]>>, Vec<Vec<[u32; 4]>>, u64);
129
130/// A mesh's skin as stored, in Unity's frame: the blend channels (`BlendWeight` and `BlendIndices`: up to four
131/// influences a vertex, as Unity draws them at four or fewer), the full influences of a mesh that stores them
132/// (`m_VariableBoneCountWeights`, which Unity draws only at unlimited skin weights; the channels then hold its first
133/// four renormalised), and per bone the bind pose, the matrix taking the renderer's local space to the bone's at bind
134/// time.
135#[derive(Clone, Debug, PartialEq)]
136pub struct MeshSkin {
137    /// Bone slots are slots of the renderer's bone list (`m_Bones`); a mesh with indices but no weights channel binds
138    /// each vertex to its one bone wholly.
139    pub channel: Blend,
140    /// The full influences of a mesh that stores more than four a vertex (`m_VariableBoneCountWeights`); `None` for a
141    /// mesh that does not.
142    pub unlimited: Option<Blend>,
143    /// `m_BindPose`, row-major (`[row][column]`, the translation in column 3).
144    pub bind_poses: Vec<[[f64; 4]; 4]>,
145}
146
147impl MeshSkin {
148    /// The blend `limit` draws and the most influences a vertex keeps.
149    fn drawn(&self, limit: SkinWeights) -> (&Blend, usize) {
150        match limit {
151            SkinWeights::Unlimited => (self.unlimited.as_ref().unwrap_or(&self.channel), usize::MAX),
152            SkinWeights::Bones(n) => (&self.channel, usize::try_from(n).unwrap_or(usize::MAX)),
153        }
154    }
155
156    /// What `limit` makes of this skin: whether the full influences are drawn, and the most influences a vertex keeps.
157    /// Two limits with the same outcome draw the same mesh.
158    pub fn outcome(&self, limit: SkinWeights) -> (bool, usize) {
159        let (blend, keep) = self.drawn(limit);
160        (
161            std::ptr::eq(blend, &self.channel),
162            blend.max_influences().min(keep),
163        )
164    }
165
166    /// How many sets of four influences a vertex has under `limit`: at least one.
167    pub fn sets(&self, limit: SkinWeights) -> usize {
168        self.outcome(limit).1.div_ceil(4).max(1)
169    }
170
171    /// The influences `limit` draws as sets of four per vertex (weights and bone slots, zero-padded); a vertex with
172    /// more influences than the limit keeps its first ones renormalised to sum to 1. Also the vertices cut so.
173    pub fn padded(&self, limit: SkinWeights) -> PaddedSets {
174        let (blend, keep) = self.drawn(limit);
175        let n = blend.vertices();
176        let sets = self.sets(limit);
177        let mut weights = vec![vec![[0.0f32; 4]; n]; sets];
178        let mut bones = vec![vec![[0u32; 4]; n]; sets];
179        let mut cut = 0u64;
180        for v in 0..n {
181            let Some((w, b)) = blend.vertex(v) else { continue };
182            let kept = w.len().min(keep);
183            let divisor = if kept < w.len() {
184                cut = cut.saturating_add(1);
185                Some(w.iter().take(kept).sum::<f32>()).filter(|sum| *sum > 0.0)
186            } else {
187                None
188            };
189            for (i, (&weight, &bone)) in w.iter().zip(b).take(kept).enumerate() {
190                let (set, slot) = (i.div_euclid(4), i.rem_euclid(4));
191                if let Some(x) = weights
192                    .get_mut(set)
193                    .and_then(|s| s.get_mut(v))
194                    .and_then(|q| q.get_mut(slot))
195                {
196                    *x = divisor.map_or(weight, |sum| weight / sum);
197                }
198                if let Some(x) = bones
199                    .get_mut(set)
200                    .and_then(|s| s.get_mut(v))
201                    .and_then(|q| q.get_mut(slot))
202                {
203                    *x = bone;
204                }
205            }
206        }
207        (weights, bones, cut)
208    }
209}
210
211/// The blend channels of an uncompressed mesh: the influences stored a vertex and each vertex's weights and bone
212/// slots, padded to four.
213type BlendChannels = (usize, Vec<[f32; 4]>, Vec<[u32; 4]>);
214
215/// A mesh as stored, in Unity's frame.
216#[derive(Clone, Debug, PartialEq)]
217pub struct UnityMeshData {
218    /// `m_Name`.
219    pub name: String,
220    /// How many vertices the mesh has.
221    pub vertex_count: usize,
222    /// By shader channel (see [`CHANNELS`]); the blend weights and indices are in [`UnityMeshData::skin`].
223    pub channels: [Option<Channel>; 12],
224    /// The mesh's skin: its blend weights and indices with the bind poses; `None` for a mesh without blend data.
225    pub skin: Option<MeshSkin>,
226    /// Whether the vertices were stored as a `CompressedMesh` (bit-packed) rather than as vertex streams.
227    pub compressed: bool,
228    /// The mesh's blend shapes; `None` for a mesh without any.
229    pub shapes: Option<MeshShapes>,
230    /// The index buffer, decoded.
231    pub indices: Vec<u32>,
232    /// The width in bytes of an index in the stored index buffer (2 or 4), which a submesh's `first_byte` counts in;
233    /// `indices` holds them widened to `u32`.
234    pub index_size: u32,
235    /// The submeshes, in order.
236    pub submeshes: Vec<SubMesh>,
237}
238
239/// One blend shape channel (`m_Shapes.channels`): a name, the CRC-32 of it that an animation curve names it by, and its
240/// frames (`frames` of them from `first_frame` in [`MeshShapes::frames`]).
241#[derive(Clone, Debug, PartialEq)]
242pub struct ShapeChannel {
243    /// The channel's name.
244    pub name: String,
245    /// The channel name's CRC-32, which an animation curve names the channel by.
246    pub name_hash: u32,
247    /// Index of the channel's first frame in [`MeshShapes::frames`].
248    pub first_frame: usize,
249    /// How many frames the channel has.
250    pub frames: usize,
251}
252
253/// One frame of a blend shape (`m_Shapes.shapes`, its `fullWeights` entry): the weight it is at, its entries
254/// (`count` from `first` in [`MeshShapes::vertices`]) and whether they carry normal and tangent deltas.
255#[derive(Clone, Debug, PartialEq)]
256pub struct ShapeFrame {
257    /// The weight the frame is at (`fullWeights`).
258    pub weight: f32,
259    /// Index of the frame's first entry in [`MeshShapes::vertices`].
260    pub first: usize,
261    /// How many entries the frame has.
262    pub count: usize,
263    /// Whether the entries carry normal deltas (`hasNormals`).
264    pub normals: bool,
265    /// Whether the entries carry tangent deltas (`hasTangents`).
266    pub tangents: bool,
267}
268
269/// A blend shape entry (`m_Shapes.vertices`): the delta of vertex `index` of the mesh, in Unity's frame.
270#[derive(Clone, Debug, PartialEq)]
271pub struct ShapeVertex {
272    /// The vertex of the mesh the delta applies to.
273    pub index: u32,
274    /// The position delta.
275    pub position: [f32; 3],
276    /// The normal delta (zero unless the frame has normals).
277    pub normal: [f32; 3],
278    /// The tangent delta (zero unless the frame has tangents).
279    pub tangent: [f32; 3],
280}
281
282/// A mesh's blend shapes as stored (`m_Shapes`): per channel its frames in increasing weight (Unity's weights run 0 to
283/// 100), per frame sparse deltas of the mesh's vertices, applied to the mesh as stored before any skin.
284#[derive(Clone, Debug, PartialEq)]
285pub struct MeshShapes {
286    /// The channels (`m_Shapes.channels`).
287    pub channels: Vec<ShapeChannel>,
288    /// Every channel's frames, one after another (`m_Shapes.shapes` with `fullWeights`).
289    pub frames: Vec<ShapeFrame>,
290    /// The frames' entries, one frame after another (`m_Shapes.vertices`).
291    pub vertices: Vec<ShapeVertex>,
292}
293
294fn vec3(v: &Value, k: &str, what: &str) -> Result<[f32; 3]> {
295    let c = v.get(k).ok_or_else(|| invalid(format!("{what}: no {k}")))?;
296    let f = |a: &str| {
297        c.get(a)
298            .and_then(Value::as_f32)
299            .ok_or_else(|| invalid(format!("{what}: {k} without {a}")))
300    };
301    Ok([f("x")?, f("y")?, f("z")?])
302}
303
304/// The blend shapes of a mesh (`m_Shapes`) with `vertex_count` vertices; `None` where it has no channel.
305fn read_shapes(m: &Value, name: &str, vertex_count: usize) -> Result<Option<MeshShapes>> {
306    let Some(s) = m.get("m_Shapes") else {
307        return Ok(None);
308    };
309    let list = |k: &str| -> Result<&[Value]> {
310        match s.get(k) {
311            Some(Value::Array(Array::Values(l))) => Ok(l.as_slice()),
312            Some(Value::Array(a)) if a.is_empty() => Ok(&[]),
313            _ => Err(invalid(format!("{name}: no m_Shapes.{k}"))),
314        }
315    };
316    let channels = list("channels")?;
317    if channels.is_empty() {
318        return Ok(None);
319    }
320    let full_weights: &[f32] = match s.get("fullWeights") {
321        Some(Value::Array(Array::F32(w))) => w,
322        Some(Value::Array(a)) if a.is_empty() => &[],
323        _ => return Err(invalid(format!("{name}: no m_Shapes.fullWeights"))),
324    };
325    let stored = list("vertices")?;
326    let shapes = list("shapes")?;
327    if full_weights.len() != shapes.len() {
328        return Err(invalid(format!(
329            "{name}: {} frame weights for {} blend shape frames",
330            full_weights.len(),
331            shapes.len()
332        )));
333    }
334    let what = format!("{name}: blend shapes");
335    let vertices = stored
336        .iter()
337        .map(|v| {
338            let index = int(v, "index")?;
339            let Some(slot) = usize::try_from(index).ok().filter(|&i| i < vertex_count) else {
340                return Err(invalid(format!("{what}: vertex {index} of {vertex_count}")));
341            };
342            let entry = ShapeVertex {
343                index: u32::try_from(slot)
344                    .map_err(|_| invalid(format!("{what}: vertex {index} of {vertex_count}")))?,
345                position: vec3(v, "vertex", &what)?,
346                normal: vec3(v, "normal", &what)?,
347                tangent: vec3(v, "tangent", &what)?,
348            };
349            if [entry.position, entry.normal, entry.tangent]
350                .iter()
351                .flatten()
352                .any(|x| !x.is_finite())
353            {
354                return Err(Error::Unsupported(format!(
355                    "{what}: a delta that is not finite (vertex {index})"
356                )));
357            }
358            Ok(entry)
359        })
360        .collect::<Result<Vec<_>>>()?;
361    let frames = shapes
362        .iter()
363        .zip(full_weights)
364        .map(|(f, &weight)| {
365            let range = run(
366                int(f, "firstVertex")?,
367                int(f, "vertexCount")?,
368                vertices.len(),
369                &what,
370            )?;
371            let flag = |k: &str| {
372                f.get(k)
373                    .and_then(Value::as_bool)
374                    .ok_or_else(|| invalid(format!("{what}: no {k}")))
375            };
376            Ok(ShapeFrame {
377                weight,
378                first: range.start,
379                count: range.len(),
380                normals: flag("hasNormals")?,
381                tangents: flag("hasTangents")?,
382            })
383        })
384        .collect::<Result<Vec<_>>>()?;
385    let channels = channels
386        .iter()
387        .map(|c| {
388            let range = run(int(c, "frameIndex")?, int(c, "frameCount")?, frames.len(), &what)?;
389            let own = frames
390                .get(range.clone())
391                .filter(|own| !own.is_empty())
392                .ok_or_else(|| invalid(format!("{what}: a channel of {} frames", range.len())))?;
393            if !own
394                .iter()
395                .zip(own.iter().skip(1))
396                .all(|(a, b)| a.weight < b.weight)
397                || own.iter().any(|f| !f.weight.is_finite() || f.weight == 0.0)
398            {
399                return Err(Error::Unsupported(format!(
400                    "{what}: frame weights not increasing or zero"
401                )));
402            }
403            Ok(ShapeChannel {
404                name: c.get("name").and_then(Value::as_str).unwrap_or("").to_owned(),
405                name_hash: uint(c, "nameHash", &what)?,
406                first_frame: range.start,
407                frames: range.len(),
408            })
409        })
410        .collect::<Result<Vec<_>>>()?;
411    Ok(Some(MeshShapes {
412        channels,
413        frames,
414        vertices,
415    }))
416}
417
418fn int(v: &Value, k: &str) -> Result<i64> {
419    v.get(k)
420        .and_then(Value::as_i64)
421        .ok_or_else(|| invalid(format!("mesh: no integer {k}")))
422}
423
424/// The integer field `k` of `v` as a `u32`: a stored value outside 0..=2^32-1 is refused, not wrapped.
425fn uint(v: &Value, k: &str, what: &str) -> Result<u32> {
426    let n = int(v, k)?;
427    u32::try_from(n).map_err(|_| invalid(format!("{what}: {k} {n} is not a 32-bit unsigned number")))
428}
429
430/// The run `first..first + count` of a list of `len` entries, from stored integers: negative numbers, a sum that
431/// overflows and a run past the list are refused.
432fn run(first: i64, count: i64, len: usize, what: &str) -> Result<std::ops::Range<usize>> {
433    first
434        .checked_add(count)
435        .and_then(|end| Some(usize::try_from(first).ok()?..usize::try_from(end).ok()?))
436        .filter(|r| r.end <= len && count >= 0)
437        .ok_or_else(|| invalid(format!("{what}: entries {first}+{count} of {len}")))
438}
439
440fn bytes<'a>(v: &'a Value, k: &str) -> Result<&'a [u8]> {
441    match v.get(k) {
442        Some(Value::Array(Array::U8(b))) => Ok(b),
443        _ => Err(invalid(format!("mesh: no bytes {k}"))),
444    }
445}
446
447/// Unity's VertexFormat component size.
448fn component_size(format: i64) -> Result<usize> {
449    Ok(match format {
450        0 | 10 | 11 => 4,
451        1 | 4 | 5 | 8 | 9 => 2,
452        2 | 3 | 6 | 7 => 1,
453        f => return Err(invalid(format!("vertex format {f}"))),
454    })
455}
456
457/// A Unity (format, dimension) as a vertex layout format.
458fn vertex_format(format: i64, dim: usize) -> Option<Format> {
459    Some(match (format, dim) {
460        (0, 2) => Format::Float2,
461        (0, 3) => Format::Float3,
462        (0, 4) => Format::Float4,
463        (1, 2) => Format::Half2,
464        (1, 4) => Format::Half4,
465        (2, 4) => Format::Unorm8x4,
466        (4, 2) => Format::Unorm16x2,
467        (5, 2) => Format::Snorm16x2,
468        (5, 4) => Format::Snorm16x4,
469        _ => return None,
470    })
471}
472
473/// A PackedBitVector's items as integers.
474fn unpack_ints(v: &Value, start: usize, count: usize) -> Result<Vec<u32>> {
475    let bits = int(v, "m_BitSize")?;
476    let bits = usize::try_from(bits)
477        .ok()
478        .filter(|&b| b <= 32)
479        .ok_or_else(|| invalid(format!("packed bit size {bits}")))?;
480    let data = bytes(v, "m_Data")?;
481    if bits == 0 {
482        if count > MAX_PACKED_ITEMS {
483            return Err(invalid(format!(
484                "{count} packed items of no bits (at most {MAX_PACKED_ITEMS})"
485            )));
486        }
487        return Ok(vec![0; count]);
488    }
489    let end_bit = start
490        .checked_add(count)
491        .and_then(|n| n.checked_mul(bits))
492        .ok_or_else(|| invalid(format!("packed items {start}+{count} of {bits} bits")))?;
493    if end_bit.div_ceil(8) > data.len() {
494        return Err(invalid(format!(
495            "packed items {start}+{count} of {bits} bits past {} bytes",
496            data.len()
497        )));
498    }
499    let mut out = Vec::with_capacity(count);
500    // an item starts at `bit` and, with its offset in the first byte, spans at most five bytes
501    let mask = shl(1, bits).saturating_sub(1);
502    let mut bit = start.saturating_mul(bits);
503    for _ in 0..count {
504        let (first, offset) = (bit / 8, bit % 8);
505        let span = offset.saturating_add(bits).div_ceil(8);
506        let window = first
507            .checked_add(span)
508            .and_then(|end| data.get(first..end))
509            .ok_or_else(|| {
510                invalid(format!(
511                    "packed item at bit {bit} of {bits} bits past {} bytes",
512                    data.len()
513                ))
514            })?;
515        let word = window
516            .iter()
517            .enumerate()
518            .fold(0u64, |w, (i, &b)| w | shl(u64::from(b), i.saturating_mul(8)));
519        let value = shr(word, offset) & mask;
520        out.push(u32::try_from(value).unwrap_or(u32::MAX));
521        bit = bit.saturating_add(bits);
522    }
523    Ok(out)
524}
525
526/// `x << n`, 0 when `n` shifts every bit out.
527fn shl(x: u64, n: usize) -> u64 {
528    u32::try_from(n).ok().and_then(|n| x.checked_shl(n)).unwrap_or(0)
529}
530
531/// `x >> n`, 0 when `n` shifts every bit out.
532fn shr(x: u64, n: usize) -> u64 {
533    u32::try_from(n).ok().and_then(|n| x.checked_shr(n)).unwrap_or(0)
534}
535
536/// A PackedBitVector's items as floats: `start + item * (range / (2^bits - 1))`, in f32.
537fn unpack_floats(v: &Value, start: usize, count: usize) -> Result<Vec<f32>> {
538    let bits = int(v, "m_BitSize")?;
539    let bits = u32::try_from(bits)
540        .ok()
541        .filter(|&b| b <= 32)
542        .ok_or_else(|| invalid(format!("packed bit size {bits}")))?;
543    let range = v
544        .get("m_Range")
545        .and_then(Value::as_f32)
546        .ok_or_else(|| invalid("packed floats: no m_Range"))?;
547    let first = v
548        .get("m_Start")
549        .and_then(Value::as_f32)
550        .ok_or_else(|| invalid("packed floats: no m_Start"))?;
551    if bits == 0 {
552        if count > MAX_PACKED_ITEMS {
553            return Err(invalid(format!(
554                "{count} packed items of no bits (at most {MAX_PACKED_ITEMS})"
555            )));
556        }
557        return Ok(vec![first; count]);
558    }
559    let scale = range / shl(1, usize::try_from(bits).unwrap_or(0)).saturating_sub(1) as f32;
560    Ok(unpack_ints(v, start, count)?
561        .into_iter()
562        .map(|x| x as f32 * scale + first)
563        .collect())
564}
565
566/// Channel `i` (0 to 11) of a mesh set to `channel`.
567fn set_channel(channels: &mut [Option<Channel>; 12], i: usize, channel: Channel) -> Result<()> {
568    *channels
569        .get_mut(i)
570        .ok_or_else(|| invalid(format!("mesh: channel {i} of 12")))? = Some(channel);
571    Ok(())
572}
573
574fn items(v: &Value) -> Result<usize> {
575    let n = int(v, "m_NumItems")?;
576    usize::try_from(n).map_err(|_| invalid(format!("mesh: m_NumItems of {n}")))
577}
578
579/// The most items one packed vector may state when it needs no bytes (a bit size of 0 stores none): 2^26, which is 4
580/// floats of each of 16 million vertices. The counts of vectors that do store bits are bounded by their bytes.
581const MAX_PACKED_ITEMS: usize = 1 << 26;
582
583fn check_vertices(name: &str, n: usize) -> Result<()> {
584    if n > MAX_VERTICES {
585        return Err(Error::Unsupported(format!(
586            "{name}: {n} vertices (at most {MAX_VERTICES})"
587        )));
588    }
589    Ok(())
590}
591
592/// The most vertices a mesh may state. A mesh of this many vertices has 16 million positions at least 12 bytes each;
593/// the bound keeps a state of 2^31 from allocating a vertex-sized array before any data is looked at.
594const MAX_VERTICES: usize = 1 << 24;
595
596/// x, y and a stored z sign to a unit vector: z = sqrt(1 - x^2 - y^2), or (x, y, 0) normalised when that is
597/// negative.
598fn rebuild_z(x: f32, y: f32, positive: bool) -> [f32; 3] {
599    let zsq = 1.0 - x * x - y * y;
600    let [x, y, z] = if zsq >= 0.0 {
601        [x, y, zsq.sqrt()]
602    } else {
603        let l = (x * x + y * y).sqrt();
604        [x / l, y / l, 0.0]
605    };
606    [x, y, if positive { z } else { -z }]
607}
608
609/// A mesh's `m_VariableBoneCountWeights` data for `n` vertices: `n + 1` start offsets (word indexes into the data, the
610/// first being `n + 1`, the last the data's length), then each vertex's influences as words, the bone slot in the low
611/// 16 bits and the weight in the high 16 (normalised), heaviest first.
612fn read_variable_weights(name: &str, data: &[u32], n: usize) -> Result<Blend> {
613    let bad = |why: String| invalid(format!("{name}: variable bone weights: {why}"));
614    let table = n.checked_add(1).ok_or_else(|| bad(format!("{n} vertices")))?;
615    let Some(starts) = data.get(..table) else {
616        return Err(bad(format!("{} words for {n} vertices", data.len())));
617    };
618    let (Some(&first), Some(&last)) = (starts.first(), starts.last()) else {
619        return Err(bad("no start offsets".into()));
620    };
621    if usize::try_from(first) != Ok(table)
622        || usize::try_from(last) != Ok(data.len())
623        || starts.iter().zip(starts.iter().skip(1)).any(|(a, b)| b <= a)
624    {
625        return Err(bad(
626            "start offsets not increasing from the end of the table to the end of the data".into(),
627        ));
628    }
629    let words = data.get(table..).unwrap_or_default();
630    Ok(Blend {
631        starts: starts.iter().map(|o| o.saturating_sub(first)).collect(),
632        weights: words
633            .iter()
634            .map(|w| f32::from(u16::try_from(w >> 16).unwrap_or(u16::MAX)) / 65535.0)
635            .collect(),
636        bones: words.iter().map(|w| w & 0xFFFF).collect(),
637    })
638}
639
640/// The bind poses of mesh `m` (`m_BindPose`, Matrix4x4f `eRC` fields), row-major.
641fn bind_poses(m: &Value, name: &str) -> Result<Vec<[[f64; 4]; 4]>> {
642    let list = match m.get("m_BindPose") {
643        Some(Value::Array(Array::Values(l))) => l.as_slice(),
644        Some(Value::Array(a)) if a.is_empty() => &[],
645        _ => return Err(invalid(format!("{name}: no m_BindPose"))),
646    };
647    list.iter()
648        .map(|p| {
649            let mut out = [[0.0; 4]; 4];
650            for (r, row) in out.iter_mut().enumerate() {
651                for (c, x) in row.iter_mut().enumerate() {
652                    let k = format!("e{r}{c}");
653                    *x = f64::from(
654                        p.get(&k)
655                            .and_then(Value::as_f32)
656                            .ok_or_else(|| invalid(format!("{name}: bind pose without {k}")))?,
657                    );
658                }
659            }
660            Ok(out)
661        })
662        .collect()
663}
664
665/// A blend weight component by Unity's `VertexFormat`: `Float` (0), `Float16` (1), `UNorm8` (2) or `UNorm16` (4).
666fn blend_weight(bytes: &[u8], format: i64) -> Result<f32> {
667    Ok(match (format, bytes) {
668        (0, &[a, b, c, d, ..]) => f32::from_le_bytes([a, b, c, d]),
669        (1, &[a, b, ..]) => f16::from_le_bytes([a, b]).to_f32(),
670        (2, &[a, ..]) => f32::from(a) / 255.0,
671        (0..=2, _) => return Err(invalid(format!("a blend weight of format {format} cut short"))),
672        (_, &[a, b, ..]) => f32::from(u16::from_le_bytes([a, b])) / 65535.0,
673        _ => return Err(invalid("a blend weight cut short")),
674    })
675}
676
677/// The `BlendWeight` and `BlendIndices` channels (12 and 13) of an uncompressed mesh: the influences stored per
678/// vertex and each vertex's weights and bone slots, padded to four with zeros. An indices channel without a weights
679/// channel is one influence of weight 1 (a rigid bind); a weights channel without indices, or of another dimension,
680/// is an error. `None` for a mesh without either channel. Indices are `UInt8` (6), `UInt16` (8) or `UInt32` (10).
681fn read_skin_channels(
682    name: &str,
683    info: &[(usize, usize, i64, usize)],
684    stream_at: &[(usize, usize)],
685    data: &[u8],
686    n: usize,
687) -> Result<Option<BlendChannels>> {
688    let channel = |ch: usize| info.get(ch).copied().filter(|c| c.3 > 0);
689    let (w, i) = (channel(12), channel(13));
690    let Some((is, io, ifmt, id)) = i else {
691        if w.is_some() {
692            return Err(invalid(format!("{name}: blend weights without blend indices")));
693        }
694        return Ok(None);
695    };
696    if id > 4 {
697        return Err(Error::Unsupported(format!(
698            "{name}: {id} bone influences a vertex"
699        )));
700    }
701    if !matches!(ifmt, 6 | 8 | 10) {
702        return Err(Error::Unsupported(format!(
703            "{name}: blend indices of format {ifmt}"
704        )));
705    }
706    if let Some((_, _, wfmt, wd)) = w {
707        if wd != id {
708            return Err(invalid(format!(
709                "{name}: {wd} blend weights and {id} blend indices a vertex"
710            )));
711        }
712        if !matches!(wfmt, 0 | 1 | 2 | 4) {
713            return Err(Error::Unsupported(format!(
714                "{name}: blend weights of format {wfmt}"
715            )));
716        }
717    } else if id != 1 {
718        return Err(invalid(format!(
719            "{name}: {id} blend indices a vertex and no blend weights"
720        )));
721    }
722    let at = |stream: usize, off: usize, row: usize, k: usize, size: usize| -> Result<&[u8]> {
723        let past = || {
724            invalid(format!(
725                "{name}: blend data past the vertex data ({} bytes)",
726                data.len()
727            ))
728        };
729        let &(base, stride) = stream_at.get(stream).ok_or_else(past)?;
730        let from = row
731            .checked_mul(stride)
732            .and_then(|r| {
733                r.checked_add(base)?
734                    .checked_add(off)?
735                    .checked_add(k.checked_mul(size)?)
736            })
737            .ok_or_else(past)?;
738        data.get(from..from.checked_add(size).ok_or_else(past)?)
739            .ok_or_else(past)
740    };
741    let index_size = component_size(ifmt)?;
742    let mut weights = vec![[0.0f32; 4]; n];
743    let mut bones = vec![[0u32; 4]; n];
744    for (v, (weight_row, bone_row)) in weights.iter_mut().zip(bones.iter_mut()).enumerate() {
745        for k in 0..id {
746            let cut = || invalid(format!("{name}: a blend index of vertex {v} cut short"));
747            let bone = match (ifmt, at(is, io, v, k, index_size)?) {
748                (6, &[a, ..]) => u32::from(a),
749                (8, &[a, b, ..]) => u32::from(u16::from_le_bytes([a, b])),
750                (_, &[a, b, c, d, ..]) => u32::from_le_bytes([a, b, c, d]),
751                _ => return Err(cut()),
752            };
753            let weight = match w {
754                Some((ws, wo, wfmt, _)) => blend_weight(at(ws, wo, v, k, component_size(wfmt)?)?, wfmt)?,
755                None => 1.0,
756            };
757            *bone_row.get_mut(k).ok_or_else(cut)? = bone;
758            *weight_row.get_mut(k).ok_or_else(cut)? = weight;
759        }
760    }
761    Ok(Some((id, weights, bones)))
762}
763
764/// The submeshes of mesh `m` (`m_SubMeshes`), their stored numbers checked to fit the 32 bits they are kept in.
765fn read_submeshes(m: &Value, name: &str) -> Result<Vec<SubMesh>> {
766    let Some(Value::Array(Array::Values(list))) = m.get("m_SubMeshes") else {
767        return Err(invalid(format!("{name}: no m_SubMeshes")));
768    };
769    list.iter()
770        .enumerate()
771        .map(|(i, s)| {
772            let what = format!("{name}: submesh {i}");
773            Ok(SubMesh {
774                first_byte: uint(s, "firstByte", &what)?,
775                index_count: uint(s, "indexCount", &what)?,
776                topology: int(s, "topology")?,
777                base_vertex: uint(s, "baseVertex", &what)?,
778            })
779        })
780        .collect()
781}
782
783impl UnityMeshData {
784    /// Decode the Mesh object `r` of `player` (its vertex data in the mesh or streamed beside its file).
785    pub fn read(player: &Player, r: &ObjRef) -> Result<Self> {
786        let m = player.decode(r)?;
787        let name = m.get("m_Name").and_then(Value::as_str).unwrap_or("").to_owned();
788        let vd = m
789            .get("m_VertexData")
790            .ok_or_else(|| invalid("mesh: no m_VertexData"))?;
791        let cm = m
792            .get("m_CompressedMesh")
793            .ok_or_else(|| invalid("mesh: no m_CompressedMesh"))?;
794        let compressed = items(cm.get("m_Vertices").ok_or_else(|| invalid("no m_Vertices"))?)? > 0;
795        if compressed && cm.get("m_Weights").map(items).transpose()?.unwrap_or(0) > 0 {
796            return Err(Error::Unsupported(format!(
797                "{name}: compressed mesh skin weights"
798            )));
799        }
800        let variable = match m.at(&["m_VariableBoneCountWeights", "m_Data"]) {
801            Some(Value::Array(Array::U32(d))) if !d.is_empty() => Some(d.as_slice()),
802            Some(Value::Array(a)) if !a.is_empty() => {
803                return Err(Error::Unsupported(format!(
804                    "{name}: variable bone weights not stored as 32-bit words"
805                )));
806            }
807            _ => None,
808        };
809        let mut channels: [Option<Channel>; 12] = Default::default();
810        let mut skin_channels = None;
811        let vertex_count;
812        if compressed {
813            let get = |k: &str| {
814                cm.get(k)
815                    .ok_or_else(|| invalid(format!("compressed mesh: no {k}")))
816            };
817            let pv = get("m_Vertices")?;
818            vertex_count = items(pv)? / 3;
819            check_vertices(&name, vertex_count)?;
820            // vertex_count is at most MAX_VERTICES: the products below stay far from overflow
821            let times = |n: usize| vertex_count.saturating_mul(n);
822            set_channel(
823                &mut channels,
824                0,
825                Channel {
826                    dim: 3,
827                    values: unpack_floats(pv, 0, times(3))?,
828                },
829            )?;
830            let uv = get("m_UV")?;
831            if items(uv)? > 0 {
832                let info = uint(cm, "m_UVInfo", "compressed mesh")?;
833                if info == 0 {
834                    return Err(Error::Unsupported("compressed mesh UVs without UV info".into()));
835                }
836                let mut at = 0usize;
837                for ch in 0..8u32 {
838                    let b = info.checked_shr(ch.saturating_mul(4)).unwrap_or(0) & 0xF;
839                    if b & 4 != 0 {
840                        let dim = usize::try_from(b & 3).unwrap_or(0).saturating_add(1);
841                        set_channel(
842                            &mut channels,
843                            usize::try_from(ch).unwrap_or(0).saturating_add(4),
844                            Channel {
845                                dim,
846                                values: unpack_floats(uv, at, times(dim))?,
847                            },
848                        )?;
849                        at = at.saturating_add(times(dim));
850                    }
851                }
852            }
853            let n = get("m_Normals")?;
854            if items(n)? > 0 {
855                let xy = unpack_floats(n, 0, times(2))?;
856                let signs = unpack_ints(get("m_NormalSigns")?, 0, vertex_count)?;
857                let values = xy
858                    .as_chunks::<2>()
859                    .0
860                    .iter()
861                    .zip(&signs)
862                    .flat_map(|(&[x, y], &sign)| rebuild_z(x, y, sign != 0))
863                    .collect();
864                set_channel(&mut channels, 1, Channel { dim: 3, values })?;
865            }
866            let t = get("m_Tangents")?;
867            if items(t)? > 0 {
868                let xy = unpack_floats(t, 0, times(2))?;
869                let signs = unpack_ints(get("m_TangentSigns")?, 0, times(2))?;
870                let values = xy
871                    .as_chunks::<2>()
872                    .0
873                    .iter()
874                    .zip(signs.as_chunks::<2>().0)
875                    .flat_map(|(&[x, y], &[z_sign, w_sign])| {
876                        let [x, y, z] = rebuild_z(x, y, z_sign != 0);
877                        [x, y, z, if w_sign > 0 { 1.0 } else { -1.0 }]
878                    })
879                    .collect();
880                set_channel(&mut channels, 2, Channel { dim: 4, values })?;
881            }
882            let c = get("m_FloatColors")?;
883            if items(c)? > 0 {
884                set_channel(
885                    &mut channels,
886                    3,
887                    Channel {
888                        dim: 4,
889                        values: unpack_floats(c, 0, times(4))?,
890                    },
891                )?;
892            }
893        } else {
894            let stated = int(vd, "m_VertexCount")?;
895            vertex_count =
896                usize::try_from(stated).map_err(|_| invalid(format!("{name}: {stated} vertices")))?;
897            check_vertices(&name, vertex_count)?;
898            let chans = match vd.get("m_Channels") {
899                Some(Value::Array(Array::Values(c))) => c,
900                _ => return Err(invalid("mesh: no m_Channels")),
901            };
902            // stream offsets and strides
903            let info: Vec<(usize, usize, i64, usize)> = chans
904                .iter()
905                .map(|c| {
906                    let non_negative = |k: &str| {
907                        let n = int(c, k)?;
908                        usize::try_from(n).map_err(|_| invalid(format!("{name}: channel {k} of {n}")))
909                    };
910                    let stream = non_negative("stream")?;
911                    // Unity's vertex data has at most four streams
912                    if stream >= 4 {
913                        return Err(invalid(format!("{name}: a channel in stream {stream}")));
914                    }
915                    Ok((
916                        stream,
917                        non_negative("offset")?,
918                        int(c, "format")?,
919                        usize::try_from(int(c, "dimension")? & 0xF).unwrap_or(0),
920                    ))
921                })
922                .collect::<Result<_>>()?;
923            let streams = info
924                .iter()
925                .filter(|c| c.3 > 0)
926                .map(|c| c.0)
927                .max()
928                .unwrap_or(0)
929                .saturating_add(1);
930            let mut stream_at = Vec::with_capacity(streams);
931            let mut offset = 0usize;
932            for s in 0..streams {
933                let mut stride = 0;
934                for &(cs, _, f, d) in &info {
935                    if cs == s && d > 0 {
936                        stride = d.saturating_mul(component_size(f)?).saturating_add(stride);
937                    }
938                }
939                stream_at.push((offset, stride));
940                // vertex_count is at most 2^24 and a stride 15 x 4 x 16 channels: no overflow; were there one, the
941                // saturated size fails the check against the data below
942                offset = offset.saturating_add(vertex_count.saturating_mul(stride));
943                offset = offset.checked_next_multiple_of(16).unwrap_or(usize::MAX);
944            }
945            let stream_data = m
946                .get("m_StreamData")
947                .map(StreamingInfo::of)
948                .transpose()?
949                .flatten();
950            let data: Vec<u8> = match stream_data {
951                Some(s) => player.read_stream(&r.file, &s)?,
952                None => bytes(vd, "m_DataSize")?.to_vec(),
953            };
954            // every stream holds its vertices, so what is allocated per vertex below is backed by the data
955            for (s, &(base, stride)) in stream_at.iter().enumerate() {
956                if stride > 0 && base.saturating_add(vertex_count.saturating_mul(stride)) > data.len() {
957                    return Err(invalid(format!(
958                        "{name}: stream {s} of {vertex_count} vertices of {stride} bytes past the {} bytes of vertex data",
959                        data.len()
960                    )));
961                }
962            }
963            skin_channels = read_skin_channels(&name, &info, &stream_at, &data, vertex_count)?;
964            for (ch, &(s, off, f, d)) in info.iter().enumerate().take(12) {
965                if d == 0 {
966                    continue;
967                }
968                let semantic = CHANNELS
969                    .get(ch)
970                    .copied()
971                    .ok_or_else(|| invalid(format!("{name}: channel {ch}")))?;
972                let format = vertex_format(f, d)
973                    .ok_or_else(|| Error::Unsupported(format!("{semantic} channel format {f} x {d}")))?;
974                let layout = resolve_layout(&[Element {
975                    semantic: semantic.to_owned(),
976                    index: 0,
977                    format,
978                    slot: u32::try_from(s).map_err(|_| invalid(format!("{name}: stream {s}")))?,
979                    offset: Some(off),
980                }])
981                .map_err(|e| invalid(format!("{semantic}: {e}")))?;
982                let &(base, stride) = stream_at.get(s).ok_or_else(|| {
983                    invalid(format!(
984                        "{name}: {semantic} names stream {s} of {}",
985                        stream_at.len()
986                    ))
987                })?;
988                let element = layout
989                    .first()
990                    .ok_or_else(|| invalid(format!("{name}: no layout for {semantic}")))?;
991                // non-finite values kept as stored: `make_finite` decides by what the stream is
992                let v = decode_stream_raw(
993                    element,
994                    VertexStream {
995                        bytes: &data,
996                        stride,
997                        offset: base,
998                    },
999                    0,
1000                    vertex_count,
1001                )
1002                .map_err(|e| invalid(format!("{name} {semantic}: {e}")))?;
1003                set_channel(
1004                    &mut channels,
1005                    ch,
1006                    Channel {
1007                        dim: d,
1008                        values: v.iter().flat_map(|x| x.iter().take(d).copied()).collect(),
1009                    },
1010                )?;
1011            }
1012        }
1013        // indices
1014        let (index_size, index_bytes): (u32, usize) = match int(&m, "m_IndexFormat")? {
1015            0 => (2, 2),
1016            1 => (4, 4),
1017            f => return Err(invalid(format!("index format {f}"))),
1018        };
1019        let ib = bytes(&m, "m_IndexBuffer")?;
1020        if ib.len().checked_rem(index_bytes) != Some(0) {
1021            return Err(invalid(format!("index buffer of {} bytes", ib.len())));
1022        }
1023        // a compressed mesh keeps its indices bit-packed (`m_Triangles`); its submeshes still count index bytes
1024        let packed = cm.get("m_Triangles").map(items).transpose()?.unwrap_or(0);
1025        let indices: Vec<u32> = if compressed && packed > 0 {
1026            unpack_ints(cm.get("m_Triangles").expect("checked"), 0, packed)?
1027        } else if index_size == 2 {
1028            ib.as_chunks::<2>()
1029                .0
1030                .iter()
1031                .map(|c| u32::from(u16::from_le_bytes([c[0], c[1]])))
1032                .collect()
1033        } else {
1034            ib.as_chunks::<4>()
1035                .0
1036                .iter()
1037                .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1038                .collect()
1039        };
1040        let submeshes = read_submeshes(&m, &name)?;
1041        if variable.is_some() && compressed {
1042            return Err(Error::Unsupported(format!(
1043                "{name}: compressed mesh with variable bone weights"
1044            )));
1045        }
1046        let skin = match skin_channels {
1047            Some((influences, weights, bones)) => {
1048                let bind_poses = bind_poses(&m, &name)?;
1049                let channel = Blend::from_padded(influences, &weights, &bones);
1050                let unlimited = variable
1051                    .map(|d| read_variable_weights(&name, d, vertex_count))
1052                    .transpose()?;
1053                for blend in std::iter::once(&channel).chain(&unlimited) {
1054                    let bad = blend.weights.iter().zip(&blend.bones).position(|(w, b)| {
1055                        *w > 0.0 && usize::try_from(*b).map_or(true, |b| b >= bind_poses.len())
1056                    });
1057                    if let Some(i) = bad {
1058                        return Err(invalid(format!(
1059                            "{name}: an influence binds bone {} of {} bind poses",
1060                            blend.bones.get(i).copied().unwrap_or_default(),
1061                            bind_poses.len()
1062                        )));
1063                    }
1064                }
1065                if bind_poses.len() > usize::from(u16::MAX) {
1066                    return Err(Error::Unsupported(format!(
1067                        "{name}: {} bind poses (bone slots are 16 bits)",
1068                        bind_poses.len()
1069                    )));
1070                }
1071                Some(MeshSkin {
1072                    channel,
1073                    unlimited,
1074                    bind_poses,
1075                })
1076            }
1077            None if variable.is_some() => {
1078                return Err(invalid(format!(
1079                    "{name}: variable bone weights without blend channels"
1080                )));
1081            }
1082            None => None,
1083        };
1084        let shapes = read_shapes(&m, &name, vertex_count)?;
1085        Ok(UnityMeshData {
1086            name,
1087            vertex_count,
1088            channels,
1089            skin,
1090            compressed,
1091            shapes,
1092            indices,
1093            index_size,
1094            submeshes,
1095        })
1096    }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101    use super::*;
1102
1103    /// A packed vector states its own item count: negative or enormous counts of no-bit items are refused before the
1104    /// vector is made, and counts of stored bits are held to the bytes.
1105    #[test]
1106    fn packed_vectors_are_bounded_by_their_bytes_or_a_constant() {
1107        let packed = |items: i64, bits: i64, data: Vec<u8>| {
1108            Value::Struct(vec![
1109                ("m_NumItems".into(), Value::Int(items)),
1110                ("m_Range".into(), Value::Float(1.0)),
1111                ("m_Start".into(), Value::Float(0.5)),
1112                ("m_Data".into(), Value::Array(Array::U8(data))),
1113                ("m_BitSize".into(), Value::Int(bits)),
1114            ])
1115        };
1116        assert!(
1117            items(&packed(-1, 0, vec![]))
1118                .unwrap_err()
1119                .to_string()
1120                .contains("m_NumItems of -1")
1121        );
1122        assert_eq!(unpack_floats(&packed(3, 0, vec![]), 0, 3).unwrap(), [0.5; 3]);
1123        let huge = MAX_PACKED_ITEMS + 1;
1124        assert!(unpack_ints(&packed(0, 0, vec![]), 0, huge).is_err());
1125        assert!(unpack_floats(&packed(0, 0, vec![]), 0, huge).is_err());
1126        // bits stored: the bytes bound the count, even when start + count wraps
1127        assert!(unpack_ints(&packed(0, 8, vec![1, 2]), 0, 3).is_err());
1128        assert!(unpack_ints(&packed(0, 8, vec![1, 2]), usize::MAX, 2).is_err());
1129        assert_eq!(unpack_ints(&packed(0, 8, vec![1, 2]), 0, 2).unwrap(), [1, 2]);
1130        assert!(unpack_ints(&packed(0, -1, vec![]), 0, 1).is_err());
1131        assert!(unpack_floats(&packed(0, 33, vec![]), 0, 1).is_err());
1132    }
1133
1134    /// The `info` rows of a mesh whose only channels are blend weights (12) and indices (13): (stream, offset, format,
1135    /// dimension).
1136    fn blend_info(
1137        weights: (usize, usize, i64, usize),
1138        indices: (usize, usize, i64, usize),
1139    ) -> Vec<(usize, usize, i64, usize)> {
1140        let mut info = vec![(0, 0, 0, 0); 14];
1141        info[12] = weights;
1142        info[13] = indices;
1143        info
1144    }
1145
1146    /// Two influences a vertex: float weights then 32-bit indices in one stream of stride 16, a rigid bind (indices
1147    /// only), 16-bit normalised weights, and the refusals: weights without indices, other dimensions, five influences,
1148    /// an unsupported format and data short of the vertices.
1149    #[test]
1150    fn blend_channels_read_by_their_formats_and_refuse_the_rest() {
1151        let mut data = Vec::new();
1152        for (w, i) in [([0.75f32, 0.25], [3u32, 1]), ([1.0, 0.0], [2, 0])] {
1153            w.iter().for_each(|x| data.extend(x.to_le_bytes()));
1154            i.iter().for_each(|x| data.extend(x.to_le_bytes()));
1155        }
1156        let info = blend_info((0, 0, 0, 2), (0, 8, 10, 2));
1157        let (k, weights, bones) = read_skin_channels("m", &info, &[(0, 16)], &data, 2)
1158            .unwrap()
1159            .unwrap();
1160        assert_eq!(k, 2);
1161        assert_eq!(weights, vec![[0.75, 0.25, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]]);
1162        assert_eq!(bones, vec![[3, 1, 0, 0], [2, 0, 0, 0]]);
1163
1164        // no weights channel: each vertex wholly on its one bone (8-bit indices here)
1165        let (k, weights, bones) = read_skin_channels(
1166            "m",
1167            &blend_info((0, 0, 0, 0), (0, 0, 6, 1)),
1168            &[(0, 1)],
1169            &[5, 7],
1170            2,
1171        )
1172        .unwrap()
1173        .unwrap();
1174        assert_eq!(
1175            (k, weights, bones),
1176            (1, vec![[1.0, 0.0, 0.0, 0.0]; 2], vec![[5, 0, 0, 0], [7, 0, 0, 0]])
1177        );
1178
1179        // 16-bit normalised weights with 16-bit indices
1180        let data = [0xFF, 0xFF, 0x00, 0x00, 4, 0, 9, 0];
1181        let (_, weights, bones) =
1182            read_skin_channels("m", &blend_info((0, 0, 4, 2), (0, 4, 8, 2)), &[(0, 8)], &data, 1)
1183                .unwrap()
1184                .unwrap();
1185        assert_eq!((weights, bones), (vec![[1.0, 0.0, 0.0, 0.0]], vec![[4, 9, 0, 0]]));
1186
1187        assert!(
1188            read_skin_channels("m", &blend_info((0, 0, 0, 0), (0, 0, 0, 0)), &[(0, 0)], &[], 0)
1189                .unwrap()
1190                .is_none()
1191        );
1192        let refused = |info: Vec<(usize, usize, i64, usize)>, stride: usize, len: usize| {
1193            read_skin_channels("m", &info, &[(0, stride)], &vec![0u8; len], 2)
1194                .unwrap_err()
1195                .to_string()
1196        };
1197        assert!(refused(blend_info((0, 0, 0, 2), (0, 0, 0, 0)), 8, 16).contains("without blend indices"));
1198        assert!(
1199            refused(blend_info((0, 0, 0, 2), (0, 8, 10, 3)), 20, 40)
1200                .contains("2 blend weights and 3 blend indices")
1201        );
1202        assert!(refused(blend_info((0, 0, 0, 0), (0, 0, 10, 2)), 8, 16).contains("no blend weights"));
1203        assert!(refused(blend_info((0, 0, 0, 4), (0, 16, 10, 5)), 36, 72).contains("5 bone influences"));
1204        assert!(
1205            refused(blend_info((0, 0, 3, 2), (0, 8, 10, 2)), 16, 32).contains("blend weights of format 3")
1206        );
1207        assert!(
1208            refused(blend_info((0, 0, 0, 2), (0, 8, 5, 2)), 12, 24).contains("blend indices of format 5")
1209        );
1210        assert!(refused(blend_info((0, 0, 0, 2), (0, 8, 10, 2)), 16, 20).contains("past the vertex data"));
1211    }
1212
1213    /// The variable-bone-count data: start offsets, then a word per influence (bone in the low 16 bits, weight in the
1214    /// high 16); its refusals.
1215    #[test]
1216    fn variable_bone_weights_read_by_their_offsets() {
1217        let word = |weight: u16, bone: u16| (u32::from(weight) << 16) | u32::from(bone);
1218        // two vertices: five influences summing to 65535, then three (the last of weight 0)
1219        let data = [
1220            3,
1221            8,
1222            11,
1223            word(32768, 6),
1224            word(16384, 7),
1225            word(8192, 5),
1226            word(4096, 15),
1227            word(4095, 3),
1228            word(43690, 2),
1229            word(21845, 1),
1230            word(0, 0),
1231        ];
1232        let b = read_variable_weights("m", &data, 2).unwrap();
1233        assert_eq!(b.starts, vec![0, 5, 8]);
1234        assert_eq!(b.bones, vec![6, 7, 5, 15, 3, 2, 1, 0]);
1235        assert_eq!(b.weights[0], 32768.0 / 65535.0);
1236        assert_eq!(b.weights[7], 0.0);
1237        assert_eq!(b.max_influences(), 5);
1238        assert_eq!(b.vertex(1).unwrap().1, &[2, 1, 0]);
1239        assert_eq!(b.vertex(9), None);
1240        let refused = |data: &[u32], n: usize| read_variable_weights("m", data, n).unwrap_err().to_string();
1241        assert!(refused(&data[..2], 2).contains("words for 2 vertices"));
1242        let mut bad = data;
1243        bad[0] = 4;
1244        assert!(refused(&bad, 2).contains("start offsets"));
1245        let mut bad = data;
1246        bad[2] = 12;
1247        assert!(refused(&bad, 2).contains("start offsets"));
1248        let mut bad = data;
1249        bad[1] = 3;
1250        assert!(refused(&bad, 2).contains("start offsets"));
1251    }
1252
1253    /// What a quality level makes of a skin: the channel's four influences under 4 or fewer bones (cut and renormalised
1254    /// below the stored count), the full influences under unlimited; levels with one outcome draw one mesh.
1255    #[test]
1256    fn the_skin_weights_limit_decides_the_influences_drawn() {
1257        let channel = Blend::from_padded(4, &[[0.5, 0.25, 0.125, 0.125]], &[[3, 2, 1, 0]]);
1258        let unlimited = Blend {
1259            starts: vec![0, 5],
1260            weights: vec![0.4, 0.25, 0.125, 0.125, 0.1],
1261            bones: vec![3, 2, 1, 0, 4],
1262        };
1263        let skin = MeshSkin {
1264            channel: channel.clone(),
1265            unlimited: Some(unlimited),
1266            bind_poses: vec![[[0.0; 4]; 4]; 5],
1267        };
1268        assert_eq!(skin.outcome(SkinWeights::Bones(4)), (true, 4));
1269        assert_eq!(skin.outcome(SkinWeights::Bones(2)), (true, 2));
1270        assert_eq!(skin.outcome(SkinWeights::Bones(1)), (true, 1));
1271        assert_eq!(skin.outcome(SkinWeights::Unlimited), (false, 5));
1272        assert_eq!(skin.sets(SkinWeights::Bones(4)), 1);
1273        assert_eq!(skin.sets(SkinWeights::Unlimited), 2);
1274        let (w, b, cut) = skin.padded(SkinWeights::Bones(4));
1275        assert_eq!(
1276            (w, b, cut),
1277            (vec![vec![[0.5, 0.25, 0.125, 0.125]]], vec![vec![[3, 2, 1, 0]]], 0)
1278        );
1279        let (w, b, cut) = skin.padded(SkinWeights::Bones(2));
1280        assert_eq!(w, vec![vec![[0.5 / 0.75, 0.25 / 0.75, 0.0, 0.0]]]);
1281        assert_eq!((b, cut), (vec![vec![[3, 2, 0, 0]]], 1));
1282        let (w, b, cut) = skin.padded(SkinWeights::Unlimited);
1283        assert_eq!(
1284            w,
1285            vec![vec![[0.4, 0.25, 0.125, 0.125]], vec![[0.1, 0.0, 0.0, 0.0]]]
1286        );
1287        assert_eq!((b, cut), (vec![vec![[3, 2, 1, 0]], vec![[4, 0, 0, 0]]], 0));
1288        // a skin with no full influences draws its channel at unlimited too
1289        let plain = MeshSkin {
1290            unlimited: None,
1291            ..skin
1292        };
1293        assert_eq!(plain.outcome(SkinWeights::Unlimited), (true, 4));
1294        assert_eq!(
1295            plain.outcome(SkinWeights::Unlimited),
1296            plain.outcome(SkinWeights::Bones(4))
1297        );
1298        assert_eq!(SkinWeights::from_setting(255).unwrap(), SkinWeights::Unlimited);
1299        assert_eq!(SkinWeights::from_setting(2).unwrap(), SkinWeights::Bones(2));
1300        assert!(SkinWeights::from_setting(3).is_err());
1301    }
1302
1303    fn field(name: &str, v: Value) -> (std::sync::Arc<str>, Value) {
1304        (name.into(), v)
1305    }
1306
1307    fn list(items: Vec<Value>) -> Value {
1308        Value::Array(Array::Values(items))
1309    }
1310
1311    /// A `m_Shapes` of one channel of one frame of one vertex, with the stored numbers given.
1312    fn shapes(first_vertex: i64, vertex_count: i64, frame_index: i64, name_hash: i64) -> Value {
1313        let v3 = |x: f32| {
1314            Value::Struct(vec![
1315                field("x", Value::Float(x)),
1316                field("y", Value::Float(0.0)),
1317                field("z", Value::Float(0.0)),
1318            ])
1319        };
1320        let m_shapes = Value::Struct(vec![
1321            field(
1322                "channels",
1323                list(vec![Value::Struct(vec![
1324                    field("name", Value::String("smile".into())),
1325                    field("nameHash", Value::Int(name_hash)),
1326                    field("frameIndex", Value::Int(frame_index)),
1327                    field("frameCount", Value::Int(1)),
1328                ])]),
1329            ),
1330            field("fullWeights", Value::Array(Array::F32(vec![100.0]))),
1331            field(
1332                "vertices",
1333                list(vec![Value::Struct(vec![
1334                    field("index", Value::Int(0)),
1335                    field("vertex", v3(1.0)),
1336                    field("normal", v3(0.0)),
1337                    field("tangent", v3(0.0)),
1338                ])]),
1339            ),
1340            field(
1341                "shapes",
1342                list(vec![Value::Struct(vec![
1343                    field("firstVertex", Value::Int(first_vertex)),
1344                    field("vertexCount", Value::Int(vertex_count)),
1345                    field("hasNormals", Value::Bool(false)),
1346                    field("hasTangents", Value::Bool(false)),
1347                ])]),
1348            ),
1349        ]);
1350        Value::Struct(vec![field("m_Shapes", m_shapes)])
1351    }
1352
1353    /// Stored runs and hashes that overflow, are negative or do not fit 32 bits are refused where the old casts and
1354    /// sums panicked or wrapped.
1355    #[test]
1356    fn blend_shape_numbers_are_checked_not_cast() {
1357        let ok = read_shapes(&shapes(0, 1, 0, 0xdead_beef), "m", 1)
1358            .unwrap()
1359            .unwrap();
1360        assert_eq!((ok.channels[0].name_hash, ok.frames[0].count), (0xdead_beef, 1));
1361        for (first, count, frame, hash, wants) in [
1362            (i64::MAX, 1, 0, 0, "entries 9223372036854775807+1 of 1"),
1363            (0, i64::MAX, 0, 0, "entries 0+9223372036854775807 of 1"),
1364            (-1, 2, 0, 0, "entries -1+2 of 1"),
1365            (0, 1, i64::MAX, 0, "entries 9223372036854775807+1 of 1"),
1366            (0, 1, 0, 1 << 32, "nameHash 4294967296"),
1367            (0, 1, 0, -1, "nameHash -1"),
1368        ] {
1369            let e = read_shapes(&shapes(first, count, frame, hash), "m", 1)
1370                .unwrap_err()
1371                .to_string();
1372            assert!(e.contains(wants), "{e}");
1373        }
1374    }
1375
1376    fn submesh(first_byte: i64, index_count: i64, base_vertex: i64) -> Value {
1377        Value::Struct(vec![
1378            field("firstByte", Value::Int(first_byte)),
1379            field("indexCount", Value::Int(index_count)),
1380            field("topology", Value::Int(0)),
1381            field("baseVertex", Value::Int(base_vertex)),
1382        ])
1383    }
1384
1385    /// A submesh's numbers are 32-bit unsigned: a stored 2^32 + 6 is refused, not read as 6, and the error names the
1386    /// submesh and the field.
1387    #[test]
1388    fn submesh_numbers_that_do_not_fit_are_refused_not_wrapped() {
1389        let mesh = |subs| Value::Struct(vec![field("m_SubMeshes", list(subs))]);
1390        let ok = read_submeshes(&mesh(vec![submesh(6, 3, 0)]), "m").unwrap();
1391        assert_eq!((ok[0].first_byte, ok[0].index_count), (6, 3));
1392        for (sub, wants) in [
1393            (submesh((1 << 32) + 6, 3, 0), "firstByte 4294967302"),
1394            (submesh(0, -1, 0), "indexCount -1"),
1395            (submesh(0, 3, 1 << 33), "baseVertex 8589934592"),
1396        ] {
1397            let e = read_submeshes(&mesh(vec![submesh(0, 3, 0), sub]), "m")
1398                .unwrap_err()
1399                .to_string();
1400            assert!(e.contains("submesh 1") && e.contains(wants), "{e}");
1401        }
1402    }
1403
1404    /// A channel offset near the top of `usize` is a read past the data, not an overflow.
1405    #[test]
1406    fn blend_offsets_that_overflow_are_refused() {
1407        let data = [0u8; 32];
1408        for offset in [usize::MAX, usize::MAX - 3] {
1409            let info = blend_info((0, 0, 0, 0), (0, offset, 10, 1));
1410            let e = read_skin_channels("m", &info, &[(0, 4)], &data, 2)
1411                .unwrap_err()
1412                .to_string();
1413            assert!(e.contains("past the vertex data (32 bytes)"), "{e}");
1414        }
1415    }
1416
1417    /// Five influences asked of the padded constructor are cut to the four a padded list holds.
1418    #[test]
1419    fn padded_blends_hold_at_most_four_influences() {
1420        assert_eq!(Blend::from_padded(9, &[[0.25; 4]], &[[1; 4]]).bones, vec![1; 4]);
1421    }
1422}