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) => {
952                    let dir = player
953                        .folders
954                        .get(&r.file)
955                        .ok_or_else(|| invalid(format!("no folder for {}", r.file)))?;
956                    s.read(dir)?
957                }
958                None => bytes(vd, "m_DataSize")?.to_vec(),
959            };
960            // every stream holds its vertices, so what is allocated per vertex below is backed by the data
961            for (s, &(base, stride)) in stream_at.iter().enumerate() {
962                if stride > 0 && base.saturating_add(vertex_count.saturating_mul(stride)) > data.len() {
963                    return Err(invalid(format!(
964                        "{name}: stream {s} of {vertex_count} vertices of {stride} bytes past the {} bytes of vertex data",
965                        data.len()
966                    )));
967                }
968            }
969            skin_channels = read_skin_channels(&name, &info, &stream_at, &data, vertex_count)?;
970            for (ch, &(s, off, f, d)) in info.iter().enumerate().take(12) {
971                if d == 0 {
972                    continue;
973                }
974                let semantic = CHANNELS
975                    .get(ch)
976                    .copied()
977                    .ok_or_else(|| invalid(format!("{name}: channel {ch}")))?;
978                let format = vertex_format(f, d)
979                    .ok_or_else(|| Error::Unsupported(format!("{semantic} channel format {f} x {d}")))?;
980                let layout = resolve_layout(&[Element {
981                    semantic: semantic.to_owned(),
982                    index: 0,
983                    format,
984                    slot: u32::try_from(s).map_err(|_| invalid(format!("{name}: stream {s}")))?,
985                    offset: Some(off),
986                }])
987                .map_err(|e| invalid(format!("{semantic}: {e}")))?;
988                let &(base, stride) = stream_at.get(s).ok_or_else(|| {
989                    invalid(format!(
990                        "{name}: {semantic} names stream {s} of {}",
991                        stream_at.len()
992                    ))
993                })?;
994                let element = layout
995                    .first()
996                    .ok_or_else(|| invalid(format!("{name}: no layout for {semantic}")))?;
997                // non-finite values kept as stored: `make_finite` decides by what the stream is
998                let v = decode_stream_raw(
999                    element,
1000                    VertexStream {
1001                        bytes: &data,
1002                        stride,
1003                        offset: base,
1004                    },
1005                    0,
1006                    vertex_count,
1007                )
1008                .map_err(|e| invalid(format!("{name} {semantic}: {e}")))?;
1009                set_channel(
1010                    &mut channels,
1011                    ch,
1012                    Channel {
1013                        dim: d,
1014                        values: v.iter().flat_map(|x| x.iter().take(d).copied()).collect(),
1015                    },
1016                )?;
1017            }
1018        }
1019        // indices
1020        let (index_size, index_bytes): (u32, usize) = match int(&m, "m_IndexFormat")? {
1021            0 => (2, 2),
1022            1 => (4, 4),
1023            f => return Err(invalid(format!("index format {f}"))),
1024        };
1025        let ib = bytes(&m, "m_IndexBuffer")?;
1026        if ib.len().checked_rem(index_bytes) != Some(0) {
1027            return Err(invalid(format!("index buffer of {} bytes", ib.len())));
1028        }
1029        // a compressed mesh keeps its indices bit-packed (`m_Triangles`); its submeshes still count index bytes
1030        let packed = cm.get("m_Triangles").map(items).transpose()?.unwrap_or(0);
1031        let indices: Vec<u32> = if compressed && packed > 0 {
1032            unpack_ints(cm.get("m_Triangles").expect("checked"), 0, packed)?
1033        } else if index_size == 2 {
1034            ib.as_chunks::<2>()
1035                .0
1036                .iter()
1037                .map(|c| u32::from(u16::from_le_bytes([c[0], c[1]])))
1038                .collect()
1039        } else {
1040            ib.as_chunks::<4>()
1041                .0
1042                .iter()
1043                .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1044                .collect()
1045        };
1046        let submeshes = read_submeshes(&m, &name)?;
1047        if variable.is_some() && compressed {
1048            return Err(Error::Unsupported(format!(
1049                "{name}: compressed mesh with variable bone weights"
1050            )));
1051        }
1052        let skin = match skin_channels {
1053            Some((influences, weights, bones)) => {
1054                let bind_poses = bind_poses(&m, &name)?;
1055                let channel = Blend::from_padded(influences, &weights, &bones);
1056                let unlimited = variable
1057                    .map(|d| read_variable_weights(&name, d, vertex_count))
1058                    .transpose()?;
1059                for blend in std::iter::once(&channel).chain(&unlimited) {
1060                    let bad = blend.weights.iter().zip(&blend.bones).position(|(w, b)| {
1061                        *w > 0.0 && usize::try_from(*b).map_or(true, |b| b >= bind_poses.len())
1062                    });
1063                    if let Some(i) = bad {
1064                        return Err(invalid(format!(
1065                            "{name}: an influence binds bone {} of {} bind poses",
1066                            blend.bones.get(i).copied().unwrap_or_default(),
1067                            bind_poses.len()
1068                        )));
1069                    }
1070                }
1071                if bind_poses.len() > usize::from(u16::MAX) {
1072                    return Err(Error::Unsupported(format!(
1073                        "{name}: {} bind poses (bone slots are 16 bits)",
1074                        bind_poses.len()
1075                    )));
1076                }
1077                Some(MeshSkin {
1078                    channel,
1079                    unlimited,
1080                    bind_poses,
1081                })
1082            }
1083            None if variable.is_some() => {
1084                return Err(invalid(format!(
1085                    "{name}: variable bone weights without blend channels"
1086                )));
1087            }
1088            None => None,
1089        };
1090        let shapes = read_shapes(&m, &name, vertex_count)?;
1091        Ok(UnityMeshData {
1092            name,
1093            vertex_count,
1094            channels,
1095            skin,
1096            compressed,
1097            shapes,
1098            indices,
1099            index_size,
1100            submeshes,
1101        })
1102    }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107    use super::*;
1108
1109    /// A packed vector states its own item count: negative or enormous counts of no-bit items are refused before the
1110    /// vector is made, and counts of stored bits are held to the bytes.
1111    #[test]
1112    fn packed_vectors_are_bounded_by_their_bytes_or_a_constant() {
1113        let packed = |items: i64, bits: i64, data: Vec<u8>| {
1114            Value::Struct(vec![
1115                ("m_NumItems".into(), Value::Int(items)),
1116                ("m_Range".into(), Value::Float(1.0)),
1117                ("m_Start".into(), Value::Float(0.5)),
1118                ("m_Data".into(), Value::Array(Array::U8(data))),
1119                ("m_BitSize".into(), Value::Int(bits)),
1120            ])
1121        };
1122        assert!(
1123            items(&packed(-1, 0, vec![]))
1124                .unwrap_err()
1125                .to_string()
1126                .contains("m_NumItems of -1")
1127        );
1128        assert_eq!(unpack_floats(&packed(3, 0, vec![]), 0, 3).unwrap(), [0.5; 3]);
1129        let huge = MAX_PACKED_ITEMS + 1;
1130        assert!(unpack_ints(&packed(0, 0, vec![]), 0, huge).is_err());
1131        assert!(unpack_floats(&packed(0, 0, vec![]), 0, huge).is_err());
1132        // bits stored: the bytes bound the count, even when start + count wraps
1133        assert!(unpack_ints(&packed(0, 8, vec![1, 2]), 0, 3).is_err());
1134        assert!(unpack_ints(&packed(0, 8, vec![1, 2]), usize::MAX, 2).is_err());
1135        assert_eq!(unpack_ints(&packed(0, 8, vec![1, 2]), 0, 2).unwrap(), [1, 2]);
1136        assert!(unpack_ints(&packed(0, -1, vec![]), 0, 1).is_err());
1137        assert!(unpack_floats(&packed(0, 33, vec![]), 0, 1).is_err());
1138    }
1139
1140    /// The `info` rows of a mesh whose only channels are blend weights (12) and indices (13): (stream, offset, format,
1141    /// dimension).
1142    fn blend_info(
1143        weights: (usize, usize, i64, usize),
1144        indices: (usize, usize, i64, usize),
1145    ) -> Vec<(usize, usize, i64, usize)> {
1146        let mut info = vec![(0, 0, 0, 0); 14];
1147        info[12] = weights;
1148        info[13] = indices;
1149        info
1150    }
1151
1152    /// Two influences a vertex: float weights then 32-bit indices in one stream of stride 16, a rigid bind (indices
1153    /// only), 16-bit normalised weights, and the refusals: weights without indices, other dimensions, five influences,
1154    /// an unsupported format and data short of the vertices.
1155    #[test]
1156    fn blend_channels_read_by_their_formats_and_refuse_the_rest() {
1157        let mut data = Vec::new();
1158        for (w, i) in [([0.75f32, 0.25], [3u32, 1]), ([1.0, 0.0], [2, 0])] {
1159            w.iter().for_each(|x| data.extend(x.to_le_bytes()));
1160            i.iter().for_each(|x| data.extend(x.to_le_bytes()));
1161        }
1162        let info = blend_info((0, 0, 0, 2), (0, 8, 10, 2));
1163        let (k, weights, bones) = read_skin_channels("m", &info, &[(0, 16)], &data, 2)
1164            .unwrap()
1165            .unwrap();
1166        assert_eq!(k, 2);
1167        assert_eq!(weights, vec![[0.75, 0.25, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]]);
1168        assert_eq!(bones, vec![[3, 1, 0, 0], [2, 0, 0, 0]]);
1169
1170        // no weights channel: each vertex wholly on its one bone (8-bit indices here)
1171        let (k, weights, bones) = read_skin_channels(
1172            "m",
1173            &blend_info((0, 0, 0, 0), (0, 0, 6, 1)),
1174            &[(0, 1)],
1175            &[5, 7],
1176            2,
1177        )
1178        .unwrap()
1179        .unwrap();
1180        assert_eq!(
1181            (k, weights, bones),
1182            (1, vec![[1.0, 0.0, 0.0, 0.0]; 2], vec![[5, 0, 0, 0], [7, 0, 0, 0]])
1183        );
1184
1185        // 16-bit normalised weights with 16-bit indices
1186        let data = [0xFF, 0xFF, 0x00, 0x00, 4, 0, 9, 0];
1187        let (_, weights, bones) =
1188            read_skin_channels("m", &blend_info((0, 0, 4, 2), (0, 4, 8, 2)), &[(0, 8)], &data, 1)
1189                .unwrap()
1190                .unwrap();
1191        assert_eq!((weights, bones), (vec![[1.0, 0.0, 0.0, 0.0]], vec![[4, 9, 0, 0]]));
1192
1193        assert!(
1194            read_skin_channels("m", &blend_info((0, 0, 0, 0), (0, 0, 0, 0)), &[(0, 0)], &[], 0)
1195                .unwrap()
1196                .is_none()
1197        );
1198        let refused = |info: Vec<(usize, usize, i64, usize)>, stride: usize, len: usize| {
1199            read_skin_channels("m", &info, &[(0, stride)], &vec![0u8; len], 2)
1200                .unwrap_err()
1201                .to_string()
1202        };
1203        assert!(refused(blend_info((0, 0, 0, 2), (0, 0, 0, 0)), 8, 16).contains("without blend indices"));
1204        assert!(
1205            refused(blend_info((0, 0, 0, 2), (0, 8, 10, 3)), 20, 40)
1206                .contains("2 blend weights and 3 blend indices")
1207        );
1208        assert!(refused(blend_info((0, 0, 0, 0), (0, 0, 10, 2)), 8, 16).contains("no blend weights"));
1209        assert!(refused(blend_info((0, 0, 0, 4), (0, 16, 10, 5)), 36, 72).contains("5 bone influences"));
1210        assert!(
1211            refused(blend_info((0, 0, 3, 2), (0, 8, 10, 2)), 16, 32).contains("blend weights of format 3")
1212        );
1213        assert!(
1214            refused(blend_info((0, 0, 0, 2), (0, 8, 5, 2)), 12, 24).contains("blend indices of format 5")
1215        );
1216        assert!(refused(blend_info((0, 0, 0, 2), (0, 8, 10, 2)), 16, 20).contains("past the vertex data"));
1217    }
1218
1219    /// The variable-bone-count data: start offsets, then a word per influence (bone in the low 16 bits, weight in the
1220    /// high 16); its refusals.
1221    #[test]
1222    fn variable_bone_weights_read_by_their_offsets() {
1223        let word = |weight: u16, bone: u16| (u32::from(weight) << 16) | u32::from(bone);
1224        // two vertices: five influences summing to 65535, then three (the last of weight 0)
1225        let data = [
1226            3,
1227            8,
1228            11,
1229            word(32768, 6),
1230            word(16384, 7),
1231            word(8192, 5),
1232            word(4096, 15),
1233            word(4095, 3),
1234            word(43690, 2),
1235            word(21845, 1),
1236            word(0, 0),
1237        ];
1238        let b = read_variable_weights("m", &data, 2).unwrap();
1239        assert_eq!(b.starts, vec![0, 5, 8]);
1240        assert_eq!(b.bones, vec![6, 7, 5, 15, 3, 2, 1, 0]);
1241        assert_eq!(b.weights[0], 32768.0 / 65535.0);
1242        assert_eq!(b.weights[7], 0.0);
1243        assert_eq!(b.max_influences(), 5);
1244        assert_eq!(b.vertex(1).unwrap().1, &[2, 1, 0]);
1245        assert_eq!(b.vertex(9), None);
1246        let refused = |data: &[u32], n: usize| read_variable_weights("m", data, n).unwrap_err().to_string();
1247        assert!(refused(&data[..2], 2).contains("words for 2 vertices"));
1248        let mut bad = data;
1249        bad[0] = 4;
1250        assert!(refused(&bad, 2).contains("start offsets"));
1251        let mut bad = data;
1252        bad[2] = 12;
1253        assert!(refused(&bad, 2).contains("start offsets"));
1254        let mut bad = data;
1255        bad[1] = 3;
1256        assert!(refused(&bad, 2).contains("start offsets"));
1257    }
1258
1259    /// What a quality level makes of a skin: the channel's four influences under 4 or fewer bones (cut and renormalised
1260    /// below the stored count), the full influences under unlimited; levels with one outcome draw one mesh.
1261    #[test]
1262    fn the_skin_weights_limit_decides_the_influences_drawn() {
1263        let channel = Blend::from_padded(4, &[[0.5, 0.25, 0.125, 0.125]], &[[3, 2, 1, 0]]);
1264        let unlimited = Blend {
1265            starts: vec![0, 5],
1266            weights: vec![0.4, 0.25, 0.125, 0.125, 0.1],
1267            bones: vec![3, 2, 1, 0, 4],
1268        };
1269        let skin = MeshSkin {
1270            channel: channel.clone(),
1271            unlimited: Some(unlimited),
1272            bind_poses: vec![[[0.0; 4]; 4]; 5],
1273        };
1274        assert_eq!(skin.outcome(SkinWeights::Bones(4)), (true, 4));
1275        assert_eq!(skin.outcome(SkinWeights::Bones(2)), (true, 2));
1276        assert_eq!(skin.outcome(SkinWeights::Bones(1)), (true, 1));
1277        assert_eq!(skin.outcome(SkinWeights::Unlimited), (false, 5));
1278        assert_eq!(skin.sets(SkinWeights::Bones(4)), 1);
1279        assert_eq!(skin.sets(SkinWeights::Unlimited), 2);
1280        let (w, b, cut) = skin.padded(SkinWeights::Bones(4));
1281        assert_eq!(
1282            (w, b, cut),
1283            (vec![vec![[0.5, 0.25, 0.125, 0.125]]], vec![vec![[3, 2, 1, 0]]], 0)
1284        );
1285        let (w, b, cut) = skin.padded(SkinWeights::Bones(2));
1286        assert_eq!(w, vec![vec![[0.5 / 0.75, 0.25 / 0.75, 0.0, 0.0]]]);
1287        assert_eq!((b, cut), (vec![vec![[3, 2, 0, 0]]], 1));
1288        let (w, b, cut) = skin.padded(SkinWeights::Unlimited);
1289        assert_eq!(
1290            w,
1291            vec![vec![[0.4, 0.25, 0.125, 0.125]], vec![[0.1, 0.0, 0.0, 0.0]]]
1292        );
1293        assert_eq!((b, cut), (vec![vec![[3, 2, 1, 0]], vec![[4, 0, 0, 0]]], 0));
1294        // a skin with no full influences draws its channel at unlimited too
1295        let plain = MeshSkin {
1296            unlimited: None,
1297            ..skin
1298        };
1299        assert_eq!(plain.outcome(SkinWeights::Unlimited), (true, 4));
1300        assert_eq!(
1301            plain.outcome(SkinWeights::Unlimited),
1302            plain.outcome(SkinWeights::Bones(4))
1303        );
1304        assert_eq!(SkinWeights::from_setting(255).unwrap(), SkinWeights::Unlimited);
1305        assert_eq!(SkinWeights::from_setting(2).unwrap(), SkinWeights::Bones(2));
1306        assert!(SkinWeights::from_setting(3).is_err());
1307    }
1308
1309    fn field(name: &str, v: Value) -> (std::sync::Arc<str>, Value) {
1310        (name.into(), v)
1311    }
1312
1313    fn list(items: Vec<Value>) -> Value {
1314        Value::Array(Array::Values(items))
1315    }
1316
1317    /// A `m_Shapes` of one channel of one frame of one vertex, with the stored numbers given.
1318    fn shapes(first_vertex: i64, vertex_count: i64, frame_index: i64, name_hash: i64) -> Value {
1319        let v3 = |x: f32| {
1320            Value::Struct(vec![
1321                field("x", Value::Float(x)),
1322                field("y", Value::Float(0.0)),
1323                field("z", Value::Float(0.0)),
1324            ])
1325        };
1326        let m_shapes = Value::Struct(vec![
1327            field(
1328                "channels",
1329                list(vec![Value::Struct(vec![
1330                    field("name", Value::String("smile".into())),
1331                    field("nameHash", Value::Int(name_hash)),
1332                    field("frameIndex", Value::Int(frame_index)),
1333                    field("frameCount", Value::Int(1)),
1334                ])]),
1335            ),
1336            field("fullWeights", Value::Array(Array::F32(vec![100.0]))),
1337            field(
1338                "vertices",
1339                list(vec![Value::Struct(vec![
1340                    field("index", Value::Int(0)),
1341                    field("vertex", v3(1.0)),
1342                    field("normal", v3(0.0)),
1343                    field("tangent", v3(0.0)),
1344                ])]),
1345            ),
1346            field(
1347                "shapes",
1348                list(vec![Value::Struct(vec![
1349                    field("firstVertex", Value::Int(first_vertex)),
1350                    field("vertexCount", Value::Int(vertex_count)),
1351                    field("hasNormals", Value::Bool(false)),
1352                    field("hasTangents", Value::Bool(false)),
1353                ])]),
1354            ),
1355        ]);
1356        Value::Struct(vec![field("m_Shapes", m_shapes)])
1357    }
1358
1359    /// Stored runs and hashes that overflow, are negative or do not fit 32 bits are refused where the old casts and
1360    /// sums panicked or wrapped.
1361    #[test]
1362    fn blend_shape_numbers_are_checked_not_cast() {
1363        let ok = read_shapes(&shapes(0, 1, 0, 0xdead_beef), "m", 1)
1364            .unwrap()
1365            .unwrap();
1366        assert_eq!((ok.channels[0].name_hash, ok.frames[0].count), (0xdead_beef, 1));
1367        for (first, count, frame, hash, wants) in [
1368            (i64::MAX, 1, 0, 0, "entries 9223372036854775807+1 of 1"),
1369            (0, i64::MAX, 0, 0, "entries 0+9223372036854775807 of 1"),
1370            (-1, 2, 0, 0, "entries -1+2 of 1"),
1371            (0, 1, i64::MAX, 0, "entries 9223372036854775807+1 of 1"),
1372            (0, 1, 0, 1 << 32, "nameHash 4294967296"),
1373            (0, 1, 0, -1, "nameHash -1"),
1374        ] {
1375            let e = read_shapes(&shapes(first, count, frame, hash), "m", 1)
1376                .unwrap_err()
1377                .to_string();
1378            assert!(e.contains(wants), "{e}");
1379        }
1380    }
1381
1382    fn submesh(first_byte: i64, index_count: i64, base_vertex: i64) -> Value {
1383        Value::Struct(vec![
1384            field("firstByte", Value::Int(first_byte)),
1385            field("indexCount", Value::Int(index_count)),
1386            field("topology", Value::Int(0)),
1387            field("baseVertex", Value::Int(base_vertex)),
1388        ])
1389    }
1390
1391    /// A submesh's numbers are 32-bit unsigned: a stored 2^32 + 6 is refused, not read as 6, and the error names the
1392    /// submesh and the field.
1393    #[test]
1394    fn submesh_numbers_that_do_not_fit_are_refused_not_wrapped() {
1395        let mesh = |subs| Value::Struct(vec![field("m_SubMeshes", list(subs))]);
1396        let ok = read_submeshes(&mesh(vec![submesh(6, 3, 0)]), "m").unwrap();
1397        assert_eq!((ok[0].first_byte, ok[0].index_count), (6, 3));
1398        for (sub, wants) in [
1399            (submesh((1 << 32) + 6, 3, 0), "firstByte 4294967302"),
1400            (submesh(0, -1, 0), "indexCount -1"),
1401            (submesh(0, 3, 1 << 33), "baseVertex 8589934592"),
1402        ] {
1403            let e = read_submeshes(&mesh(vec![submesh(0, 3, 0), sub]), "m")
1404                .unwrap_err()
1405                .to_string();
1406            assert!(e.contains("submesh 1") && e.contains(wants), "{e}");
1407        }
1408    }
1409
1410    /// A channel offset near the top of `usize` is a read past the data, not an overflow.
1411    #[test]
1412    fn blend_offsets_that_overflow_are_refused() {
1413        let data = [0u8; 32];
1414        for offset in [usize::MAX, usize::MAX - 3] {
1415            let info = blend_info((0, 0, 0, 0), (0, offset, 10, 1));
1416            let e = read_skin_channels("m", &info, &[(0, 4)], &data, 2)
1417                .unwrap_err()
1418                .to_string();
1419            assert!(e.contains("past the vertex data (32 bytes)"), "{e}");
1420        }
1421    }
1422
1423    /// Five influences asked of the padded constructor are cut to the four a padded list holds.
1424    #[test]
1425    fn padded_blends_hold_at_most_four_influences() {
1426        assert_eq!(Blend::from_padded(9, &[[0.25; 4]], &[[1; 4]]).bones, vec![1; 4]);
1427    }
1428}