1use crate::appearance::{MaterialReference, TextureReference};
2use crate::error::{CjseqError, Result};
3use crate::semantics::Semantics;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8pub type Ring = Vec<usize>;
10pub type Surface = Vec<Ring>;
12pub type Shell = Vec<Surface>;
14
15#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
18pub enum GeometryType {
19 MultiPoint,
20 MultiLineString,
21 MultiSurface,
22 CompositeSurface,
23 Solid,
24 MultiSolid,
25 CompositeSolid,
26 GeometryInstance,
27}
28
29impl GeometryType {
30 pub fn boundary_depth(self) -> usize {
33 match self {
34 GeometryType::MultiPoint | GeometryType::GeometryInstance => 1,
35 GeometryType::MultiLineString => 2,
36 GeometryType::MultiSurface | GeometryType::CompositeSurface => 3,
37 GeometryType::Solid => 4,
38 GeometryType::MultiSolid | GeometryType::CompositeSolid => 5,
39 }
40 }
41}
42
43#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
46pub struct GeometryCommon {
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub semantics: Option<Semantics>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub material: Option<HashMap<String, MaterialReference>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub texture: Option<HashMap<String, TextureReference>>,
53}
54
55fn nesting_depth(v: &Value) -> usize {
58 match v {
59 Value::Array(a) => 1 + a.iter().map(nesting_depth).max().unwrap_or(0),
60 _ => 0,
61 }
62}
63
64#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
71#[serde(tag = "type")]
72pub enum Geometry {
73 MultiPoint {
74 #[serde(skip_serializing_if = "Option::is_none")]
75 lod: Option<String>,
76 boundaries: Ring,
77 #[serde(flatten)]
78 common: GeometryCommon,
79 },
80 MultiLineString {
81 #[serde(skip_serializing_if = "Option::is_none")]
82 lod: Option<String>,
83 boundaries: Vec<Ring>,
84 #[serde(flatten)]
85 common: GeometryCommon,
86 },
87 MultiSurface {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 lod: Option<String>,
90 boundaries: Vec<Surface>,
91 #[serde(flatten)]
92 common: GeometryCommon,
93 },
94 CompositeSurface {
95 #[serde(skip_serializing_if = "Option::is_none")]
96 lod: Option<String>,
97 boundaries: Vec<Surface>,
98 #[serde(flatten)]
99 common: GeometryCommon,
100 },
101 Solid {
102 #[serde(skip_serializing_if = "Option::is_none")]
103 lod: Option<String>,
104 boundaries: Vec<Shell>,
105 #[serde(flatten)]
106 common: GeometryCommon,
107 },
108 MultiSolid {
109 #[serde(skip_serializing_if = "Option::is_none")]
110 lod: Option<String>,
111 boundaries: Vec<Vec<Shell>>,
112 #[serde(flatten)]
113 common: GeometryCommon,
114 },
115 CompositeSolid {
116 #[serde(skip_serializing_if = "Option::is_none")]
117 lod: Option<String>,
118 boundaries: Vec<Vec<Shell>>,
119 #[serde(flatten)]
120 common: GeometryCommon,
121 },
122 GeometryInstance {
123 boundaries: Ring,
124 template: usize,
125 #[serde(rename = "transformationMatrix")]
126 transformation_matrix: [f64; 16],
127 },
128}
129
130impl Geometry {
131 pub fn from_json_value(v: Value) -> Result<Geometry> {
141 let err = match Geometry::deserialize(&v) {
145 Ok(g) => return Ok(g),
146 Err(e) => e,
147 };
148 let thetype = v
150 .get("type")
151 .and_then(|t| GeometryType::deserialize(t).ok());
152 if let (Some(thetype), Some(boundaries)) = (thetype, v.get("boundaries")) {
153 let expected = thetype.boundary_depth();
154 let found = nesting_depth(boundaries);
155 if found != expected {
156 return Err(CjseqError::GeometryDepth {
157 geometry_type: thetype,
158 expected,
159 found,
160 });
161 }
162 }
163 Err(CjseqError::Json(err))
164 }
165
166 pub fn geometry_type(&self) -> GeometryType {
167 match self {
168 Geometry::MultiPoint { .. } => GeometryType::MultiPoint,
169 Geometry::MultiLineString { .. } => GeometryType::MultiLineString,
170 Geometry::MultiSurface { .. } => GeometryType::MultiSurface,
171 Geometry::CompositeSurface { .. } => GeometryType::CompositeSurface,
172 Geometry::Solid { .. } => GeometryType::Solid,
173 Geometry::MultiSolid { .. } => GeometryType::MultiSolid,
174 Geometry::CompositeSolid { .. } => GeometryType::CompositeSolid,
175 Geometry::GeometryInstance { .. } => GeometryType::GeometryInstance,
176 }
177 }
178
179 pub fn lod(&self) -> Option<&str> {
181 match self {
182 Geometry::MultiPoint { lod, .. }
183 | Geometry::MultiLineString { lod, .. }
184 | Geometry::MultiSurface { lod, .. }
185 | Geometry::CompositeSurface { lod, .. }
186 | Geometry::Solid { lod, .. }
187 | Geometry::MultiSolid { lod, .. }
188 | Geometry::CompositeSolid { lod, .. } => lod.as_deref(),
189 Geometry::GeometryInstance { .. } => None,
190 }
191 }
192
193 pub fn common(&self) -> Option<&GeometryCommon> {
197 match self {
198 Geometry::MultiPoint { common, .. }
199 | Geometry::MultiLineString { common, .. }
200 | Geometry::MultiSurface { common, .. }
201 | Geometry::CompositeSurface { common, .. }
202 | Geometry::Solid { common, .. }
203 | Geometry::MultiSolid { common, .. }
204 | Geometry::CompositeSolid { common, .. } => Some(common),
205 Geometry::GeometryInstance { .. } => None,
206 }
207 }
208
209 pub fn common_mut(&mut self) -> Option<&mut GeometryCommon> {
210 match self {
211 Geometry::MultiPoint { common, .. }
212 | Geometry::MultiLineString { common, .. }
213 | Geometry::MultiSurface { common, .. }
214 | Geometry::CompositeSurface { common, .. }
215 | Geometry::Solid { common, .. }
216 | Geometry::MultiSolid { common, .. }
217 | Geometry::CompositeSolid { common, .. } => Some(common),
218 Geometry::GeometryInstance { .. } => None,
219 }
220 }
221
222 fn boundary_indices_mut(&mut self) -> Box<dyn Iterator<Item = &mut usize> + '_> {
226 match self {
227 Geometry::MultiPoint { boundaries, .. }
228 | Geometry::GeometryInstance { boundaries, .. } => Box::new(boundaries.iter_mut()),
229 Geometry::MultiLineString { boundaries, .. } => {
230 Box::new(boundaries.iter_mut().flatten())
231 }
232 Geometry::MultiSurface { boundaries, .. }
233 | Geometry::CompositeSurface { boundaries, .. } => {
234 Box::new(boundaries.iter_mut().flatten().flatten())
235 }
236 Geometry::Solid { boundaries, .. } => {
237 Box::new(boundaries.iter_mut().flatten().flatten().flatten())
238 }
239 Geometry::MultiSolid { boundaries, .. }
240 | Geometry::CompositeSolid { boundaries, .. } => Box::new(
241 boundaries
242 .iter_mut()
243 .flatten()
244 .flatten()
245 .flatten()
246 .flatten(),
247 ),
248 }
249 }
250
251 pub(crate) fn update_geometry_boundaries(&mut self, violdnew: &mut HashMap<usize, usize>) {
252 for vi in self.boundary_indices_mut() {
253 let next = violdnew.len();
254 *vi = *violdnew.entry(*vi).or_insert(next);
255 }
256 }
257
258 pub(crate) fn offset_geometry_boundaries(&mut self, offset: usize) {
259 for vi in self.boundary_indices_mut() {
260 *vi += offset;
261 }
262 }
263
264 pub(crate) fn update_material(&mut self, m_oldnew: &mut HashMap<usize, usize>) {
268 let Some(common) = self.common_mut() else {
269 return;
270 };
271 let Some(materials) = common.material.as_mut() else {
272 return;
273 };
274 for mat in materials.values_mut() {
275 if let Some(thevalue) = mat.value {
277 let l = m_oldnew.len();
278 mat.value = Some(*m_oldnew.entry(thevalue).or_insert(l));
279 continue;
280 }
281 let Some(values) = mat.values.as_mut().and_then(|v| v.as_mut()) else {
285 continue;
286 };
287 for x in values.indices_mut() {
288 let l = m_oldnew.len();
289 *x = *m_oldnew.entry(*x).or_insert(l);
290 }
291 }
292 }
293
294 pub(crate) fn update_texture(
299 &mut self,
300 t_oldnew: &mut HashMap<usize, usize>,
301 t_v_oldnew: &mut HashMap<usize, usize>,
302 offset: usize,
303 ) {
304 let Some(common) = self.common_mut() else {
305 return;
306 };
307 let Some(textures) = common.texture.as_mut() else {
308 return;
309 };
310 let mut remap = |is_texture: bool, thevalue: usize| -> usize {
313 if is_texture {
314 let l = t_oldnew.len();
315 *t_oldnew.entry(thevalue).or_insert(l)
316 } else {
317 match t_v_oldnew.get(&thevalue) {
318 Some(y) => *y,
319 None => {
320 let l = t_v_oldnew.len();
321 t_v_oldnew.insert(thevalue, l + offset);
322 l
323 }
324 }
325 }
326 };
327 for tex in textures.values_mut() {
328 let Some(values) = tex.values.as_mut() else {
330 continue;
331 };
332 for ring in values.rings_mut() {
333 for (k, z) in ring.iter_mut().enumerate() {
334 if let Some(thevalue) = *z {
335 *z = Some(remap(k == 0, thevalue));
336 }
337 }
338 }
339 }
340 }
341}
342
343#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
344pub struct GeometryTemplates {
345 pub templates: Vec<Geometry>,
346 #[serde(rename = "vertices-templates")]
347 pub vertices_templates: Value,
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 fn parse(v: serde_json::Value) -> std::result::Result<Geometry, serde_json::Error> {
355 serde_json::from_value(v)
356 }
357
358 #[test]
359 fn each_variant_has_its_own_depth() {
360 let cases = [
361 (GeometryType::MultiPoint, serde_json::json!([0, 1])),
362 (GeometryType::MultiLineString, serde_json::json!([[0, 1]])),
363 (GeometryType::MultiSurface, serde_json::json!([[[0, 1, 2]]])),
364 (
365 GeometryType::CompositeSurface,
366 serde_json::json!([[[0, 1, 2]]]),
367 ),
368 (GeometryType::Solid, serde_json::json!([[[[0, 1, 2]]]])),
369 (
370 GeometryType::MultiSolid,
371 serde_json::json!([[[[[0, 1, 2]]]]]),
372 ),
373 (
374 GeometryType::CompositeSolid,
375 serde_json::json!([[[[[0, 1, 2]]]]]),
376 ),
377 ];
378 for (thetype, boundaries) in cases {
379 let name = serde_json::to_value(thetype).unwrap();
380 let g = parse(serde_json::json!({
381 "type": name, "lod": "1", "boundaries": boundaries.clone()
382 }))
383 .unwrap_or_else(|e| panic!("{name} must accept its own depth: {e}"));
384 assert_eq!(g.geometry_type(), thetype);
385 assert_eq!(g.lod(), Some("1"));
386 assert_eq!(
388 serde_json::to_value(&g).unwrap()["boundaries"],
389 boundaries,
390 "{name} must round-trip its boundaries"
391 );
392
393 let too_deep = serde_json::json!([boundaries]);
395 assert!(
396 parse(serde_json::json!({
397 "type": name, "lod": "1", "boundaries": too_deep
398 }))
399 .is_err(),
400 "{name} must reject boundaries one level too deep"
401 );
402
403 let too_shallow = boundaries[0].clone();
405 assert!(
406 parse(serde_json::json!({
407 "type": name, "lod": "1", "boundaries": too_shallow
408 }))
409 .is_err(),
410 "{name} must reject boundaries one level too shallow"
411 );
412 }
413 }
414
415 #[test]
416 fn geometry_instance_roundtrips() {
417 let input = serde_json::json!({
418 "type": "GeometryInstance",
419 "boundaries": [372],
420 "template": 0,
421 "transformationMatrix": [
422 1.0, 0.0, 0.0, 0.0,
423 0.0, 1.0, 0.0, 0.0,
424 0.0, 0.0, 1.0, 0.0,
425 0.0, 0.0, 0.0, 1.0
426 ]
427 });
428 let g: Geometry = serde_json::from_value(input.clone()).unwrap();
429 assert_eq!(g.geometry_type(), GeometryType::GeometryInstance);
430 assert!(g.common().is_none());
431 assert_eq!(serde_json::to_value(&g).unwrap(), input);
432 }
433
434 #[test]
435 fn offset_walks_every_depth() {
436 let mut g: Geometry = serde_json::from_value(serde_json::json!({
437 "type": "CompositeSolid", "lod": "2",
438 "boundaries": [[[[[0, 1]]]], [[[[2, 3]]]]]
439 }))
440 .unwrap();
441 g.offset_geometry_boundaries(10);
442 assert_eq!(
443 serde_json::to_value(&g).unwrap()["boundaries"],
444 serde_json::json!([[[[[10, 11]]]], [[[[12, 13]]]]])
445 );
446 }
447
448 #[test]
449 fn update_boundaries_compacts_indices() {
450 let mut g: Geometry = serde_json::from_value(serde_json::json!({
451 "type": "MultiSurface", "lod": "2",
452 "boundaries": [[[7, 9, 7]], [[9, 4, 4]]]
453 }))
454 .unwrap();
455 let mut map: HashMap<usize, usize> = HashMap::new();
456 g.update_geometry_boundaries(&mut map);
457 assert_eq!(
458 serde_json::to_value(&g).unwrap()["boundaries"],
459 serde_json::json!([[[0, 1, 0]], [[1, 2, 2]]])
460 );
461 assert_eq!(map.len(), 3);
462 }
463
464 #[test]
468 fn update_boundaries_compacts_indices_at_max_depth() {
469 let mut g: Geometry = serde_json::from_value(serde_json::json!({
470 "type": "CompositeSolid", "lod": "2",
471 "boundaries": [[[[[7, 9, 7]]]], [[[[9, 4, 4]]]]]
472 }))
473 .unwrap();
474 let mut map: HashMap<usize, usize> = HashMap::new();
475 g.update_geometry_boundaries(&mut map);
476 assert_eq!(
477 serde_json::to_value(&g).unwrap()["boundaries"],
478 serde_json::json!([[[[[0, 1, 0]]]], [[[[1, 2, 2]]]]])
479 );
480 let mut entries: Vec<(usize, usize)> = map.into_iter().collect();
481 entries.sort();
482 assert_eq!(entries, vec![(4, 2), (7, 0), (9, 1)]);
483 }
484
485 #[test]
496 fn update_texture_renumbers_textures_and_texture_vertices() {
497 let mut g: Geometry = serde_json::from_value(serde_json::json!({
498 "type": "MultiSurface", "lod": "2",
499 "boundaries": [[[0, 1, 2, 3]], [[4, 5, 6, 7]], [[8, 9, 10, 11]]],
500 "texture": {"winter": {"values": [
501 [[5, 20, 21, 22, 20]],
502 [[5, 22, 21, 30, 22]],
503 [[null]]
504 ]}}
505 }))
506 .unwrap();
507 let mut t_oldnew: HashMap<usize, usize> = HashMap::new();
508 let mut t_v_oldnew: HashMap<usize, usize> = HashMap::new();
509 g.update_texture(&mut t_oldnew, &mut t_v_oldnew, 100);
510
511 let values =
514 serde_json::to_value(&g.common().unwrap().texture.as_ref().unwrap()["winter"].values)
515 .unwrap();
516 assert_eq!(
517 values,
518 serde_json::json!([[[0, 0, 1, 2, 100]], [[0, 102, 101, 3, 102]], [[null]]]),
519 "texture values must renumber exactly as the old implementation did"
520 );
521
522 let mut t: Vec<(usize, usize)> = t_oldnew.into_iter().collect();
523 t.sort();
524 assert_eq!(t, vec![(5, 0)], "the repeated texture index 5 maps once");
525
526 let mut tv: Vec<(usize, usize)> = t_v_oldnew.into_iter().collect();
527 tv.sort();
528 assert_eq!(
529 tv,
530 vec![(20, 100), (21, 101), (22, 102), (30, 103)],
531 "texture vertices are stored offset by `offset`"
532 );
533 }
534
535 #[test]
540 fn update_material_preserves_null_holes() {
541 let mut g: Geometry = serde_json::from_value(serde_json::json!({
542 "type": "Solid", "lod": "2",
543 "boundaries": [[[[0, 1, 2, 3]], [[4, 5, 6, 7]], [[8, 9, 10, 11]]]],
544 "material": {"irradiation": {"values": [[0, null, 2], [2, null, 0]]}}
545 }))
546 .unwrap();
547 let mut m_oldnew: HashMap<usize, usize> = HashMap::new();
548 g.update_material(&mut m_oldnew);
549
550 let values = serde_json::to_value(
552 g.common().unwrap().material.as_ref().unwrap()["irradiation"]
553 .values
554 .as_ref()
555 .unwrap(),
556 )
557 .unwrap();
558 assert_eq!(
559 values,
560 serde_json::json!([[0, null, 1], [1, null, 0]]),
561 "null holes must survive and indices must compact in first-encounter order"
562 );
563
564 let mut m: Vec<(usize, usize)> = m_oldnew.into_iter().collect();
565 m.sort();
566 assert_eq!(m, vec![(0, 0), (2, 1)]);
567 }
568
569 #[test]
573 fn wrong_depth_reports_which_geometry_and_which_depth() {
574 let err = Geometry::from_json_value(serde_json::json!({
575 "type": "MultiSurface", "lod": "2",
576 "boundaries": [[[[0, 1, 2]]]]
577 }))
578 .unwrap_err();
579
580 match &err {
581 CjseqError::GeometryDepth {
582 geometry_type,
583 expected,
584 found,
585 } => {
586 assert_eq!(*geometry_type, GeometryType::MultiSurface);
587 assert_eq!(*expected, 3, "a MultiSurface nests boundaries 3 deep");
588 assert_eq!(
589 *found, 4,
590 "the depth actually found must be reported as a number, not prose"
591 );
592 }
593 other => panic!("expected a GeometryDepth error, got {other:?}"),
594 }
595
596 let msg = err.to_string();
597 assert!(
598 msg.contains("MultiSurface") && msg.contains('3'),
599 "the message must name the geometry and the depth it expected, got {msg:?}"
600 );
601 }
602
603 #[test]
606 fn a_non_depth_failure_stays_a_json_error() {
607 let err = Geometry::from_json_value(serde_json::json!({
608 "type": "MultiSurface", "lod": "2",
609 "boundaries": [[["not an index"]]]
610 }))
611 .unwrap_err();
612 assert!(
613 matches!(err, CjseqError::Json(_)),
614 "expected a Json error, got {err:?}"
615 );
616 }
617
618 #[test]
619 fn from_json_value_accepts_a_good_geometry() {
620 let g = Geometry::from_json_value(serde_json::json!({
621 "type": "Solid", "lod": "2", "boundaries": [[[[0, 1, 2]]]]
622 }))
623 .unwrap();
624 assert_eq!(g.geometry_type(), GeometryType::Solid);
625 }
626
627 #[test]
628 fn absent_members_are_not_serialized_as_null() {
629 let g: Geometry = serde_json::from_value(serde_json::json!({
630 "type": "MultiSurface", "boundaries": [[[0, 1, 2]]]
631 }))
632 .unwrap();
633 assert_eq!(
634 serde_json::to_value(&g).unwrap(),
635 serde_json::json!({"type": "MultiSurface", "boundaries": [[[0, 1, 2]]]})
636 );
637 }
638}