1use glam::{Vec3, Vec2};
4use super::{GeoMesh, Triangle};
5
6pub trait ScalarField: Send + Sync {
8 fn evaluate(&self, p: Vec3) -> f32;
9
10 fn gradient(&self, p: Vec3) -> Vec3 {
12 let eps = 1e-4;
13 Vec3::new(
14 self.evaluate(p + Vec3::X * eps) - self.evaluate(p - Vec3::X * eps),
15 self.evaluate(p + Vec3::Y * eps) - self.evaluate(p - Vec3::Y * eps),
16 self.evaluate(p + Vec3::Z * eps) - self.evaluate(p - Vec3::Z * eps),
17 ) / (2.0 * eps)
18 }
19}
20
21#[derive(Debug, Clone, Copy)]
23pub struct IsoVertex {
24 pub position: Vec3,
25 pub normal: Vec3,
26}
27
28pub struct SdfSphere { pub center: Vec3, pub radius: f32 }
31impl ScalarField for SdfSphere {
32 fn evaluate(&self, p: Vec3) -> f32 {
33 (p - self.center).length() - self.radius
34 }
35}
36
37pub struct SdfBox { pub center: Vec3, pub half_extents: Vec3 }
38impl ScalarField for SdfBox {
39 fn evaluate(&self, p: Vec3) -> f32 {
40 let d = (p - self.center).abs() - self.half_extents;
41 let outside = Vec3::new(d.x.max(0.0), d.y.max(0.0), d.z.max(0.0)).length();
42 let inside = d.x.max(d.y.max(d.z)).min(0.0);
43 outside + inside
44 }
45}
46
47pub struct SdfTorus { pub center: Vec3, pub major: f32, pub minor: f32 }
48impl ScalarField for SdfTorus {
49 fn evaluate(&self, p: Vec3) -> f32 {
50 let q = p - self.center;
51 let xz = Vec2::new(q.x, q.z).length() - self.major;
52 Vec2::new(xz, q.y).length() - self.minor
53 }
54}
55
56pub struct SdfGyroid { pub scale: f32, pub thickness: f32 }
57impl ScalarField for SdfGyroid {
58 fn evaluate(&self, p: Vec3) -> f32 {
59 let s = self.scale;
60 let val = (p.x * s).sin() * (p.y * s).cos()
61 + (p.y * s).sin() * (p.z * s).cos()
62 + (p.z * s).sin() * (p.x * s).cos();
63 val.abs() - self.thickness
64 }
65}
66
67pub struct CustomField<F: Fn(Vec3) -> f32 + Send + Sync> { pub func: F }
69impl<F: Fn(Vec3) -> f32 + Send + Sync> ScalarField for CustomField<F> {
70 fn evaluate(&self, p: Vec3) -> f32 { (self.func)(p) }
71}
72
73pub struct MarchingCubes {
77 pub resolution: u32,
78 pub bounds_min: Vec3,
79 pub bounds_max: Vec3,
80 pub iso_level: f32,
81}
82
83impl MarchingCubes {
84 pub fn new(resolution: u32, bounds_min: Vec3, bounds_max: Vec3) -> Self {
85 Self { resolution, bounds_min, bounds_max, iso_level: 0.0 }
86 }
87
88 pub fn extract(&self, field: &dyn ScalarField) -> GeoMesh {
90 let mut mesh = GeoMesh::new();
91 let res = self.resolution;
92 let step = (self.bounds_max - self.bounds_min) / res as f32;
93
94 let grid_size = (res + 1) as usize;
96 let mut values = vec![0.0f32; grid_size * grid_size * grid_size];
97
98 for z in 0..=res {
99 for y in 0..=res {
100 for x in 0..=res {
101 let p = self.bounds_min + Vec3::new(x as f32, y as f32, z as f32) * step;
102 let idx = x as usize + y as usize * grid_size + z as usize * grid_size * grid_size;
103 values[idx] = field.evaluate(p);
104 }
105 }
106 }
107
108 for z in 0..res {
110 for y in 0..res {
111 for x in 0..res {
112 self.process_cube(&mut mesh, field, &values, x, y, z, grid_size, step);
113 }
114 }
115 }
116
117 mesh.recompute_normals();
118 mesh
119 }
120
121 fn process_cube(
122 &self,
123 mesh: &mut GeoMesh,
124 field: &dyn ScalarField,
125 values: &[f32],
126 x: u32, y: u32, z: u32,
127 grid_size: usize,
128 step: Vec3,
129 ) {
130 let idx = |xi: u32, yi: u32, zi: u32| -> usize {
131 xi as usize + yi as usize * grid_size + zi as usize * grid_size * grid_size
132 };
133
134 let corners = [
136 values[idx(x, y, z)],
137 values[idx(x + 1, y, z)],
138 values[idx(x + 1, y + 1, z)],
139 values[idx(x, y + 1, z)],
140 values[idx(x, y, z + 1)],
141 values[idx(x + 1, y, z + 1)],
142 values[idx(x + 1, y + 1, z + 1)],
143 values[idx(x, y + 1, z + 1)],
144 ];
145
146 let mut cube_index = 0u8;
148 for i in 0..8 {
149 if corners[i] < self.iso_level {
150 cube_index |= 1 << i;
151 }
152 }
153
154 if cube_index == 0 || cube_index == 0xFF { return; }
156
157 let corner_positions = [
158 self.bounds_min + Vec3::new(x as f32, y as f32, z as f32) * step,
159 self.bounds_min + Vec3::new(x as f32 + 1.0, y as f32, z as f32) * step,
160 self.bounds_min + Vec3::new(x as f32 + 1.0, y as f32 + 1.0, z as f32) * step,
161 self.bounds_min + Vec3::new(x as f32, y as f32 + 1.0, z as f32) * step,
162 self.bounds_min + Vec3::new(x as f32, y as f32, z as f32 + 1.0) * step,
163 self.bounds_min + Vec3::new(x as f32 + 1.0, y as f32, z as f32 + 1.0) * step,
164 self.bounds_min + Vec3::new(x as f32 + 1.0, y as f32 + 1.0, z as f32 + 1.0) * step,
165 self.bounds_min + Vec3::new(x as f32, y as f32 + 1.0, z as f32 + 1.0) * step,
166 ];
167
168 let edge_mask = EDGE_TABLE[cube_index as usize];
170 if edge_mask == 0 { return; }
171
172 let mut edge_verts = [Vec3::ZERO; 12];
174 let edges: [(usize, usize); 12] = [
175 (0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)
176 ];
177 for i in 0..12 {
178 if (edge_mask & (1 << i)) != 0 {
179 let (a, b) = edges[i];
180 edge_verts[i] = interpolate_vertex(
181 corner_positions[a], corner_positions[b],
182 corners[a], corners[b], self.iso_level
183 );
184 }
185 }
186
187 let row = &TRI_TABLE[cube_index as usize];
189 let mut i = 0;
190 while i < row.len() && row[i] != -1 {
191 let v0 = mesh.add_vertex(edge_verts[row[i] as usize], Vec3::Y, Vec2::ZERO);
192 let v1 = mesh.add_vertex(edge_verts[row[i + 1] as usize], Vec3::Y, Vec2::ZERO);
193 let v2 = mesh.add_vertex(edge_verts[row[i + 2] as usize], Vec3::Y, Vec2::ZERO);
194 mesh.add_triangle(v0, v1, v2);
195 i += 3;
196 }
197 }
198}
199
200fn interpolate_vertex(p1: Vec3, p2: Vec3, v1: f32, v2: f32, iso: f32) -> Vec3 {
201 let denom = v2 - v1;
202 if denom.abs() < 1e-8 { return (p1 + p2) * 0.5; }
203 let t = (iso - v1) / denom;
204 p1 + (p2 - p1) * t.clamp(0.0, 1.0)
205}
206
207static EDGE_TABLE: [u16; 256] = {
211 let mut table = [0u16; 256];
212 let edges: [(usize, usize); 12] = [
215 (0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)
216 ];
217 let mut ci = 0u16;
218 while ci < 256 {
219 let mut mask = 0u16;
220 let mut e = 0;
221 while e < 12 {
222 let (a, b) = edges[e];
223 let a_in = (ci >> a) & 1;
224 let b_in = (ci >> b) & 1;
225 if a_in != b_in {
226 mask |= 1 << e;
227 }
228 e += 1;
229 }
230 table[ci as usize] = mask;
231 ci += 1;
232 }
233 table
234};
235
236static TRI_TABLE: [[i8; 16]; 256] = {
240 let empty = [-1i8; 16];
241 let mut table = [empty; 256];
242 table[1] = [0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
244 table[2] = [0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
245 table[3] = [1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
246 table[4] = [1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
247 table[5] = [0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
248 table[6] = [9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
249 table
252};
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn sphere_sdf_at_surface_is_zero() {
260 let s = SdfSphere { center: Vec3::ZERO, radius: 1.0 };
261 let val = s.evaluate(Vec3::new(1.0, 0.0, 0.0));
262 assert!(val.abs() < 0.01);
263 }
264
265 #[test]
266 fn sphere_sdf_inside_is_negative() {
267 let s = SdfSphere { center: Vec3::ZERO, radius: 1.0 };
268 assert!(s.evaluate(Vec3::ZERO) < 0.0);
269 }
270
271 #[test]
272 fn marching_cubes_sphere_produces_mesh() {
273 let field = SdfSphere { center: Vec3::ZERO, radius: 1.0 };
274 let mc = MarchingCubes::new(8, Vec3::splat(-2.0), Vec3::splat(2.0));
275 let mesh = mc.extract(&field);
276 assert!(mesh.vertex_count() > 0, "MC should produce vertices for a sphere");
277 }
278
279 #[test]
280 fn box_sdf_inside_is_negative() {
281 let b = SdfBox { center: Vec3::ZERO, half_extents: Vec3::ONE };
282 assert!(b.evaluate(Vec3::ZERO) < 0.0);
283 }
284
285 #[test]
286 fn gyroid_sdf_evaluates() {
287 let g = SdfGyroid { scale: 5.0, thickness: 0.3 };
288 let _v = g.evaluate(Vec3::new(0.5, 0.5, 0.5));
289 }
291}