mesh_tools/
builder_primitives.rs

1//! # Primitive Shape Generation Implementation
2//!
3//! This module implements the primitive shape generation methods for the `GltfBuilder` struct.
4//! It provides functionality for creating standard 3D shapes such as boxes, spheres, planes,
5//! cylinders, cones, tori, and more.
6//!
7//! Each shape generation method:
8//! 1. Generates the geometry data (vertices, indices, normals, UVs)
9//! 2. Creates the necessary buffer views and accessors
10//! 3. Creates a mesh with the appropriate primitives
11//! 4. Returns the index of the created mesh
12//!
13//! These methods are the high-level interface for the low-level geometry generation
14//! functions in the `primitives` module.
15
16use crate::builder::GltfBuilder;
17use crate::constants::{accessor_type, buffer_view_target, component_type};
18use crate::models::Primitive;
19use crate::primitives;
20use std::collections::HashMap;
21use crate::compat::{Point3, Vector2, Vector3};
22
23/// A triangle represented by three vertex indices
24///
25/// Uses u32 indices.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Triangle {
28    /// First vertex index
29    pub a: u32,
30    /// Second vertex index
31    pub b: u32,
32    /// Third vertex index
33    pub c: u32,
34}
35
36impl Triangle {
37    /// Create a new triangle with the given vertex indices
38    ///
39    /// # Arguments
40    /// * `a` - First vertex index
41    /// * `b` - Second vertex index
42    /// * `c` - Third vertex index
43    ///
44    /// # Returns
45    /// A new Triangle instance
46    pub fn new(a: u32, b: u32, c: u32) -> Self {
47        Self { a, b, c }
48    }
49}
50
51impl GltfBuilder {
52    /// Create a simple cubic box mesh with the specified size
53    ///
54    /// This method creates a cube centered at the origin with equal dimensions on all sides.
55    /// The cube has properly generated normals and texture coordinates for each face.
56    ///
57    /// # Parameters
58    /// * `size` - The length of each side of the cube
59    ///
60    /// # Returns
61    /// The index of the created mesh in the glTF document's meshes array
62    ///
63    /// # Example
64    /// ```
65    /// use mesh_tools::GltfBuilder;
66    /// let mut builder = GltfBuilder::new();
67    /// let box_mesh = builder.create_box(2.0); // Creates a 2x2x2 cube
68    /// ```
69    pub fn create_box(&mut self, size: f32) -> usize {
70        // Box centered at origin with given size
71        let half_size = size / 2.0;
72        
73        // 8 vertices for a cube (8 corners) using Point3
74        let positions = vec![
75            // Front face (z+)
76            crate::compat::point3::new(-half_size, -half_size,  half_size),  // 0: bottom-left-front
77            crate::compat::point3::new( half_size, -half_size,  half_size),  // 1: bottom-right-front
78            crate::compat::point3::new( half_size,  half_size,  half_size),  // 2: top-right-front
79            crate::compat::point3::new(-half_size,  half_size,  half_size),  // 3: top-left-front
80            
81            // Back face (z-)
82            crate::compat::point3::new(-half_size, -half_size, -half_size),  // 4: bottom-left-back
83            crate::compat::point3::new( half_size, -half_size, -half_size),  // 5: bottom-right-back
84            crate::compat::point3::new( half_size,  half_size, -half_size),  // 6: top-right-back
85            crate::compat::point3::new(-half_size,  half_size, -half_size),  // 7: top-left-back
86        ];
87        
88        // 12 triangles (2 per face * 6 faces) using Triangle structs
89        let indices = vec![
90            // Front face (z+)
91            Triangle { a: 0, b: 1, c: 2 }, Triangle { a: 0, b: 2, c: 3 },
92            
93            // Back face (z-)
94            Triangle { a: 5, b: 4, c: 7 }, Triangle { a: 5, b: 7, c: 6 },
95            
96            // Top face (y+)
97            Triangle { a: 3, b: 2, c: 6 }, Triangle { a: 3, b: 6, c: 7 },
98            
99            // Bottom face (y-)
100            Triangle { a: 4, b: 5, c: 1 }, Triangle { a: 4, b: 1, c: 0 },
101            
102            // Right face (x+)
103            Triangle { a: 1, b: 5, c: 6 }, Triangle { a: 1, b: 6, c: 2 },
104            
105            // Left face (x-)
106            Triangle { a: 4, b: 0, c: 3 }, Triangle { a: 4, b: 3, c: 7 },
107        ];
108        
109        // Normals for each vertex using Vector3
110        let normals = vec![
111            // Front face (z+)
112            crate::compat::vector3::new(0.0, 0.0, 1.0),
113            crate::compat::vector3::new(0.0, 0.0, 1.0),
114            crate::compat::vector3::new(0.0, 0.0, 1.0),
115            crate::compat::vector3::new(0.0, 0.0, 1.0),
116            
117            // Back face (z-)
118            crate::compat::vector3::new(0.0, 0.0, -1.0),
119            crate::compat::vector3::new(0.0, 0.0, -1.0),
120            crate::compat::vector3::new(0.0, 0.0, -1.0),
121            crate::compat::vector3::new(0.0, 0.0, -1.0),
122            
123            // This is simplified for example purposes
124            // In a real implementation we would need more vertices with unique normals
125            // or use a better normal calculation strategy
126        ];
127        
128        // Simple UV mapping using Vector2
129        let uvs = vec![
130            // Front face
131            crate::compat::vector2::new(0.0, 1.0),
132            crate::compat::vector2::new(1.0, 1.0),
133            crate::compat::vector2::new(1.0, 0.0),
134            crate::compat::vector2::new(0.0, 0.0),
135            
136            // Back face
137            crate::compat::vector2::new(1.0, 1.0),
138            crate::compat::vector2::new(0.0, 1.0),
139            crate::compat::vector2::new(0.0, 0.0),
140            crate::compat::vector2::new(1.0, 0.0),
141        ];
142        
143        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), None)
144    }
145
146    /// Create a box with the specified material
147    pub fn create_box_with_material(&mut self, size: f32, material: Option<usize>) -> usize {
148        // Box centered at origin with given size
149        let half_size = size / 2.0;
150        
151        // For a proper cube with separate normals per face, we need to duplicate vertices
152        // 24 vertices for a cube (4 per face * 6 faces) using Point3
153        let positions = vec![
154            // Front face (z+)
155            crate::compat::point3::new(-half_size, -half_size,  half_size),
156            crate::compat::point3::new( half_size, -half_size,  half_size),
157            crate::compat::point3::new( half_size,  half_size,  half_size),
158            crate::compat::point3::new(-half_size,  half_size,  half_size),
159            
160            // Back face (z-)
161            crate::compat::point3::new( half_size, -half_size, -half_size),
162            crate::compat::point3::new(-half_size, -half_size, -half_size),
163            crate::compat::point3::new(-half_size,  half_size, -half_size),
164            crate::compat::point3::new( half_size,  half_size, -half_size),
165            
166            // Top face (y+)
167            crate::compat::point3::new(-half_size,  half_size,  half_size),
168            crate::compat::point3::new( half_size,  half_size,  half_size),
169            crate::compat::point3::new( half_size,  half_size, -half_size),
170            crate::compat::point3::new(-half_size,  half_size, -half_size),
171            
172            // Bottom face (y-)
173            crate::compat::point3::new( half_size, -half_size,  half_size),
174            crate::compat::point3::new(-half_size, -half_size,  half_size),
175            crate::compat::point3::new(-half_size, -half_size, -half_size),
176            crate::compat::point3::new( half_size, -half_size, -half_size),
177            
178            // Right face (x+)
179            crate::compat::point3::new( half_size, -half_size,  half_size),
180            crate::compat::point3::new( half_size, -half_size, -half_size),
181            crate::compat::point3::new( half_size,  half_size, -half_size),
182            crate::compat::point3::new( half_size,  half_size,  half_size),
183            
184            // Left face (x-)
185            crate::compat::point3::new(-half_size, -half_size, -half_size),
186            crate::compat::point3::new(-half_size, -half_size,  half_size),
187            crate::compat::point3::new(-half_size,  half_size,  half_size),
188            crate::compat::point3::new(-half_size,  half_size, -half_size),
189        ];
190        
191        // Convert positions to flat array for create_simple_mesh
192        
193        // Triangle indices (6 faces * 2 triangles * 3 vertices = 36 indices)
194        let indices = vec![
195            // Front face
196            Triangle { a: 0, b: 1, c: 2 },
197            Triangle { a: 0, b: 2, c: 3 },
198            
199            // Back face
200            Triangle { a: 4, b: 5, c: 6 },
201            Triangle { a: 4, b: 6, c: 7 },
202            
203            // Top face
204            Triangle { a: 8, b: 9, c: 10 },
205            Triangle { a: 8, b: 10, c: 11 },
206            
207            // Bottom face
208            Triangle { a: 12, b: 13, c: 14 },
209            Triangle { a: 12, b: 14, c: 15 },
210            
211            // Right face
212            Triangle { a: 16, b: 17, c: 18 },
213            Triangle { a: 16, b: 18, c: 19 },
214            
215            // Left face
216            Triangle { a: 20, b: 21, c: 22 },
217            Triangle { a: 20, b: 22, c: 23 },
218        ];
219        
220        // Convert indices to u16 for create_simple_mesh
221        
222        // Normals for each vertex
223        let normals = vec![
224            // Front face (z+)
225            crate::compat::vector3::new(0.0, 0.0, 1.0),
226            crate::compat::vector3::new(0.0, 0.0, 1.0),
227            crate::compat::vector3::new(0.0, 0.0, 1.0),
228            crate::compat::vector3::new(0.0, 0.0, 1.0),
229            
230            // Back face (z-)
231            crate::compat::vector3::new(0.0, 0.0, -1.0),
232            crate::compat::vector3::new(0.0, 0.0, -1.0),
233            crate::compat::vector3::new(0.0, 0.0, -1.0),
234            crate::compat::vector3::new(0.0, 0.0, -1.0),
235            
236            // Top face (y+)
237            crate::compat::vector3::new(0.0, 1.0, 0.0),
238            crate::compat::vector3::new(0.0, 1.0, 0.0),
239            crate::compat::vector3::new(0.0, 1.0, 0.0),
240            crate::compat::vector3::new(0.0, 1.0, 0.0),
241            
242            // Bottom face (y-)
243            crate::compat::vector3::new(0.0, -1.0, 0.0),
244            crate::compat::vector3::new(0.0, -1.0, 0.0),
245            crate::compat::vector3::new(0.0, -1.0, 0.0),
246            crate::compat::vector3::new(0.0, -1.0, 0.0),
247            
248            // Right face (x+)
249            crate::compat::vector3::new(1.0, 0.0, 0.0),
250            crate::compat::vector3::new(1.0, 0.0, 0.0),
251            crate::compat::vector3::new(1.0, 0.0, 0.0),
252            crate::compat::vector3::new(1.0, 0.0, 0.0),
253            
254            // Left face (x-)
255            crate::compat::vector3::new(-1.0, 0.0, 0.0),
256            crate::compat::vector3::new(-1.0, 0.0, 0.0),
257            crate::compat::vector3::new(-1.0, 0.0, 0.0),
258            crate::compat::vector3::new(-1.0, 0.0, 0.0),
259        ];
260        
261        // Convert normals to flat array for create_simple_mesh
262        
263        // UVs for each face using Vector2
264        let uvs = vec![
265            // Front face
266            crate::compat::vector2::new(0.0, 1.0),
267            crate::compat::vector2::new(1.0, 1.0),
268            crate::compat::vector2::new(1.0, 0.0),
269            crate::compat::vector2::new(0.0, 0.0),
270            
271            // Back face
272            crate::compat::vector2::new(1.0, 1.0),
273            crate::compat::vector2::new(1.0, 0.0),
274            crate::compat::vector2::new(0.0, 0.0),
275            crate::compat::vector2::new(0.0, 1.0),
276            
277            // Top face
278            crate::compat::vector2::new(0.0, 1.0),
279            crate::compat::vector2::new(0.0, 0.0),
280            crate::compat::vector2::new(1.0, 0.0),
281            crate::compat::vector2::new(1.0, 1.0),
282            
283            // Bottom face
284            crate::compat::vector2::new(1.0, 1.0),
285            crate::compat::vector2::new(0.0, 1.0),
286            crate::compat::vector2::new(0.0, 0.0),
287            crate::compat::vector2::new(1.0, 0.0),
288            
289            // Right face
290            crate::compat::vector2::new(1.0, 1.0),
291            crate::compat::vector2::new(1.0, 0.0),
292            crate::compat::vector2::new(0.0, 0.0),
293            crate::compat::vector2::new(0.0, 1.0),
294            
295            // Left face
296            crate::compat::vector2::new(0.0, 1.0),
297            crate::compat::vector2::new(1.0, 1.0),
298            crate::compat::vector2::new(1.0, 0.0),
299            crate::compat::vector2::new(0.0, 0.0),
300        ];
301        
302        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
303    }
304    
305    /// Create a mesh with custom geometry and UV mapping
306    /// 
307    /// # Parameters
308    /// * `name` - Optional name for the mesh
309    /// * `positions` - Vertex positions as Vec<Point3<f32>>
310    /// * `indices` - List of triangles, each containing three vertex indices
311    /// * `normals` - Optional vertex normals as Vec<Vector3<f32>>
312    /// * `texcoords` - Optional array of UV coordinate sets, each as Vec<Vector2<f32>>. 
313    ///                 The first set becomes TEXCOORD_0, the second TEXCOORD_1, etc.
314    /// * `material` - Optional material index to use for the mesh
315    /// 
316    /// # Returns
317    /// The index of the created mesh
318    pub fn create_custom_mesh(&mut self, 
319                            name: Option<String>,
320                            positions: &[Point3<f32>], 
321                            indices: &[Triangle], 
322                            normals: Option<Vec<Vector3<f32>>>, 
323                            texcoords: Option<Vec<Vec<Vector2<f32>>>>,
324                            material: Option<usize>) -> usize {
325        // Calculate bounds for the positions
326        let (min_point, max_point) = if !positions.is_empty() {
327            let mut min = crate::compat::point3::new(f32::MAX, f32::MAX, f32::MAX);
328            let mut max = crate::compat::point3::new(f32::MIN, f32::MIN, f32::MIN);
329            
330            for point in positions {
331                min.x = min.x.min(point.x);
332                min.y = min.y.min(point.y);
333                min.z = min.z.min(point.z);
334                
335                max.x = max.x.max(point.x);
336                max.y = max.y.max(point.y);
337                max.z = max.z.max(point.z);
338            }
339            
340            (Some(min), Some(max))
341        } else {
342            (None, None)
343        };
344        
345        // Convert Point3 min/max to Vec<f32> for accessor
346        let min = min_point.map(|p| vec![p.x, p.y, p.z]);
347        let max = max_point.map(|p| vec![p.x, p.y, p.z]);
348        
349        // Convert positions from Point3 to flat array for buffer
350        let flat_positions: Vec<f32> = positions.iter().flat_map(|p| vec![p.x, p.y, p.z]).collect();
351        
352        // Add position data to buffer
353        let pos_bytes = unsafe {
354            std::slice::from_raw_parts(
355                flat_positions.as_ptr() as *const u8,
356                flat_positions.len() * std::mem::size_of::<f32>()
357            )
358        };
359        let (pos_offset, pos_length) = self.add_buffer_data(pos_bytes);
360        let pos_buffer_view = self.add_buffer_view(pos_offset, pos_length, Some(buffer_view_target::ARRAY_BUFFER));
361        
362        // Add position accessor
363        let vertex_count = positions.len();
364        let pos_accessor = self.add_accessor(
365            pos_buffer_view,
366            component_type::FLOAT,
367            vertex_count,
368            accessor_type::VEC3.to_string(),
369            None,
370            min,
371            max
372        );
373        
374        // Flatten the Triangle structs into a flat list of indices
375        let flat_indices: Vec<u32> = indices.iter()
376            .flat_map(|triangle| vec![triangle.a, triangle.b, triangle.c])
377            .collect();
378            
379        // Add index data to buffer
380        let idx_bytes = unsafe {
381            std::slice::from_raw_parts(
382                flat_indices.as_ptr() as *const u8,
383                flat_indices.len() * std::mem::size_of::<u32>()
384            )
385        };
386        let (idx_offset, idx_length) = self.add_buffer_data(idx_bytes);
387        let idx_buffer_view = self.add_buffer_view(idx_offset, idx_length, Some(buffer_view_target::ELEMENT_ARRAY_BUFFER));
388        
389        // Add index accessor
390        let idx_accessor = self.add_accessor(
391            idx_buffer_view,
392            component_type::UNSIGNED_INT,  // Use UNSIGNED_INT for u32 indices
393            flat_indices.len(),
394            accessor_type::SCALAR.to_string(),
395            None,
396            None,
397            None
398        );
399        
400        // Build attributes map
401        let mut attributes = HashMap::new();
402        attributes.insert("POSITION".to_string(), pos_accessor);
403        
404        // Add normals if provided
405        if let Some(normal_data) = normals {
406            // Convert normals from Vector3 to flat array for buffer
407            let flat_normals: Vec<f32> = normal_data.iter().flat_map(|n| vec![n.x, n.y, n.z]).collect();
408            
409            let norm_bytes = unsafe {
410                std::slice::from_raw_parts(
411                    flat_normals.as_ptr() as *const u8,
412                    flat_normals.len() * std::mem::size_of::<f32>()
413                )
414            };
415            let (norm_offset, norm_length) = self.add_buffer_data(norm_bytes);
416            let norm_buffer_view = self.add_buffer_view(norm_offset, norm_length, Some(buffer_view_target::ARRAY_BUFFER));
417            
418            let normal_accessor = self.add_accessor(
419                norm_buffer_view,
420                component_type::FLOAT,
421                normal_data.len(),
422                accessor_type::VEC3.to_string(),
423                None,
424                None,
425                None
426            );
427            
428            attributes.insert("NORMAL".to_string(), normal_accessor);
429        }
430        
431        // Add texture coordinates if provided
432        let mut texcoord_accessors = Vec::new();
433        if let Some(texcoord_sets) = texcoords {
434            for (i, texcoord_data) in texcoord_sets.iter().enumerate() {
435                // Convert Vector2 to flat array for buffer
436                let flat_texcoords: Vec<f32> = texcoord_data.iter().flat_map(|uv| vec![uv.x, uv.y]).collect();
437                
438                let tc_bytes = unsafe {
439                    std::slice::from_raw_parts(
440                        flat_texcoords.as_ptr() as *const u8,
441                        flat_texcoords.len() * std::mem::size_of::<f32>()
442                    )
443                };
444                let (tc_offset, tc_length) = self.add_buffer_data(tc_bytes);
445                let tc_buffer_view = self.add_buffer_view(tc_offset, tc_length, Some(buffer_view_target::ARRAY_BUFFER));
446                
447                let tc_accessor = self.add_accessor(
448                    tc_buffer_view,
449                    component_type::FLOAT,
450                    texcoord_data.len(), // Number of Vector2 elements
451                    accessor_type::VEC2.to_string(),
452                    None,
453                    None,
454                    None
455                );
456                
457                attributes.insert(format!("TEXCOORD_{}", i), tc_accessor);
458                texcoord_accessors.push(tc_accessor);
459            }
460        }
461        
462        // Create primitive
463        let primitive = Primitive {
464            attributes,
465            indices: Some(idx_accessor),
466            material,
467            mode: None, // Default mode (triangles)
468        };
469        
470        // Create and add mesh
471        self.add_mesh(name, vec![primitive])
472    }
473    
474    
475    /// Create a mesh with custom geometry and single UV channel using types
476    /// 
477    /// Simplified version of create_custom_mesh for the common case of a single UV channel,
478    /// but using proper 3D math types instead of raw float arrays.
479    /// 
480    /// # Parameters
481    /// * `name` - Optional name for the mesh
482    /// * `positions` - Vertex positions as &[Point3<f32>]
483    /// * `indices` - List of triangles using the Triangle struct
484    /// * `normals` - Optional vertex normals as Vec<Vector3<f32>>
485    /// * `texcoords` - Optional UV coordinates as Vec<Vector2<f32>>
486    /// * `material` - Optional material index to use for the mesh
487    /// 
488    /// # Returns
489    /// The index of the created mesh
490    pub fn create_simple_mesh(&mut self, 
491                               name: Option<String>,
492                               positions: &[Point3<f32>], 
493                               indices: &[Triangle], 
494                               normals: Option<Vec<Vector3<f32>>>, 
495                               texcoords: Option<Vec<Vector2<f32>>>,
496                               material: Option<usize>) -> usize {
497        // If we have texture coordinates, create a texcoord set for the mesh
498        let texcoord_sets = if let Some(uvs) = texcoords {
499            let mut sets = Vec::new();
500            sets.push(uvs);
501            Some(sets)
502        } else {
503            None
504        };
505        
506        self.create_custom_mesh(name, positions, indices, normals, texcoord_sets, material)
507    }
508    
509    /// Create a flat plane mesh with subdivisions
510    /// 
511    /// This method creates a flat rectangular plane on the XZ plane (with Y as up).
512    /// The plane is centered at the origin and can be subdivided into a grid of triangles.
513    /// Subdividing the plane is useful for terrain or deformation effects.
514    /// 
515    /// # Parameters
516    /// * `width` - Width of the plane along X axis
517    /// * `depth` - Depth of the plane along Z axis 
518    /// * `width_segments` - Number of subdivisions along width (min: 1)
519    /// * `depth_segments` - Number of subdivisions along depth (min: 1)
520    /// * `material` - Optional material index to use for the mesh
521    /// 
522    /// # Returns
523    /// The index of the created mesh in the glTF document's meshes array
524    /// 
525    /// # Example
526    /// ```
527    /// use mesh_tools::GltfBuilder;
528    /// let mut builder = GltfBuilder::new();
529    /// 
530    /// // Create a material
531    /// let ground_material = builder.create_basic_material(
532    ///     Some("Ground".to_string()),
533    ///     [0.5, 0.5, 0.5, 1.0]
534    /// );
535    /// 
536    /// // Create a 10x10 ground plane with 20x20 grid subdivisions
537    /// let ground_mesh = builder.create_plane(10.0, 10.0, 20, 20, Some(ground_material));
538    /// ```
539    pub fn create_plane(&mut self, 
540                      width: f32, 
541                      depth: f32, 
542                      width_segments: usize, 
543                      depth_segments: usize,
544                      material: Option<usize>) -> usize {
545        // Get the mesh data directly as types
546        let (positions, indices, normals, uvs) = primitives::generate_plane(
547            width, depth, width_segments, depth_segments
548        );
549        
550        // Create the mesh using the mint types returned by the primitives module
551        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
552    }
553    
554    /// Create a sphere mesh with specified radius and resolution
555    /// 
556    /// This method creates a UV-mapped sphere centered at the origin. The sphere is generated
557    /// using latitude/longitude segmentation, with vertices distributed evenly around the surface.
558    /// 
559    /// # Parameters
560    /// * `radius` - Radius of the sphere
561    /// * `width_segments` - Number of horizontal subdivisions (longitude lines, min: 3)
562    /// * `height_segments` - Number of vertical subdivisions (latitude lines, min: 2)
563    /// * `material` - Optional material index to use for the mesh
564    /// 
565    /// # Returns
566    /// The index of the created mesh in the glTF document's meshes array
567    /// 
568    /// # Example
569    /// ```
570    /// use mesh_tools::GltfBuilder;
571    /// let mut builder = GltfBuilder::new();
572    /// 
573    /// // Create a red material
574    /// let red_material = builder.create_basic_material(
575    ///     Some("Red".to_string()),
576    ///     [1.0, 0.0, 0.0, 1.0]
577    /// );
578    /// 
579    /// // Create a high-detail red sphere with radius 2.0
580    /// let sphere_mesh = builder.create_sphere(2.0, 32, 16, Some(red_material));
581    /// ```
582    pub fn create_sphere(&mut self, 
583                       radius: f32, 
584                       width_segments: usize, 
585                       height_segments: usize,
586                       material: Option<usize>) -> usize {
587        // Get the mesh data directly as mint types
588        let (positions, indices, normals, uvs) = primitives::generate_sphere(
589            radius, width_segments, height_segments
590        );
591        
592        // Create the mesh using the mint types returned by the primitives module
593        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
594    }
595    
596    /// Create a cylinder mesh with customizable dimensions
597    /// 
598    /// This method creates a cylinder or a truncated cone (when top and bottom radii differ).
599    /// The cylinder is centered at the origin and extends along the Y axis.
600    /// The cylinder can be open-ended (without caps) or closed with caps.
601    /// 
602    /// # Parameters
603    /// * `radius_top` - Radius at the top of the cylinder
604    /// * `radius_bottom` - Radius at the bottom of the cylinder
605    /// * `height` - Height of the cylinder along the Y axis
606    /// * `radial_segments` - Number of subdivisions around the circumference (min: 3)
607    /// * `height_segments` - Number of subdivisions along the height (min: 1)
608    /// * `open_ended` - When `true`, the cylinder has no top or bottom caps
609    /// * `material` - Optional material index to use for the mesh
610    /// 
611    /// # Returns
612    /// The index of the created mesh in the glTF document's meshes array
613    /// 
614    /// # Example
615    /// ```
616    /// use mesh_tools::GltfBuilder;
617    /// let mut builder = GltfBuilder::new();
618    /// 
619    /// // Create a blue material
620    /// let blue_material = builder.create_basic_material(
621    ///     Some("Blue".to_string()),
622    ///     [0.0, 0.0, 0.8, 1.0]
623    /// );
624    /// 
625    /// // Create a cylinder with different top and bottom radii (truncated cone)
626    /// let cylinder_mesh = builder.create_cylinder(
627    ///     0.5,   // radius top
628    ///     1.0,   // radius bottom
629    ///     2.0,   // height
630    ///     16,    // radial segments
631    ///     1,     // height segments
632    ///     false, // closed with caps
633    ///     Some(blue_material)
634    /// );
635    /// ```
636    pub fn create_cylinder(&mut self, 
637                         radius_top: f32, 
638                         radius_bottom: f32, 
639                         height: f32, 
640                         radial_segments: usize, 
641                         height_segments: usize,
642                         open_ended: bool,
643                         material: Option<usize>) -> usize {
644        // Get the mesh data directly as types
645        let (positions, indices, normals, uvs) = primitives::generate_cylinder(
646            radius_top, radius_bottom, height, radial_segments, height_segments, open_ended
647        );
648        
649        // Create the mesh using the types returned by the primitives module
650        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
651    }
652    
653    /// Create a cone mesh
654    /// 
655    /// # Parameters
656    /// * `radius` - Radius at the base of the cone
657    /// * `height` - Height of the cone
658    /// * `radial_segments` - Number of subdivisions around the circumference
659    /// * `height_segments` - Number of subdivisions along the height
660    /// * `open_ended` - Whether to include the base cap
661    /// * `material` - Optional material index to use for the mesh
662    /// 
663    /// # Returns
664    /// The index of the created mesh
665    pub fn create_cone(&mut self, 
666                      radius: f32, 
667                      height: f32, 
668                      radial_segments: usize, 
669                      height_segments: usize,
670                      open_ended: bool,
671                      material: Option<usize>) -> usize {
672        // Get the mesh data directly as types
673        let (positions, indices, normals, uvs) = primitives::generate_cone(
674            radius, height, radial_segments, height_segments, open_ended
675        );
676        
677        // Create the mesh using the types returned by the primitives module
678        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
679    }
680    
681    /// Create a torus (donut shape) mesh
682    /// 
683    /// # Parameters
684    /// * `radius` - Distance from the center of the tube to the center of the torus
685    /// * `tube` - Radius of the tube
686    /// * `radial_segments` - Number of subdivisions around the main circle
687    /// * `tubular_segments` - Number of subdivisions around the tube
688    /// * `material` - Optional material index to use for the mesh
689    /// 
690    /// # Returns
691    /// The index of the created mesh
692    pub fn create_torus(&mut self, 
693                       radius: f32, 
694                       tube: f32, 
695                       radial_segments: usize, 
696                       tubular_segments: usize,
697                       material: Option<usize>) -> usize {
698        // Get the mesh data directly as types
699        let (positions, indices, normals, uvs) = primitives::generate_torus(
700            radius, tube, radial_segments, tubular_segments
701        );
702        
703        // Create the mesh using the types returned by the primitives module
704        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
705    }
706    
707    /// Create an icosahedron (20-sided polyhedron) mesh
708    /// 
709    /// # Parameters
710    /// * `radius` - Radius of the circumscribed sphere
711    /// * `material` - Optional material index to use for the mesh
712    /// 
713    /// # Returns
714    /// The index of the created mesh
715    pub fn create_icosahedron(&mut self, 
716                            radius: f32,
717                            material: Option<usize>) -> usize {
718        // Get the mesh data directly as types
719        let (positions, indices, normals, uvs) = primitives::generate_icosahedron(radius);
720        
721        // Create the mesh using the types returned by the primitives module
722        self.create_simple_mesh(None, &positions, &indices, Some(normals), Some(uvs), material)
723    }
724}