Skip to main content

wde_gltf/
model.rs

1//! Core glTF model structures used by the loader and parser.
2#![allow(dead_code)]
3
4use std::collections::HashMap;
5
6use bevy::prelude::*;
7use wde_animation::AnimationClip;
8
9use crate::material::GltfMaterial;
10
11/// Definitions of glTF model, buffers, meshes, accessors, and materials.
12#[derive(Debug, Clone)]
13pub struct GltfModel {
14    pub path: String,
15    pub filename: String,
16    pub buffers: Vec<GltfBuffer>,
17    pub meshes: Vec<GltfMesh>,
18    pub materials: Vec<GltfMaterial>,
19    /// Named properties (e.g. spawn points) extracted from meshes prefixed with `%P%`,
20    /// mapping the property name to its `(position, scale)`.
21    pub properties: HashMap<String, (Vec3, Vec3)>,
22    /// Bones of a rigged model, parents first.
23    pub bones: Vec<GltfBone>,
24    /// Node animations of the bones.
25    pub animations: Vec<AnimationClip>,
26    /// Lights of the model (`KHR_lights_punctual`).
27    pub lights: Vec<GltfLight>
28}
29
30/// Light of a model, shining along its local -Z.
31#[derive(Debug, Clone)]
32pub struct GltfLight {
33    pub name: String,
34    pub kind: GltfLightKind,
35    /// Position and orientation in model space.
36    pub transform: Transform,
37    /// Linear RGB color.
38    pub color: Vec3,
39    pub intensity: f32,
40    /// Distance beyond which the light has no effect.
41    pub range: f32
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum GltfLightKind {
46    Point,
47    /// Cone angles from the light axis, in radians.
48    Spot {
49        inner_cone: f32,
50        outer_cone: f32
51    }
52}
53
54/// Rigid part of a rigged model, from a node named `%B%<bone>`.
55#[derive(Debug, Clone)]
56pub struct GltfBone {
57    pub name: String,
58    /// Index of the parent bone, `None` for a root bone.
59    pub parent: Option<usize>,
60    /// Local transform, relative to the parent bone or to the model.
61    pub rest: Transform
62}
63
64/// Representation of a glTF buffer with its raw data and bufferView slices.
65#[derive(Clone)]
66pub struct GltfBuffer {
67    pub data: Vec<u8>,
68    pub slices: Vec<BufferSliceData>
69}
70impl std::fmt::Debug for GltfBuffer {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        f.debug_struct("GltfBuffer")
73            .field("data_length", &self.data.len())
74            .field("slices", &self.slices)
75            .finish()
76    }
77}
78
79/// Representation of a bufferView slice within a glTF buffer.
80#[derive(Debug, Clone)]
81pub struct BufferSliceData {
82    /// Byte offset within the buffer for this view
83    pub byte_offset: usize
84}
85
86/// Representation of a glTF mesh with its primitives.
87#[derive(Debug, Clone)]
88pub struct GltfMesh {
89    pub primitives: Vec<MeshPrimitive>
90}
91
92/// Representation of a glTF mesh primitive with its attributes and indices.
93#[derive(Debug, Clone)]
94pub struct MeshPrimitive {
95    /// Name of the primitive (if any)
96    pub name: String,
97    /// Optional indices accessor if the primitive is indexed
98    pub vertex_indexed: Option<AccessorData>,
99    /// Map of attribute name to its accessor data
100    pub vertex_attributes: Vec<(String, AccessorData)>,
101    /// Material index of the primitive
102    pub material_id: Option<u32>,
103    /// Node translation [x, y, z]
104    pub translation: [f32; 3],
105    /// Node rotation as quaternion [x, y, z, w]
106    pub rotation: [f32; 4],
107    /// Node scale [x, y, z]
108    pub scale: [f32; 3],
109    /// Connection condition, for nodes named `%V%<anchor>+` / `%V%<anchor>-`
110    pub condition: Option<ModelCondition>,
111    /// Bone the primitive moves with: its vertices are then left relative to the bone
112    pub bone: Option<usize>
113}
114
115/// Makes a mesh conditional on the connection state of a named anchor (`%P%` property).
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct ModelCondition {
118    /// Anchor property name, e.g. `AnchorIn.001`
119    pub anchor: String,
120    /// Shown when the anchor is connected (`+`), or when it is not (`-`)
121    pub connected: bool
122}
123impl ModelCondition {
124    /// Parses a `%V%<anchor>+` / `%V%<anchor>-` node name.
125    pub fn from_node_name(name: &str) -> Option<Self> {
126        let rest = name.strip_prefix("%V%")?;
127        // Blender suffixes duplicate names with `.001`, after the +/- marker
128        let rest = match rest.rfind(['+', '-']) {
129            Some(i) => &rest[..=i],
130            None => return None
131        };
132        let (anchor, sign) = rest.split_at(rest.len() - 1);
133        Some(Self {
134            anchor: anchor.to_string(),
135            connected: sign == "+"
136        })
137    }
138}
139
140/// Representation of a glTF accessor for accessing buffer data.
141#[derive(Debug, Clone)]
142pub struct AccessorData {
143    /// Index of the bufferView this accessor references
144    pub buffer_view_index: i64,
145    /// Additional byte offset within the bufferView
146    pub byte_offset: usize,
147    /// Component type of the accessor values
148    pub component_type: GltfAccessorComponentType,
149    /// Number of elements in the accessor
150    pub count: usize,
151    /// Logical accessor type (e.g., SCALAR, VEC2, VEC3, ...)
152    pub accessor_type: String,
153    /// Optional minimum values (for bounding box computation)
154    pub min: Option<Vec<f32>>,
155    /// Optional maximum values (for bounding box computation)
156    pub max: Option<Vec<f32>>
157}
158
159/// Enumeration of glTF accessor component types.
160#[derive(Debug, Clone, Copy)]
161pub enum GltfAccessorComponentType {
162    Byte = 5120,
163    UnsignedByte = 5121,
164    Short = 5122,
165    UnsignedShort = 5123,
166    UnsignedInt = 5125,
167    Float = 5126
168}
169impl GltfAccessorComponentType {
170    /// Create a GltfAccessorComponentType from its integer value.
171    pub fn from_i64(value: i64) -> Option<Self> {
172        match value {
173            5120 => Some(GltfAccessorComponentType::Byte),
174            5121 => Some(GltfAccessorComponentType::UnsignedByte),
175            5122 => Some(GltfAccessorComponentType::Short),
176            5123 => Some(GltfAccessorComponentType::UnsignedShort),
177            5125 => Some(GltfAccessorComponentType::UnsignedInt),
178            5126 => Some(GltfAccessorComponentType::Float),
179            _ => unreachable!("Unsupported component type: {}", value)
180        }
181    }
182}