lib3mf_core/model/
mesh.rs

1use crate::model::ResourceId;
2use glam::Vec3;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// Type of 3MF object determining validation requirements and build behavior.
7///
8/// Per 3MF Core Specification:
9/// - Model/SolidSupport: Must be manifold, closed volumes
10/// - Support/Surface/Other: Can be non-manifold, open meshes
11///
12/// # Examples
13///
14/// ```
15/// use lib3mf_core::model::ObjectType;
16///
17/// let obj_type = ObjectType::default();
18/// assert_eq!(obj_type, ObjectType::Model);
19///
20/// // Check validation requirements
21/// assert!(ObjectType::Model.requires_manifold());
22/// assert!(!ObjectType::Support.requires_manifold());
23///
24/// // Check build constraints
25/// assert!(ObjectType::Model.can_be_in_build());
26/// assert!(!ObjectType::Other.can_be_in_build());
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
29#[serde(rename_all = "lowercase")]
30pub enum ObjectType {
31    /// Printable part - requires manifold mesh (default)
32    #[default]
33    Model,
34    /// Support structure - non-manifold allowed, can be ignored by consumer
35    Support,
36    /// Solid support structure - manifold required, filled like model
37    #[serde(rename = "solidsupport")]
38    SolidSupport,
39    /// Surface geometry - non-manifold allowed
40    Surface,
41    /// Other geometry - non-manifold allowed, cannot be referenced in build
42    Other,
43}
44
45impl std::fmt::Display for ObjectType {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            ObjectType::Model => write!(f, "model"),
49            ObjectType::Support => write!(f, "support"),
50            ObjectType::SolidSupport => write!(f, "solidsupport"),
51            ObjectType::Surface => write!(f, "surface"),
52            ObjectType::Other => write!(f, "other"),
53        }
54    }
55}
56
57impl ObjectType {
58    /// Returns true if this type requires manifold mesh validation
59    pub fn requires_manifold(&self) -> bool {
60        matches!(self, ObjectType::Model | ObjectType::SolidSupport)
61    }
62
63    /// Returns true if this type can be referenced in build items
64    pub fn can_be_in_build(&self) -> bool {
65        !matches!(self, ObjectType::Other)
66    }
67}
68
69/// A 3D object resource containing geometry and metadata.
70///
71/// Objects are the primary reusable resources in a 3MF model. They define geometry
72/// (meshes, components, boolean shapes, etc.) and can be referenced by build items
73/// or composed into other objects via components.
74///
75/// Each object has an [`ObjectType`] that determines its validation requirements and
76/// whether it can appear in the build. For example, `Model` and `SolidSupport` types
77/// must be manifold closed volumes, while `Support` and `Surface` types can be
78/// non-manifold.
79///
80/// # Examples
81///
82/// See [`Mesh`] for examples of creating geometry to place in an object.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Object {
85    /// Unique identifier for this resource within the model.
86    /// See [`ResourceId`] for details on the global namespace.
87    pub id: ResourceId,
88    /// Object type determining validation rules and build behavior.
89    #[serde(default)]
90    pub object_type: ObjectType,
91    /// Human-readable name for the object (optional).
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub name: Option<String>,
94    /// Part number for inventory/manufacturing tracking (optional).
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub part_number: Option<String>,
97    /// Production Extension UUID for unique identification (optional).
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub uuid: Option<Uuid>,
100    /// Default property resource ID (material/color) for this object (optional).
101    /// Used when triangles don't specify their own properties.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub pid: Option<ResourceId>,
104    /// Default property index within the property resource (optional).
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub pindex: Option<u32>,
107    /// Path to the thumbnail image in the 3MF package (optional).
108    /// Used for object-level thumbnails (distinct from package thumbnail).
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub thumbnail: Option<String>,
111    /// The actual geometric content of the object.
112    pub geometry: Geometry,
113}
114
115/// The geometric data contained in an object.
116///
117/// Represents the different types of geometry that can be stored in a 3MF object.
118/// The basic types are meshes and component assemblies, with various extensions
119/// adding support for slices, voxels, boolean operations, and displacement mapping.
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub enum Geometry {
122    /// A triangle mesh (the most common geometry type).
123    Mesh(Mesh),
124    /// A hierarchical assembly of other objects via components.
125    Components(Components),
126    /// A stack of 2D slices for layer-based printing (Slice Extension).
127    /// References a slice stack resource by ID.
128    SliceStack(ResourceId),
129    /// Voxel-based volumetric data (Volumetric Extension).
130    /// References a volumetric stack resource by ID.
131    VolumetricStack(ResourceId),
132    /// Constructive solid geometry from boolean operations (Boolean Operations Extension).
133    BooleanShape(BooleanShape),
134    /// A mesh with displacement mapping for fine surface detail (Displacement Extension).
135    DisplacementMesh(DisplacementMesh),
136}
137
138impl Geometry {
139    /// Returns true if this geometry contains actual content (non-empty mesh,
140    /// components, boolean shapes, or displacement meshes).
141    ///
142    /// A default-constructed `Geometry::Mesh(Mesh::default())` has no content
143    /// (no vertices, no triangles). This is the default return from
144    /// `parse_object_geometry` when no `<mesh>` or `<components>` child element
145    /// is present.
146    pub fn has_content(&self) -> bool {
147        match self {
148            Geometry::Mesh(mesh) => !mesh.vertices.is_empty() || !mesh.triangles.is_empty(),
149            Geometry::Components(c) => !c.components.is_empty(),
150            Geometry::BooleanShape(_) => true,
151            Geometry::DisplacementMesh(_) => true,
152            // SliceStack and VolumetricStack are references, not inline content
153            Geometry::SliceStack(_) | Geometry::VolumetricStack(_) => false,
154        }
155    }
156}
157
158/// A collection of components forming a hierarchical assembly.
159///
160/// Components allow building complex objects by composing and transforming
161/// other objects. This enables reuse and efficient representation of
162/// assemblies with repeated parts.
163#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
164pub struct Components {
165    /// The list of component instances in this assembly.
166    pub components: Vec<Component>,
167}
168
169/// A reference to another object with optional transformation.
170///
171/// Components enable hierarchical object composition by referencing other
172/// objects (which can themselves contain meshes or more components). Each
173/// component can apply a transformation matrix to position/rotate/scale
174/// the referenced object.
175///
176/// # Examples
177///
178/// Components are commonly used to:
179/// - Create assemblies from multiple parts
180/// - Reuse the same object in different positions (instances)
181/// - Apply transformations (rotation, scaling, translation) to objects
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct Component {
184    /// ID of the object being referenced.
185    pub object_id: ResourceId,
186    /// External reference path for production tracking (optional).
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub path: Option<String>,
189    /// Production Extension UUID for this component instance (optional).
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub uuid: Option<Uuid>,
192    /// Transformation matrix applied to the referenced object.
193    /// Defaults to identity (no transformation).
194    #[serde(default = "default_transform", skip_serializing_if = "is_identity")]
195    pub transform: glam::Mat4,
196}
197
198fn default_transform() -> glam::Mat4 {
199    glam::Mat4::IDENTITY
200}
201
202fn is_identity(transform: &glam::Mat4) -> bool {
203    *transform == glam::Mat4::IDENTITY
204}
205
206/// Type of boolean operation to apply between shapes.
207///
208/// Per 3MF Boolean Operations Extension v1.1.1:
209/// - Union: Combines both shapes (default)
210/// - Difference: Subtracts operation shape from base
211/// - Intersection: Only keeps overlapping volume
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
213#[serde(rename_all = "lowercase")]
214pub enum BooleanOperationType {
215    /// Combine both shapes (default)
216    #[default]
217    Union,
218    /// Subtract operation shape from base
219    Difference,
220    /// Keep only overlapping volume
221    Intersection,
222}
223
224/// A single boolean operation applied to a shape.
225///
226/// Represents one `<boolean>` element within a `<booleanshape>`.
227#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
228pub struct BooleanOperation {
229    /// Type of boolean operation (union, difference, intersection)
230    #[serde(default)]
231    pub operation_type: BooleanOperationType,
232    /// Reference to the object to apply
233    pub object_id: ResourceId,
234    /// Transformation matrix applied to the operation object
235    #[serde(default = "default_transform", skip_serializing_if = "is_identity")]
236    pub transform: glam::Mat4,
237    /// Optional external reference path (p:path)
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub path: Option<String>,
240}
241
242/// A boolean shape combining multiple objects with CSG operations.
243///
244/// Represents a `<booleanshape>` resource that defines geometry through
245/// constructive solid geometry (CSG) operations.
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct BooleanShape {
248    /// Base object to start with
249    pub base_object_id: ResourceId,
250    /// Transformation applied to base object
251    #[serde(default = "default_transform", skip_serializing_if = "is_identity")]
252    pub base_transform: glam::Mat4,
253    /// Optional external reference path for base (p:path)
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub base_path: Option<String>,
256    /// Ordered list of boolean operations to apply
257    pub operations: Vec<BooleanOperation>,
258}
259
260/// A triangle mesh representing 3D geometry.
261///
262/// A mesh is the fundamental geometry container in 3MF, consisting of vertices
263/// (3D points) and triangles that connect those vertices. Meshes can optionally
264/// include beam lattice structures for lightweight, high-strength geometry.
265///
266/// # Examples
267///
268/// ```
269/// use lib3mf_core::model::Mesh;
270///
271/// let mut mesh = Mesh::new();
272/// let v1 = mesh.add_vertex(0.0, 0.0, 0.0);
273/// let v2 = mesh.add_vertex(1.0, 0.0, 0.0);
274/// let v3 = mesh.add_vertex(0.0, 1.0, 0.0);
275/// mesh.add_triangle(v1, v2, v3);
276/// assert_eq!(mesh.triangles.len(), 1);
277/// ```
278#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
279pub struct Mesh {
280    /// List of vertices (points in 3D space).
281    pub vertices: Vec<Vertex>,
282    /// List of triangles connecting vertices by their indices.
283    pub triangles: Vec<Triangle>,
284    /// Beam Lattice extension data for structural lattice geometry (optional).
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub beam_lattice: Option<BeamLattice>,
287}
288
289/// Beam lattice structure for lightweight, high-strength geometry.
290///
291/// The Beam Lattice extension allows representing cylindrical beams between
292/// vertices as an alternative to solid triangle meshes. This is particularly
293/// useful for lightweight structures, scaffolding, and lattice infill patterns.
294///
295/// Each beam is a cylinder connecting two vertices with potentially different
296/// radii at each end (creating tapered beams).
297#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
298pub struct BeamLattice {
299    /// Minimum beam length threshold (beams shorter than this may be ignored).
300    #[serde(default)]
301    pub min_length: f32,
302    /// Precision for beam representation (implementation-defined).
303    #[serde(default)]
304    pub precision: f32,
305    /// How beams should be clipped by the mesh boundary.
306    #[serde(default)]
307    pub clipping_mode: ClippingMode,
308    /// The list of beams in this lattice.
309    pub beams: Vec<Beam>,
310    /// Named groups of beams for organization and material assignment.
311    pub beam_sets: Vec<BeamSet>,
312}
313
314/// Clipping mode for beam lattice geometry.
315///
316/// Controls how beams interact with the mesh boundary.
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
318#[serde(rename_all = "lowercase")]
319pub enum ClippingMode {
320    /// No clipping applied (default).
321    #[default]
322    None,
323    /// Clip beams to inside the mesh boundary.
324    Inside,
325    /// Clip beams to outside the mesh boundary.
326    Outside,
327}
328
329/// A cylindrical beam connecting two vertices.
330///
331/// Beams are defined by two vertex indices and a radius at each end,
332/// allowing for tapered beams. They can have different materials at
333/// each endpoint via property indices.
334#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
335pub struct Beam {
336    /// Index of the first vertex.
337    pub v1: u32,
338    /// Index of the second vertex.
339    pub v2: u32,
340    /// Radius at the first vertex.
341    pub r1: f32,
342    /// Radius at the second vertex.
343    pub r2: f32,
344    /// Property index at the first vertex (optional).
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub p1: Option<u32>,
347    /// Property index at the second vertex (optional).
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub p2: Option<u32>,
350    /// Cap style for the beam ends.
351    #[serde(default)]
352    pub cap_mode: CapMode,
353}
354
355/// End cap style for beams.
356///
357/// Determines how the ends of beams are terminated.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
359#[serde(rename_all = "lowercase")]
360pub enum CapMode {
361    /// Spherical cap (default) - fully rounded.
362    #[default]
363    Sphere,
364    /// Hemispherical cap - half sphere.
365    Hemisphere,
366    /// Flat cap - no rounding.
367    Butt,
368}
369
370/// A named group of beams.
371///
372/// Beam sets allow organizing beams and applying properties to groups.
373#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
374pub struct BeamSet {
375    /// Human-readable name for this beam set (optional).
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub name: Option<String>,
378    /// Machine-readable identifier (optional).
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub identifier: Option<String>,
381    /// Indices of beams in this set (references into the beams array).
382    pub refs: Vec<u32>,
383}
384
385impl Mesh {
386    /// Creates a new empty mesh.
387    pub fn new() -> Self {
388        Self::default()
389    }
390
391    /// Adds a vertex to the mesh and returns its index.
392    ///
393    /// # Arguments
394    ///
395    /// * `x` - X coordinate in model units
396    /// * `y` - Y coordinate in model units
397    /// * `z` - Z coordinate in model units
398    ///
399    /// # Returns
400    ///
401    /// The index of the newly added vertex, which can be used to reference this vertex in triangles.
402    pub fn add_vertex(&mut self, x: f32, y: f32, z: f32) -> u32 {
403        self.vertices.push(Vertex { x, y, z });
404        (self.vertices.len() - 1) as u32
405    }
406
407    /// Adds a triangle to the mesh connecting three vertices.
408    ///
409    /// # Arguments
410    ///
411    /// * `v1` - Index of the first vertex
412    /// * `v2` - Index of the second vertex
413    /// * `v3` - Index of the third vertex
414    ///
415    /// The vertex indices should be in counter-clockwise order when viewed from outside
416    /// the mesh for correct normal orientation.
417    pub fn add_triangle(&mut self, v1: u32, v2: u32, v3: u32) {
418        self.triangles.push(Triangle {
419            v1,
420            v2,
421            v3,
422            ..Default::default()
423        });
424    }
425
426    /// Computes the axis-aligned bounding box (AABB) of the mesh.
427    ///
428    /// Returns `None` if the mesh has no vertices.
429    pub fn compute_aabb(&self) -> Option<crate::model::stats::BoundingBox> {
430        if self.vertices.is_empty() {
431            return None;
432        }
433
434        let initial = (
435            f32::INFINITY,
436            f32::INFINITY,
437            f32::INFINITY,
438            f32::NEG_INFINITY,
439            f32::NEG_INFINITY,
440            f32::NEG_INFINITY,
441        );
442
443        #[cfg(feature = "parallel")]
444        let (min_x, min_y, min_z, max_x, max_y, max_z) = {
445            use rayon::prelude::*;
446            self.vertices
447                .par_iter()
448                .fold(
449                    || initial,
450                    |acc, v| {
451                        (
452                            acc.0.min(v.x),
453                            acc.1.min(v.y),
454                            acc.2.min(v.z),
455                            acc.3.max(v.x),
456                            acc.4.max(v.y),
457                            acc.5.max(v.z),
458                        )
459                    },
460                )
461                .reduce(
462                    || initial,
463                    |a, b| {
464                        (
465                            a.0.min(b.0),
466                            a.1.min(b.1),
467                            a.2.min(b.2),
468                            a.3.max(b.3),
469                            a.4.max(b.4),
470                            a.5.max(b.5),
471                        )
472                    },
473                )
474        };
475
476        #[cfg(not(feature = "parallel"))]
477        let (min_x, min_y, min_z, max_x, max_y, max_z) =
478            self.vertices.iter().fold(initial, |acc, v| {
479                (
480                    acc.0.min(v.x),
481                    acc.1.min(v.y),
482                    acc.2.min(v.z),
483                    acc.3.max(v.x),
484                    acc.4.max(v.y),
485                    acc.5.max(v.z),
486                )
487            });
488
489        Some(crate::model::stats::BoundingBox {
490            min: [min_x, min_y, min_z],
491            max: [max_x, max_y, max_z],
492        })
493    }
494
495    /// Computes the total surface area and volume of the mesh.
496    ///
497    /// Uses triangle area calculation and signed tetrahedron volumes.
498    /// Returns (0.0, 0.0) if the mesh has no triangles.
499    ///
500    /// # Returns
501    ///
502    /// A tuple of (surface_area, volume) in square and cubic model units respectively.
503    pub fn compute_area_and_volume(&self) -> (f64, f64) {
504        if self.triangles.is_empty() {
505            return (0.0, 0.0);
506        }
507
508        #[cfg(feature = "parallel")]
509        let (area, volume) = {
510            use rayon::prelude::*;
511            self.triangles
512                .par_iter()
513                .fold(
514                    || (0.0f64, 0.0f64),
515                    |acc, t| {
516                        let (area, volume) = self.compute_triangle_stats(t);
517                        (acc.0 + area, acc.1 + volume)
518                    },
519                )
520                .reduce(|| (0.0, 0.0), |a, b| (a.0 + b.0, a.1 + b.1))
521        };
522
523        #[cfg(not(feature = "parallel"))]
524        let (area, volume) = self.triangles.iter().fold((0.0f64, 0.0f64), |acc, t| {
525            let (area, volume) = self.compute_triangle_stats(t);
526            (acc.0 + area, acc.1 + volume)
527        });
528
529        (area, volume)
530    }
531
532    fn compute_triangle_stats(&self, t: &Triangle) -> (f64, f64) {
533        let v1 = glam::Vec3::new(
534            self.vertices[t.v1 as usize].x,
535            self.vertices[t.v1 as usize].y,
536            self.vertices[t.v1 as usize].z,
537        );
538        let v2 = glam::Vec3::new(
539            self.vertices[t.v2 as usize].x,
540            self.vertices[t.v2 as usize].y,
541            self.vertices[t.v2 as usize].z,
542        );
543        let v3 = glam::Vec3::new(
544            self.vertices[t.v3 as usize].x,
545            self.vertices[t.v3 as usize].y,
546            self.vertices[t.v3 as usize].z,
547        );
548
549        // Area using cross product
550        let edge1 = v2 - v1;
551        let edge2 = v3 - v1;
552        let cross = edge1.cross(edge2);
553        let triangle_area = 0.5 * cross.length() as f64;
554
555        // Signed volume of tetrahedron from origin
556        let triangle_volume = (v1.dot(v2.cross(v3)) / 6.0) as f64;
557
558        (triangle_area, triangle_volume)
559    }
560
561    /// Computes the area of a single triangle.
562    ///
563    /// # Arguments
564    ///
565    /// * `triangle` - Reference to the triangle whose area to compute
566    ///
567    /// # Returns
568    ///
569    /// The area of the triangle in square model units.
570    pub fn compute_triangle_area(&self, triangle: &Triangle) -> f64 {
571        let v1 = glam::Vec3::new(
572            self.vertices[triangle.v1 as usize].x,
573            self.vertices[triangle.v1 as usize].y,
574            self.vertices[triangle.v1 as usize].z,
575        );
576        let v2 = glam::Vec3::new(
577            self.vertices[triangle.v2 as usize].x,
578            self.vertices[triangle.v2 as usize].y,
579            self.vertices[triangle.v2 as usize].z,
580        );
581        let v3 = glam::Vec3::new(
582            self.vertices[triangle.v3 as usize].x,
583            self.vertices[triangle.v3 as usize].y,
584            self.vertices[triangle.v3 as usize].z,
585        );
586
587        let edge1 = v2 - v1;
588        let edge2 = v3 - v1;
589        let cross = edge1.cross(edge2);
590        0.5 * cross.length() as f64
591    }
592}
593
594/// A single point in 3D space.
595///
596/// Represents a vertex position in the mesh coordinate system.
597/// Coordinates are in model units (see [`Unit`](crate::model::Unit)).
598#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
599pub struct Vertex {
600    /// X coordinate in model units
601    pub x: f32,
602    /// Y coordinate in model units
603    pub y: f32,
604    /// Z coordinate in model units
605    pub z: f32,
606}
607
608impl From<Vec3> for Vertex {
609    fn from(v: Vec3) -> Self {
610        Self {
611            x: v.x,
612            y: v.y,
613            z: v.z,
614        }
615    }
616}
617
618/// A triangle face defined by three vertex indices.
619///
620/// Triangles are the fundamental building blocks of 3MF meshes. They reference
621/// vertices by index and can optionally specify material properties per-vertex
622/// or per-triangle.
623///
624/// # Material Properties
625///
626/// The property system allows materials/colors to be assigned at different levels:
627/// - **Triangle-level**: Use `pid` to assign a property resource to the entire triangle
628/// - **Vertex-level**: Use `p1`, `p2`, `p3` to assign different properties to each vertex
629/// - **Object-level**: If no triangle properties are set, the object's default `pid`/`pindex` apply
630///
631/// The property resolution hierarchy is: Vertex → Triangle → Object → None
632#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
633pub struct Triangle {
634    /// Index of the first vertex (counter-clockwise winding).
635    pub v1: u32,
636    /// Index of the second vertex (counter-clockwise winding).
637    pub v2: u32,
638    /// Index of the third vertex (counter-clockwise winding).
639    pub v3: u32,
640
641    /// Property index for v1 (optional, for per-vertex material assignment).
642    #[serde(skip_serializing_if = "Option::is_none")]
643    pub p1: Option<u32>,
644    /// Property index for v2 (optional, for per-vertex material assignment).
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub p2: Option<u32>,
647    /// Property index for v3 (optional, for per-vertex material assignment).
648    #[serde(skip_serializing_if = "Option::is_none")]
649    pub p3: Option<u32>,
650
651    /// Property ID resource for the entire triangle (optional, for per-triangle material assignment).
652    #[serde(skip_serializing_if = "Option::is_none")]
653    pub pid: Option<u32>,
654}
655
656/// A normal vector for displacement mesh vertices.
657#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
658pub struct NormalVector {
659    pub nx: f32,
660    pub ny: f32,
661    pub nz: f32,
662}
663
664/// A gradient vector for displacement texture mapping.
665#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
666pub struct GradientVector {
667    pub gu: f32,
668    pub gv: f32,
669}
670
671/// A triangle in a displacement mesh with displacement coordinate indices.
672#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
673pub struct DisplacementTriangle {
674    /// Index of the first vertex.
675    pub v1: u32,
676    /// Index of the second vertex.
677    pub v2: u32,
678    /// Index of the third vertex.
679    pub v3: u32,
680
681    /// Displacement coordinate index for v1 (optional).
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub d1: Option<u32>,
684    /// Displacement coordinate index for v2 (optional).
685    #[serde(skip_serializing_if = "Option::is_none")]
686    pub d2: Option<u32>,
687    /// Displacement coordinate index for v3 (optional).
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub d3: Option<u32>,
690
691    /// Property index for v1 (optional).
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub p1: Option<u32>,
694    /// Property index for v2 (optional).
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub p2: Option<u32>,
697    /// Property index for v3 (optional).
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub p3: Option<u32>,
700
701    /// Property ID for the entire triangle (optional).
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub pid: Option<u32>,
704}
705
706/// A mesh with displacement mapping support.
707#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
708pub struct DisplacementMesh {
709    /// List of vertices (points in 3D space).
710    pub vertices: Vec<Vertex>,
711    /// List of triangles connecting vertices.
712    pub triangles: Vec<DisplacementTriangle>,
713    /// Per-vertex normal vectors (must match vertex count).
714    pub normals: Vec<NormalVector>,
715    /// Per-vertex gradient vectors (optional).
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub gradients: Option<Vec<GradientVector>>,
718}