mesh_tools/
builder.rs

1//! # GltfBuilder: Main Builder Interface
2//!
3//! This module provides the main `GltfBuilder` struct which serves as the primary interface
4//! for creating and exporting glTF/GLB files. It handles the construction of the complete
5//! glTF document including scenes, nodes, meshes, materials, and binary data.
6//!
7//! The builder follows a fluent API pattern where methods can be chained together to
8//! build up the document structure incrementally.
9
10use std::fs::File;
11use std::io::{self, Write, Seek};
12use byteorder::{LittleEndian, WriteBytesExt};
13use serde_json;
14
15use crate::error::{GltfError, Result};
16use crate::models::*;
17
18/// The main builder for creating and exporting glTF models
19///
20/// `GltfBuilder` provides methods for:
21/// - Creating primitive shapes (box, sphere, plane, etc.)
22/// - Creating and managing materials
23/// - Creating scene hierarchies with nodes
24/// - Managing buffer data for vertex attributes
25/// - Exporting to glTF and GLB formats
26pub struct GltfBuilder {
27    pub gltf: Gltf,
28    pub buffer_data: Vec<u8>,
29}
30
31impl GltfBuilder {
32    /// Create a new glTF builder
33    pub fn new() -> Self {
34        // Create default minimal glTF with required asset info
35        let mut gltf = Gltf::default();
36        
37        // Asset information is required by the glTF spec
38        let asset = Asset {
39            version: "2.0".to_string(),
40            generator: Some("Rust glTF Export Library".to_string()),
41            copyright: None,
42        };
43        
44        gltf.asset = asset;
45        
46        // Initialize empty collections
47        gltf.scenes = Some(Vec::new());
48        gltf.nodes = Some(Vec::new());
49        gltf.meshes = Some(Vec::new());
50        gltf.accessors = Some(Vec::new());
51        gltf.buffer_views = Some(Vec::new());
52        gltf.buffers = Some(Vec::new());
53        
54        // Create initial buffer in buffer list
55        let buffer = Buffer {
56            byte_length: 0, // Will be updated during export
57            uri: None,     // Will be embedded in GLB
58        };
59        
60        if let Some(buffers) = &mut gltf.buffers {
61            buffers.push(buffer);
62        }
63        
64        GltfBuilder {
65            gltf,
66            buffer_data: Vec::new(),
67        }
68    }
69
70    /// Add a scene to the glTF document
71    pub fn add_scene(&mut self, name: Option<String>, nodes: Option<Vec<usize>>) -> usize {
72        let scene = Scene {
73            name,
74            nodes,
75        };
76        
77        if let Some(scenes) = &mut self.gltf.scenes {
78            let index = scenes.len();
79            scenes.push(scene);
80            
81            // Set as default scene if this is the first one
82            if self.gltf.scene.is_none() {
83                self.gltf.scene = Some(0);
84            }
85            
86            index
87        } else {
88            self.gltf.scenes = Some(vec![scene]);
89            self.gltf.scene = Some(0);
90            0
91        }
92    }
93
94    /// Add a node to the glTF document
95    pub fn add_node(&mut self, name: Option<String>, mesh: Option<usize>, 
96                   translation: Option<[f32; 3]>, rotation: Option<[f32; 4]>,
97                   scale: Option<[f32; 3]>) -> usize {
98        let node = Node {
99            name,
100            mesh,
101            translation,
102            rotation,
103            scale,
104            matrix: None,
105            children: None,
106        };
107        
108        if let Some(nodes) = &mut self.gltf.nodes {
109            let index = nodes.len();
110            nodes.push(node);
111            index
112        } else {
113            self.gltf.nodes = Some(vec![node]);
114            0
115        }
116    }
117
118    /// Add a node with a list of children to the glTF document
119    pub fn add_node_with_children(&mut self, name: Option<String>, mesh: Option<usize>, 
120                          translation: Option<[f32; 3]>, rotation: Option<[f32; 4]>,
121                          scale: Option<[f32; 3]>, children: Vec<usize>) -> usize {
122        let node = Node {
123            name,
124            mesh,
125            translation,
126            rotation,
127            scale,
128            matrix: None,
129            children: Some(children),
130        };
131        
132        if let Some(nodes) = &mut self.gltf.nodes {
133            let index = nodes.len();
134            nodes.push(node);
135            index
136        } else {
137            self.gltf.nodes = Some(vec![node]);
138            0
139        }
140    }
141
142    /// Add a child to an existing node
143    pub fn add_child_to_node(&mut self, parent_index: usize, child_index: usize) -> Result<()> {
144        if let Some(nodes) = &mut self.gltf.nodes {
145            if parent_index < nodes.len() && child_index < nodes.len() {
146                let parent = &mut nodes[parent_index];
147                
148                if let Some(children) = &mut parent.children {
149                    if !children.contains(&child_index) {
150                        children.push(child_index);
151                    }
152                } else {
153                    parent.children = Some(vec![child_index]);
154                }
155                
156                Ok(())
157            } else {
158                Err(GltfError::InvalidIndex)
159            }
160        } else {
161            Err(GltfError::InvalidData("No nodes in document".to_string()))
162        }
163    }
164
165    /// Create a parent node with multiple child nodes
166    pub fn create_node_hierarchy(&mut self, parent_name: Option<String>, 
167                               parent_translation: Option<[f32; 3]>,
168                               parent_rotation: Option<[f32; 4]>,
169                               parent_scale: Option<[f32; 3]>,
170                               child_indices: Vec<usize>) -> usize {
171        // Create the parent node with the children
172        self.add_node_with_children(
173            parent_name,
174            None, // No mesh on the parent
175            parent_translation,
176            parent_rotation,
177            parent_scale,
178            child_indices
179        )
180    }
181
182    /// Add a mesh to the glTF document
183    pub fn add_mesh(&mut self, name: Option<String>, primitives: Vec<Primitive>) -> usize {
184        let mesh = Mesh {
185            name,
186            primitives,
187        };
188        
189        if let Some(meshes) = &mut self.gltf.meshes {
190            let index = meshes.len();
191            meshes.push(mesh);
192            index
193        } else {
194            self.gltf.meshes = Some(vec![mesh]);
195            0
196        }
197    }
198    
199    /// Add an accessor to the glTF document
200    pub(crate) fn add_accessor(&mut self, buffer_view: usize, component_type: usize, 
201                       count: usize, type_: String, byte_offset: Option<usize>,
202                       min: Option<Vec<f32>>, max: Option<Vec<f32>>) -> usize {
203        let accessor = Accessor {
204            buffer_view: buffer_view,
205            component_type: component_type,
206            count,
207            type_,
208            byte_offset: byte_offset,
209            min,
210            max,
211            normalized: None,
212        };
213        
214        if let Some(accessors) = &mut self.gltf.accessors {
215            let index = accessors.len();
216            accessors.push(accessor);
217            index
218        } else {
219            self.gltf.accessors = Some(vec![accessor]);
220            0
221        }
222    }
223    
224    /// Add a buffer view to the glTF document
225    pub(crate) fn add_buffer_view(&mut self, byte_offset: usize, byte_length: usize, 
226                          target: Option<usize>) -> usize {
227        let buffer_view = BufferView {
228            buffer: 0, // We only use a single buffer
229            byte_offset: byte_offset,
230            byte_length: byte_length,
231            byte_stride: None,
232            target,
233        };
234        
235        if let Some(buffer_views) = &mut self.gltf.buffer_views {
236            let index = buffer_views.len();
237            buffer_views.push(buffer_view);
238            index
239        } else {
240            self.gltf.buffer_views = Some(vec![buffer_view]);
241            0
242        }
243    }
244    
245    /// Add binary data to the buffer and return the byte offset
246    pub(crate) fn add_buffer_data(&mut self, data: &[u8]) -> (usize, usize) {
247        // Ensure alignment to 4-byte boundary
248        while self.buffer_data.len() % 4 != 0 {
249            self.buffer_data.push(0);
250        }
251        
252        let byte_offset = self.buffer_data.len();
253        let byte_length = data.len();
254        
255        self.buffer_data.extend_from_slice(data);
256        
257        // Update the buffer size in the glTF model
258        if let Some(buffers) = &mut self.gltf.buffers {
259            if !buffers.is_empty() {
260                buffers[0].byte_length = self.buffer_data.len();
261            }
262        }
263        
264        (byte_offset, byte_length)
265    }
266
267    /// Export the glTF as a GLB file
268    pub fn export_glb(&self, path: &str) -> Result<()> {
269        let mut file = File::create(path)?;
270        
271        // Write GLB header (magic, version, length)
272        file.write_all(b"glTF")?;
273        file.write_u32::<LittleEndian>(2)?; // version
274        
275        // We'll write the total length later, once we know it
276        let length_pos = file.stream_position()?;
277        file.write_u32::<LittleEndian>(0)?; // placeholder total length
278        
279        // JSON chunk
280        let json = serde_json::to_string(&self.gltf)?;
281        let json_len = json.len();
282        let json_pad = (4 - (json_len % 4)) % 4; // Padding to 4-byte boundary
283        
284        file.write_u32::<LittleEndian>((json_len + json_pad) as u32)?; // chunk length
285        file.write_u32::<LittleEndian>(0x4E4F534A)?; // chunk type "JSON"
286        file.write_all(json.as_bytes())?;
287        
288        // Add padding
289        for _ in 0..json_pad {
290            file.write_u8(0x20)?; // Space character for padding
291        }
292        
293        // BIN chunk
294        if !self.buffer_data.is_empty() {
295            let bin_len = self.buffer_data.len();
296            let bin_pad = (4 - (bin_len % 4)) % 4; // Padding to 4-byte boundary
297            
298            file.write_u32::<LittleEndian>((bin_len + bin_pad) as u32)?; // chunk length
299            file.write_u32::<LittleEndian>(0x004E4942)?; // chunk type "BIN"
300            file.write_all(&self.buffer_data)?;
301            
302            // Add padding
303            for _ in 0..bin_pad {
304                file.write_u8(0)?;
305            }
306        }
307        
308        // Go back and write the total length
309        let current_pos = file.stream_position()?;
310        file.seek(io::SeekFrom::Start(length_pos))?;
311        file.write_u32::<LittleEndian>(current_pos as u32)?;
312        
313        Ok(())
314    }
315}