ifc_lite_geometry/csg/
normals.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::mesh::Mesh;
6use nalgebra::{Point3, Vector3};
7
8/// Calculate smooth normals for a mesh.
9///
10/// One real implementation on every target. This used to be a no-op on
11/// native (a leftover of the decommissioned desktop IPC path, which
12/// recomputed normals in JS): the server silently shipped EMPTY normal
13/// buffers for brep/surface/swept meshes, which the parquet writer
14/// zero-padded and the glTF exporter dropped — while the same model loaded
15/// via wasm had real normals (alignment audit).
16#[inline]
17pub fn calculate_normals(mesh: &mut Mesh) {
18    let vertex_count = mesh.vertex_count();
19    if vertex_count == 0 {
20        return;
21    }
22
23    let positions_len = mesh.positions.len();
24
25    // Initialize normals to zero
26    let mut normals = vec![Vector3::zeros(); vertex_count];
27
28    // Accumulate face normals
29    for i in (0..mesh.indices.len()).step_by(3) {
30        // Bounds check for indices array
31        if i + 2 >= mesh.indices.len() {
32            break;
33        }
34
35        let i0 = mesh.indices[i] as usize;
36        let i1 = mesh.indices[i + 1] as usize;
37        let i2 = mesh.indices[i + 2] as usize;
38
39        // Bounds check for vertex indices - skip invalid triangles
40        if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
41            continue;
42        }
43        if i0 * 3 + 2 >= positions_len || i1 * 3 + 2 >= positions_len || i2 * 3 + 2 >= positions_len
44        {
45            continue;
46        }
47
48        // Get triangle vertices
49        let v0 = Point3::new(
50            mesh.positions[i0 * 3] as f64,
51            mesh.positions[i0 * 3 + 1] as f64,
52            mesh.positions[i0 * 3 + 2] as f64,
53        );
54        let v1 = Point3::new(
55            mesh.positions[i1 * 3] as f64,
56            mesh.positions[i1 * 3 + 1] as f64,
57            mesh.positions[i1 * 3 + 2] as f64,
58        );
59        let v2 = Point3::new(
60            mesh.positions[i2 * 3] as f64,
61            mesh.positions[i2 * 3 + 1] as f64,
62            mesh.positions[i2 * 3 + 2] as f64,
63        );
64
65        // Calculate face normal
66        let edge1 = v1 - v0;
67        let edge2 = v2 - v0;
68        let normal = edge1.cross(&edge2);
69
70        // Accumulate normal for each vertex
71        normals[i0] += normal;
72        normals[i1] += normal;
73        normals[i2] += normal;
74    }
75
76    // Normalize and write back
77    mesh.normals.clear();
78    mesh.normals.reserve(vertex_count * 3);
79
80    for normal in normals {
81        let normalized = normal
82            .try_normalize(1e-6)
83            .unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0));
84        mesh.normals.push(normalized.x as f32);
85        mesh.normals.push(normalized.y as f32);
86        mesh.normals.push(normalized.z as f32);
87    }
88}
89