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) => {
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 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 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 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 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 #[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 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 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 #[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 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 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 #[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 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 #[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 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 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 #[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 #[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 #[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 #[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}