schematic_mesher/mesher/
element.rs

1//! Convert model elements to mesh geometry.
2
3use crate::atlas::{AtlasBuilder, TextureAtlas};
4use crate::error::{MesherError, Result};
5use crate::mesher::face_culler::FaceCuller;
6use crate::mesher::geometry::{Mesh, Vertex};
7use crate::mesher::MesherConfig;
8use crate::resolver::{resolve_block, ModelResolver, ResolvedModel};
9use crate::resource_pack::{BlockModel, ModelElement, ModelFace, ResourcePack};
10use crate::types::{BlockPosition, BlockTransform, Direction, InputBlock};
11use glam::{Mat3, Vec3};
12use std::collections::HashSet;
13
14/// Tracks texture mapping for a face (4 vertices).
15struct FaceTextureMapping {
16    /// Starting vertex index.
17    vertex_start: u32,
18    /// Texture path for this face.
19    texture_path: String,
20    /// Whether this face uses a transparent texture.
21    is_transparent: bool,
22}
23
24/// Builds a mesh from multiple blocks.
25pub struct MeshBuilder<'a> {
26    resource_pack: &'a ResourcePack,
27    config: &'a MesherConfig,
28    mesh: Mesh,
29    texture_refs: HashSet<String>,
30    model_resolver: ModelResolver<'a>,
31    /// Track which texture each face uses for UV remapping.
32    face_textures: Vec<FaceTextureMapping>,
33}
34
35impl<'a> MeshBuilder<'a> {
36    pub fn new(resource_pack: &'a ResourcePack, config: &'a MesherConfig) -> Self {
37        Self {
38            resource_pack,
39            config,
40            mesh: Mesh::new(),
41            texture_refs: HashSet::new(),
42            model_resolver: ModelResolver::new(resource_pack),
43            face_textures: Vec::new(),
44        }
45    }
46
47    /// Add a block to the mesh.
48    pub fn add_block(
49        &mut self,
50        pos: BlockPosition,
51        block: &InputBlock,
52        culler: Option<&FaceCuller>,
53    ) -> Result<()> {
54        // Resolve the block to models
55        let resolved_models = match resolve_block(self.resource_pack, block) {
56            Ok(models) => models,
57            Err(e) => {
58                // Log warning but continue
59                eprintln!("Warning: Failed to resolve block {}: {}", block.name, e);
60                return Ok(());
61            }
62        };
63
64        // Generate geometry for each model
65        for resolved in resolved_models {
66            self.add_model(pos, block, &resolved, culler)?;
67        }
68
69        Ok(())
70    }
71
72    /// Add a resolved model to the mesh.
73    fn add_model(
74        &mut self,
75        pos: BlockPosition,
76        block: &InputBlock,
77        resolved: &ResolvedModel,
78        culler: Option<&FaceCuller>,
79    ) -> Result<()> {
80        let model = &resolved.model;
81        let transform = &resolved.transform;
82
83        // Resolve textures for this model
84        let resolved_textures = self.model_resolver.resolve_textures(model);
85
86        // Process each element
87        for element in &model.elements {
88            self.add_element(pos, block, element, transform, &resolved_textures, culler)?;
89        }
90
91        Ok(())
92    }
93
94    /// Add an element to the mesh.
95    fn add_element(
96        &mut self,
97        pos: BlockPosition,
98        block: &InputBlock,
99        element: &ModelElement,
100        transform: &BlockTransform,
101        resolved_textures: &std::collections::HashMap<String, String>,
102        culler: Option<&FaceCuller>,
103    ) -> Result<()> {
104        // Process each face
105        for (direction, face) in &element.faces {
106            // Transform the face direction by block rotation (for AO and cullface)
107            let world_direction = direction.rotate_by_transform(transform.x, transform.y);
108
109            // Check if face should be culled
110            if let Some(cullface) = &face.cullface {
111                if let Some(culler) = culler {
112                    // Transform cullface direction to world space
113                    let world_cullface = cullface.rotate_by_transform(transform.x, transform.y);
114                    if culler.should_cull(pos, world_cullface) {
115                        continue;
116                    }
117                }
118            }
119
120            // Resolve the texture reference
121            let texture_path = self.resolve_face_texture(&face.texture, resolved_textures);
122            self.texture_refs.insert(texture_path.clone());
123
124            // Check if texture has transparency
125            let is_transparent = self.resource_pack
126                .get_texture(&texture_path)
127                .map(|t| t.has_transparency())
128                .unwrap_or(false);
129
130            // Track texture mapping for UV remapping
131            let vertex_start = self.mesh.vertex_count() as u32;
132            self.face_textures.push(FaceTextureMapping {
133                vertex_start,
134                texture_path,
135                is_transparent,
136            });
137
138            // Calculate AO if enabled (use world direction for neighbor checks)
139            let ao_values = if self.config.ambient_occlusion {
140                culler.map(|c| c.calculate_ao(pos, world_direction))
141            } else {
142                None
143            };
144
145            // Generate face geometry
146            self.add_face(pos, block, element, *direction, face, transform, ao_values)?;
147        }
148
149        Ok(())
150    }
151
152    /// Resolve a texture reference to a path.
153    fn resolve_face_texture(
154        &self,
155        reference: &str,
156        resolved_textures: &std::collections::HashMap<String, String>,
157    ) -> String {
158        if reference.starts_with('#') {
159            let key = &reference[1..];
160            resolved_textures
161                .get(key)
162                .cloned()
163                .unwrap_or_else(|| "block/missing".to_string())
164        } else {
165            reference.to_string()
166        }
167    }
168
169    /// Add a face to the mesh.
170    fn add_face(
171        &mut self,
172        pos: BlockPosition,
173        block: &InputBlock,
174        element: &ModelElement,
175        direction: Direction,
176        face: &ModelFace,
177        transform: &BlockTransform,
178        ao_values: Option<[u8; 4]>,
179    ) -> Result<()> {
180        let normal = direction.normal();
181        let uv = face.normalized_uv();
182
183        // Get element bounds in normalized space
184        let from = element.normalized_from();
185        let to = element.normalized_to();
186
187        // Generate the 4 vertices for this face
188        let (positions, uvs) = self.generate_face_vertices(direction, from, to, uv, face.rotation);
189
190        // Apply element rotation if present
191        let positions = if let Some(rot) = &element.rotation {
192            self.apply_element_rotation(&positions, rot)
193        } else {
194            positions
195        };
196
197        // Apply block transform rotation
198        let positions = self.apply_block_transform(&positions, transform);
199        let normal = self.rotate_normal(normal, transform);
200
201        // Translate to world position
202        let offset = [pos.x as f32, pos.y as f32, pos.z as f32];
203
204        // Get tint color from the tint provider based on block type and tint index
205        let base_color = self.config.tint_provider.get_tint(block, face.tintindex);
206
207        // Calculate per-vertex colors with AO
208        let colors = if let Some(ao) = ao_values {
209            let intensity = self.config.ao_intensity;
210            [
211                self.apply_ao_to_color(base_color, ao[0], intensity),
212                self.apply_ao_to_color(base_color, ao[1], intensity),
213                self.apply_ao_to_color(base_color, ao[2], intensity),
214                self.apply_ao_to_color(base_color, ao[3], intensity),
215            ]
216        } else {
217            [base_color; 4]
218        };
219
220        let v0 = self.mesh.add_vertex(
221            Vertex::new(
222                [
223                    positions[0][0] + offset[0],
224                    positions[0][1] + offset[1],
225                    positions[0][2] + offset[2],
226                ],
227                normal,
228                uvs[0],
229            )
230            .with_color(colors[0]),
231        );
232        let v1 = self.mesh.add_vertex(
233            Vertex::new(
234                [
235                    positions[1][0] + offset[0],
236                    positions[1][1] + offset[1],
237                    positions[1][2] + offset[2],
238                ],
239                normal,
240                uvs[1],
241            )
242            .with_color(colors[1]),
243        );
244        let v2 = self.mesh.add_vertex(
245            Vertex::new(
246                [
247                    positions[2][0] + offset[0],
248                    positions[2][1] + offset[1],
249                    positions[2][2] + offset[2],
250                ],
251                normal,
252                uvs[2],
253            )
254            .with_color(colors[2]),
255        );
256        let v3 = self.mesh.add_vertex(
257            Vertex::new(
258                [
259                    positions[3][0] + offset[0],
260                    positions[3][1] + offset[1],
261                    positions[3][2] + offset[2],
262                ],
263                normal,
264                uvs[3],
265            )
266            .with_color(colors[3]),
267        );
268
269        // Use AO-aware quad triangulation to fix anisotropy
270        if let Some(ao) = ao_values {
271            self.mesh.add_quad_ao(v0, v1, v2, v3, ao);
272        } else {
273            self.mesh.add_quad(v0, v1, v2, v3);
274        }
275
276        Ok(())
277    }
278
279    /// Apply ambient occlusion to a color.
280    /// ao_level: 0-3 (0=darkest, 3=brightest)
281    /// intensity: 0.0-1.0 (how much to darken)
282    fn apply_ao_to_color(&self, color: [f32; 4], ao_level: u8, intensity: f32) -> [f32; 4] {
283        // Map AO level 0-3 to brightness factor
284        // Level 3 = full brightness (1.0)
285        // Level 0 = minimum brightness (1.0 - intensity)
286        let brightness = 1.0 - intensity * (1.0 - ao_level as f32 / 3.0);
287        [
288            color[0] * brightness,
289            color[1] * brightness,
290            color[2] * brightness,
291            color[3], // Alpha unchanged
292        ]
293    }
294
295    /// Generate the 4 vertices for a face.
296    /// Returns (positions, uvs) in CCW order.
297    fn generate_face_vertices(
298        &self,
299        direction: Direction,
300        from: [f32; 3],
301        to: [f32; 3],
302        uv: [f32; 4],
303        rotation: i32,
304    ) -> ([[f32; 3]; 4], [[f32; 2]; 4]) {
305        let (u1, v1, u2, v2) = (uv[0], uv[1], uv[2], uv[3]);
306
307        // Base UVs (before rotation)
308        // glTF uses v=0 at top (same as Minecraft), so no V flip needed
309        // UV order: top-left, top-right, bottom-right, bottom-left (CCW)
310        let base_uvs = [[u1, v1], [u2, v1], [u2, v2], [u1, v2]];
311
312        // Rotate UVs
313        let uvs = self.rotate_uvs(base_uvs, rotation);
314
315        // Generate positions based on direction
316        let positions = match direction {
317            Direction::Down => [
318                [from[0], from[1], to[2]],
319                [to[0], from[1], to[2]],
320                [to[0], from[1], from[2]],
321                [from[0], from[1], from[2]],
322            ],
323            Direction::Up => [
324                [from[0], to[1], from[2]],
325                [to[0], to[1], from[2]],
326                [to[0], to[1], to[2]],
327                [from[0], to[1], to[2]],
328            ],
329            Direction::North => [
330                [to[0], to[1], from[2]],
331                [from[0], to[1], from[2]],
332                [from[0], from[1], from[2]],
333                [to[0], from[1], from[2]],
334            ],
335            Direction::South => [
336                [from[0], to[1], to[2]],
337                [to[0], to[1], to[2]],
338                [to[0], from[1], to[2]],
339                [from[0], from[1], to[2]],
340            ],
341            Direction::West => [
342                [from[0], to[1], from[2]],
343                [from[0], to[1], to[2]],
344                [from[0], from[1], to[2]],
345                [from[0], from[1], from[2]],
346            ],
347            Direction::East => [
348                [to[0], to[1], to[2]],
349                [to[0], to[1], from[2]],
350                [to[0], from[1], from[2]],
351                [to[0], from[1], to[2]],
352            ],
353        };
354
355        (positions, uvs)
356    }
357
358    /// Rotate UV coordinates.
359    fn rotate_uvs(&self, uvs: [[f32; 2]; 4], rotation: i32) -> [[f32; 2]; 4] {
360        let steps = ((rotation / 90) % 4 + 4) % 4;
361        let mut result = uvs;
362        for _ in 0..steps {
363            result = [result[3], result[0], result[1], result[2]];
364        }
365        result
366    }
367
368    /// Apply element rotation to positions.
369    fn apply_element_rotation(
370        &self,
371        positions: &[[f32; 3]; 4],
372        rotation: &crate::types::ElementRotation,
373    ) -> [[f32; 3]; 4] {
374        let origin = rotation.normalized_origin();
375        let angle = rotation.angle_radians();
376        let rescale = rotation.rescale_factor();
377
378        let rotation_matrix = match rotation.axis {
379            crate::types::Axis::X => Mat3::from_rotation_x(angle),
380            crate::types::Axis::Y => Mat3::from_rotation_y(angle),
381            crate::types::Axis::Z => Mat3::from_rotation_z(angle),
382        };
383
384        let mut result = [[0.0; 3]; 4];
385        for (i, pos) in positions.iter().enumerate() {
386            // Translate to origin
387            let p = Vec3::new(pos[0] - origin[0], pos[1] - origin[1], pos[2] - origin[2]);
388
389            // Rotate
390            let rotated = rotation_matrix * p;
391
392            // Rescale if needed
393            let scaled = if rescale != 1.0 {
394                match rotation.axis {
395                    crate::types::Axis::X => {
396                        Vec3::new(rotated.x, rotated.y * rescale, rotated.z * rescale)
397                    }
398                    crate::types::Axis::Y => {
399                        Vec3::new(rotated.x * rescale, rotated.y, rotated.z * rescale)
400                    }
401                    crate::types::Axis::Z => {
402                        Vec3::new(rotated.x * rescale, rotated.y * rescale, rotated.z)
403                    }
404                }
405            } else {
406                rotated
407            };
408
409            // Translate back
410            result[i] = [
411                scaled.x + origin[0],
412                scaled.y + origin[1],
413                scaled.z + origin[2],
414            ];
415        }
416        result
417    }
418
419    /// Apply block-level transform to positions.
420    fn apply_block_transform(
421        &self,
422        positions: &[[f32; 3]; 4],
423        transform: &BlockTransform,
424    ) -> [[f32; 3]; 4] {
425        if transform.is_identity() {
426            return *positions;
427        }
428
429        let x_rot = Mat3::from_rotation_x((transform.x as f32).to_radians());
430        let y_rot = Mat3::from_rotation_y((transform.y as f32).to_radians());
431        let rotation_matrix = y_rot * x_rot;
432
433        let mut result = [[0.0; 3]; 4];
434        for (i, pos) in positions.iter().enumerate() {
435            let p = Vec3::new(pos[0], pos[1], pos[2]);
436            let rotated = rotation_matrix * p;
437            result[i] = [rotated.x, rotated.y, rotated.z];
438        }
439        result
440    }
441
442    /// Rotate a normal by the block transform.
443    fn rotate_normal(&self, normal: [f32; 3], transform: &BlockTransform) -> [f32; 3] {
444        if transform.is_identity() {
445            return normal;
446        }
447
448        let x_rot = Mat3::from_rotation_x((transform.x as f32).to_radians());
449        let y_rot = Mat3::from_rotation_y((transform.y as f32).to_radians());
450        let rotation_matrix = y_rot * x_rot;
451
452        let n = Vec3::new(normal[0], normal[1], normal[2]);
453        let rotated = rotation_matrix * n;
454        [rotated.x, rotated.y, rotated.z]
455    }
456
457    /// Build the final meshes (opaque and transparent) and atlas.
458    /// Returns (opaque_mesh, transparent_mesh, atlas).
459    /// Opaque geometry should be rendered first, then transparent.
460    pub fn build(mut self) -> Result<(Mesh, Mesh, TextureAtlas)> {
461        // Build texture atlas
462        let mut atlas_builder = AtlasBuilder::new(
463            self.config.atlas_max_size,
464            self.config.atlas_padding,
465        );
466
467        for texture_ref in &self.texture_refs {
468            if let Some(texture) = self.resource_pack.get_texture(texture_ref) {
469                atlas_builder.add_texture(texture_ref.clone(), texture.first_frame());
470            }
471        }
472
473        let atlas = atlas_builder.build()?;
474
475        // Remap UVs to atlas coordinates
476        for face_mapping in &self.face_textures {
477            if let Some(region) = atlas.get_region(&face_mapping.texture_path) {
478                // Each face has 4 vertices
479                for i in 0..4 {
480                    let vertex_idx = face_mapping.vertex_start as usize + i;
481                    if vertex_idx < self.mesh.vertices.len() {
482                        let vertex = &mut self.mesh.vertices[vertex_idx];
483                        // Transform UV from [0,1] local space to atlas region
484                        vertex.uv = region.transform_uv(vertex.uv[0], vertex.uv[1]);
485                    }
486                }
487            }
488        }
489
490        // Separate into opaque and transparent meshes
491        let (opaque_mesh, transparent_mesh) = self.separate_by_transparency();
492
493        Ok((opaque_mesh, transparent_mesh, atlas))
494    }
495
496    /// Separate the mesh into opaque and transparent parts based on texture transparency.
497    fn separate_by_transparency(&self) -> (Mesh, Mesh) {
498        let mut opaque_mesh = Mesh::new();
499        let mut transparent_mesh = Mesh::new();
500
501        // Process each face (4 vertices + 2 triangles per face)
502        for face_mapping in &self.face_textures {
503            let start = face_mapping.vertex_start as usize;
504
505            // Get the 4 vertices for this face
506            let vertices: Vec<_> = (0..4)
507                .filter_map(|i| self.mesh.vertices.get(start + i).copied())
508                .collect();
509
510            if vertices.len() != 4 {
511                continue;
512            }
513
514            // Find the triangles that use these vertices
515            // In our mesh, faces are added as quads which become 2 triangles
516            // We need to find the triangle indices that reference these vertices
517
518            // Add to appropriate mesh
519            let target_mesh = if face_mapping.is_transparent {
520                &mut transparent_mesh
521            } else {
522                &mut opaque_mesh
523            };
524
525            // Add the 4 vertices and get their new indices
526            let v0 = target_mesh.add_vertex(vertices[0]);
527            let v1 = target_mesh.add_vertex(vertices[1]);
528            let v2 = target_mesh.add_vertex(vertices[2]);
529            let v3 = target_mesh.add_vertex(vertices[3]);
530
531            // Find the triangle winding from original mesh
532            // Search for triangles that use the original vertex indices
533            let orig_v0 = face_mapping.vertex_start;
534            let orig_v1 = orig_v0 + 1;
535            let orig_v2 = orig_v0 + 2;
536            let orig_v3 = orig_v0 + 3;
537
538            // Find triangles in original mesh that use these vertices
539            for tri_idx in (0..self.mesh.indices.len()).step_by(3) {
540                let i0 = self.mesh.indices[tri_idx];
541                let i1 = self.mesh.indices[tri_idx + 1];
542                let i2 = self.mesh.indices[tri_idx + 2];
543
544                // Check if this triangle uses our face's vertices
545                if i0 >= orig_v0 && i0 <= orig_v3 &&
546                   i1 >= orig_v0 && i1 <= orig_v3 &&
547                   i2 >= orig_v0 && i2 <= orig_v3 {
548                    // Map old indices to new
549                    let new_i0 = match i0 - orig_v0 {
550                        0 => v0, 1 => v1, 2 => v2, _ => v3
551                    };
552                    let new_i1 = match i1 - orig_v0 {
553                        0 => v0, 1 => v1, 2 => v2, _ => v3
554                    };
555                    let new_i2 = match i2 - orig_v0 {
556                        0 => v0, 1 => v1, 2 => v2, _ => v3
557                    };
558                    target_mesh.add_triangle(new_i0, new_i1, new_i2);
559                }
560            }
561        }
562
563        (opaque_mesh, transparent_mesh)
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570
571    #[test]
572    fn test_rotate_uvs() {
573        let pack = ResourcePack::new();
574        let config = MesherConfig::default();
575        let builder = MeshBuilder::new(&pack, &config);
576
577        let uvs = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
578
579        // 0 degrees - no change
580        assert_eq!(builder.rotate_uvs(uvs, 0), uvs);
581
582        // 90 degrees
583        let rotated_90 = builder.rotate_uvs(uvs, 90);
584        assert_eq!(rotated_90[0], uvs[3]);
585        assert_eq!(rotated_90[1], uvs[0]);
586
587        // 180 degrees
588        let rotated_180 = builder.rotate_uvs(uvs, 180);
589        assert_eq!(rotated_180[0], uvs[2]);
590        assert_eq!(rotated_180[2], uvs[0]);
591    }
592}