1use 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
22pub 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#[derive(Clone, Debug, PartialEq)]
42pub struct Channel {
43 pub dim: usize,
45 pub values: Vec<f32>,
47}
48
49impl Channel {
50 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct SubMesh {
60 pub first_byte: u32,
62 pub index_count: u32,
64 pub topology: i64,
66 pub base_vertex: u32,
68}
69
70#[derive(Clone, Debug, Default, PartialEq)]
73pub struct Blend {
74 pub starts: Vec<u32>,
77 pub weights: Vec<f32>,
79 pub bones: Vec<u32>,
81}
82
83impl Blend {
84 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 pub fn vertices(&self) -> usize {
104 self.starts.len().saturating_sub(1)
105 }
106
107 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 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
126pub type PaddedSets = (Vec<Vec<[f32; 4]>>, Vec<Vec<[u32; 4]>>, u64);
129
130#[derive(Clone, Debug, PartialEq)]
136pub struct MeshSkin {
137 pub channel: Blend,
140 pub unlimited: Option<Blend>,
143 pub bind_poses: Vec<[[f64; 4]; 4]>,
145}
146
147impl MeshSkin {
148 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 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 pub fn sets(&self, limit: SkinWeights) -> usize {
168 self.outcome(limit).1.div_ceil(4).max(1)
169 }
170
171 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
211type BlendChannels = (usize, Vec<[f32; 4]>, Vec<[u32; 4]>);
214
215#[derive(Clone, Debug, PartialEq)]
217pub struct UnityMeshData {
218 pub name: String,
220 pub vertex_count: usize,
222 pub channels: [Option<Channel>; 12],
224 pub skin: Option<MeshSkin>,
226 pub compressed: bool,
228 pub shapes: Option<MeshShapes>,
230 pub indices: Vec<u32>,
232 pub index_size: u32,
235 pub submeshes: Vec<SubMesh>,
237}
238
239#[derive(Clone, Debug, PartialEq)]
242pub struct ShapeChannel {
243 pub name: String,
245 pub name_hash: u32,
247 pub first_frame: usize,
249 pub frames: usize,
251}
252
253#[derive(Clone, Debug, PartialEq)]
256pub struct ShapeFrame {
257 pub weight: f32,
259 pub first: usize,
261 pub count: usize,
263 pub normals: bool,
265 pub tangents: bool,
267}
268
269#[derive(Clone, Debug, PartialEq)]
271pub struct ShapeVertex {
272 pub index: u32,
274 pub position: [f32; 3],
276 pub normal: [f32; 3],
278 pub tangent: [f32; 3],
280}
281
282#[derive(Clone, Debug, PartialEq)]
285pub struct MeshShapes {
286 pub channels: Vec<ShapeChannel>,
288 pub frames: Vec<ShapeFrame>,
290 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
304fn 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
424fn 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
430fn 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
447fn 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
457fn 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
473fn 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 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
526fn shl(x: u64, n: usize) -> u64 {
528 u32::try_from(n).ok().and_then(|n| x.checked_shl(n)).unwrap_or(0)
529}
530
531fn shr(x: u64, n: usize) -> u64 {
533 u32::try_from(n).ok().and_then(|n| x.checked_shr(n)).unwrap_or(0)
534}
535
536fn 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
566fn 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
579const 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
592const MAX_VERTICES: usize = 1 << 24;
595
596fn 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
609fn 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
640fn 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
665fn 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
677fn 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
764fn 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 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 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 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 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 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 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 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 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 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 #[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 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 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 #[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 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 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 #[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 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 #[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 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 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 #[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 #[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 #[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 #[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}