1use crate::json::Value;
4use crate::{
5 ComponentType, Error, GeometryError, Import, MeshIndex, PackedAttribute, PackedGeometry,
6 PrimitiveIndex, Result, ValidationProfile,
7};
8
9#[derive(Clone, Copy, Debug, Default)]
11pub enum GeometryEncoding {
12 #[default]
14 Raw,
15 #[cfg(feature = "draco-encode")]
17 Draco(crate::CompressionOptions),
18}
19
20#[derive(Clone, Copy, Debug, Default)]
22pub struct GeometryWriteOptions {
23 pub encoding: GeometryEncoding,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum PreserveReason {
30 ExistingReferences,
33}
34
35#[derive(Clone, Debug)]
37pub struct GeometryWriteReport {
38 pub primitive: PrimitiveIndex,
40 pub encoding: GeometryEncoding,
42 pub source_bytes: usize,
44 pub output_bytes: usize,
46 pub encoded_bytes: usize,
48 pub reclaimed_bytes: usize,
50 pub preserve_reasons: Vec<PreserveReason>,
52}
53
54impl Import {
55 pub fn write_primitive(
61 &mut self,
62 primitive: PrimitiveIndex,
63 geometry: &PackedGeometry,
64 options: GeometryWriteOptions,
65 ) -> Result<GeometryWriteReport> {
66 geometry.validate(self.validation_profile())?;
67 let mut candidate = self.clone();
68 let source_bytes = total_bytes(&candidate)?;
69 let raw_bytes = candidate.write_raw_primitive_inner(primitive, geometry)?;
70 #[allow(unused_mut)]
71 let mut report = GeometryWriteReport {
72 primitive,
73 encoding: options.encoding,
74 source_bytes,
75 output_bytes: total_bytes(&candidate)?,
76 encoded_bytes: raw_bytes,
77 reclaimed_bytes: 0,
78 preserve_reasons: if source_bytes == 0 {
79 Vec::new()
80 } else {
81 vec![PreserveReason::ExistingReferences]
82 },
83 };
84 #[cfg(feature = "draco-encode")]
85 if let GeometryEncoding::Draco(draco) = options.encoding {
86 let compressed =
87 candidate.compress_primitive(primitive.mesh, primitive.primitive, draco)?;
88 report.output_bytes = compressed.output_bytes;
89 report.encoded_bytes = compressed.encoded_bytes;
90 report.reclaimed_bytes = compressed.reclaimed_bytes;
91 if compressed.reclaimed_bytes > 0 {
92 report.preserve_reasons.clear();
93 }
94 }
95 candidate.validate_after_write()?;
96 *self = candidate;
97 Ok(report)
98 }
99
100 pub fn push_primitive(
105 &mut self,
106 mesh: MeshIndex,
107 geometry: &PackedGeometry,
108 options: GeometryWriteOptions,
109 ) -> Result<PrimitiveIndex> {
110 geometry.validate(self.validation_profile())?;
111 let mut candidate = self.clone();
112 let primitives = candidate
113 .document
114 .as_value_mut()
115 .get_mut("meshes")
116 .and_then(Value::as_array_mut)
117 .and_then(|meshes| meshes.get_mut(mesh.0))
118 .and_then(|mesh| mesh.get_mut("primitives"))
119 .and_then(Value::as_array_mut)
120 .ok_or_else(|| Error::Validation(vec!["mesh primitives are invalid".into()]))?;
121 let primitive = PrimitiveIndex::new(mesh, primitives.len());
122 primitives.push(Value::object([("attributes", Value::Object(Vec::new()))]));
123 candidate.write_primitive(primitive, geometry, options)?;
124 *self = candidate;
125 Ok(primitive)
126 }
127
128 pub fn from_geometry(
149 geometry: &PackedGeometry,
150 profile: ValidationProfile,
151 options: GeometryWriteOptions,
152 ) -> Result<Self> {
153 geometry.validate(profile)?;
154 let version = match profile {
155 ValidationProfile::Gltf20 => "2.0",
156 ValidationProfile::Gltf21Draft => "2.1",
157 };
158 let document = format!(
159 "{{\"asset\":{{\"version\":\"{version}\"}},\"buffers\":[],\"bufferViews\":[],\"accessors\":[],\"meshes\":[{{\"primitives\":[]}}],\"nodes\":[{{\"mesh\":0}}],\"scenes\":[{{\"nodes\":[0]}}],\"scene\":0}}"
160 );
161 let mut import = crate::parse(document.as_bytes(), profile)?;
162 import.push_primitive(MeshIndex(0), geometry, options)?;
163 Ok(import)
164 }
165
166 pub(crate) fn write_raw_primitive_inner(
167 &mut self,
168 location: PrimitiveIndex,
169 geometry: &PackedGeometry,
170 ) -> Result<usize> {
171 let primitive = self
172 .document
173 .primitive(location.mesh, location.primitive)
174 .ok_or_else(|| Error::Validation(vec!["primitive is out of range".into()]))?;
175 validate_morph_targets(self, primitive, geometry.vertex_count())?;
176
177 let buffer_index = self.resources.buffers.len();
178 let mut bytes = Vec::new();
179 let mut views = Vec::new();
180 for attribute in geometry.attributes() {
181 pad_to_four(&mut bytes);
182 let offset = bytes.len();
183 bytes.extend_from_slice(attribute.bytes());
184 views.push((offset, attribute.bytes().len(), 34962u32));
185 }
186 let index_view = if let Some(indices) = geometry.indices() {
187 pad_to_four(&mut bytes);
188 let offset = bytes.len();
189 bytes.extend_from_slice(indices.bytes());
190 views.push((offset, indices.bytes().len(), 34963u32));
191 Some(views.len() - 1)
192 } else {
193 None
194 };
195 let encoded_bytes = bytes.len();
196
197 let root = self.document.as_value_mut();
198 ensure_root_array(root, "buffers")?
199 .push(Value::object([("byteLength", Value::from(bytes.len()))]));
200 let first_view = ensure_root_array(root, "bufferViews")?.len();
201 for (offset, length, target) in &views {
202 ensure_root_array(root, "bufferViews")?.push(Value::object([
203 ("buffer", Value::from(buffer_index)),
204 ("byteOffset", Value::from(*offset)),
205 ("byteLength", Value::from(*length)),
206 ("target", Value::from(*target as u64)),
207 ]));
208 }
209
210 let first_accessor = ensure_root_array(root, "accessors")?.len();
211 for (offset, attribute) in geometry.attributes().iter().enumerate() {
212 let mut accessor = Value::object([
213 ("bufferView", Value::from(first_view + offset)),
214 (
215 "componentType",
216 Value::from(attribute.component_type().to_gltf() as u64),
217 ),
218 ("count", Value::from(attribute.count())),
219 ("type", Value::from(accessor_type(attribute.components()))),
220 ]);
221 if attribute.normalized() {
222 accessor["normalized"] = Value::Bool(true);
223 }
224 if attribute.semantic() == "POSITION" {
225 let (min, max) = position_bounds(attribute)?;
226 accessor["min"] = Value::Array(min);
227 accessor["max"] = Value::Array(max);
228 }
229 ensure_root_array(root, "accessors")?.push(accessor);
230 }
231 let index_accessor = if let (Some(indices), Some(view)) = (geometry.indices(), index_view) {
232 let index = ensure_root_array(root, "accessors")?.len();
233 ensure_root_array(root, "accessors")?.push(Value::object([
234 ("bufferView", Value::from(first_view + view)),
235 (
236 "componentType",
237 Value::from(indices.component_type().to_gltf() as u64),
238 ),
239 ("count", Value::from(indices.count())),
240 ("type", Value::from("SCALAR")),
241 ]));
242 Some(index)
243 } else {
244 None
245 };
246
247 let primitive = root["meshes"][location.mesh.0]["primitives"]
248 .as_array_mut()
249 .and_then(|primitives| primitives.get_mut(location.primitive))
250 .ok_or_else(|| Error::Validation(vec!["primitive changed during write".into()]))?;
251 primitive["mode"] = Value::from(geometry.mode().to_gltf() as u64);
252 primitive["attributes"] = Value::Object(
253 geometry
254 .attributes()
255 .iter()
256 .enumerate()
257 .map(|(offset, attribute)| {
258 (
259 attribute.semantic().to_owned(),
260 Value::from(first_accessor + offset),
261 )
262 })
263 .collect(),
264 );
265 if let Some(index) = index_accessor {
266 primitive["indices"] = Value::from(index);
267 } else {
268 remove_key(primitive, "indices");
269 }
270 remove_draco_extension(primitive);
271 remove_unused_draco_name(root);
272 self.resources.buffers.push(bytes);
273 Ok(encoded_bytes)
274 }
275}
276
277#[derive(Clone, Copy)]
278enum BoundScalar {
279 Signed(i64),
280 Unsigned(u64),
281 Float(f64),
282}
283
284impl BoundScalar {
285 fn is_less_than(self, other: Self) -> bool {
286 match (self, other) {
287 (Self::Signed(left), Self::Signed(right)) => left < right,
288 (Self::Unsigned(left), Self::Unsigned(right)) => left < right,
289 (Self::Float(left), Self::Float(right)) => left < right,
290 _ => unreachable!("one accessor cannot mix component types"),
291 }
292 }
293
294 fn into_json(self) -> Value {
295 let lexeme = match self {
296 Self::Signed(value) => value.to_string(),
297 Self::Unsigned(value) => value.to_string(),
298 Self::Float(value) => finite_float_lexeme(value),
299 };
300 Value::Number(lexeme)
301 }
302}
303
304pub(crate) fn finite_float_lexeme(value: f64) -> String {
305 let bits = value.to_bits();
306 let negative = bits >> 63 != 0;
307 let exponent = ((bits >> 52) & 0x7ff) as i32;
308 let fraction = bits & ((1u64 << 52) - 1);
309 if exponent == 0 && fraction == 0 {
310 return if negative { "-0" } else { "0" }.into();
311 }
312 let (significand, binary_exponent) = if exponent == 0 {
313 (fraction, -1074)
314 } else {
315 ((1u64 << 52) | fraction, exponent - 1023 - 52)
316 };
317 let mut digits = significand
318 .to_string()
319 .bytes()
320 .rev()
321 .map(|digit| digit - b'0')
322 .collect::<Vec<_>>();
323 let mut scale = 0usize;
324 if binary_exponent >= 0 {
325 for _ in 0..binary_exponent {
326 multiply_decimal(&mut digits, 2);
327 }
328 } else {
329 scale = (-binary_exponent) as usize;
330 for _ in 0..scale {
331 multiply_decimal(&mut digits, 5);
332 }
333 while scale > 0 && digits.first() == Some(&0) {
334 digits.remove(0);
335 scale -= 1;
336 }
337 }
338 let mut out = String::with_capacity(digits.len() + 3 + scale.saturating_sub(digits.len()));
339 if negative {
340 out.push('-');
341 }
342 if scale == 0 {
343 out.extend(digits.iter().rev().map(|digit| char::from(b'0' + digit)));
344 } else if digits.len() > scale {
345 for (index, digit) in digits.iter().rev().enumerate() {
346 if index == digits.len() - scale {
347 out.push('.');
348 }
349 out.push(char::from(b'0' + digit));
350 }
351 } else {
352 out.push_str("0.");
353 out.extend(std::iter::repeat_n('0', scale - digits.len()));
354 out.extend(digits.iter().rev().map(|digit| char::from(b'0' + digit)));
355 }
356 out
357}
358
359fn multiply_decimal(digits: &mut Vec<u8>, factor: u8) {
360 let mut carry = 0u16;
361 for digit in digits.iter_mut() {
362 let value = u16::from(*digit) * u16::from(factor) + carry;
363 *digit = (value % 10) as u8;
364 carry = value / 10;
365 }
366 while carry != 0 {
367 digits.push((carry % 10) as u8);
368 carry /= 10;
369 }
370}
371
372fn position_bounds(attribute: &PackedAttribute) -> Result<(Vec<Value>, Vec<Value>)> {
373 let scalar_width = attribute.component_type().byte_width();
374 let row_width = scalar_width
375 .checked_mul(attribute.components() as usize)
376 .ok_or(GeometryError::ByteSizeOverflow)?;
377 let first = attribute
378 .bytes()
379 .get(..row_width)
380 .ok_or(GeometryError::EmptyGeometry)?;
381 let mut min = (0..attribute.components())
382 .map(|component| {
383 read_bound_scalar(
384 first,
385 component as usize * scalar_width,
386 attribute.component_type(),
387 )
388 })
389 .collect::<std::result::Result<Vec<_>, _>>()?;
390 let mut max = min.clone();
391 for row in attribute.bytes().chunks_exact(row_width).skip(1) {
392 for component in 0..attribute.components() as usize {
393 let value =
394 read_bound_scalar(row, component * scalar_width, attribute.component_type())?;
395 if value.is_less_than(min[component]) {
396 min[component] = value;
397 }
398 if max[component].is_less_than(value) {
399 max[component] = value;
400 }
401 }
402 }
403 Ok((
404 min.into_iter().map(BoundScalar::into_json).collect(),
405 max.into_iter().map(BoundScalar::into_json).collect(),
406 ))
407}
408
409fn read_bound_scalar(
410 bytes: &[u8],
411 offset: usize,
412 component_type: ComponentType,
413) -> std::result::Result<BoundScalar, GeometryError> {
414 let bytes = &bytes[offset..offset + component_type.byte_width()];
415 let scalar = match component_type {
416 ComponentType::I8 => BoundScalar::Signed(bytes[0] as i8 as i64),
417 ComponentType::U8 => BoundScalar::Unsigned(bytes[0] as u64),
418 ComponentType::I16 => {
419 BoundScalar::Signed(i16::from_le_bytes(bytes.try_into().unwrap()) as i64)
420 }
421 ComponentType::U16 => {
422 BoundScalar::Unsigned(u16::from_le_bytes(bytes.try_into().unwrap()) as u64)
423 }
424 ComponentType::I32 => {
425 BoundScalar::Signed(i32::from_le_bytes(bytes.try_into().unwrap()) as i64)
426 }
427 ComponentType::U32 => {
428 BoundScalar::Unsigned(u32::from_le_bytes(bytes.try_into().unwrap()) as u64)
429 }
430 ComponentType::F32 => {
431 BoundScalar::Float(f32::from_le_bytes(bytes.try_into().unwrap()) as f64)
432 }
433 ComponentType::F16 => {
434 BoundScalar::Float(half_to_f32(u16::from_le_bytes(bytes.try_into().unwrap())) as f64)
435 }
436 ComponentType::F64 => BoundScalar::Float(f64::from_le_bytes(bytes.try_into().unwrap())),
437 ComponentType::I64 => BoundScalar::Signed(i64::from_le_bytes(bytes.try_into().unwrap())),
438 ComponentType::U64 => BoundScalar::Unsigned(u64::from_le_bytes(bytes.try_into().unwrap())),
439 };
440 if matches!(scalar, BoundScalar::Float(value) if !value.is_finite()) {
441 return Err(GeometryError::NonFinitePosition);
442 }
443 Ok(scalar)
444}
445
446fn half_to_f32(bits: u16) -> f32 {
447 let sign = ((bits & 0x8000) as u32) << 16;
448 let exponent = (bits >> 10) & 0x1f;
449 let fraction = (bits & 0x03ff) as u32;
450 let value = match exponent {
451 0 if fraction == 0 => sign,
452 0 => {
453 let leading = fraction.leading_zeros() - 22;
454 let normalized = fraction << (leading + 1);
455 let exponent = 127 - 15 - leading;
456 sign | (exponent << 23) | ((normalized & 0x03ff) << 13)
457 }
458 0x1f => sign | 0x7f80_0000 | (fraction << 13),
459 _ => sign | ((exponent as u32 + 112) << 23) | (fraction << 13),
460 };
461 f32::from_bits(value)
462}
463
464fn total_bytes(import: &Import) -> Result<usize> {
465 import
466 .resources
467 .buffers
468 .iter()
469 .try_fold(0usize, |total, bytes| {
470 total
471 .checked_add(bytes.len())
472 .ok_or_else(|| Error::ResourceLimit("total resource size overflow".into()))
473 })
474}
475
476fn validate_morph_targets(
477 import: &Import,
478 primitive: crate::PrimitiveRef<'_>,
479 vertex_count: usize,
480) -> Result<()> {
481 for target in primitive.morph_targets() {
482 for (_, accessor) in target {
483 let count = accessor
484 .as_u64()
485 .and_then(|index| usize::try_from(index).ok())
486 .and_then(|index| import.document.accessor(crate::AccessorIndex(index)))
487 .and_then(|accessor| accessor.count())
488 .and_then(|count| usize::try_from(count).ok())
489 .ok_or_else(|| {
490 Error::Validation(vec!["morph target accessor is invalid".into()])
491 })?;
492 if count != vertex_count {
493 return Err(Error::Geometry(crate::GeometryError::MorphTargetCount {
494 expected: count,
495 actual: vertex_count,
496 }));
497 }
498 }
499 }
500 Ok(())
501}
502
503fn ensure_root_array<'a>(root: &'a mut Value, name: &str) -> Result<&'a mut Vec<Value>> {
504 if root.get(name).is_none() {
505 root[name] = Value::Array(Vec::new());
506 }
507 root.get_mut(name)
508 .and_then(Value::as_array_mut)
509 .ok_or_else(|| Error::Validation(vec![format!("{name} is not an array")]))
510}
511
512fn accessor_type(components: u8) -> &'static str {
513 match components {
514 1 => "SCALAR",
515 2 => "VEC2",
516 3 => "VEC3",
517 4 => "VEC4",
518 _ => unreachable!("PackedAttribute validates component counts"),
519 }
520}
521
522fn pad_to_four(bytes: &mut Vec<u8>) {
523 while !bytes.len().is_multiple_of(4) {
524 bytes.push(0);
525 }
526}
527
528fn remove_key(value: &mut Value, key: &str) {
529 if let Some(entries) = value.as_object_mut() {
530 entries.retain(|(name, _)| name != key);
531 }
532}
533
534fn remove_draco_extension(primitive: &mut Value) {
535 let Some(extensions) = primitive
536 .get_mut("extensions")
537 .and_then(Value::as_object_mut)
538 else {
539 return;
540 };
541 extensions.retain(|(name, _)| name != crate::KHR_DRACO_MESH_COMPRESSION);
542 if extensions.is_empty() {
543 remove_key(primitive, "extensions");
544 }
545}
546
547fn remove_unused_draco_name(root: &mut Value) {
548 let still_used = root
549 .get("meshes")
550 .and_then(Value::as_array)
551 .into_iter()
552 .flatten()
553 .filter_map(|mesh| mesh.get("primitives").and_then(Value::as_array))
554 .flatten()
555 .any(|primitive| {
556 primitive
557 .get("extensions")
558 .and_then(|extensions| extensions.get(crate::KHR_DRACO_MESH_COMPRESSION))
559 .is_some()
560 });
561 if still_used {
562 return;
563 }
564 for name in ["extensionsUsed", "extensionsRequired"] {
565 if let Some(values) = root.get_mut(name).and_then(Value::as_array_mut) {
566 values.retain(|value| value.as_str() != Some(crate::KHR_DRACO_MESH_COMPRESSION));
567 if values.is_empty() {
568 remove_key(root, name);
569 }
570 }
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::finite_float_lexeme;
577
578 #[test]
579 fn exact_float_lexemes_roundtrip() {
580 for value in [
581 0.0,
582 -0.0,
583 0.1,
584 -12345.75,
585 f64::MIN_POSITIVE,
586 f64::from_bits(1),
587 f64::MAX,
588 ] {
589 let parsed = finite_float_lexeme(value).parse::<f64>().unwrap();
590 assert_eq!(parsed.to_bits(), value.to_bits());
591 }
592 }
593}