1use gpui::SharedString;
43use serde::Deserialize;
44
45const GLB_MAGIC: &[u8; 4] = b"glTF";
47const GLB_HEADER: usize = 12;
48const GLB_CHUNK_HEADER: usize = 8;
49const GLB_CHUNK_JSON: u32 = 0x4E4F_534A;
50const GLB_CHUNK_BIN: u32 = 0x004E_4942;
51
52const COMPONENT_UNSIGNED_BYTE: u32 = 5121;
54const COMPONENT_UNSIGNED_SHORT: u32 = 5123;
55const COMPONENT_UNSIGNED_INT: u32 = 5125;
56const COMPONENT_FLOAT: u32 = 5126;
57
58const MODE_TRIANGLES: u32 = 4;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum ModelLimit {
64 Bytes,
65 Nodes,
66 Depth,
67 Primitives,
68 Vertices,
69 Triangles,
70}
71
72impl ModelLimit {
73 pub fn name(self) -> &'static str {
75 match self {
76 Self::Bytes => "bytes",
77 Self::Nodes => "nodes",
78 Self::Depth => "depth",
79 Self::Primitives => "primitives",
80 Self::Vertices => "vertices",
81 Self::Triangles => "triangles",
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum ModelDefect {
93 NotJson,
95 BadContainer,
97 NotGltf,
99 UnsupportedVersion,
101 ExternalResource,
103 UnsupportedEncoding,
105 SparseAccessor,
107 UnsupportedPrimitive,
109 UnsupportedComponentType,
111 UnsupportedAccessorType,
113 MissingPositions,
115 DanglingIndex,
118 TruncatedBuffer,
120 IndexOutOfRange,
122 EmptyScene,
124}
125
126impl ModelDefect {
127 pub fn name(self) -> &'static str {
129 match self {
130 Self::NotJson => "not-json",
131 Self::BadContainer => "bad-container",
132 Self::NotGltf => "not-gltf",
133 Self::UnsupportedVersion => "unsupported-version",
134 Self::ExternalResource => "external-resource",
135 Self::UnsupportedEncoding => "unsupported-encoding",
136 Self::SparseAccessor => "sparse-accessor",
137 Self::UnsupportedPrimitive => "unsupported-primitive",
138 Self::UnsupportedComponentType => "unsupported-component-type",
139 Self::UnsupportedAccessorType => "unsupported-accessor-type",
140 Self::MissingPositions => "missing-positions",
141 Self::DanglingIndex => "dangling-index",
142 Self::TruncatedBuffer => "truncated-buffer",
143 Self::IndexOutOfRange => "index-out-of-range",
144 Self::EmptyScene => "empty-scene",
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum ModelError {
152 TooLarge {
154 limit: ModelLimit,
155 found: usize,
157 allowed: usize,
159 },
160 Rejected(ModelDefect),
162}
163
164impl ModelError {
165 pub fn name(self) -> &'static str {
167 match self {
168 Self::TooLarge { .. } => "too-large",
169 Self::Rejected(_) => "rejected",
170 }
171 }
172
173 pub fn code(self) -> &'static str {
175 match self {
176 Self::TooLarge { limit, .. } => limit.name(),
177 Self::Rejected(defect) => defect.name(),
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub struct ModelBounds {
190 pub max_bytes: usize,
191 pub max_nodes: usize,
192 pub max_depth: usize,
193 pub max_primitives: usize,
194 pub max_vertices: usize,
195 pub max_triangles: usize,
196}
197
198impl Default for ModelBounds {
199 fn default() -> Self {
200 Self {
201 max_bytes: 8 * 1024 * 1024,
202 max_nodes: 1024,
203 max_depth: 16,
204 max_primitives: 128,
205 max_vertices: 65_536,
206 max_triangles: 24_576,
207 }
208 }
209}
210
211impl ModelBounds {
212 pub fn max_bytes(mut self, bytes: usize) -> Self {
213 self.max_bytes = bytes;
214 self
215 }
216
217 pub fn max_nodes(mut self, nodes: usize) -> Self {
218 self.max_nodes = nodes;
219 self
220 }
221
222 pub fn max_depth(mut self, depth: usize) -> Self {
223 self.max_depth = depth;
224 self
225 }
226
227 pub fn max_primitives(mut self, primitives: usize) -> Self {
228 self.max_primitives = primitives;
229 self
230 }
231
232 pub fn max_vertices(mut self, vertices: usize) -> Self {
233 self.max_vertices = vertices;
234 self
235 }
236
237 pub fn max_triangles(mut self, triangles: usize) -> Self {
238 self.max_triangles = triangles;
239 self
240 }
241
242 fn check(self, limit: ModelLimit, found: usize) -> Result<(), ModelError> {
243 let allowed = match limit {
244 ModelLimit::Bytes => self.max_bytes,
245 ModelLimit::Nodes => self.max_nodes,
246 ModelLimit::Depth => self.max_depth,
247 ModelLimit::Primitives => self.max_primitives,
248 ModelLimit::Vertices => self.max_vertices,
249 ModelLimit::Triangles => self.max_triangles,
250 };
251 if found > allowed {
252 return Err(ModelError::TooLarge {
253 limit,
254 found,
255 allowed,
256 });
257 }
258 Ok(())
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq)]
264pub struct ModelAabb {
265 pub min: [f32; 3],
266 pub max: [f32; 3],
267}
268
269impl ModelAabb {
270 pub fn centre(self) -> [f32; 3] {
271 [
272 (self.min[0] + self.max[0]) / 2.0,
273 (self.min[1] + self.max[1]) / 2.0,
274 (self.min[2] + self.max[2]) / 2.0,
275 ]
276 }
277
278 pub fn radius(self) -> f32 {
281 let half = [
282 (self.max[0] - self.min[0]) / 2.0,
283 (self.max[1] - self.min[1]) / 2.0,
284 (self.max[2] - self.min[2]) / 2.0,
285 ];
286 (half[0] * half[0] + half[1] * half[1] + half[2] * half[2]).sqrt()
287 }
288}
289
290#[derive(Debug, Clone, PartialEq)]
292pub struct ModelMesh {
293 name: SharedString,
294 positions: Vec<[f32; 3]>,
295 indices: Vec<u32>,
296}
297
298impl ModelMesh {
299 pub fn name(&self) -> &SharedString {
300 &self.name
301 }
302
303 pub fn positions(&self) -> &[[f32; 3]] {
304 &self.positions
305 }
306
307 pub fn indices(&self) -> &[u32] {
309 &self.indices
310 }
311
312 pub fn triangle_count(&self) -> usize {
313 self.indices.len() / 3
314 }
315}
316
317#[derive(Debug, Clone, PartialEq)]
319pub struct ModelScene {
320 meshes: Vec<ModelMesh>,
321 vertices: usize,
322 triangles: usize,
323 aabb: ModelAabb,
324}
325
326impl ModelScene {
327 pub fn parse(bytes: &[u8], bounds: ModelBounds) -> Result<Self, ModelError> {
330 bounds.check(ModelLimit::Bytes, bytes.len())?;
331 let (json, binary) = split_container(bytes)?;
332 let document: Document =
333 serde_json::from_slice(json).map_err(|_| ModelError::Rejected(ModelDefect::NotJson))?;
334 read(&document, binary, bounds)
335 }
336
337 pub fn meshes(&self) -> &[ModelMesh] {
338 &self.meshes
339 }
340
341 pub fn mesh_count(&self) -> usize {
342 self.meshes.len()
343 }
344
345 pub fn vertex_count(&self) -> usize {
346 self.vertices
347 }
348
349 pub fn triangle_count(&self) -> usize {
350 self.triangles
351 }
352
353 pub fn aabb(&self) -> ModelAabb {
354 self.aabb
355 }
356}
357
358fn split_container(bytes: &[u8]) -> Result<(&[u8], &[u8]), ModelError> {
368 if bytes.len() < 4 || &bytes[..4] != GLB_MAGIC {
369 return Ok((bytes, &[]));
370 }
371 let bad = ModelError::Rejected(ModelDefect::BadContainer);
372 if bytes.len() < GLB_HEADER {
373 return Err(bad);
374 }
375 if read_u32(bytes, 4).ok_or(bad)? != 2 {
376 return Err(ModelError::Rejected(ModelDefect::UnsupportedVersion));
377 }
378 let declared = read_u32(bytes, 8).ok_or(bad)? as usize;
379 if declared > bytes.len() || declared < GLB_HEADER {
380 return Err(bad);
381 }
382
383 let mut json: Option<&[u8]> = None;
384 let mut binary: &[u8] = &[];
385 let mut at = GLB_HEADER;
386 while at + GLB_CHUNK_HEADER <= declared {
387 let length = read_u32(bytes, at).ok_or(bad)? as usize;
388 let kind = read_u32(bytes, at + 4).ok_or(bad)?;
389 let start = at + GLB_CHUNK_HEADER;
390 let end = start.checked_add(length).ok_or(bad)?;
391 if end > declared {
392 return Err(bad);
393 }
394 match kind {
395 GLB_CHUNK_JSON if json.is_none() => json = Some(&bytes[start..end]),
396 GLB_CHUNK_BIN if binary.is_empty() => binary = &bytes[start..end],
397 _ => {}
400 }
401 at = end + (4 - end % 4) % 4;
403 }
404 Ok((json.ok_or(bad)?, binary))
405}
406
407fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
408 let slice = bytes.get(at..at + 4)?;
409 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
410}
411
412#[derive(Debug, Deserialize)]
417#[serde(rename_all = "camelCase")]
418struct Document {
419 #[serde(default)]
420 asset: Asset,
421 #[serde(default)]
422 scene: Option<usize>,
423 #[serde(default)]
424 scenes: Vec<SceneNode>,
425 #[serde(default)]
426 nodes: Vec<Node>,
427 #[serde(default)]
428 meshes: Vec<Mesh>,
429 #[serde(default)]
430 accessors: Vec<Accessor>,
431 #[serde(default)]
432 buffer_views: Vec<BufferView>,
433 #[serde(default)]
434 buffers: Vec<Buffer>,
435}
436
437#[derive(Debug, Default, Deserialize)]
438struct Asset {
439 #[serde(default)]
440 version: Option<String>,
441}
442
443#[derive(Debug, Deserialize)]
444struct SceneNode {
445 #[serde(default)]
446 nodes: Vec<usize>,
447}
448
449#[derive(Debug, Deserialize)]
450struct Node {
451 #[serde(default)]
452 name: Option<String>,
453 #[serde(default)]
454 mesh: Option<usize>,
455 #[serde(default)]
456 children: Vec<usize>,
457 #[serde(default)]
458 matrix: Option<[f32; 16]>,
459 #[serde(default)]
460 translation: Option<[f32; 3]>,
461 #[serde(default)]
462 rotation: Option<[f32; 4]>,
463 #[serde(default)]
464 scale: Option<[f32; 3]>,
465}
466
467#[derive(Debug, Deserialize)]
468struct Mesh {
469 #[serde(default)]
470 name: Option<String>,
471 #[serde(default)]
472 primitives: Vec<Primitive>,
473}
474
475#[derive(Debug, Deserialize)]
476struct Primitive {
477 #[serde(default)]
478 attributes: Attributes,
479 #[serde(default)]
480 indices: Option<usize>,
481 #[serde(default)]
482 mode: Option<u32>,
483}
484
485#[derive(Debug, Default, Deserialize)]
486struct Attributes {
487 #[serde(rename = "POSITION", default)]
488 position: Option<usize>,
489}
490
491#[derive(Debug, Deserialize)]
492#[serde(rename_all = "camelCase")]
493struct Accessor {
494 #[serde(default)]
495 buffer_view: Option<usize>,
496 #[serde(default)]
497 byte_offset: usize,
498 component_type: u32,
499 count: usize,
500 #[serde(rename = "type")]
501 kind: String,
502 #[serde(default)]
503 sparse: Option<serde_json::Value>,
504}
505
506#[derive(Debug, Deserialize)]
507#[serde(rename_all = "camelCase")]
508struct BufferView {
509 buffer: usize,
510 #[serde(default)]
511 byte_offset: usize,
512 byte_length: usize,
513 #[serde(default)]
514 byte_stride: Option<usize>,
515}
516
517#[derive(Debug, Deserialize)]
518struct Buffer {
519 #[serde(default)]
520 uri: Option<String>,
521}
522
523type Mat4 = [f32; 16];
529
530const IDENTITY: Mat4 = [
531 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
535];
536
537fn multiply(a: &Mat4, b: &Mat4) -> Mat4 {
538 let mut out = [0.0; 16];
539 for column in 0..4 {
540 for row in 0..4 {
541 let mut sum = 0.0;
542 for step in 0..4 {
543 sum += a[step * 4 + row] * b[column * 4 + step];
544 }
545 out[column * 4 + row] = sum;
546 }
547 }
548 out
549}
550
551fn transform(matrix: &Mat4, point: [f32; 3]) -> [f32; 3] {
552 let [x, y, z] = point;
553 [
554 matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12],
555 matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13],
556 matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14],
557 ]
558}
559
560fn local(node: &Node) -> Mat4 {
562 if let Some(matrix) = node.matrix {
563 return matrix;
564 }
565 let [tx, ty, tz] = node.translation.unwrap_or([0.0; 3]);
566 let [sx, sy, sz] = node.scale.unwrap_or([1.0; 3]);
567 let [x, y, z, w] = node.rotation.unwrap_or([0.0, 0.0, 0.0, 1.0]);
568 let length = (x * x + y * y + z * z + w * w).sqrt();
571 let (x, y, z, w) = if length > f32::EPSILON {
572 (x / length, y / length, z / length, w / length)
573 } else {
574 (0.0, 0.0, 0.0, 1.0)
575 };
576 let (xx, yy, zz) = (x * x, y * y, z * z);
577 let (xy, xz, yz) = (x * y, x * z, y * z);
578 let (wx, wy, wz) = (w * x, w * y, w * z);
579 [
580 (1.0 - 2.0 * (yy + zz)) * sx,
581 (2.0 * (xy + wz)) * sx,
582 (2.0 * (xz - wy)) * sx,
583 0.0,
584 (2.0 * (xy - wz)) * sy,
585 (1.0 - 2.0 * (xx + zz)) * sy,
586 (2.0 * (yz + wx)) * sy,
587 0.0,
588 (2.0 * (xz + wy)) * sz,
589 (2.0 * (yz - wx)) * sz,
590 (1.0 - 2.0 * (xx + yy)) * sz,
591 0.0,
592 tx,
593 ty,
594 tz,
595 1.0,
596 ]
597}
598
599struct Reader<'a> {
601 document: &'a Document,
602 buffers: Vec<Vec<u8>>,
603 binary: &'a [u8],
604 bounds: ModelBounds,
605 meshes: Vec<ModelMesh>,
606 vertices: usize,
607 triangles: usize,
608 visited: usize,
609 min: [f32; 3],
610 max: [f32; 3],
611}
612
613fn read(document: &Document, binary: &[u8], bounds: ModelBounds) -> Result<ModelScene, ModelError> {
614 let version = document
615 .asset
616 .version
617 .as_deref()
618 .ok_or(ModelError::Rejected(ModelDefect::NotGltf))?;
619 if !version.starts_with('2') {
620 return Err(ModelError::Rejected(ModelDefect::UnsupportedVersion));
621 }
622 bounds.check(ModelLimit::Nodes, document.nodes.len())?;
623
624 let mut buffers = Vec::with_capacity(document.buffers.len());
625 for buffer in &document.buffers {
626 buffers.push(resolve(buffer, binary, bounds)?);
627 }
628
629 let mut reader = Reader {
630 document,
631 buffers,
632 binary,
633 bounds,
634 meshes: Vec::new(),
635 vertices: 0,
636 triangles: 0,
637 visited: 0,
638 min: [f32::INFINITY; 3],
639 max: [f32::NEG_INFINITY; 3],
640 };
641
642 let roots: Vec<usize> = match document.scenes.get(document.scene.unwrap_or(0)) {
643 Some(scene) => scene.nodes.clone(),
644 None => (0..document.nodes.len()).collect(),
647 };
648 for root in roots {
649 reader.walk(root, &IDENTITY, 0)?;
650 }
651
652 if reader.triangles == 0 {
653 return Err(ModelError::Rejected(ModelDefect::EmptyScene));
654 }
655 Ok(ModelScene {
656 meshes: reader.meshes,
657 vertices: reader.vertices,
658 triangles: reader.triangles,
659 aabb: ModelAabb {
660 min: reader.min,
661 max: reader.max,
662 },
663 })
664}
665
666fn resolve(buffer: &Buffer, binary: &[u8], bounds: ModelBounds) -> Result<Vec<u8>, ModelError> {
668 let Some(uri) = buffer.uri.as_deref() else {
669 return Ok(binary.to_vec());
670 };
671 let Some(rest) = uri.strip_prefix("data:") else {
672 return Err(ModelError::Rejected(ModelDefect::ExternalResource));
673 };
674 let Some((_, payload)) = rest.split_once(";base64,") else {
675 return Err(ModelError::Rejected(ModelDefect::UnsupportedEncoding));
676 };
677 bounds.check(ModelLimit::Bytes, payload.len() / 4 * 3)?;
680 base64(payload).ok_or(ModelError::Rejected(ModelDefect::UnsupportedEncoding))
681}
682
683fn base64(payload: &str) -> Option<Vec<u8>> {
685 fn sextet(byte: u8) -> Option<u32> {
686 match byte {
687 b'A'..=b'Z' => Some(u32::from(byte - b'A')),
688 b'a'..=b'z' => Some(u32::from(byte - b'a') + 26),
689 b'0'..=b'9' => Some(u32::from(byte - b'0') + 52),
690 b'+' => Some(62),
691 b'/' => Some(63),
692 _ => None,
693 }
694 }
695 let payload = payload.trim_end_matches('=');
696 let mut out = Vec::with_capacity(payload.len() / 4 * 3);
697 let mut accumulator = 0_u32;
698 let mut bits = 0_u32;
699 for byte in payload.bytes() {
700 if byte.is_ascii_whitespace() {
701 continue;
702 }
703 accumulator = (accumulator << 6) | sextet(byte)?;
704 bits += 6;
705 if bits >= 8 {
706 bits -= 8;
707 out.push(((accumulator >> bits) & 0xFF) as u8);
708 }
709 }
710 if accumulator & ((1 << bits) - 1) != 0 {
713 return None;
714 }
715 Some(out)
716}
717
718impl Reader<'_> {
719 fn walk(&mut self, index: usize, parent: &Mat4, depth: usize) -> Result<(), ModelError> {
720 self.bounds.check(ModelLimit::Depth, depth)?;
721 self.visited += 1;
722 self.bounds.check(ModelLimit::Nodes, self.visited)?;
726
727 let node = self
728 .document
729 .nodes
730 .get(index)
731 .ok_or(ModelError::Rejected(ModelDefect::DanglingIndex))?;
732 let world = multiply(parent, &local(node));
733
734 if let Some(mesh) = node.mesh {
735 let mesh = self
736 .document
737 .meshes
738 .get(mesh)
739 .ok_or(ModelError::Rejected(ModelDefect::DanglingIndex))?;
740 let name = mesh
741 .name
742 .clone()
743 .or_else(|| node.name.clone())
744 .unwrap_or_else(|| format!("mesh-{index}"));
745 for primitive in &mesh.primitives {
746 self.primitive(primitive, &world, &name)?;
747 }
748 }
749
750 for child in &node.children {
751 self.walk(*child, &world, depth + 1)?;
752 }
753 Ok(())
754 }
755
756 fn primitive(
757 &mut self,
758 primitive: &Primitive,
759 world: &Mat4,
760 name: &str,
761 ) -> Result<(), ModelError> {
762 if primitive.mode.unwrap_or(MODE_TRIANGLES) != MODE_TRIANGLES {
763 return Err(ModelError::Rejected(ModelDefect::UnsupportedPrimitive));
764 }
765 let accessor = primitive
766 .attributes
767 .position
768 .ok_or(ModelError::Rejected(ModelDefect::MissingPositions))?;
769 self.bounds
770 .check(ModelLimit::Primitives, self.meshes.len() + 1)?;
771
772 let positions = self.positions(accessor, world)?;
773 let indices = match primitive.indices {
774 Some(accessor) => self.indices(accessor, positions.len())?,
775 None => (0..positions.len() as u32).collect(),
777 };
778 if indices.len() < 3 {
779 return Ok(());
780 }
781 let indices: Vec<u32> = indices[..indices.len() - indices.len() % 3].to_vec();
782
783 self.triangles += indices.len() / 3;
784 self.bounds.check(ModelLimit::Triangles, self.triangles)?;
785 self.meshes.push(ModelMesh {
786 name: SharedString::from(name.to_owned()),
787 positions,
788 indices,
789 });
790 Ok(())
791 }
792
793 fn view(&self, accessor: &Accessor, size: usize) -> Result<(&[u8], usize), ModelError> {
795 if accessor.sparse.is_some() {
796 return Err(ModelError::Rejected(ModelDefect::SparseAccessor));
797 }
798 let truncated = ModelError::Rejected(ModelDefect::TruncatedBuffer);
799 let dangling = ModelError::Rejected(ModelDefect::DanglingIndex);
800 let view = accessor
801 .buffer_view
802 .and_then(|index| self.document.buffer_views.get(index))
803 .ok_or(dangling)?;
804 let buffer = self
805 .buffers
806 .get(view.buffer)
807 .map(Vec::as_slice)
808 .or(if view.buffer == 0 {
809 Some(self.binary)
810 } else {
811 None
812 })
813 .ok_or(dangling)?;
814
815 let start = view
816 .byte_offset
817 .checked_add(accessor.byte_offset)
818 .ok_or(truncated)?;
819 let stride = view.byte_stride.unwrap_or(size).max(size);
820 let span = stride
823 .checked_mul(accessor.count.saturating_sub(1))
824 .and_then(|span| span.checked_add(size))
825 .ok_or(truncated)?;
826 let end = start.checked_add(span).ok_or(truncated)?;
827 if end > buffer.len() || view.byte_offset + view.byte_length > buffer.len() {
828 return Err(truncated);
829 }
830 Ok((&buffer[start..end], stride))
831 }
832
833 fn positions(&mut self, index: usize, world: &Mat4) -> Result<Vec<[f32; 3]>, ModelError> {
834 let accessor = self
835 .document
836 .accessors
837 .get(index)
838 .ok_or(ModelError::Rejected(ModelDefect::DanglingIndex))?;
839 if accessor.kind != "VEC3" {
840 return Err(ModelError::Rejected(ModelDefect::UnsupportedAccessorType));
841 }
842 if accessor.component_type != COMPONENT_FLOAT {
843 return Err(ModelError::Rejected(ModelDefect::UnsupportedComponentType));
844 }
845 self.vertices += accessor.count;
846 self.bounds.check(ModelLimit::Vertices, self.vertices)?;
847
848 let (bytes, stride) = self.view(accessor, 12)?;
849 let mut positions = Vec::with_capacity(accessor.count);
850 for element in 0..accessor.count {
851 let at = element * stride;
852 let float = |offset: usize| {
853 let slice = &bytes[at + offset..at + offset + 4];
854 f32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]])
855 };
856 positions.push(transform(world, [float(0), float(4), float(8)]));
857 }
858 for point in &positions {
859 for (axis, value) in point.iter().enumerate() {
860 self.min[axis] = self.min[axis].min(*value);
861 self.max[axis] = self.max[axis].max(*value);
862 }
863 }
864 Ok(positions)
865 }
866
867 fn indices(&self, index: usize, vertices: usize) -> Result<Vec<u32>, ModelError> {
868 let accessor = self
869 .document
870 .accessors
871 .get(index)
872 .ok_or(ModelError::Rejected(ModelDefect::DanglingIndex))?;
873 if accessor.kind != "SCALAR" {
874 return Err(ModelError::Rejected(ModelDefect::UnsupportedAccessorType));
875 }
876 let size = match accessor.component_type {
877 COMPONENT_UNSIGNED_BYTE => 1,
878 COMPONENT_UNSIGNED_SHORT => 2,
879 COMPONENT_UNSIGNED_INT => 4,
880 _ => return Err(ModelError::Rejected(ModelDefect::UnsupportedComponentType)),
881 };
882 self.bounds
883 .check(ModelLimit::Triangles, self.triangles + accessor.count / 3)?;
884
885 let (bytes, stride) = self.view(accessor, size)?;
886 let mut indices = Vec::with_capacity(accessor.count);
887 for element in 0..accessor.count {
888 let at = element * stride;
889 let value = match size {
890 1 => u32::from(bytes[at]),
891 2 => u32::from(u16::from_le_bytes([bytes[at], bytes[at + 1]])),
892 _ => u32::from_le_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]),
893 };
894 if value as usize >= vertices {
895 return Err(ModelError::Rejected(ModelDefect::IndexOutOfRange));
896 }
897 indices.push(value);
898 }
899 Ok(indices)
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906
907 fn triangle() -> String {
912 let positions: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
913 let mut bytes = Vec::new();
914 for value in positions {
915 bytes.extend_from_slice(&value.to_le_bytes());
916 }
917 format!(
918 r#"{{
919 "asset": {{"version": "2.0"}},
920 "scene": 0,
921 "scenes": [{{"nodes": [0]}}],
922 "nodes": [{{"mesh": 0}}],
923 "meshes": [{{"name": "tri", "primitives": [{{"attributes": {{"POSITION": 0}}}}]}}],
924 "accessors": [
925 {{"bufferView": 0, "componentType": 5126, "count": 3, "type": "VEC3"}}
926 ],
927 "bufferViews": [{{"buffer": 0, "byteOffset": 0, "byteLength": {length}}}],
928 "buffers": [{{"uri": "data:application/octet-stream;base64,{payload}"}}]
929 }}"#,
930 length = bytes.len(),
931 payload = encode(&bytes),
932 )
933 }
934
935 fn encode(bytes: &[u8]) -> String {
938 const ALPHABET: &[u8; 64] =
939 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
940 let mut out = String::new();
941 for chunk in bytes.chunks(3) {
942 let mut block = [0_u8; 3];
943 block[..chunk.len()].copy_from_slice(chunk);
944 let packed =
945 (u32::from(block[0]) << 16) | (u32::from(block[1]) << 8) | u32::from(block[2]);
946 for step in 0..4 {
947 if step <= chunk.len() {
948 let sextet = ((packed >> (18 - step * 6)) & 0x3F) as usize;
949 out.push(char::from(ALPHABET[sextet]));
950 } else {
951 out.push('=');
952 }
953 }
954 }
955 out
956 }
957
958 #[test]
959 fn a_triangle_reads_as_one_mesh_of_three_vertices() {
960 let scene = ModelScene::parse(triangle().as_bytes(), ModelBounds::default())
961 .expect("a triangle is inside the subset");
962 assert_eq!(scene.mesh_count(), 1);
963 assert_eq!(scene.vertex_count(), 3);
964 assert_eq!(scene.triangle_count(), 1);
965 assert_eq!(scene.meshes()[0].name().as_ref(), "tri");
966 assert_eq!(scene.aabb().min, [0.0, 0.0, 0.0]);
967 assert_eq!(scene.aabb().max, [1.0, 1.0, 0.0]);
968 }
969
970 #[test]
971 fn a_base64_round_trip_holds_and_a_truncated_payload_does_not() {
972 let bytes: Vec<u8> = (0..32).collect();
973 assert_eq!(base64(&encode(&bytes)), Some(bytes));
974 assert_eq!(
975 base64("!!!!"),
976 None,
977 "a byte outside the alphabet is not data"
978 );
979 }
980
981 #[test]
982 fn a_document_larger_than_the_caller_allowed_is_refused_by_its_size() {
983 let bounds = ModelBounds::default().max_bytes(16);
984 assert_eq!(
985 ModelScene::parse(triangle().as_bytes(), bounds),
986 Err(ModelError::TooLarge {
987 limit: ModelLimit::Bytes,
988 found: triangle().len(),
989 allowed: 16,
990 })
991 );
992 }
993
994 #[test]
995 fn a_model_past_a_geometry_cap_is_refused_before_it_is_built() {
996 let bounds = ModelBounds::default().max_vertices(2);
997 assert_eq!(
998 ModelScene::parse(triangle().as_bytes(), bounds),
999 Err(ModelError::TooLarge {
1000 limit: ModelLimit::Vertices,
1001 found: 3,
1002 allowed: 2,
1003 })
1004 );
1005 let bounds = ModelBounds::default().max_triangles(0);
1006 assert!(matches!(
1007 ModelScene::parse(triangle().as_bytes(), bounds),
1008 Err(ModelError::TooLarge {
1009 limit: ModelLimit::Triangles,
1010 ..
1011 })
1012 ));
1013 }
1014
1015 #[test]
1016 fn a_buffer_this_crate_would_have_to_fetch_is_refused() {
1017 let document = triangle().replace(
1018 "data:application/octet-stream;base64,",
1019 "https://example.invalid/scene.bin?",
1020 );
1021 assert_eq!(
1022 ModelScene::parse(document.as_bytes(), ModelBounds::default()),
1023 Err(ModelError::Rejected(ModelDefect::ExternalResource)),
1024 "resolving a URI is I/O, and this crate performs none"
1025 );
1026 }
1027
1028 #[test]
1029 fn a_document_outside_the_subset_names_what_it_asked_for() {
1030 let lines = triangle().replace(r#""POSITION": 0}}"#, r#""POSITION": 0}, "mode": 1}"#);
1031 assert_eq!(
1032 ModelScene::parse(lines.as_bytes(), ModelBounds::default()),
1033 Err(ModelError::Rejected(ModelDefect::UnsupportedPrimitive))
1034 );
1035
1036 let sparse = triangle().replace(
1037 r#""type": "VEC3"}"#,
1038 r#""type": "VEC3", "sparse": {"count": 1}}"#,
1039 );
1040 assert_eq!(
1041 ModelScene::parse(sparse.as_bytes(), ModelBounds::default()),
1042 Err(ModelError::Rejected(ModelDefect::SparseAccessor))
1043 );
1044
1045 let doubles = triangle().replace(r#""componentType": 5126"#, r#""componentType": 5130"#);
1046 assert_eq!(
1047 ModelScene::parse(doubles.as_bytes(), ModelBounds::default()),
1048 Err(ModelError::Rejected(ModelDefect::UnsupportedComponentType))
1049 );
1050 }
1051
1052 #[test]
1053 fn bytes_that_are_not_a_document_are_refused_as_such() {
1054 assert_eq!(
1055 ModelScene::parse(b"not a model", ModelBounds::default()),
1056 Err(ModelError::Rejected(ModelDefect::NotJson))
1057 );
1058 assert_eq!(
1059 ModelScene::parse(br#"{"nodes": []}"#, ModelBounds::default()),
1060 Err(ModelError::Rejected(ModelDefect::NotGltf))
1061 );
1062 assert_eq!(
1063 ModelScene::parse(br#"{"asset": {"version": "1.0"}}"#, ModelBounds::default()),
1064 Err(ModelError::Rejected(ModelDefect::UnsupportedVersion))
1065 );
1066 }
1067
1068 #[test]
1069 fn an_accessor_that_reads_past_its_buffer_is_refused() {
1070 let overrun = triangle().replace(
1071 r#""count": 3, "type": "VEC3""#,
1072 r#""count": 9, "type": "VEC3""#,
1073 );
1074 assert_eq!(
1075 ModelScene::parse(overrun.as_bytes(), ModelBounds::default()),
1076 Err(ModelError::Rejected(ModelDefect::TruncatedBuffer))
1077 );
1078 }
1079
1080 #[test]
1081 fn a_glb_container_carries_the_same_document() {
1082 let json = triangle();
1083 let mut padded = json.into_bytes();
1084 while !padded.len().is_multiple_of(4) {
1085 padded.push(b' ');
1086 }
1087 let mut glb = Vec::new();
1088 glb.extend_from_slice(GLB_MAGIC);
1089 glb.extend_from_slice(&2_u32.to_le_bytes());
1090 glb.extend_from_slice(
1091 &((GLB_HEADER + GLB_CHUNK_HEADER + padded.len()) as u32).to_le_bytes(),
1092 );
1093 glb.extend_from_slice(&(padded.len() as u32).to_le_bytes());
1094 glb.extend_from_slice(&GLB_CHUNK_JSON.to_le_bytes());
1095 glb.extend_from_slice(&padded);
1096
1097 let scene =
1098 ModelScene::parse(&glb, ModelBounds::default()).expect("a GLB is inside the subset");
1099 assert_eq!(scene.triangle_count(), 1);
1100
1101 let mut lying = glb.clone();
1104 lying[8..12].copy_from_slice(&u32::MAX.to_le_bytes());
1105 assert_eq!(
1106 ModelScene::parse(&lying, ModelBounds::default()),
1107 Err(ModelError::Rejected(ModelDefect::BadContainer))
1108 );
1109 }
1110
1111 #[test]
1112 fn a_node_that_reaches_itself_stops_at_the_visit_budget() {
1113 let looping = r#"{
1114 "asset": {"version": "2.0"},
1115 "scenes": [{"nodes": [0]}],
1116 "nodes": [{"children": [1]}, {"children": [0]}]
1117 }"#;
1118 assert!(matches!(
1119 ModelScene::parse(looping.as_bytes(), ModelBounds::default()),
1120 Err(ModelError::TooLarge {
1121 limit: ModelLimit::Depth | ModelLimit::Nodes,
1122 ..
1123 })
1124 ));
1125 }
1126
1127 #[test]
1128 fn a_transform_places_the_geometry_the_node_moved() {
1129 let moved = triangle().replace(
1130 r#"{"mesh": 0}"#,
1131 r#"{"mesh": 0, "translation": [10.0, 0.0, 0.0]}"#,
1132 );
1133 let scene = ModelScene::parse(moved.as_bytes(), ModelBounds::default()).expect("read");
1134 assert_eq!(scene.aabb().min, [10.0, 0.0, 0.0]);
1135 assert_eq!(scene.aabb().max, [11.0, 1.0, 0.0]);
1136 }
1137
1138 #[test]
1139 fn a_bounding_box_answers_a_centre_and_a_radius() {
1140 let aabb = ModelAabb {
1141 min: [-1.0, -1.0, -1.0],
1142 max: [1.0, 1.0, 1.0],
1143 };
1144 assert_eq!(aabb.centre(), [0.0, 0.0, 0.0]);
1145 assert!((aabb.radius() - 3.0_f32.sqrt()).abs() < 0.001);
1146 }
1147}