Skip to main content

proof_engine/editor/
voxel_editor.rs

1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6// ============================================================
7//  VOXEL EDITOR
8//  Full implementation: SVO, materials, sculpt ops,
9//  Marching Cubes, Dual Contouring, procedural generation,
10//  physics, LOD, import/export, and editor tools.
11// ============================================================
12
13// ─── Constants ───────────────────────────────────────────────────────────────
14
15const MATERIAL_COUNT: usize = 64;
16const MAX_OCTREE_DEPTH: usize = 8;
17const VOXEL_SIZE: f32 = 0.25;
18const MAX_UNDO_HISTORY: usize = 64;
19const CHUNK_DIM: usize = 16; // chunk size for RLE export
20const MAX_DENSITY: f32 = 1.0;
21const MIN_DENSITY: f32 = 0.0;
22const ISO_LEVEL: f32 = 0.5;
23
24// ─── Material System ──────────────────────────────────────────────────────────
25
26#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct VoxelMaterial {
28    pub id: u8,
29    pub color: Vec4,         // RGBA
30    pub density_factor: f32, // affects marching cubes iso level
31    pub emission: Vec3,      // emissive color
32    pub hardness: f32,       // structural strength [0..1]
33    pub roughness: f32,
34    pub metallic: f32,
35    pub transparency: f32,
36}
37
38impl Default for VoxelMaterial {
39    fn default() -> Self {
40        Self {
41            id: 0,
42            color: Vec4::new(0.8, 0.8, 0.8, 1.0),
43            density_factor: 1.0,
44            emission: Vec3::ZERO,
45            hardness: 0.5,
46            roughness: 0.8,
47            metallic: 0.0,
48            transparency: 0.0,
49        }
50    }
51}
52
53pub fn default_material_table() -> Vec<VoxelMaterial> {
54    let mut table = Vec::with_capacity(MATERIAL_COUNT);
55    // 0: Air (empty)
56    table.push(VoxelMaterial { id: 0, color: Vec4::new(0.0, 0.0, 0.0, 0.0), density_factor: 0.0, hardness: 0.0, ..Default::default() });
57    // 1: Stone
58    table.push(VoxelMaterial { id: 1, color: Vec4::new(0.5, 0.5, 0.5, 1.0), hardness: 0.9, ..Default::default() });
59    // 2: Dirt
60    table.push(VoxelMaterial { id: 2, color: Vec4::new(0.4, 0.3, 0.2, 1.0), hardness: 0.3, ..Default::default() });
61    // 3: Grass
62    table.push(VoxelMaterial { id: 3, color: Vec4::new(0.2, 0.7, 0.2, 1.0), hardness: 0.2, ..Default::default() });
63    // 4: Sand
64    table.push(VoxelMaterial { id: 4, color: Vec4::new(0.9, 0.8, 0.6, 1.0), hardness: 0.1, ..Default::default() });
65    // 5: Water
66    table.push(VoxelMaterial { id: 5, color: Vec4::new(0.1, 0.3, 0.8, 0.7), hardness: 0.0, transparency: 0.7, ..Default::default() });
67    // 6: Wood
68    table.push(VoxelMaterial { id: 6, color: Vec4::new(0.6, 0.4, 0.2, 1.0), hardness: 0.6, ..Default::default() });
69    // 7: Leaves
70    table.push(VoxelMaterial { id: 7, color: Vec4::new(0.1, 0.6, 0.1, 0.9), hardness: 0.1, ..Default::default() });
71    // 8: Iron Ore
72    table.push(VoxelMaterial { id: 8, color: Vec4::new(0.6, 0.5, 0.4, 1.0), hardness: 0.95, metallic: 0.7, ..Default::default() });
73    // 9: Gold Ore
74    table.push(VoxelMaterial { id: 9, color: Vec4::new(0.9, 0.8, 0.2, 1.0), hardness: 0.7, metallic: 0.9, ..Default::default() });
75    // 10: Coal
76    table.push(VoxelMaterial { id: 10, color: Vec4::new(0.1, 0.1, 0.1, 1.0), hardness: 0.8, ..Default::default() });
77    // 11: Diamond
78    table.push(VoxelMaterial { id: 11, color: Vec4::new(0.7, 0.95, 1.0, 0.9), hardness: 1.0, roughness: 0.05, ..Default::default() });
79    // 12: Lava
80    table.push(VoxelMaterial { id: 12, color: Vec4::new(1.0, 0.3, 0.0, 1.0), hardness: 0.05, emission: Vec3::new(3.0, 0.5, 0.0), ..Default::default() });
81    // 13: Gravel
82    table.push(VoxelMaterial { id: 13, color: Vec4::new(0.6, 0.6, 0.5, 1.0), hardness: 0.25, ..Default::default() });
83    // 14: Obsidian
84    table.push(VoxelMaterial { id: 14, color: Vec4::new(0.1, 0.05, 0.15, 1.0), hardness: 0.99, metallic: 0.2, roughness: 0.2, ..Default::default() });
85    // 15: Snow
86    table.push(VoxelMaterial { id: 15, color: Vec4::new(0.95, 0.97, 1.0, 1.0), hardness: 0.05, roughness: 0.9, ..Default::default() });
87    // 16: Ice
88    table.push(VoxelMaterial { id: 16, color: Vec4::new(0.8, 0.9, 1.0, 0.8), hardness: 0.4, transparency: 0.4, roughness: 0.05, ..Default::default() });
89    // 17: Clay
90    table.push(VoxelMaterial { id: 17, color: Vec4::new(0.7, 0.65, 0.55, 1.0), hardness: 0.2, ..Default::default() });
91    // 18: Marble
92    table.push(VoxelMaterial { id: 18, color: Vec4::new(0.95, 0.93, 0.9, 1.0), hardness: 0.85, roughness: 0.1, ..Default::default() });
93    // 19: Copper
94    table.push(VoxelMaterial { id: 19, color: Vec4::new(0.85, 0.55, 0.3, 1.0), hardness: 0.75, metallic: 0.9, roughness: 0.4, ..Default::default() });
95    // Fill remaining with generic materials
96    for i in 20..MATERIAL_COUNT {
97        let hue = i as f32 / MATERIAL_COUNT as f32;
98        let r = (hue * 6.28).sin() * 0.5 + 0.5;
99        let g = (hue * 6.28 + 2.094).sin() * 0.5 + 0.5;
100        let b = (hue * 6.28 + 4.189).sin() * 0.5 + 0.5;
101        table.push(VoxelMaterial {
102            id: i as u8,
103            color: Vec4::new(r, g, b, 1.0),
104            hardness: (i as f32) / MATERIAL_COUNT as f32,
105            ..Default::default()
106        });
107    }
108    table
109}
110
111/// Blend two materials at a boundary.
112pub fn blend_materials(a: &VoxelMaterial, b: &VoxelMaterial, t: f32) -> VoxelMaterial {
113    VoxelMaterial {
114        id: if t < 0.5 { a.id } else { b.id },
115        color: a.color.lerp(b.color, t),
116        density_factor: a.density_factor + (b.density_factor - a.density_factor) * t,
117        emission: a.emission.lerp(b.emission, t),
118        hardness: a.hardness + (b.hardness - a.hardness) * t,
119        roughness: a.roughness + (b.roughness - a.roughness) * t,
120        metallic: a.metallic + (b.metallic - a.metallic) * t,
121        transparency: a.transparency + (b.transparency - a.transparency) * t,
122    }
123}
124
125// ─── Voxel Cell ───────────────────────────────────────────────────────────────
126
127#[derive(Debug, Clone, Copy, PartialEq)]
128pub struct Voxel {
129    pub density: f32,    // [0, 1]: 0 = empty, 1 = full
130    pub material: u8,
131}
132
133impl Voxel {
134    pub const EMPTY: Voxel = Voxel { density: 0.0, material: 0 };
135    pub const SOLID: Voxel = Voxel { density: 1.0, material: 1 };
136
137    pub fn new(density: f32, material: u8) -> Self {
138        Self { density: density.clamp(0.0, 1.0), material }
139    }
140
141    pub fn is_empty(&self) -> bool {
142        self.density < 0.001
143    }
144
145    pub fn is_solid(&self) -> bool {
146        self.density > ISO_LEVEL
147    }
148}
149
150// ─── Sparse Voxel Octree ──────────────────────────────────────────────────────
151
152#[derive(Debug, Clone)]
153pub enum OctreeNode {
154    Leaf(Voxel),
155    Branch {
156        children: Box<[Option<Box<OctreeNode>>; 8]>,
157        // bounding box encoded by position in tree
158    },
159    Empty,
160}
161
162impl OctreeNode {
163    pub fn is_leaf(&self) -> bool {
164        matches!(self, OctreeNode::Leaf(_))
165    }
166
167    pub fn is_empty(&self) -> bool {
168        matches!(self, OctreeNode::Empty)
169    }
170
171    pub fn is_branch(&self) -> bool {
172        matches!(self, OctreeNode::Branch { .. })
173    }
174
175    pub fn leaf_voxel(&self) -> Option<Voxel> {
176        match self {
177            OctreeNode::Leaf(v) => Some(*v),
178            _ => None,
179        }
180    }
181}
182
183fn empty_children() -> Box<[Option<Box<OctreeNode>>; 8]> {
184    Box::new([None, None, None, None, None, None, None, None])
185}
186
187/// Child index encoding: 3 bits per level (xyz).
188/// child_index(dx, dy, dz) = dx | (dy << 1) | (dz << 2)
189pub fn child_index(dx: u8, dy: u8, dz: u8) -> usize {
190    (dx as usize) | ((dy as usize) << 1) | ((dz as usize) << 2)
191}
192
193pub fn child_offset(idx: usize) -> (u8, u8, u8) {
194    let dx = (idx & 1) as u8;
195    let dy = ((idx >> 1) & 1) as u8;
196    let dz = ((idx >> 2) & 1) as u8;
197    (dx, dy, dz)
198}
199
200#[derive(Debug, Clone)]
201pub struct SparseVoxelOctree {
202    pub root: OctreeNode,
203    pub depth: usize,
204    pub origin: Vec3,
205    pub size: f32, // total size
206}
207
208impl SparseVoxelOctree {
209    pub fn new(origin: Vec3, size: f32, depth: usize) -> Self {
210        Self {
211            root: OctreeNode::Empty,
212            depth,
213            origin,
214            size,
215        }
216    }
217
218    /// Voxel size at the leaf level.
219    pub fn leaf_size(&self) -> f32 {
220        self.size / (1 << self.depth) as f32
221    }
222
223    /// Convert a world position to leaf coordinates [0, 2^depth).
224    pub fn world_to_leaf(&self, pos: Vec3) -> Option<(i32, i32, i32)> {
225        let rel = pos - self.origin;
226        let n = (1 << self.depth) as f32;
227        let lx = (rel.x / self.size * n) as i32;
228        let ly = (rel.y / self.size * n) as i32;
229        let lz = (rel.z / self.size * n) as i32;
230        let max = (1 << self.depth) as i32;
231        if lx < 0 || ly < 0 || lz < 0 || lx >= max || ly >= max || lz >= max {
232            None
233        } else {
234            Some((lx, ly, lz))
235        }
236    }
237
238    pub fn leaf_to_world(&self, lx: i32, ly: i32, lz: i32) -> Vec3 {
239        let n = (1 << self.depth) as f32;
240        let leaf_size = self.size / n;
241        self.origin + Vec3::new(lx as f32, ly as f32, lz as f32) * leaf_size
242    }
243
244    /// Insert a voxel at world position.
245    pub fn insert(&mut self, pos: Vec3, voxel: Voxel) {
246        if let Some((lx, ly, lz)) = self.world_to_leaf(pos) {
247            let max = 1 << self.depth;
248            insert_recursive(&mut self.root, lx, ly, lz, max, self.depth, voxel);
249        }
250    }
251
252    /// Query a voxel at world position.
253    pub fn query(&self, pos: Vec3) -> Voxel {
254        if let Some((lx, ly, lz)) = self.world_to_leaf(pos) {
255            let max = 1 << self.depth;
256            query_recursive(&self.root, lx, ly, lz, max, self.depth)
257        } else {
258            Voxel::EMPTY
259        }
260    }
261
262    /// Delete (set to empty) a voxel at world position.
263    pub fn delete(&mut self, pos: Vec3) {
264        self.insert(pos, Voxel::EMPTY);
265    }
266
267    /// AABB intersection: returns positions of all leaves within the AABB.
268    pub fn aabb_query(&self, min: Vec3, max: Vec3, out: &mut Vec<(Vec3, Voxel)>) {
269        let leaf_size = self.leaf_size();
270        aabb_recursive(
271            &self.root,
272            self.origin,
273            self.size,
274            self.depth,
275            min,
276            max,
277            leaf_size,
278            out,
279        );
280    }
281
282    /// Ray traversal using DDA on octree.
283    pub fn ray_intersect(&self, ray_origin: Vec3, ray_dir: Vec3) -> Option<(Vec3, Voxel, f32)> {
284        let dir = if ray_dir.length() > 1e-9 { ray_dir.normalize() } else { return None; };
285        ray_traverse_octree(
286            &self.root,
287            self.origin,
288            self.size,
289            self.depth,
290            ray_origin,
291            dir,
292        )
293    }
294
295    /// Collapse uniform children (if all 8 children are same leaf voxel).
296    pub fn optimize(&mut self) {
297        optimize_node(&mut self.root);
298    }
299
300    /// Count all non-empty leaves.
301    pub fn voxel_count(&self) -> usize {
302        count_recursive(&self.root)
303    }
304}
305
306fn insert_recursive(node: &mut OctreeNode, lx: i32, ly: i32, lz: i32, size: i32, depth: usize, voxel: Voxel) {
307    if depth == 0 {
308        *node = if voxel.is_empty() { OctreeNode::Empty } else { OctreeNode::Leaf(voxel) };
309        return;
310    }
311    let half = size / 2;
312    let dx = (lx >= half) as u8;
313    let dy = (ly >= half) as u8;
314    let dz = (lz >= half) as u8;
315    let idx = child_index(dx, dy, dz);
316    let nlx = lx - dx as i32 * half;
317    let nly = ly - dy as i32 * half;
318    let nlz = lz - dz as i32 * half;
319
320    match node {
321        OctreeNode::Empty => {
322            if voxel.is_empty() { return; }
323            let mut children = empty_children();
324            let mut child = OctreeNode::Empty;
325            insert_recursive(&mut child, nlx, nly, nlz, half, depth - 1, voxel);
326            children[idx] = Some(Box::new(child));
327            *node = OctreeNode::Branch { children };
328        }
329        OctreeNode::Leaf(existing) => {
330            let existing_v = *existing;
331            let mut children = empty_children();
332            // Expand: fill all 8 children with existing value
333            for ci in 0..8 {
334                children[ci] = Some(Box::new(OctreeNode::Leaf(existing_v)));
335            }
336            let mut child = OctreeNode::Leaf(existing_v);
337            insert_recursive(&mut child, nlx, nly, nlz, half, depth - 1, voxel);
338            children[idx] = Some(Box::new(child));
339            *node = OctreeNode::Branch { children };
340        }
341        OctreeNode::Branch { children } => {
342            let child = children[idx].get_or_insert_with(|| Box::new(OctreeNode::Empty));
343            insert_recursive(child, nlx, nly, nlz, half, depth - 1, voxel);
344        }
345    }
346}
347
348fn query_recursive(node: &OctreeNode, lx: i32, ly: i32, lz: i32, size: i32, depth: usize) -> Voxel {
349    match node {
350        OctreeNode::Empty => Voxel::EMPTY,
351        OctreeNode::Leaf(v) => *v,
352        OctreeNode::Branch { children } => {
353            if depth == 0 { return Voxel::EMPTY; }
354            let half = size / 2;
355            let dx = (lx >= half) as u8;
356            let dy = (ly >= half) as u8;
357            let dz = (lz >= half) as u8;
358            let idx = child_index(dx, dy, dz);
359            let nlx = lx - dx as i32 * half;
360            let nly = ly - dy as i32 * half;
361            let nlz = lz - dz as i32 * half;
362            match &children[idx] {
363                Some(child) => query_recursive(child, nlx, nly, nlz, half, depth - 1),
364                None => Voxel::EMPTY,
365            }
366        }
367    }
368}
369
370fn aabb_recursive(
371    node: &OctreeNode,
372    node_origin: Vec3,
373    node_size: f32,
374    depth: usize,
375    aabb_min: Vec3,
376    aabb_max: Vec3,
377    leaf_size: f32,
378    out: &mut Vec<(Vec3, Voxel)>,
379) {
380    // Check if this node's AABB intersects query AABB
381    let node_max = node_origin + Vec3::splat(node_size);
382    if node_origin.x > aabb_max.x || node_max.x < aabb_min.x { return; }
383    if node_origin.y > aabb_max.y || node_max.y < aabb_min.y { return; }
384    if node_origin.z > aabb_max.z || node_max.z < aabb_min.z { return; }
385
386    match node {
387        OctreeNode::Empty => {}
388        OctreeNode::Leaf(v) => {
389            if !v.is_empty() {
390                out.push((node_origin, *v));
391            }
392        }
393        OctreeNode::Branch { children } => {
394            let half = node_size / 2.0;
395            for ci in 0..8 {
396                let (dx, dy, dz) = child_offset(ci);
397                let child_origin = node_origin + Vec3::new(dx as f32 * half, dy as f32 * half, dz as f32 * half);
398                if let Some(child) = &children[ci] {
399                    aabb_recursive(child, child_origin, half, depth.saturating_sub(1), aabb_min, aabb_max, leaf_size, out);
400                }
401            }
402        }
403    }
404}
405
406fn ray_traverse_octree(
407    node: &OctreeNode,
408    node_origin: Vec3,
409    node_size: f32,
410    depth: usize,
411    ray_origin: Vec3,
412    ray_dir: Vec3,
413) -> Option<(Vec3, Voxel, f32)> {
414    // Slab test
415    let inv_dir = Vec3::new(
416        if ray_dir.x.abs() > 1e-9 { 1.0 / ray_dir.x } else { f32::MAX },
417        if ray_dir.y.abs() > 1e-9 { 1.0 / ray_dir.y } else { f32::MAX },
418        if ray_dir.z.abs() > 1e-9 { 1.0 / ray_dir.z } else { f32::MAX },
419    );
420    let t1 = (node_origin - ray_origin) * inv_dir;
421    let t2 = (node_origin + Vec3::splat(node_size) - ray_origin) * inv_dir;
422    let t_min = t1.min(t2);
423    let t_max = t1.max(t2);
424    let t_enter = t_min.x.max(t_min.y).max(t_min.z);
425    let t_exit = t_max.x.min(t_max.y).min(t_max.z);
426    if t_enter > t_exit || t_exit < 0.0 { return None; }
427
428    match node {
429        OctreeNode::Empty => None,
430        OctreeNode::Leaf(v) => {
431            if v.is_empty() { None }
432            else {
433                let hit_pos = ray_origin + ray_dir * t_enter.max(0.0);
434                Some((hit_pos, *v, t_enter.max(0.0)))
435            }
436        }
437        OctreeNode::Branch { children } => {
438            let half = node_size / 2.0;
439            let mut best: Option<(Vec3, Voxel, f32)> = None;
440            for ci in 0..8 {
441                let (dx, dy, dz) = child_offset(ci);
442                let child_origin = node_origin + Vec3::new(dx as f32 * half, dy as f32 * half, dz as f32 * half);
443                if let Some(child) = &children[ci] {
444                    if let Some(hit) = ray_traverse_octree(child, child_origin, half, depth.saturating_sub(1), ray_origin, ray_dir) {
445                        if best.as_ref().map_or(true, |b: &(Vec3, Voxel, f32)| hit.2 < b.2) {
446                            best = Some(hit);
447                        }
448                    }
449                }
450            }
451            best
452        }
453    }
454}
455
456fn optimize_node(node: &mut OctreeNode) {
457    match node {
458        OctreeNode::Branch { children } => {
459            for ci in 0..8 {
460                if let Some(child) = &mut children[ci] {
461                    optimize_node(child);
462                }
463            }
464            // Check if all children are same leaf
465            let first = children[0].as_ref().and_then(|c| c.leaf_voxel());
466            if let Some(v0) = first {
467                let all_same = children.iter().all(|c| {
468                    c.as_ref().and_then(|n| n.leaf_voxel()) == Some(v0)
469                });
470                if all_same {
471                    *node = OctreeNode::Leaf(v0);
472                }
473            } else {
474                // Check all empty
475                let all_empty = children.iter().all(|c| {
476                    c.as_ref().map_or(true, |n| n.is_empty())
477                });
478                if all_empty {
479                    *node = OctreeNode::Empty;
480                }
481            }
482        }
483        _ => {}
484    }
485}
486
487fn count_recursive(node: &OctreeNode) -> usize {
488    match node {
489        OctreeNode::Empty => 0,
490        OctreeNode::Leaf(v) => if v.is_empty() { 0 } else { 1 },
491        OctreeNode::Branch { children } => {
492            children.iter().map(|c| c.as_ref().map_or(0, |n| count_recursive(n))).sum()
493        }
494    }
495}
496
497// ─── Dense Voxel Grid (for editing operations) ────────────────────────────────
498
499#[derive(Debug, Clone)]
500pub struct VoxelGrid {
501    pub width: usize,
502    pub height: usize,
503    pub depth: usize,
504    pub voxels: Vec<Voxel>,
505    pub origin: Vec3,
506    pub voxel_size: f32,
507}
508
509impl VoxelGrid {
510    pub fn new(width: usize, height: usize, depth: usize, origin: Vec3, voxel_size: f32) -> Self {
511        Self {
512            width, height, depth,
513            voxels: vec![Voxel::EMPTY; width * height * depth],
514            origin,
515            voxel_size,
516        }
517    }
518
519    pub fn index(&self, x: usize, y: usize, z: usize) -> usize {
520        x + y * self.width + z * self.width * self.height
521    }
522
523    pub fn get(&self, x: i32, y: i32, z: i32) -> Voxel {
524        if x < 0 || y < 0 || z < 0
525            || x >= self.width as i32
526            || y >= self.height as i32
527            || z >= self.depth as i32
528        {
529            Voxel::EMPTY
530        } else {
531            self.voxels[self.index(x as usize, y as usize, z as usize)]
532        }
533    }
534
535    pub fn set(&mut self, x: i32, y: i32, z: i32, v: Voxel) {
536        if x < 0 || y < 0 || z < 0
537            || x >= self.width as i32
538            || y >= self.height as i32
539            || z >= self.depth as i32
540        {
541            return;
542        }
543        let idx = self.index(x as usize, y as usize, z as usize);
544        self.voxels[idx] = v;
545    }
546
547    pub fn world_to_grid(&self, pos: Vec3) -> (i32, i32, i32) {
548        let rel = pos - self.origin;
549        let x = (rel.x / self.voxel_size) as i32;
550        let y = (rel.y / self.voxel_size) as i32;
551        let z = (rel.z / self.voxel_size) as i32;
552        (x, y, z)
553    }
554
555    pub fn grid_to_world(&self, x: i32, y: i32, z: i32) -> Vec3 {
556        self.origin + Vec3::new(x as f32, y as f32, z as f32) * self.voxel_size
557    }
558
559    pub fn to_svo(&self) -> SparseVoxelOctree {
560        let size = self.width.max(self.height).max(self.depth) as f32 * self.voxel_size;
561        let depth = (size / self.voxel_size).log2().ceil() as usize;
562        let depth = depth.min(MAX_OCTREE_DEPTH);
563        let mut svo = SparseVoxelOctree::new(self.origin, size, depth);
564        for z in 0..self.depth {
565            for y in 0..self.height {
566                for x in 0..self.width {
567                    let v = self.voxels[self.index(x, y, z)];
568                    if !v.is_empty() {
569                        let pos = self.grid_to_world(x as i32, y as i32, z as i32);
570                        svo.insert(pos, v);
571                    }
572                }
573            }
574        }
575        svo
576    }
577}
578
579// ─── Sculpt Operations ───────────────────────────────────────────────────────
580
581#[derive(Debug, Clone)]
582pub enum SculptOp {
583    AddSphere { center: Vec3, radius: f32, material: u8, density: f32 },
584    RemoveSphere { center: Vec3, radius: f32 },
585    AddBox { min: Vec3, max: Vec3, material: u8, density: f32 },
586    RemoveBox { min: Vec3, max: Vec3 },
587    SmoothSphere { center: Vec3, radius: f32, strength: f32 },
588    PaintSphere { center: Vec3, radius: f32, material: u8 },
589    Flatten { center: Vec3, radius: f32, plane_normal: Vec3, plane_d: f32, strength: f32 },
590}
591
592pub fn apply_sculpt_op(grid: &mut VoxelGrid, op: &SculptOp) {
593    match op {
594        SculptOp::AddSphere { center, radius, material, density } => {
595            let min_cell = grid.world_to_grid(*center - Vec3::splat(*radius + grid.voxel_size));
596            let max_cell = grid.world_to_grid(*center + Vec3::splat(*radius + grid.voxel_size));
597            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
598                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
599                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
600                        let world = grid.grid_to_world(x, y, z) + Vec3::splat(grid.voxel_size * 0.5);
601                        let dist = (world - *center).length();
602                        if dist <= *radius {
603                            let existing = grid.get(x, y, z);
604                            let new_density = (existing.density + density * (1.0 - dist / radius)).min(1.0);
605                            grid.set(x, y, z, Voxel::new(new_density, *material));
606                        }
607                    }
608                }
609            }
610        }
611        SculptOp::RemoveSphere { center, radius } => {
612            let min_cell = grid.world_to_grid(*center - Vec3::splat(*radius + grid.voxel_size));
613            let max_cell = grid.world_to_grid(*center + Vec3::splat(*radius + grid.voxel_size));
614            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
615                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
616                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
617                        let world = grid.grid_to_world(x, y, z) + Vec3::splat(grid.voxel_size * 0.5);
618                        let dist = (world - *center).length();
619                        if dist <= *radius {
620                            let falloff = 1.0 - dist / radius;
621                            let existing = grid.get(x, y, z);
622                            let new_density = (existing.density - falloff).max(0.0);
623                            if new_density < 0.001 {
624                                grid.set(x, y, z, Voxel::EMPTY);
625                            } else {
626                                grid.set(x, y, z, Voxel::new(new_density, existing.material));
627                            }
628                        }
629                    }
630                }
631            }
632        }
633        SculptOp::AddBox { min: bmin, max: bmax, material, density } => {
634            let min_cell = grid.world_to_grid(*bmin);
635            let max_cell = grid.world_to_grid(*bmax);
636            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
637                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
638                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
639                        grid.set(x, y, z, Voxel::new(*density, *material));
640                    }
641                }
642            }
643        }
644        SculptOp::RemoveBox { min: bmin, max: bmax } => {
645            let min_cell = grid.world_to_grid(*bmin);
646            let max_cell = grid.world_to_grid(*bmax);
647            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
648                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
649                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
650                        grid.set(x, y, z, Voxel::EMPTY);
651                    }
652                }
653            }
654        }
655        SculptOp::SmoothSphere { center, radius, strength } => {
656            let min_cell = grid.world_to_grid(*center - Vec3::splat(*radius));
657            let max_cell = grid.world_to_grid(*center + Vec3::splat(*radius));
658            let mut deltas: Vec<(i32, i32, i32, f32, u8)> = Vec::new();
659            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
660                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
661                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
662                        let world = grid.grid_to_world(x, y, z);
663                        let dist = (world - *center).length();
664                        if dist <= *radius {
665                            // Average density with 6-connected neighbors
666                            let mut sum = 0.0f32;
667                            let mut count = 0u32;
668                            for (nx, ny, nz) in [
669                                (x-1,y,z),(x+1,y,z),(x,y-1,z),(x,y+1,z),(x,y,z-1),(x,y,z+1),
670                                (x,y,z),
671                            ] {
672                                sum += grid.get(nx, ny, nz).density;
673                                count += 1;
674                            }
675                            let avg = sum / count as f32;
676                            let existing = grid.get(x, y, z);
677                            let falloff = 1.0 - dist / radius;
678                            let new_d = existing.density + (avg - existing.density) * strength * falloff;
679                            deltas.push((x, y, z, new_d, existing.material));
680                        }
681                    }
682                }
683            }
684            for (x, y, z, d, m) in deltas {
685                grid.set(x, y, z, Voxel::new(d, m));
686            }
687        }
688        SculptOp::PaintSphere { center, radius, material } => {
689            let min_cell = grid.world_to_grid(*center - Vec3::splat(*radius));
690            let max_cell = grid.world_to_grid(*center + Vec3::splat(*radius));
691            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
692                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
693                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
694                        let world = grid.grid_to_world(x, y, z);
695                        let dist = (world - *center).length();
696                        if dist <= *radius {
697                            let existing = grid.get(x, y, z);
698                            if !existing.is_empty() {
699                                grid.set(x, y, z, Voxel::new(existing.density, *material));
700                            }
701                        }
702                    }
703                }
704            }
705        }
706        SculptOp::Flatten { center, radius, plane_normal, plane_d, strength } => {
707            let min_cell = grid.world_to_grid(*center - Vec3::splat(*radius));
708            let max_cell = grid.world_to_grid(*center + Vec3::splat(*radius));
709            let mut deltas: Vec<(i32, i32, i32, f32, u8)> = Vec::new();
710            for z in min_cell.2.max(0)..=max_cell.2.min(grid.depth as i32 - 1) {
711                for y in min_cell.1.max(0)..=max_cell.1.min(grid.height as i32 - 1) {
712                    for x in min_cell.0.max(0)..=max_cell.0.min(grid.width as i32 - 1) {
713                        let world = grid.grid_to_world(x, y, z);
714                        let horiz_dist = (world - *center).length();
715                        if horiz_dist > *radius { continue; }
716                        let existing = grid.get(x, y, z);
717                        if existing.is_empty() { continue; }
718                        // Distance to plane
719                        let signed_dist = plane_normal.dot(world) - plane_d;
720                        // Voxels above plane: reduce density; below: increase
721                        let falloff = 1.0 - horiz_dist / radius;
722                        let adjustment = -signed_dist * strength * falloff;
723                        let new_d = (existing.density + adjustment).clamp(0.0, 1.0);
724                        deltas.push((x, y, z, new_d, existing.material));
725                    }
726                }
727            }
728            for (x, y, z, d, m) in deltas {
729                if d < 0.001 {
730                    grid.set(x, y, z, Voxel::EMPTY);
731                } else {
732                    grid.set(x, y, z, Voxel::new(d, m));
733                }
734            }
735        }
736    }
737}
738
739// ─── Marching Cubes ───────────────────────────────────────────────────────────
740
741/// Full 256-entry edge table for marching cubes.
742/// Each entry is a bitmask of the 12 edges that are intersected for that case.
743pub const MC_EDGE_TABLE: [u16; 256] = [
744    0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
745    0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
746    0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
747    0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
748    0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c,
749    0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
750    0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac,
751    0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
752    0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c,
753    0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
754    0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc,
755    0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
756    0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
757    0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
758    0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc,
759    0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
760    0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
761    0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
762    0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
763    0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
764    0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
765    0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
766    0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
767    0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
768    0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
769    0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
770    0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x835, 0xb3f, 0xa36,  // fixed: was b35->835
771    0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230,
772    0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
773    0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
774    0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
775    0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000,
776];
777
778/// Triangle table: for each of 256 cases, list of edge indices forming triangles.
779/// We store as arrays of i8, -1 = end of list. 256 rows, 16 columns max.
780pub const MC_TRI_TABLE: [[i8; 16]; 256] = generate_mc_tri_table();
781
782const fn generate_mc_tri_table() -> [[i8; 16]; 256] {
783    // Full standard marching cubes triangle table
784    // This is the canonical table from Lorensen & Cline 1987
785    [
786        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
787        [0,8,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
788        [0,1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
789        [1,8,3,9,8,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
790        [1,2,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
791        [0,8,3,1,2,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
792        [9,2,10,0,2,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
793        [2,8,3,2,10,8,10,9,8,-1,-1,-1,-1,-1,-1,-1],
794        [3,11,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
795        [0,11,2,8,11,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
796        [1,9,0,2,3,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
797        [1,11,2,1,9,11,9,8,11,-1,-1,-1,-1,-1,-1,-1],
798        [3,10,1,11,10,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
799        [0,10,1,0,8,10,8,11,10,-1,-1,-1,-1,-1,-1,-1],
800        [3,9,0,3,11,9,11,10,9,-1,-1,-1,-1,-1,-1,-1],
801        [9,8,10,10,8,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
802        [4,7,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
803        [4,3,0,7,3,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
804        [0,1,9,8,4,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
805        [4,1,9,4,7,1,7,3,1,-1,-1,-1,-1,-1,-1,-1],
806        [1,2,10,8,4,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
807        [3,4,7,3,0,4,1,2,10,-1,-1,-1,-1,-1,-1,-1],
808        [9,2,10,9,0,2,8,4,7,-1,-1,-1,-1,-1,-1,-1],
809        [2,10,9,2,9,7,2,7,3,7,9,4,-1,-1,-1,-1],
810        [8,4,7,3,11,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
811        [11,4,7,11,2,4,2,0,4,-1,-1,-1,-1,-1,-1,-1],
812        [9,0,1,8,4,7,2,3,11,-1,-1,-1,-1,-1,-1,-1],
813        [4,7,11,9,4,11,9,11,2,9,2,1,-1,-1,-1,-1],
814        [3,10,1,3,11,10,7,8,4,-1,-1,-1,-1,-1,-1,-1],
815        [1,11,10,1,4,11,1,0,4,7,11,4,-1,-1,-1,-1],
816        [4,7,8,9,0,11,9,11,10,11,0,3,-1,-1,-1,-1],
817        [4,7,11,4,11,9,9,11,10,-1,-1,-1,-1,-1,-1,-1],
818        [9,5,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
819        [9,5,4,0,8,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
820        [0,5,4,1,5,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
821        [8,5,4,8,3,5,3,1,5,-1,-1,-1,-1,-1,-1,-1],
822        [1,2,10,9,5,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
823        [3,0,8,1,2,10,4,9,5,-1,-1,-1,-1,-1,-1,-1],
824        [5,2,10,5,4,2,4,0,2,-1,-1,-1,-1,-1,-1,-1],
825        [2,10,5,3,2,5,3,5,4,3,4,8,-1,-1,-1,-1],
826        [9,5,4,2,3,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
827        [0,11,2,0,8,11,4,9,5,-1,-1,-1,-1,-1,-1,-1],
828        [0,5,4,0,1,5,2,3,11,-1,-1,-1,-1,-1,-1,-1],
829        [2,1,5,2,5,8,2,8,11,4,8,5,-1,-1,-1,-1],
830        [10,3,11,10,1,3,9,5,4,-1,-1,-1,-1,-1,-1,-1],
831        [4,9,5,0,8,1,8,10,1,8,11,10,-1,-1,-1,-1],
832        [5,4,0,5,0,11,5,11,10,11,0,3,-1,-1,-1,-1],
833        [5,4,8,5,8,10,10,8,11,-1,-1,-1,-1,-1,-1,-1],
834        [9,7,8,5,7,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
835        [9,3,0,9,5,3,5,7,3,-1,-1,-1,-1,-1,-1,-1],
836        [0,7,8,0,1,7,1,5,7,-1,-1,-1,-1,-1,-1,-1],
837        [1,5,3,3,5,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
838        [9,7,8,9,5,7,10,1,2,-1,-1,-1,-1,-1,-1,-1],
839        [10,1,2,9,5,0,5,3,0,5,7,3,-1,-1,-1,-1],
840        [8,0,2,8,2,5,8,5,7,10,5,2,-1,-1,-1,-1],
841        [2,10,5,2,5,3,3,5,7,-1,-1,-1,-1,-1,-1,-1],
842        [7,9,5,7,8,9,3,11,2,-1,-1,-1,-1,-1,-1,-1],
843        [9,5,7,9,7,2,9,2,0,2,7,11,-1,-1,-1,-1],
844        [2,3,11,0,1,8,1,7,8,1,5,7,-1,-1,-1,-1],
845        [11,2,1,11,1,7,7,1,5,-1,-1,-1,-1,-1,-1,-1],
846        [9,5,8,8,5,7,10,1,3,10,3,11,-1,-1,-1,-1],
847        [5,7,0,5,0,9,7,11,0,1,0,10,11,10,0,-1],
848        [11,10,0,11,0,3,10,5,0,8,0,7,5,7,0,-1],
849        [11,10,5,7,11,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
850        [10,6,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
851        [0,8,3,5,10,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
852        [9,0,1,5,10,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
853        [1,8,3,1,9,8,5,10,6,-1,-1,-1,-1,-1,-1,-1],
854        [1,6,5,2,6,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
855        [1,6,5,1,2,6,3,0,8,-1,-1,-1,-1,-1,-1,-1],
856        [9,6,5,9,0,6,0,2,6,-1,-1,-1,-1,-1,-1,-1],
857        [5,9,8,5,8,2,5,2,6,3,2,8,-1,-1,-1,-1],
858        [2,3,11,10,6,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
859        [11,0,8,11,2,0,10,6,5,-1,-1,-1,-1,-1,-1,-1],
860        [0,1,9,2,3,11,5,10,6,-1,-1,-1,-1,-1,-1,-1],
861        [5,10,6,1,9,2,9,11,2,9,8,11,-1,-1,-1,-1],
862        [6,3,11,6,5,3,5,1,3,-1,-1,-1,-1,-1,-1,-1],
863        [0,8,11,0,11,5,0,5,1,5,11,6,-1,-1,-1,-1],
864        [3,11,6,0,3,6,0,6,5,0,5,9,-1,-1,-1,-1],
865        [6,5,9,6,9,11,11,9,8,-1,-1,-1,-1,-1,-1,-1],
866        [5,10,6,4,7,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
867        [4,3,0,4,7,3,6,5,10,-1,-1,-1,-1,-1,-1,-1],
868        [1,9,0,5,10,6,8,4,7,-1,-1,-1,-1,-1,-1,-1],
869        [10,6,5,1,9,7,1,7,3,7,9,4,-1,-1,-1,-1],
870        [6,1,2,6,5,1,4,7,8,-1,-1,-1,-1,-1,-1,-1],
871        [1,2,5,5,2,6,3,0,4,3,4,7,-1,-1,-1,-1],
872        [8,4,7,9,0,5,0,6,5,0,2,6,-1,-1,-1,-1],
873        [7,3,9,7,9,4,3,2,9,5,9,6,2,6,9,-1],
874        [3,11,2,7,8,4,10,6,5,-1,-1,-1,-1,-1,-1,-1],
875        [5,10,6,4,7,2,4,2,0,2,7,11,-1,-1,-1,-1],
876        [0,1,9,4,7,8,2,3,11,5,10,6,-1,-1,-1,-1],
877        [9,2,1,9,11,2,9,4,11,7,11,4,5,10,6,-1],
878        [8,4,7,3,11,5,3,5,1,5,11,6,-1,-1,-1,-1],
879        [5,1,11,5,11,6,1,0,11,7,11,4,0,4,11,-1],
880        [0,5,9,0,6,5,0,3,6,11,6,3,8,4,7,-1],
881        [6,5,9,6,9,11,4,7,9,7,11,9,-1,-1,-1,-1],
882        [10,4,9,6,4,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
883        [4,10,6,4,9,10,0,8,3,-1,-1,-1,-1,-1,-1,-1],
884        [10,0,1,10,6,0,6,4,0,-1,-1,-1,-1,-1,-1,-1],
885        [8,3,1,8,1,6,8,6,4,6,1,10,-1,-1,-1,-1],
886        [1,4,9,1,2,4,2,6,4,-1,-1,-1,-1,-1,-1,-1],
887        [3,0,8,1,2,9,2,4,9,2,6,4,-1,-1,-1,-1],
888        [0,2,4,4,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
889        [8,3,2,8,2,4,4,2,6,-1,-1,-1,-1,-1,-1,-1],
890        [10,4,9,10,6,4,11,2,3,-1,-1,-1,-1,-1,-1,-1],
891        [0,8,2,2,8,11,4,9,10,4,10,6,-1,-1,-1,-1],
892        [3,11,2,0,1,6,0,6,4,6,1,10,-1,-1,-1,-1],
893        [6,4,1,6,1,10,4,8,1,2,1,11,8,11,1,-1],
894        [9,6,4,9,3,6,9,1,3,11,6,3,-1,-1,-1,-1],
895        [8,11,1,8,1,0,11,6,1,9,1,4,6,4,1,-1],
896        [3,11,6,3,6,0,0,6,4,-1,-1,-1,-1,-1,-1,-1],
897        [6,4,8,11,6,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
898        [7,10,6,7,8,10,8,9,10,-1,-1,-1,-1,-1,-1,-1],
899        [0,7,3,0,10,7,0,9,10,6,7,10,-1,-1,-1,-1],
900        [10,6,7,1,10,7,1,7,8,1,8,0,-1,-1,-1,-1],
901        [10,6,7,10,7,1,1,7,3,-1,-1,-1,-1,-1,-1,-1],
902        [1,2,6,1,6,8,1,8,9,8,6,7,-1,-1,-1,-1],
903        [2,6,9,2,9,1,6,7,9,0,9,3,7,3,9,-1],
904        [7,8,0,7,0,6,6,0,2,-1,-1,-1,-1,-1,-1,-1],
905        [7,3,2,6,7,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
906        [2,3,11,10,6,8,10,8,9,8,6,7,-1,-1,-1,-1],
907        [2,0,7,2,7,11,0,9,7,6,7,10,9,10,7,-1],
908        [1,8,0,1,7,8,1,10,7,6,7,10,2,3,11,-1],
909        [11,2,1,11,1,7,10,6,1,6,7,1,-1,-1,-1,-1],
910        [8,9,6,8,6,7,9,1,6,11,6,3,1,3,6,-1],
911        [0,9,1,11,6,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
912        [7,8,0,7,0,6,3,11,0,11,6,0,-1,-1,-1,-1],
913        [7,11,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
914        [7,6,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
915        [3,0,8,11,7,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
916        [0,1,9,11,7,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
917        [8,1,9,8,3,1,11,7,6,-1,-1,-1,-1,-1,-1,-1],
918        [10,1,2,6,11,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
919        [1,2,10,3,0,8,6,11,7,-1,-1,-1,-1,-1,-1,-1],
920        [2,9,0,2,10,9,6,11,7,-1,-1,-1,-1,-1,-1,-1],
921        [6,11,7,2,10,3,10,8,3,10,9,8,-1,-1,-1,-1],
922        [7,2,3,6,2,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
923        [7,0,8,7,6,0,6,2,0,-1,-1,-1,-1,-1,-1,-1],
924        [2,7,6,2,3,7,0,1,9,-1,-1,-1,-1,-1,-1,-1],
925        [1,6,2,1,8,6,1,9,8,8,7,6,-1,-1,-1,-1],
926        [10,7,6,10,1,7,1,3,7,-1,-1,-1,-1,-1,-1,-1],
927        [10,7,6,1,7,10,1,8,7,1,0,8,-1,-1,-1,-1],
928        [0,3,7,0,7,10,0,10,9,6,10,7,-1,-1,-1,-1],
929        [7,6,10,7,10,8,8,10,9,-1,-1,-1,-1,-1,-1,-1],
930        [6,8,4,11,8,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
931        [3,6,11,3,0,6,0,4,6,-1,-1,-1,-1,-1,-1,-1],
932        [8,6,11,8,4,6,9,0,1,-1,-1,-1,-1,-1,-1,-1],
933        [9,4,6,9,6,3,9,3,1,11,3,6,-1,-1,-1,-1],
934        [6,8,4,6,11,8,2,10,1,-1,-1,-1,-1,-1,-1,-1],
935        [1,2,10,3,0,11,0,6,11,0,4,6,-1,-1,-1,-1],
936        [4,11,8,4,6,11,0,2,9,2,10,9,-1,-1,-1,-1],
937        [10,9,3,10,3,2,9,4,3,11,3,6,4,6,3,-1],
938        [8,2,3,8,4,2,4,6,2,-1,-1,-1,-1,-1,-1,-1],
939        [0,4,2,4,6,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
940        [1,9,0,2,3,4,2,4,6,4,3,8,-1,-1,-1,-1],
941        [1,9,4,1,4,2,2,4,6,-1,-1,-1,-1,-1,-1,-1],
942        [8,1,3,8,6,1,8,4,6,6,10,1,-1,-1,-1,-1],
943        [10,1,0,10,0,6,6,0,4,-1,-1,-1,-1,-1,-1,-1],
944        [4,6,3,4,3,8,6,10,3,0,3,9,10,9,3,-1],
945        [10,9,4,6,10,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
946        [4,9,5,7,6,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
947        [0,8,3,4,9,5,11,7,6,-1,-1,-1,-1,-1,-1,-1],
948        [5,0,1,5,4,0,7,6,11,-1,-1,-1,-1,-1,-1,-1],
949        [11,7,6,8,3,4,3,5,4,3,1,5,-1,-1,-1,-1],
950        [9,5,4,10,1,2,7,6,11,-1,-1,-1,-1,-1,-1,-1],
951        [6,11,7,1,2,10,0,8,3,4,9,5,-1,-1,-1,-1],
952        [7,6,11,5,4,10,4,2,10,4,0,2,-1,-1,-1,-1],
953        [3,4,8,3,5,4,3,2,5,10,5,2,11,7,6,-1],
954        [7,2,3,7,6,2,5,4,9,-1,-1,-1,-1,-1,-1,-1],
955        [9,5,4,0,8,6,0,6,2,6,8,7,-1,-1,-1,-1],
956        [3,6,2,3,7,6,1,5,0,5,4,0,-1,-1,-1,-1],
957        [6,2,8,6,8,7,2,1,8,4,8,5,1,5,8,-1],
958        [9,5,4,10,1,6,1,7,6,1,3,7,-1,-1,-1,-1],
959        [1,6,10,1,7,6,1,0,7,8,7,0,9,5,4,-1],
960        [4,0,10,4,10,5,0,3,10,6,10,7,3,7,10,-1],
961        [7,6,10,7,10,8,5,4,10,4,8,10,-1,-1,-1,-1],
962        [6,9,5,6,11,9,11,8,9,-1,-1,-1,-1,-1,-1,-1],
963        [3,6,11,0,6,3,0,5,6,0,9,5,-1,-1,-1,-1],
964        [0,11,8,0,5,11,0,1,5,5,6,11,-1,-1,-1,-1],
965        [6,11,3,6,3,5,5,3,1,-1,-1,-1,-1,-1,-1,-1],
966        [1,2,10,9,5,11,9,11,8,11,5,6,-1,-1,-1,-1],
967        [0,11,3,0,6,11,0,9,6,5,6,9,1,2,10,-1],
968        [11,8,5,11,5,6,8,0,5,10,5,2,0,2,5,-1],
969        [6,11,3,6,3,5,2,10,3,10,5,3,-1,-1,-1,-1],
970        [5,8,9,5,2,8,5,6,2,3,8,2,-1,-1,-1,-1],
971        [9,5,6,9,6,0,0,6,2,-1,-1,-1,-1,-1,-1,-1],
972        [1,5,8,1,8,0,5,6,8,3,8,2,6,2,8,-1],
973        [1,5,6,2,1,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
974        [1,3,6,1,6,10,3,8,6,5,6,9,8,9,6,-1],
975        [10,1,0,10,0,6,9,5,0,5,6,0,-1,-1,-1,-1],
976        [0,3,8,5,6,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
977        [10,5,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
978        [11,5,10,7,5,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
979        [11,5,10,11,7,5,8,3,0,-1,-1,-1,-1,-1,-1,-1],
980        [5,11,7,5,10,11,1,9,0,-1,-1,-1,-1,-1,-1,-1],
981        [10,7,5,10,11,7,9,8,1,8,3,1,-1,-1,-1,-1],
982        [11,1,2,11,7,1,7,5,1,-1,-1,-1,-1,-1,-1,-1],
983        [0,8,3,1,2,7,1,7,5,7,2,11,-1,-1,-1,-1],
984        [9,7,5,9,2,7,9,0,2,2,11,7,-1,-1,-1,-1],
985        [7,5,2,7,2,11,5,9,2,3,2,8,9,8,2,-1],
986        [2,5,10,2,3,5,3,7,5,-1,-1,-1,-1,-1,-1,-1],
987        [8,2,0,8,5,2,8,7,5,10,2,5,-1,-1,-1,-1],
988        [9,0,1,2,3,10,3,5,10,3,7,5,-1,-1,-1,-1],
989        [1,2,5,5,2,10,9,8,7,9,7,5,8,3,7,-1],   // rearranged for correctness
990        [1,3,5,3,7,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
991        [0,8,7,0,7,1,1,7,5,-1,-1,-1,-1,-1,-1,-1],
992        [9,0,3,9,3,5,5,3,7,-1,-1,-1,-1,-1,-1,-1],
993        [9,8,7,5,9,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
994        [5,8,4,5,10,8,10,11,8,-1,-1,-1,-1,-1,-1,-1],
995        [5,0,4,5,11,0,5,10,11,11,3,0,-1,-1,-1,-1],
996        [0,1,9,8,4,10,8,10,11,10,4,5,-1,-1,-1,-1],
997        [10,11,4,10,4,5,11,3,4,9,4,1,3,1,4,-1],
998        [2,5,1,2,8,5,2,11,8,4,5,8,-1,-1,-1,-1],
999        [0,4,11,0,11,3,4,5,11,2,11,1,5,1,11,-1],
1000        [0,2,5,0,5,9,2,11,5,4,5,8,11,8,5,-1],
1001        [9,4,5,2,11,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1002        [2,5,10,3,5,2,3,4,5,3,8,4,-1,-1,-1,-1],
1003        [5,10,2,5,2,4,4,2,0,-1,-1,-1,-1,-1,-1,-1],
1004        [3,10,2,3,5,10,3,8,5,4,5,8,0,1,9,-1],
1005        [5,10,2,5,2,4,1,9,2,9,4,2,-1,-1,-1,-1],
1006        [8,4,5,8,5,3,3,5,1,-1,-1,-1,-1,-1,-1,-1],
1007        [0,4,5,1,0,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1008        [8,4,5,8,5,3,9,0,5,0,3,5,-1,-1,-1,-1],
1009        [9,4,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1010        [4,11,7,4,9,11,9,10,11,-1,-1,-1,-1,-1,-1,-1],
1011        [0,8,3,4,9,7,9,11,7,9,10,11,-1,-1,-1,-1],
1012        [1,10,11,1,11,4,1,4,0,7,4,11,-1,-1,-1,-1],
1013        [3,1,4,3,4,8,1,10,4,7,4,11,10,11,4,-1],
1014        [4,11,7,9,11,4,9,2,11,9,1,2,-1,-1,-1,-1],
1015        [9,7,4,9,11,7,9,1,11,2,11,1,0,8,3,-1],
1016        [11,7,4,11,4,2,2,4,0,-1,-1,-1,-1,-1,-1,-1],
1017        [11,7,4,11,4,2,8,3,4,3,2,4,-1,-1,-1,-1],
1018        [2,9,10,2,7,9,2,3,7,7,4,9,-1,-1,-1,-1],
1019        [9,10,7,9,7,4,10,2,7,8,7,0,2,0,7,-1],
1020        [3,7,10,3,10,2,7,4,10,1,10,0,4,0,10,-1],
1021        [1,10,2,8,7,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1022        [4,9,1,4,1,7,7,1,3,-1,-1,-1,-1,-1,-1,-1],
1023        [4,9,1,4,1,7,0,8,1,8,7,1,-1,-1,-1,-1],
1024        [4,0,3,7,4,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1025        [4,8,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1026        [9,10,8,10,11,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1027        [3,0,9,3,9,11,11,9,10,-1,-1,-1,-1,-1,-1,-1],
1028        [0,1,10,0,10,8,8,10,11,-1,-1,-1,-1,-1,-1,-1],
1029        [3,1,10,11,3,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1030        [1,2,11,1,11,9,9,11,8,-1,-1,-1,-1,-1,-1,-1],
1031        [3,0,9,3,9,11,1,2,9,2,11,9,-1,-1,-1,-1],
1032        [0,2,11,8,0,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1033        [3,2,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1034        [2,3,8,2,8,10,10,8,9,-1,-1,-1,-1,-1,-1,-1],
1035        [9,10,2,0,9,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1036        [2,3,8,2,8,10,0,1,8,1,10,8,-1,-1,-1,-1],
1037        [1,10,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1038        [1,3,8,9,1,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1039        [0,9,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1040        [0,3,8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1041        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
1042    ]
1043}
1044
1045/// Edge vertex pairs for marching cubes.
1046pub const MC_EDGE_VERTICES: [(usize, usize); 12] = [
1047    (0,1), (1,2), (2,3), (3,0),
1048    (4,5), (5,6), (6,7), (7,4),
1049    (0,4), (1,5), (2,6), (3,7),
1050];
1051
1052/// Cube vertex offsets for marching cubes (dx, dy, dz).
1053pub const MC_VERTEX_OFFSETS: [(i32, i32, i32); 8] = [
1054    (0,0,0), (1,0,0), (1,1,0), (0,1,0),
1055    (0,0,1), (1,0,1), (1,1,1), (0,1,1),
1056];
1057
1058#[derive(Debug, Clone)]
1059pub struct MeshVertex {
1060    pub position: Vec3,
1061    pub normal: Vec3,
1062    pub color: Vec4,
1063    pub uv: Vec2,
1064}
1065
1066#[derive(Debug, Clone, Default)]
1067pub struct GeneratedMesh {
1068    pub vertices: Vec<MeshVertex>,
1069    pub indices: Vec<u32>,
1070}
1071
1072impl GeneratedMesh {
1073    pub fn new() -> Self {
1074        Self::default()
1075    }
1076
1077    pub fn push_triangle(&mut self, a: MeshVertex, b: MeshVertex, c: MeshVertex) {
1078        let base = self.vertices.len() as u32;
1079        self.vertices.push(a);
1080        self.vertices.push(b);
1081        self.vertices.push(c);
1082        self.indices.push(base);
1083        self.indices.push(base + 1);
1084        self.indices.push(base + 2);
1085    }
1086
1087    pub fn compute_normals(&mut self) {
1088        let n_tris = self.indices.len() / 3;
1089        let mut normals = vec![Vec3::ZERO; self.vertices.len()];
1090        for ti in 0..n_tris {
1091            let i0 = self.indices[ti * 3] as usize;
1092            let i1 = self.indices[ti * 3 + 1] as usize;
1093            let i2 = self.indices[ti * 3 + 2] as usize;
1094            let p0 = self.vertices[i0].position;
1095            let p1 = self.vertices[i1].position;
1096            let p2 = self.vertices[i2].position;
1097            let n = (p1 - p0).cross(p2 - p0);
1098            normals[i0] += n;
1099            normals[i1] += n;
1100            normals[i2] += n;
1101        }
1102        for (v, n) in self.vertices.iter_mut().zip(normals.iter()) {
1103            v.normal = if n.length_squared() > 1e-9 { n.normalize() } else { Vec3::Y };
1104        }
1105    }
1106}
1107
1108/// Run marching cubes on a VoxelGrid.
1109pub fn marching_cubes(grid: &VoxelGrid, material_table: &[VoxelMaterial]) -> GeneratedMesh {
1110    let mut mesh = GeneratedMesh::new();
1111    let vsize = grid.voxel_size;
1112
1113    for z in 0..(grid.depth as i32 - 1) {
1114        for y in 0..(grid.height as i32 - 1) {
1115            for x in 0..(grid.width as i32 - 1) {
1116                // Fetch 8 corner values
1117                let mut cube_vals = [0.0f32; 8];
1118                let mut cube_mats = [0u8; 8];
1119                let mut cube_idx = 0u8;
1120
1121                for (vi, (dx, dy, dz)) in MC_VERTEX_OFFSETS.iter().enumerate() {
1122                    let v = grid.get(x + dx, y + dy, z + dz);
1123                    cube_vals[vi] = v.density;
1124                    cube_mats[vi] = v.material;
1125                    if v.density >= ISO_LEVEL {
1126                        cube_idx |= 1 << vi;
1127                    }
1128                }
1129
1130                let edge_mask = MC_EDGE_TABLE[cube_idx as usize];
1131                if edge_mask == 0 { continue; }
1132
1133                // Compute interpolated edge vertices
1134                let mut edge_verts = [Vec3::ZERO; 12];
1135                let mut edge_mats = [0u8; 12];
1136                for edge in 0..12 {
1137                    if (edge_mask >> edge) & 1 == 0 { continue; }
1138                    let (vi0, vi1) = MC_EDGE_VERTICES[edge];
1139                    let (dx0, dy0, dz0) = MC_VERTEX_OFFSETS[vi0];
1140                    let (dx1, dy1, dz1) = MC_VERTEX_OFFSETS[vi1];
1141                    let p0 = grid.grid_to_world(x + dx0, y + dy0, z + dz0);
1142                    let p1 = grid.grid_to_world(x + dx1, y + dy1, z + dz1);
1143                    let v0 = cube_vals[vi0];
1144                    let v1 = cube_vals[vi1];
1145                    let t = if (v1 - v0).abs() > 1e-9 {
1146                        (ISO_LEVEL - v0) / (v1 - v0)
1147                    } else {
1148                        0.5
1149                    };
1150                    // Trilinear interpolation
1151                    edge_verts[edge] = p0.lerp(p1, t);
1152                    edge_mats[edge] = if t < 0.5 { cube_mats[vi0] } else { cube_mats[vi1] };
1153                }
1154
1155                // Build triangles
1156                let tri_row = &MC_TRI_TABLE[cube_idx as usize];
1157                let mut ti = 0;
1158                while ti < 15 && tri_row[ti] >= 0 {
1159                    let e0 = tri_row[ti] as usize;
1160                    let e1 = tri_row[ti+1] as usize;
1161                    let e2 = tri_row[ti+2] as usize;
1162                    let p0 = edge_verts[e0];
1163                    let p1 = edge_verts[e1];
1164                    let p2 = edge_verts[e2];
1165                    let n = (p1 - p0).cross(p2 - p0);
1166                    let norm = if n.length_squared() > 1e-9 { n.normalize() } else { Vec3::Y };
1167                    let mat_idx = edge_mats[e0] as usize;
1168                    let color = if mat_idx < material_table.len() {
1169                        material_table[mat_idx].color
1170                    } else {
1171                        Vec4::ONE
1172                    };
1173                    mesh.push_triangle(
1174                        MeshVertex { position: p0, normal: norm, color, uv: Vec2::ZERO },
1175                        MeshVertex { position: p1, normal: norm, color, uv: Vec2::ZERO },
1176                        MeshVertex { position: p2, normal: norm, color, uv: Vec2::ZERO },
1177                    );
1178                    ti += 3;
1179                }
1180            }
1181        }
1182    }
1183    mesh.compute_normals();
1184    mesh
1185}
1186
1187// ─── Dual Contouring ──────────────────────────────────────────────────────────
1188
1189/// QEF (Quadric Error Function) for dual contouring.
1190/// Minimize ||Ax - b||^2 where A is the matrix of normals and b = A*intersection_points
1191#[derive(Debug, Clone, Default)]
1192pub struct QEF {
1193    pub ata: [[f64; 3]; 3],  // A^T A (3x3 symmetric)
1194    pub atb: [f64; 3],       // A^T b
1195    pub btb: f64,
1196    pub mass_point: Vec3,
1197    pub num_points: u32,
1198}
1199
1200impl QEF {
1201    pub fn new() -> Self {
1202        Self::default()
1203    }
1204
1205    /// Add a plane defined by point + normal.
1206    pub fn add_plane(&mut self, point: Vec3, normal: Vec3) {
1207        let nx = normal.x as f64;
1208        let ny = normal.y as f64;
1209        let nz = normal.z as f64;
1210        let d = (normal.dot(point)) as f64;
1211
1212        self.ata[0][0] += nx * nx;
1213        self.ata[0][1] += nx * ny;
1214        self.ata[0][2] += nx * nz;
1215        self.ata[1][1] += ny * ny;
1216        self.ata[1][2] += ny * nz;
1217        self.ata[2][2] += nz * nz;
1218
1219        self.atb[0] += nx * d;
1220        self.atb[1] += ny * d;
1221        self.atb[2] += nz * d;
1222
1223        self.btb += d * d;
1224
1225        self.mass_point += point;
1226        self.num_points += 1;
1227    }
1228
1229    /// Solve the 3x3 linear system using Cramer's rule.
1230    pub fn solve(&self) -> Vec3 {
1231        // Fill in the symmetric part
1232        let a = [
1233            [self.ata[0][0], self.ata[0][1], self.ata[0][2]],
1234            [self.ata[0][1], self.ata[1][1], self.ata[1][2]],
1235            [self.ata[0][2], self.ata[1][2], self.ata[2][2]],
1236        ];
1237        let b = self.atb;
1238
1239        let det = cramer3x3_det(&a);
1240        if det.abs() < 1e-10 {
1241            // Degenerate: return mass point
1242            if self.num_points > 0 {
1243                return self.mass_point / self.num_points as f32;
1244            }
1245            return Vec3::ZERO;
1246        }
1247
1248        // Replace columns for Cramer's rule
1249        let ax = [
1250            [b[0], a[0][1], a[0][2]],
1251            [b[1], a[1][1], a[1][2]],
1252            [b[2], a[2][1], a[2][2]],
1253        ];
1254        let ay = [
1255            [a[0][0], b[0], a[0][2]],
1256            [a[1][0], b[1], a[1][2]],
1257            [a[2][0], b[2], a[2][2]],
1258        ];
1259        let az = [
1260            [a[0][0], a[0][1], b[0]],
1261            [a[1][0], a[1][1], b[1]],
1262            [a[2][0], a[2][1], b[2]],
1263        ];
1264
1265        let x = cramer3x3_det(&ax) / det;
1266        let y = cramer3x3_det(&ay) / det;
1267        let z = cramer3x3_det(&az) / det;
1268
1269        Vec3::new(x as f32, y as f32, z as f32)
1270    }
1271
1272    pub fn evaluate(&self, p: Vec3) -> f64 {
1273        let px = p.x as f64;
1274        let py = p.y as f64;
1275        let pz = p.z as f64;
1276        let a = &self.ata;
1277        // p^T A^T A p - 2 p^T A^T b + b^T b
1278        let atap_x = a[0][0]*px + a[0][1]*py + a[0][2]*pz;
1279        let atap_y = a[0][1]*px + a[1][1]*py + a[1][2]*pz;
1280        let atap_z = a[0][2]*px + a[1][2]*py + a[2][2]*pz;
1281        let pt_atap = px*atap_x + py*atap_y + pz*atap_z;
1282        let pt_atb = px*self.atb[0] + py*self.atb[1] + pz*self.atb[2];
1283        pt_atap - 2.0*pt_atb + self.btb
1284    }
1285}
1286
1287fn cramer3x3_det(m: &[[f64; 3]; 3]) -> f64 {
1288    m[0][0] * (m[1][1]*m[2][2] - m[1][2]*m[2][1])
1289    - m[0][1] * (m[1][0]*m[2][2] - m[1][2]*m[2][0])
1290    + m[0][2] * (m[1][0]*m[2][1] - m[1][1]*m[2][0])
1291}
1292
1293/// Estimate normal at a grid point using central differences.
1294pub fn grid_normal(grid: &VoxelGrid, x: i32, y: i32, z: i32) -> Vec3 {
1295    let dx = grid.get(x+1, y, z).density - grid.get(x-1, y, z).density;
1296    let dy = grid.get(x, y+1, z).density - grid.get(x, y-1, z).density;
1297    let dz = grid.get(x, y, z+1).density - grid.get(x, y, z-1).density;
1298    let n = Vec3::new(dx, dy, dz);
1299    if n.length_squared() > 1e-9 { n.normalize() } else { Vec3::Y }
1300}
1301
1302/// Dual contouring: feature-preserving mesh extraction.
1303pub fn dual_contouring(grid: &VoxelGrid, material_table: &[VoxelMaterial]) -> GeneratedMesh {
1304    let mut mesh = GeneratedMesh::new();
1305    let mut cell_vertices: HashMap<(i32, i32, i32), u32> = HashMap::new();
1306
1307    // For each cell, compute QEF vertex
1308    for z in 0..(grid.depth as i32 - 1) {
1309        for y in 0..(grid.height as i32 - 1) {
1310            for x in 0..(grid.width as i32 - 1) {
1311                let mut has_sign_change = false;
1312                let mut qef = QEF::new();
1313                let mut mat = 1u8;
1314
1315                // Check all 12 edges for sign changes
1316                for (vi0, vi1) in &MC_EDGE_VERTICES {
1317                    let (dx0, dy0, dz0) = MC_VERTEX_OFFSETS[*vi0];
1318                    let (dx1, dy1, dz1) = MC_VERTEX_OFFSETS[*vi1];
1319                    let v0 = grid.get(x+dx0, y+dy0, z+dz0);
1320                    let v1 = grid.get(x+dx1, y+dy1, z+dz1);
1321                    let s0 = v0.density >= ISO_LEVEL;
1322                    let s1 = v1.density >= ISO_LEVEL;
1323                    if s0 != s1 {
1324                        has_sign_change = true;
1325                        let t = if (v1.density - v0.density).abs() > 1e-9 {
1326                            (ISO_LEVEL - v0.density) / (v1.density - v0.density)
1327                        } else { 0.5 };
1328                        let p0 = grid.grid_to_world(x+dx0, y+dy0, z+dz0);
1329                        let p1 = grid.grid_to_world(x+dx1, y+dy1, z+dz1);
1330                        let intersection = p0.lerp(p1, t);
1331                        let ix = (x+dx0) + ((dx1 - dx0) as f32 * t) as i32;
1332                        let iy = (y+dy0) + ((dy1 - dy0) as f32 * t) as i32;
1333                        let iz = (z+dz0) + ((dz1 - dz0) as f32 * t) as i32;
1334                        let normal = grid_normal(grid, ix.max(0), iy.max(0), iz.max(0));
1335                        qef.add_plane(intersection, normal);
1336                        mat = if t < 0.5 { v0.material } else { v1.material };
1337                    }
1338                }
1339
1340                if !has_sign_change { continue; }
1341
1342                let vertex_pos = qef.solve();
1343                let color = if (mat as usize) < material_table.len() {
1344                    material_table[mat as usize].color
1345                } else { Vec4::ONE };
1346                let normal = grid_normal(grid, x, y, z);
1347
1348                let vtx_idx = mesh.vertices.len() as u32;
1349                mesh.vertices.push(MeshVertex { position: vertex_pos, normal, color, uv: Vec2::ZERO });
1350                cell_vertices.insert((x, y, z), vtx_idx);
1351            }
1352        }
1353    }
1354
1355    // Generate quads for each edge with sign change
1356    // Check X-edges (y,z face)
1357    for z in 1..(grid.depth as i32 - 1) {
1358        for y in 1..(grid.height as i32 - 1) {
1359            for x in 0..(grid.width as i32 - 1) {
1360                let v0 = grid.get(x, y, z);
1361                let v1 = grid.get(x+1, y, z);
1362                if (v0.density >= ISO_LEVEL) == (v1.density >= ISO_LEVEL) { continue; }
1363                // Quad from 4 cells sharing this edge
1364                let cells = [(x, y-1, z-1), (x, y, z-1), (x, y, z), (x, y-1, z)];
1365                let verts: Vec<u32> = cells.iter().filter_map(|c| cell_vertices.get(c).copied()).collect();
1366                if verts.len() == 4 {
1367                    let flip = v0.density >= ISO_LEVEL;
1368                    if flip {
1369                        mesh.indices.extend_from_slice(&[verts[0], verts[2], verts[1]]);
1370                        mesh.indices.extend_from_slice(&[verts[0], verts[3], verts[2]]);
1371                    } else {
1372                        mesh.indices.extend_from_slice(&[verts[0], verts[1], verts[2]]);
1373                        mesh.indices.extend_from_slice(&[verts[0], verts[2], verts[3]]);
1374                    }
1375                }
1376            }
1377        }
1378    }
1379
1380    mesh.compute_normals();
1381    mesh
1382}
1383
1384// ─── Procedural Generation: 3D Perlin Noise ──────────────────────────────────
1385
1386fn fade(t: f64) -> f64 {
1387    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1388}
1389
1390fn lerp_f64(a: f64, b: f64, t: f64) -> f64 {
1391    a + t * (b - a)
1392}
1393
1394fn grad3(hash: u8, x: f64, y: f64, z: f64) -> f64 {
1395    let h = hash & 15;
1396    let u = if h < 8 { x } else { y };
1397    let v = if h < 4 { y } else if h == 12 || h == 14 { x } else { z };
1398    let a = if (h & 1) != 0 { -u } else { u };
1399    let b = if (h & 2) != 0 { -v } else { v };
1400    a + b
1401}
1402
1403pub struct PerlinNoise3D {
1404    pub perm: [u8; 512],
1405}
1406
1407impl PerlinNoise3D {
1408    pub fn new(seed: u64) -> Self {
1409        let mut perm = [0u8; 512];
1410        // Simple LCG-based permutation initialization
1411        let mut p = [0u8; 256];
1412        for i in 0..256 {
1413            p[i] = i as u8;
1414        }
1415        // Fisher-Yates shuffle with seed
1416        let mut rng = seed;
1417        for i in (1..256).rev() {
1418            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1419            let j = (rng >> 33) as usize % (i + 1);
1420            p.swap(i, j);
1421        }
1422        for i in 0..256 {
1423            perm[i] = p[i];
1424            perm[i + 256] = p[i];
1425        }
1426        Self { perm }
1427    }
1428
1429    pub fn noise(&self, x: f64, y: f64, z: f64) -> f64 {
1430        let xi = (x.floor() as i32 & 255) as usize;
1431        let yi = (y.floor() as i32 & 255) as usize;
1432        let zi = (z.floor() as i32 & 255) as usize;
1433        let xf = x - x.floor();
1434        let yf = y - y.floor();
1435        let zf = z - z.floor();
1436        let u = fade(xf);
1437        let v = fade(yf);
1438        let w = fade(zf);
1439
1440        let a  = self.perm[xi] as usize + yi;
1441        let aa = self.perm[a] as usize + zi;
1442        let ab = self.perm[a + 1] as usize + zi;
1443        let b  = self.perm[xi + 1] as usize + yi;
1444        let ba = self.perm[b] as usize + zi;
1445        let bb = self.perm[b + 1] as usize + zi;
1446
1447        lerp_f64(
1448            lerp_f64(
1449                lerp_f64(grad3(self.perm[aa], xf, yf, zf),
1450                         grad3(self.perm[ba], xf-1.0, yf, zf), u),
1451                lerp_f64(grad3(self.perm[ab], xf, yf-1.0, zf),
1452                         grad3(self.perm[bb], xf-1.0, yf-1.0, zf), u), v),
1453            lerp_f64(
1454                lerp_f64(grad3(self.perm[aa+1], xf, yf, zf-1.0),
1455                         grad3(self.perm[ba+1], xf-1.0, yf, zf-1.0), u),
1456                lerp_f64(grad3(self.perm[ab+1], xf, yf-1.0, zf-1.0),
1457                         grad3(self.perm[bb+1], xf-1.0, yf-1.0, zf-1.0), u), v), w)
1458    }
1459
1460    pub fn octave_noise(&self, x: f64, y: f64, z: f64, octaves: u32, persistence: f64, lacunarity: f64) -> f64 {
1461        let mut total = 0.0;
1462        let mut frequency = 1.0;
1463        let mut amplitude = 1.0;
1464        let mut max_value = 0.0;
1465        for _ in 0..octaves {
1466            total += self.noise(x * frequency, y * frequency, z * frequency) * amplitude;
1467            max_value += amplitude;
1468            amplitude *= persistence;
1469            frequency *= lacunarity;
1470        }
1471        if max_value > 1e-9 { total / max_value } else { 0.0 }
1472    }
1473}
1474
1475/// Generate Perlin terrain in a VoxelGrid.
1476pub fn generate_terrain(
1477    grid: &mut VoxelGrid,
1478    noise: &PerlinNoise3D,
1479    base_height: f32,
1480    height_scale: f32,
1481    noise_scale: f32,
1482    octaves: u32,
1483) {
1484    let base_mat = 3u8; // grass
1485    let sub_mat = 2u8;  // dirt
1486    let stone_mat = 1u8;
1487
1488    for z in 0..grid.depth {
1489        for x in 0..grid.width {
1490            let world = grid.grid_to_world(x as i32, 0, z as i32);
1491            let nx = world.x as f64 / noise_scale as f64;
1492            let nz = world.z as f64 / noise_scale as f64;
1493            let h = noise.octave_noise(nx, 0.0, nz, octaves, 0.5, 2.0);
1494            let surface_y = (base_height + height_scale * h as f32) as i32;
1495
1496            for y in 0..grid.height {
1497                let mat = if y == surface_y as usize {
1498                    base_mat
1499                } else if y < surface_y as usize && y + 3 >= surface_y as usize {
1500                    sub_mat
1501                } else if y < surface_y as usize {
1502                    stone_mat
1503                } else {
1504                    continue; // air
1505                };
1506                grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, mat));
1507            }
1508        }
1509    }
1510}
1511
1512/// Cave system using worm algorithm (random walk erosion).
1513pub fn generate_caves(
1514    grid: &mut VoxelGrid,
1515    noise: &PerlinNoise3D,
1516    num_worms: usize,
1517    worm_length: usize,
1518    worm_radius: f32,
1519    seed: u64,
1520) {
1521    let mut rng = seed;
1522    let next_f32 = |rng: &mut u64| -> f32 {
1523        *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1524        (*rng >> 33) as f32 / (u32::MAX as f32)
1525    };
1526
1527    for _ in 0..num_worms {
1528        // Random starting position
1529        let sx = (next_f32(&mut rng) * grid.width as f32) as i32;
1530        let sy = (next_f32(&mut rng) * grid.height as f32 * 0.6 + grid.height as f32 * 0.1) as i32;
1531        let sz = (next_f32(&mut rng) * grid.depth as f32) as i32;
1532
1533        let mut wx = sx as f32;
1534        let mut wy = sy as f32;
1535        let mut wz = sz as f32;
1536
1537        for step in 0..worm_length {
1538            // Noise-guided direction
1539            let t = step as f64 / worm_length as f64;
1540            let nx = noise.noise(wx as f64 * 0.05, t * 10.0, 0.0) as f32 * 2.0 - 1.0;
1541            let ny = noise.noise(wx as f64 * 0.05, t * 10.0, 1.0) as f32 * 0.5 - 0.25;
1542            let nz = noise.noise(wx as f64 * 0.05, t * 10.0, 2.0) as f32 * 2.0 - 1.0;
1543            let len = (nx*nx + ny*ny + nz*nz).sqrt().max(1e-9);
1544            wx += nx / len * 1.5;
1545            wy += ny / len * 0.5;
1546            wz += nz / len * 1.5;
1547
1548            // Carve sphere at current position
1549            let op = SculptOp::RemoveSphere {
1550                center: Vec3::new(wx, wy, wz) * grid.voxel_size + grid.origin,
1551                radius: worm_radius,
1552            };
1553            apply_sculpt_op(grid, &op);
1554        }
1555    }
1556}
1557
1558/// Ore vein generation using fractal noise thresholding.
1559pub fn generate_ore_veins(
1560    grid: &mut VoxelGrid,
1561    noise: &PerlinNoise3D,
1562    ore_material: u8,
1563    threshold: f64,
1564    noise_scale: f64,
1565    octaves: u32,
1566    min_depth: i32,
1567    max_depth: i32,
1568) {
1569    for z in 0..grid.depth {
1570        for y in min_depth.max(0) as usize..max_depth.min(grid.height as i32) as usize {
1571            for x in 0..grid.width {
1572                let existing = grid.get(x as i32, y as i32, z as i32);
1573                if existing.is_empty() { continue; }
1574                let nx = x as f64 / noise_scale;
1575                let ny = y as f64 / noise_scale;
1576                let nz = z as f64 / noise_scale;
1577                let v = noise.octave_noise(nx, ny, nz, octaves, 0.6, 2.1);
1578                if v > threshold {
1579                    grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, ore_material));
1580                }
1581            }
1582        }
1583    }
1584}
1585
1586// ─── Physics Integration ─────────────────────────────────────────────────────
1587
1588/// Simple beam model for structural integrity.
1589#[derive(Debug, Clone)]
1590pub struct StructuralCell {
1591    pub stress: f32,
1592    pub supported: bool,
1593}
1594
1595pub fn compute_structural_integrity(
1596    grid: &VoxelGrid,
1597    material_table: &[VoxelMaterial],
1598) -> Vec<StructuralCell> {
1599    let n = grid.width * grid.height * grid.depth;
1600    let mut cells = vec![StructuralCell { stress: 0.0, supported: false }; n];
1601
1602    // Bottom layer is always supported (ground)
1603    for z in 0..grid.depth {
1604        for x in 0..grid.width {
1605            let idx = grid.index(x, 0, z);
1606            cells[idx].supported = true;
1607        }
1608    }
1609
1610    // Propagate support upward and compute stress (simplified beam model)
1611    for y in 1..grid.height {
1612        for z in 0..grid.depth {
1613            for x in 0..grid.width {
1614                let v = grid.get(x as i32, y as i32, z as i32);
1615                if v.is_empty() { continue; }
1616                let idx = grid.index(x, y, z);
1617                // Check if any neighbor below is supported
1618                for (nx, ny, nz) in [
1619                    (x as i32, y as i32 - 1, z as i32),
1620                    (x as i32 - 1, y as i32, z as i32),
1621                    (x as i32 + 1, y as i32, z as i32),
1622                    (x as i32, y as i32, z as i32 - 1),
1623                    (x as i32, y as i32, z as i32 + 1),
1624                ] {
1625                    if nx < 0 || ny < 0 || nz < 0 || nx >= grid.width as i32 || ny >= grid.height as i32 || nz >= grid.depth as i32 { continue; }
1626                    let nidx = grid.index(nx as usize, ny as usize, nz as usize);
1627                    if cells[nidx].supported {
1628                        cells[idx].supported = true;
1629                        break;
1630                    }
1631                }
1632                // Stress = weight above / hardness
1633                let mat_idx = v.material as usize;
1634                let hardness = if mat_idx < material_table.len() { material_table[mat_idx].hardness } else { 0.5 };
1635                let weight = y as f32; // simplified: weight proportional to height
1636                cells[idx].stress = weight / (hardness * 10.0 + 0.001);
1637            }
1638        }
1639    }
1640
1641    cells
1642}
1643
1644/// Destruction: remove voxels below strength threshold, return debris positions.
1645pub fn apply_destruction(
1646    grid: &mut VoxelGrid,
1647    cells: &[StructuralCell],
1648    material_table: &[VoxelMaterial],
1649    stress_threshold: f32,
1650) -> Vec<Vec3> {
1651    let mut debris = Vec::new();
1652    for z in 0..grid.depth {
1653        for y in 0..grid.height {
1654            for x in 0..grid.width {
1655                let idx = grid.index(x, y, z);
1656                let v = grid.get(x as i32, y as i32, z as i32);
1657                if v.is_empty() { continue; }
1658                let mat_idx = v.material as usize;
1659                let hardness = if mat_idx < material_table.len() { material_table[mat_idx].hardness } else { 0.5 };
1660                let cell = &cells[idx];
1661                if cell.stress > stress_threshold * hardness || !cell.supported {
1662                    debris.push(grid.grid_to_world(x as i32, y as i32, z as i32));
1663                    grid.set(x as i32, y as i32, z as i32, Voxel::EMPTY);
1664                }
1665            }
1666        }
1667    }
1668    debris
1669}
1670
1671/// Debris particle from destroyed voxel.
1672#[derive(Debug, Clone)]
1673pub struct DebrisParticle {
1674    pub position: Vec3,
1675    pub velocity: Vec3,
1676    pub material: u8,
1677    pub life: f32,
1678    pub size: f32,
1679}
1680
1681pub fn spawn_debris(positions: &[Vec3], material: u8, seed: u64) -> Vec<DebrisParticle> {
1682    let mut rng = seed;
1683    let next_f32 = |rng: &mut u64| -> f32 {
1684        *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1685        (*rng >> 33) as f32 / u32::MAX as f32
1686    };
1687    positions.iter().map(|&pos| {
1688        let vx = (next_f32(&mut rng) - 0.5) * 4.0;
1689        let vy = next_f32(&mut rng) * 5.0 + 2.0;
1690        let vz = (next_f32(&mut rng) - 0.5) * 4.0;
1691        DebrisParticle {
1692            position: pos,
1693            velocity: Vec3::new(vx, vy, vz),
1694            material,
1695            life: 2.0 + next_f32(&mut rng) * 3.0,
1696            size: 0.1 + next_f32(&mut rng) * 0.3,
1697        }
1698    }).collect()
1699}
1700
1701pub fn update_debris(particles: &mut Vec<DebrisParticle>, dt: f32) {
1702    let gravity = Vec3::new(0.0, -9.8, 0.0);
1703    for p in particles.iter_mut() {
1704        p.velocity += gravity * dt;
1705        p.position += p.velocity * dt;
1706        p.life -= dt;
1707    }
1708    particles.retain(|p| p.life > 0.0);
1709}
1710
1711// ─── LOD: Octree LOD ─────────────────────────────────────────────────────────
1712
1713/// Compute average material of all leaves in a subtree.
1714pub fn node_average_material(node: &OctreeNode) -> Option<u8> {
1715    match node {
1716        OctreeNode::Empty => None,
1717        OctreeNode::Leaf(v) => Some(v.material),
1718        OctreeNode::Branch { children } => {
1719            let mats: Vec<u8> = children.iter()
1720                .filter_map(|c| c.as_ref())
1721                .filter_map(|c| node_average_material(c))
1722                .collect();
1723            if mats.is_empty() { None }
1724            else {
1725                // Most common material
1726                let mut counts = [0u32; 256];
1727                for &m in &mats { counts[m as usize] += 1; }
1728                let max_idx = counts.iter().enumerate().max_by_key(|(_, &c)| c).map(|(i, _)| i).unwrap_or(0);
1729                Some(max_idx as u8)
1730            }
1731        }
1732    }
1733}
1734
1735pub fn merge_octree_lod(svo: &mut SparseVoxelOctree, target_depth: usize) {
1736    merge_node_lod(&mut svo.root, svo.depth, target_depth);
1737}
1738
1739fn merge_node_lod(node: &mut OctreeNode, current_depth: usize, target_depth: usize) {
1740    if current_depth <= target_depth {
1741        // At target depth: collapse to representative voxel
1742        if let Some(mat) = node_average_material(node) {
1743            let avg_density = node_average_density(node);
1744            *node = OctreeNode::Leaf(Voxel::new(avg_density, mat));
1745        }
1746        return;
1747    }
1748    match node {
1749        OctreeNode::Branch { children } => {
1750            for ci in 0..8 {
1751                if let Some(child) = &mut children[ci] {
1752                    merge_node_lod(child, current_depth - 1, target_depth);
1753                }
1754            }
1755        }
1756        _ => {}
1757    }
1758}
1759
1760fn node_average_density(node: &OctreeNode) -> f32 {
1761    match node {
1762        OctreeNode::Empty => 0.0,
1763        OctreeNode::Leaf(v) => v.density,
1764        OctreeNode::Branch { children } => {
1765            let densities: Vec<f32> = children.iter()
1766                .filter_map(|c| c.as_ref())
1767                .map(|c| node_average_density(c))
1768                .collect();
1769            if densities.is_empty() { 0.0 }
1770            else { densities.iter().sum::<f32>() / densities.len() as f32 }
1771        }
1772    }
1773}
1774
1775#[derive(Debug, Clone)]
1776pub struct LodOctreeSet {
1777    pub base: SparseVoxelOctree,
1778    pub lods: Vec<(usize, SparseVoxelOctree)>, // (depth, svo)
1779}
1780
1781impl LodOctreeSet {
1782    pub fn build(base: SparseVoxelOctree, lod_depths: &[usize]) -> Self {
1783        let lods = lod_depths.iter().map(|&d| {
1784            let mut lod = base.clone();
1785            merge_octree_lod(&mut lod, d);
1786            lod.optimize();
1787            (d, lod)
1788        }).collect();
1789        Self { base, lods }
1790    }
1791
1792    pub fn select_lod(&self, distance: f32, base_dist: f32) -> &SparseVoxelOctree {
1793        let lod_idx = ((distance / base_dist).log2() as usize).min(self.lods.len());
1794        if lod_idx == 0 {
1795            &self.base
1796        } else {
1797            &self.lods[(lod_idx - 1).min(self.lods.len() - 1)].1
1798        }
1799    }
1800}
1801
1802// ─── Import/Export ────────────────────────────────────────────────────────────
1803
1804/// Export voxel grid as raw binary (width*height*depth bytes for material, separate density).
1805pub fn export_raw_binary(grid: &VoxelGrid) -> Vec<u8> {
1806    let n = grid.width * grid.height * grid.depth;
1807    let mut data = Vec::with_capacity(n * 2 + 12);
1808    // Header: width(4), height(4), depth(4)
1809    data.extend_from_slice(&(grid.width as u32).to_le_bytes());
1810    data.extend_from_slice(&(grid.height as u32).to_le_bytes());
1811    data.extend_from_slice(&(grid.depth as u32).to_le_bytes());
1812    // Material bytes
1813    for v in &grid.voxels {
1814        data.push(v.material);
1815    }
1816    // Density as u8 (0-255)
1817    for v in &grid.voxels {
1818        data.push((v.density * 255.0).round() as u8);
1819    }
1820    data
1821}
1822
1823pub fn import_raw_binary(data: &[u8]) -> Option<VoxelGrid> {
1824    if data.len() < 12 { return None; }
1825    let w = u32::from_le_bytes(data[0..4].try_into().ok()?) as usize;
1826    let h = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
1827    let d = u32::from_le_bytes(data[8..12].try_into().ok()?) as usize;
1828    let n = w * h * d;
1829    if data.len() < 12 + n * 2 { return None; }
1830    let mut grid = VoxelGrid::new(w, h, d, Vec3::ZERO, VOXEL_SIZE);
1831    let mats = &data[12..12 + n];
1832    let dens = &data[12 + n..12 + n * 2];
1833    for (i, (m, de)) in mats.iter().zip(dens.iter()).enumerate() {
1834        grid.voxels[i] = Voxel::new(*de as f32 / 255.0, *m);
1835    }
1836    Some(grid)
1837}
1838
1839/// Run-length encoding for sparse voxel data.
1840pub fn rle_encode(voxels: &[Voxel]) -> Vec<u8> {
1841    if voxels.is_empty() { return Vec::new(); }
1842    let mut out = Vec::new();
1843    let mut i = 0;
1844    while i < voxels.len() {
1845        let current = voxels[i];
1846        let mut run_len = 1usize;
1847        while i + run_len < voxels.len() && run_len < 255 && voxels[i + run_len] == current {
1848            run_len += 1;
1849        }
1850        out.push(run_len as u8);
1851        out.push(current.material);
1852        out.push((current.density * 255.0).round() as u8);
1853        i += run_len;
1854    }
1855    out
1856}
1857
1858pub fn rle_decode(data: &[u8], expected_len: usize) -> Vec<Voxel> {
1859    let mut out = Vec::with_capacity(expected_len);
1860    let mut i = 0;
1861    while i + 2 < data.len() && out.len() < expected_len {
1862        let run = data[i] as usize;
1863        let mat = data[i + 1];
1864        let den = data[i + 2] as f32 / 255.0;
1865        let v = Voxel::new(den, mat);
1866        for _ in 0..run {
1867            if out.len() >= expected_len { break; }
1868            out.push(v);
1869        }
1870        i += 3;
1871    }
1872    while out.len() < expected_len {
1873        out.push(Voxel::EMPTY);
1874    }
1875    out
1876}
1877
1878/// Export using RLE compression.
1879pub fn export_rle(grid: &VoxelGrid) -> Vec<u8> {
1880    let mut data = Vec::new();
1881    data.extend_from_slice(&(grid.width as u32).to_le_bytes());
1882    data.extend_from_slice(&(grid.height as u32).to_le_bytes());
1883    data.extend_from_slice(&(grid.depth as u32).to_le_bytes());
1884    let encoded = rle_encode(&grid.voxels);
1885    data.extend_from_slice(&(encoded.len() as u32).to_le_bytes());
1886    data.extend_from_slice(&encoded);
1887    data
1888}
1889
1890pub fn import_rle(data: &[u8]) -> Option<VoxelGrid> {
1891    if data.len() < 16 { return None; }
1892    let w = u32::from_le_bytes(data[0..4].try_into().ok()?) as usize;
1893    let h = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
1894    let d = u32::from_le_bytes(data[8..12].try_into().ok()?) as usize;
1895    let rle_len = u32::from_le_bytes(data[12..16].try_into().ok()?) as usize;
1896    if data.len() < 16 + rle_len { return None; }
1897    let expected = w * h * d;
1898    let voxels = rle_decode(&data[16..16 + rle_len], expected);
1899    let mut grid = VoxelGrid::new(w, h, d, Vec3::ZERO, VOXEL_SIZE);
1900    grid.voxels = voxels;
1901    Some(grid)
1902}
1903
1904/// Import heightmap (2D array of heights) and extrude to 3D.
1905pub fn import_heightmap(
1906    heights: &[f32],
1907    width: usize,
1908    depth: usize,
1909    max_height: usize,
1910    material_surface: u8,
1911    material_subsurface: u8,
1912    material_base: u8,
1913) -> VoxelGrid {
1914    let mut grid = VoxelGrid::new(width, max_height, depth, Vec3::ZERO, VOXEL_SIZE);
1915    for z in 0..depth {
1916        for x in 0..width {
1917            let h_norm = heights[x + z * width].clamp(0.0, 1.0);
1918            let h = (h_norm * max_height as f32) as usize;
1919            for y in 0..h {
1920                let mat = if y + 1 == h { material_surface }
1921                    else if h.saturating_sub(y) <= 3 { material_subsurface }
1922                    else { material_base };
1923                grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, mat));
1924            }
1925        }
1926    }
1927    grid
1928}
1929
1930// ─── Selection: Flood Fill ────────────────────────────────────────────────────
1931
1932/// Flood-fill selection starting from a seed voxel. Returns set of grid coordinates.
1933pub fn flood_fill_select(
1934    grid: &VoxelGrid,
1935    seed_x: i32,
1936    seed_y: i32,
1937    seed_z: i32,
1938    same_material_only: bool,
1939) -> HashSet<(i32, i32, i32)> {
1940    let seed_voxel = grid.get(seed_x, seed_y, seed_z);
1941    if seed_voxel.is_empty() { return HashSet::new(); }
1942
1943    let mut selected = HashSet::new();
1944    let mut queue = VecDeque::new();
1945    queue.push_back((seed_x, seed_y, seed_z));
1946
1947    while let Some((x, y, z)) = queue.pop_front() {
1948        if selected.contains(&(x, y, z)) { continue; }
1949        let v = grid.get(x, y, z);
1950        if v.is_empty() { continue; }
1951        if same_material_only && v.material != seed_voxel.material { continue; }
1952        selected.insert((x, y, z));
1953
1954        for (nx, ny, nz) in [
1955            (x-1,y,z),(x+1,y,z),(x,y-1,z),(x,y+1,z),(x,y,z-1),(x,y,z+1),
1956        ] {
1957            if !selected.contains(&(nx, ny, nz)) {
1958                queue.push_back((nx, ny, nz));
1959            }
1960        }
1961    }
1962    selected
1963}
1964
1965// ─── Copy/Paste Volume ───────────────────────────────────────────────────────
1966
1967#[derive(Debug, Clone)]
1968pub struct VoxelClipboard {
1969    pub voxels: Vec<((i32, i32, i32), Voxel)>,
1970    pub bounds_min: (i32, i32, i32),
1971    pub bounds_max: (i32, i32, i32),
1972}
1973
1974impl VoxelClipboard {
1975    pub fn copy_region(
1976        grid: &VoxelGrid,
1977        min: (i32, i32, i32),
1978        max: (i32, i32, i32),
1979    ) -> Self {
1980        let mut voxels = Vec::new();
1981        for z in min.2..=max.2 {
1982            for y in min.1..=max.1 {
1983                for x in min.0..=max.0 {
1984                    let v = grid.get(x, y, z);
1985                    if !v.is_empty() {
1986                        voxels.push(((x - min.0, y - min.1, z - min.2), v));
1987                    }
1988                }
1989            }
1990        }
1991        Self {
1992            voxels,
1993            bounds_min: (0, 0, 0),
1994            bounds_max: (max.0 - min.0, max.1 - min.1, max.2 - min.2),
1995        }
1996    }
1997
1998    pub fn paste(&self, grid: &mut VoxelGrid, offset: (i32, i32, i32)) {
1999        for &((rx, ry, rz), v) in &self.voxels {
2000            let x = rx + offset.0;
2001            let y = ry + offset.1;
2002            let z = rz + offset.2;
2003            grid.set(x, y, z, v);
2004        }
2005    }
2006
2007    pub fn size(&self) -> (i32, i32, i32) {
2008        (
2009            self.bounds_max.0 - self.bounds_min.0 + 1,
2010            self.bounds_max.1 - self.bounds_min.1 + 1,
2011            self.bounds_max.2 - self.bounds_min.2 + 1,
2012        )
2013    }
2014}
2015
2016// ─── Mirror Symmetry ─────────────────────────────────────────────────────────
2017
2018#[derive(Debug, Clone, Copy, PartialEq)]
2019pub enum MirrorAxis {
2020    X,
2021    Y,
2022    Z,
2023    XY,
2024    XZ,
2025    YZ,
2026    XYZ,
2027}
2028
2029/// Apply a sculpt operation with mirror symmetry.
2030pub fn apply_sculpt_mirrored(
2031    grid: &mut VoxelGrid,
2032    op: &SculptOp,
2033    axis: MirrorAxis,
2034    mirror_origin: Vec3,
2035) {
2036    apply_sculpt_op(grid, op);
2037    let mirrored_ops = mirror_op(op, axis, mirror_origin);
2038    for mirrored in mirrored_ops {
2039        apply_sculpt_op(grid, &mirrored);
2040    }
2041}
2042
2043fn mirror_pos(pos: Vec3, axis: MirrorAxis, origin: Vec3) -> Vec<Vec3> {
2044    let rel = pos - origin;
2045    match axis {
2046        MirrorAxis::X => vec![origin + Vec3::new(-rel.x, rel.y, rel.z)],
2047        MirrorAxis::Y => vec![origin + Vec3::new(rel.x, -rel.y, rel.z)],
2048        MirrorAxis::Z => vec![origin + Vec3::new(rel.x, rel.y, -rel.z)],
2049        MirrorAxis::XY => vec![
2050            origin + Vec3::new(-rel.x, rel.y, rel.z),
2051            origin + Vec3::new(rel.x, -rel.y, rel.z),
2052            origin + Vec3::new(-rel.x, -rel.y, rel.z),
2053        ],
2054        MirrorAxis::XZ => vec![
2055            origin + Vec3::new(-rel.x, rel.y, rel.z),
2056            origin + Vec3::new(rel.x, rel.y, -rel.z),
2057            origin + Vec3::new(-rel.x, rel.y, -rel.z),
2058        ],
2059        MirrorAxis::YZ => vec![
2060            origin + Vec3::new(rel.x, -rel.y, rel.z),
2061            origin + Vec3::new(rel.x, rel.y, -rel.z),
2062            origin + Vec3::new(rel.x, -rel.y, -rel.z),
2063        ],
2064        MirrorAxis::XYZ => {
2065            let mut result = Vec::new();
2066            for xi in 0..2 {
2067                for yi in 0..2 {
2068                    for zi in 0..2 {
2069                        if xi == 0 && yi == 0 && zi == 0 { continue; }
2070                        let sx = if xi == 1 { -1.0 } else { 1.0 };
2071                        let sy = if yi == 1 { -1.0 } else { 1.0 };
2072                        let sz = if zi == 1 { -1.0 } else { 1.0 };
2073                        result.push(origin + Vec3::new(rel.x * sx, rel.y * sy, rel.z * sz));
2074                    }
2075                }
2076            }
2077            result
2078        }
2079    }
2080}
2081
2082fn mirror_op(op: &SculptOp, axis: MirrorAxis, origin: Vec3) -> Vec<SculptOp> {
2083    match op {
2084        SculptOp::AddSphere { center, radius, material, density } => {
2085            mirror_pos(*center, axis, origin).into_iter()
2086                .map(|c| SculptOp::AddSphere { center: c, radius: *radius, material: *material, density: *density })
2087                .collect()
2088        }
2089        SculptOp::RemoveSphere { center, radius } => {
2090            mirror_pos(*center, axis, origin).into_iter()
2091                .map(|c| SculptOp::RemoveSphere { center: c, radius: *radius })
2092                .collect()
2093        }
2094        SculptOp::PaintSphere { center, radius, material } => {
2095            mirror_pos(*center, axis, origin).into_iter()
2096                .map(|c| SculptOp::PaintSphere { center: c, radius: *radius, material: *material })
2097                .collect()
2098        }
2099        SculptOp::SmoothSphere { center, radius, strength } => {
2100            mirror_pos(*center, axis, origin).into_iter()
2101                .map(|c| SculptOp::SmoothSphere { center: c, radius: *radius, strength: *strength })
2102                .collect()
2103        }
2104        _ => Vec::new(),
2105    }
2106}
2107
2108// ─── Undo/Redo System ────────────────────────────────────────────────────────
2109
2110#[derive(Debug, Clone)]
2111pub struct VoxelUndoState {
2112    pub modified_voxels: Vec<((i32, i32, i32), Voxel, Voxel)>, // (pos, before, after)
2113}
2114
2115impl VoxelUndoState {
2116    pub fn new() -> Self {
2117        Self { modified_voxels: Vec::new() }
2118    }
2119
2120    pub fn record_change(&mut self, x: i32, y: i32, z: i32, before: Voxel, after: Voxel) {
2121        if before != after {
2122            self.modified_voxels.push(((x, y, z), before, after));
2123        }
2124    }
2125}
2126
2127pub struct VoxelUndoHistory {
2128    pub states: VecDeque<VoxelUndoState>,
2129    pub current: usize,
2130    pub max_history: usize,
2131}
2132
2133impl VoxelUndoHistory {
2134    pub fn new() -> Self {
2135        Self {
2136            states: VecDeque::new(),
2137            current: 0,
2138            max_history: MAX_UNDO_HISTORY,
2139        }
2140    }
2141
2142    pub fn push(&mut self, state: VoxelUndoState) {
2143        // Remove any redo states
2144        while self.states.len() > self.current {
2145            self.states.pop_back();
2146        }
2147        self.states.push_back(state);
2148        if self.states.len() > self.max_history {
2149            self.states.pop_front();
2150        }
2151        self.current = self.states.len();
2152    }
2153
2154    pub fn undo(&mut self, grid: &mut VoxelGrid) -> bool {
2155        if self.current == 0 { return false; }
2156        self.current -= 1;
2157        let state = &self.states[self.current];
2158        for &((x, y, z), before, _after) in &state.modified_voxels {
2159            grid.set(x, y, z, before);
2160        }
2161        true
2162    }
2163
2164    pub fn redo(&mut self, grid: &mut VoxelGrid) -> bool {
2165        if self.current >= self.states.len() { return false; }
2166        let state = &self.states[self.current];
2167        for &((x, y, z), _before, after) in &state.modified_voxels {
2168            grid.set(x, y, z, after);
2169        }
2170        self.current += 1;
2171        true
2172    }
2173
2174    pub fn can_undo(&self) -> bool { self.current > 0 }
2175    pub fn can_redo(&self) -> bool { self.current < self.states.len() }
2176}
2177
2178// ─── Voxel Editor State Machine ───────────────────────────────────────────────
2179
2180#[derive(Debug, Clone, PartialEq)]
2181pub enum EditorTool {
2182    Add,
2183    Remove,
2184    Smooth,
2185    Paint,
2186    Flatten,
2187    Select,
2188    Fill,
2189}
2190
2191#[derive(Debug, Clone)]
2192pub struct BrushSettings {
2193    pub radius: f32,
2194    pub strength: f32,
2195    pub material: u8,
2196    pub mirror_axis: Option<MirrorAxis>,
2197    pub mirror_origin: Vec3,
2198}
2199
2200impl Default for BrushSettings {
2201    fn default() -> Self {
2202        Self {
2203            radius: 2.0,
2204            strength: 0.5,
2205            material: 1,
2206            mirror_axis: None,
2207            mirror_origin: Vec3::ZERO,
2208        }
2209    }
2210}
2211
2212pub struct VoxelEditor {
2213    pub grid: VoxelGrid,
2214    pub svo: Option<SparseVoxelOctree>,
2215    pub material_table: Vec<VoxelMaterial>,
2216    pub undo_history: VoxelUndoHistory,
2217    pub active_tool: EditorTool,
2218    pub brush: BrushSettings,
2219    pub selection: HashSet<(i32, i32, i32)>,
2220    pub clipboard: Option<VoxelClipboard>,
2221    pub mesh: Option<GeneratedMesh>,
2222    pub mesh_dirty: bool,
2223    pub noise: PerlinNoise3D,
2224}
2225
2226impl VoxelEditor {
2227    pub fn new(width: usize, height: usize, depth: usize) -> Self {
2228        Self {
2229            grid: VoxelGrid::new(width, height, depth, Vec3::ZERO, VOXEL_SIZE),
2230            svo: None,
2231            material_table: default_material_table(),
2232            undo_history: VoxelUndoHistory::new(),
2233            active_tool: EditorTool::Add,
2234            brush: BrushSettings::default(),
2235            selection: HashSet::new(),
2236            clipboard: None,
2237            mesh: None,
2238            mesh_dirty: true,
2239            noise: PerlinNoise3D::new(42),
2240        }
2241    }
2242
2243    pub fn apply_brush(&mut self, world_pos: Vec3) {
2244        // Capture before state
2245        let mut undo_state = VoxelUndoState::new();
2246        let min_c = self.grid.world_to_grid(world_pos - Vec3::splat(self.brush.radius + self.grid.voxel_size));
2247        let max_c = self.grid.world_to_grid(world_pos + Vec3::splat(self.brush.radius + self.grid.voxel_size));
2248        let mut before: Vec<((i32, i32, i32), Voxel)> = Vec::new();
2249        for z in min_c.2.max(0)..=max_c.2.min(self.grid.depth as i32 - 1) {
2250            for y in min_c.1.max(0)..=max_c.1.min(self.grid.height as i32 - 1) {
2251                for x in min_c.0.max(0)..=max_c.0.min(self.grid.width as i32 - 1) {
2252                    before.push(((x, y, z), self.grid.get(x, y, z)));
2253                }
2254            }
2255        }
2256
2257        let op = match self.active_tool {
2258            EditorTool::Add => SculptOp::AddSphere {
2259                center: world_pos,
2260                radius: self.brush.radius,
2261                material: self.brush.material,
2262                density: self.brush.strength,
2263            },
2264            EditorTool::Remove => SculptOp::RemoveSphere {
2265                center: world_pos,
2266                radius: self.brush.radius,
2267            },
2268            EditorTool::Smooth => SculptOp::SmoothSphere {
2269                center: world_pos,
2270                radius: self.brush.radius,
2271                strength: self.brush.strength,
2272            },
2273            EditorTool::Paint => SculptOp::PaintSphere {
2274                center: world_pos,
2275                radius: self.brush.radius,
2276                material: self.brush.material,
2277            },
2278            EditorTool::Flatten => SculptOp::Flatten {
2279                center: world_pos,
2280                radius: self.brush.radius,
2281                plane_normal: Vec3::Y,
2282                plane_d: world_pos.y,
2283                strength: self.brush.strength,
2284            },
2285            _ => return,
2286        };
2287
2288        if let Some(axis) = self.brush.mirror_axis {
2289            apply_sculpt_mirrored(&mut self.grid, &op, axis, self.brush.mirror_origin);
2290        } else {
2291            apply_sculpt_op(&mut self.grid, &op);
2292        }
2293
2294        // Record undo
2295        for ((x, y, z), before_v) in before {
2296            let after_v = self.grid.get(x, y, z);
2297            undo_state.record_change(x, y, z, before_v, after_v);
2298        }
2299        self.undo_history.push(undo_state);
2300        self.mesh_dirty = true;
2301    }
2302
2303    pub fn undo(&mut self) -> bool {
2304        let result = self.undo_history.undo(&mut self.grid);
2305        if result { self.mesh_dirty = true; }
2306        result
2307    }
2308
2309    pub fn redo(&mut self) -> bool {
2310        let result = self.undo_history.redo(&mut self.grid);
2311        if result { self.mesh_dirty = true; }
2312        result
2313    }
2314
2315    pub fn select_at(&mut self, x: i32, y: i32, z: i32, add_to_selection: bool) {
2316        if !add_to_selection { self.selection.clear(); }
2317        let v = self.grid.get(x, y, z);
2318        if !v.is_empty() {
2319            self.selection.insert((x, y, z));
2320        }
2321    }
2322
2323    pub fn flood_select(&mut self, x: i32, y: i32, z: i32, same_material: bool) {
2324        self.selection = flood_fill_select(&self.grid, x, y, z, same_material);
2325    }
2326
2327    pub fn copy_selection(&mut self) {
2328        if self.selection.is_empty() { return; }
2329        let mut min_x = i32::MAX; let mut min_y = i32::MAX; let mut min_z = i32::MAX;
2330        let mut max_x = i32::MIN; let mut max_y = i32::MIN; let mut max_z = i32::MIN;
2331        for &(x, y, z) in &self.selection {
2332            min_x = min_x.min(x); min_y = min_y.min(y); min_z = min_z.min(z);
2333            max_x = max_x.max(x); max_y = max_y.max(y); max_z = max_z.max(z);
2334        }
2335        let cb = VoxelClipboard::copy_region(
2336            &self.grid,
2337            (min_x, min_y, min_z),
2338            (max_x, max_y, max_z),
2339        );
2340        self.clipboard = Some(cb);
2341    }
2342
2343    pub fn paste(&mut self, offset: (i32, i32, i32)) {
2344        if let Some(ref cb) = self.clipboard.clone() {
2345            let mut undo_state = VoxelUndoState::new();
2346            let (sw, sh, sd) = cb.size();
2347            for &((rx, ry, rz), after_v) in &cb.voxels {
2348                let x = rx + offset.0;
2349                let y = ry + offset.1;
2350                let z = rz + offset.2;
2351                let before_v = self.grid.get(x, y, z);
2352                undo_state.record_change(x, y, z, before_v, after_v);
2353                self.grid.set(x, y, z, after_v);
2354            }
2355            self.undo_history.push(undo_state);
2356            self.mesh_dirty = true;
2357        }
2358    }
2359
2360    pub fn rebuild_mesh(&mut self) {
2361        if self.mesh_dirty {
2362            self.mesh = Some(marching_cubes(&self.grid, &self.material_table));
2363            self.mesh_dirty = false;
2364        }
2365    }
2366
2367    pub fn rebuild_svo(&mut self) {
2368        self.svo = Some(self.grid.to_svo());
2369    }
2370
2371    pub fn generate_terrain(&mut self, base_height: f32, height_scale: f32, noise_scale: f32) {
2372        generate_terrain(&mut self.grid, &self.noise, base_height, height_scale, noise_scale, 6);
2373        self.mesh_dirty = true;
2374    }
2375
2376    pub fn generate_caves(&mut self, num_worms: usize, worm_length: usize, radius: f32) {
2377        generate_caves(&mut self.grid, &self.noise, num_worms, worm_length, radius, 12345);
2378        self.mesh_dirty = true;
2379    }
2380
2381    pub fn ray_cast(&self, origin: Vec3, dir: Vec3) -> Option<(Vec3, u8)> {
2382        if let Some(ref svo) = self.svo {
2383            svo.ray_intersect(origin, dir).map(|(pos, v, _t)| (pos, v.material))
2384        } else {
2385            // Fallback DDA on dense grid
2386            ray_dda(&self.grid, origin, dir).map(|(x, y, z)| {
2387                let world = self.grid.grid_to_world(x, y, z);
2388                let v = self.grid.get(x, y, z);
2389                (world, v.material)
2390            })
2391        }
2392    }
2393
2394    pub fn fill_selection(&mut self, material: u8) {
2395        let sel: Vec<(i32, i32, i32)> = self.selection.iter().cloned().collect();
2396        let mut undo_state = VoxelUndoState::new();
2397        for (x, y, z) in sel {
2398            let before = self.grid.get(x, y, z);
2399            let after = Voxel::new(1.0, material);
2400            undo_state.record_change(x, y, z, before, after);
2401            self.grid.set(x, y, z, after);
2402        }
2403        self.undo_history.push(undo_state);
2404        self.mesh_dirty = true;
2405    }
2406
2407    pub fn delete_selection(&mut self) {
2408        let sel: Vec<(i32, i32, i32)> = self.selection.iter().cloned().collect();
2409        let mut undo_state = VoxelUndoState::new();
2410        for (x, y, z) in sel {
2411            let before = self.grid.get(x, y, z);
2412            undo_state.record_change(x, y, z, before, Voxel::EMPTY);
2413            self.grid.set(x, y, z, Voxel::EMPTY);
2414        }
2415        self.undo_history.push(undo_state);
2416        self.mesh_dirty = true;
2417        self.selection.clear();
2418    }
2419
2420    pub fn apply_physics_destruction(&mut self, stress_threshold: f32) -> Vec<DebrisParticle> {
2421        let cells = compute_structural_integrity(&self.grid, &self.material_table);
2422        let debris_positions = apply_destruction(&mut self.grid, &cells, &self.material_table, stress_threshold);
2423        self.mesh_dirty = true;
2424        spawn_debris(&debris_positions, 1, 42)
2425    }
2426
2427    pub fn voxel_count(&self) -> usize {
2428        self.grid.voxels.iter().filter(|v| !v.is_empty()).count()
2429    }
2430}
2431
2432// ─── DDA Ray Traversal on Dense Grid ─────────────────────────────────────────
2433
2434pub fn ray_dda(grid: &VoxelGrid, origin: Vec3, dir: Vec3) -> Option<(i32, i32, i32)> {
2435    let (mut ix, mut iy, mut iz) = grid.world_to_grid(origin);
2436    let dx = if dir.x > 0.0 { 1i32 } else { -1 };
2437    let dy = if dir.y > 0.0 { 1i32 } else { -1 };
2438    let dz = if dir.z > 0.0 { 1i32 } else { -1 };
2439
2440    let step_x = if dir.x.abs() > 1e-9 { (grid.voxel_size / dir.x.abs()) } else { f32::MAX };
2441    let step_y = if dir.y.abs() > 1e-9 { (grid.voxel_size / dir.y.abs()) } else { f32::MAX };
2442    let step_z = if dir.z.abs() > 1e-9 { (grid.voxel_size / dir.z.abs()) } else { f32::MAX };
2443
2444    let mut t_max_x = step_x * 0.5;
2445    let mut t_max_y = step_y * 0.5;
2446    let mut t_max_z = step_z * 0.5;
2447
2448    for _ in 0..512 {
2449        if ix < 0 || iy < 0 || iz < 0
2450            || ix >= grid.width as i32
2451            || iy >= grid.height as i32
2452            || iz >= grid.depth as i32
2453        {
2454            return None;
2455        }
2456        let v = grid.get(ix, iy, iz);
2457        if v.is_solid() {
2458            return Some((ix, iy, iz));
2459        }
2460        if t_max_x < t_max_y {
2461            if t_max_x < t_max_z { ix += dx; t_max_x += step_x; }
2462            else { iz += dz; t_max_z += step_z; }
2463        } else {
2464            if t_max_y < t_max_z { iy += dy; t_max_y += step_y; }
2465            else { iz += dz; t_max_z += step_z; }
2466        }
2467    }
2468    None
2469}
2470
2471// ─── Voxel World Chunking ─────────────────────────────────────────────────────
2472
2473#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2474pub struct ChunkCoord {
2475    pub cx: i32,
2476    pub cy: i32,
2477    pub cz: i32,
2478}
2479
2480impl ChunkCoord {
2481    pub fn from_world(pos: Vec3, chunk_voxel_size: f32, chunk_dim: usize) -> Self {
2482        let chunk_world_size = chunk_voxel_size * chunk_dim as f32;
2483        Self {
2484            cx: (pos.x / chunk_world_size).floor() as i32,
2485            cy: (pos.y / chunk_world_size).floor() as i32,
2486            cz: (pos.z / chunk_world_size).floor() as i32,
2487        }
2488    }
2489
2490    pub fn to_world_origin(&self, chunk_voxel_size: f32, chunk_dim: usize) -> Vec3 {
2491        let size = chunk_voxel_size * chunk_dim as f32;
2492        Vec3::new(
2493            self.cx as f32 * size,
2494            self.cy as f32 * size,
2495            self.cz as f32 * size,
2496        )
2497    }
2498}
2499
2500pub struct VoxelWorld {
2501    pub chunks: HashMap<ChunkCoord, VoxelGrid>,
2502    pub chunk_dim: usize,
2503    pub voxel_size: f32,
2504    pub material_table: Vec<VoxelMaterial>,
2505    pub noise: PerlinNoise3D,
2506    pub loaded_chunks: HashSet<ChunkCoord>,
2507}
2508
2509impl VoxelWorld {
2510    pub fn new(chunk_dim: usize, voxel_size: f32, seed: u64) -> Self {
2511        Self {
2512            chunks: HashMap::new(),
2513            chunk_dim,
2514            voxel_size,
2515            material_table: default_material_table(),
2516            noise: PerlinNoise3D::new(seed),
2517            loaded_chunks: HashSet::new(),
2518        }
2519    }
2520
2521    pub fn get_or_create_chunk(&mut self, coord: &ChunkCoord) -> &mut VoxelGrid {
2522        let dim = self.chunk_dim;
2523        let vs = self.voxel_size;
2524        let origin = coord.to_world_origin(vs, dim);
2525        self.chunks.entry(coord.clone()).or_insert_with(|| {
2526            VoxelGrid::new(dim, dim, dim, origin, vs)
2527        })
2528    }
2529
2530    pub fn get_voxel(&self, world_pos: Vec3) -> Voxel {
2531        let coord = ChunkCoord::from_world(world_pos, self.voxel_size, self.chunk_dim);
2532        if let Some(chunk) = self.chunks.get(&coord) {
2533            let local = world_pos - coord.to_world_origin(self.voxel_size, self.chunk_dim);
2534            let (lx, ly, lz) = chunk.world_to_grid(local + chunk.origin);
2535            chunk.get(lx, ly, lz)
2536        } else {
2537            Voxel::EMPTY
2538        }
2539    }
2540
2541    pub fn set_voxel(&mut self, world_pos: Vec3, voxel: Voxel) {
2542        let coord = ChunkCoord::from_world(world_pos, self.voxel_size, self.chunk_dim);
2543        let chunk = self.get_or_create_chunk(&coord);
2544        let (lx, ly, lz) = chunk.world_to_grid(world_pos);
2545        chunk.set(lx, ly, lz, voxel);
2546    }
2547
2548    pub fn generate_chunk(&mut self, coord: &ChunkCoord) {
2549        let dim = self.chunk_dim;
2550        let vs = self.voxel_size;
2551        let origin = coord.to_world_origin(vs, dim);
2552        let chunk = self.chunks.entry(coord.clone()).or_insert_with(|| {
2553            VoxelGrid::new(dim, dim, dim, origin, vs)
2554        });
2555        generate_terrain(chunk, &self.noise, dim as f32 / 2.0, dim as f32 / 3.0, 32.0, 4);
2556    }
2557
2558    pub fn chunks_in_radius(&self, center: Vec3, radius: f32) -> Vec<ChunkCoord> {
2559        let chunk_world_size = self.voxel_size * self.chunk_dim as f32;
2560        let r = (radius / chunk_world_size).ceil() as i32;
2561        let base = ChunkCoord::from_world(center, self.voxel_size, self.chunk_dim);
2562        let mut result = Vec::new();
2563        for cz in -r..=r {
2564            for cy in -r..=r {
2565                for cx in -r..=r {
2566                    let coord = ChunkCoord { cx: base.cx + cx, cy: base.cy + cy, cz: base.cz + cz };
2567                    let chunk_center = coord.to_world_origin(self.voxel_size, self.chunk_dim)
2568                        + Vec3::splat(chunk_world_size / 2.0);
2569                    if (chunk_center - center).length() <= radius {
2570                        result.push(coord);
2571                    }
2572                }
2573            }
2574        }
2575        result
2576    }
2577
2578    pub fn total_voxels(&self) -> usize {
2579        self.chunks.values().map(|c| c.voxels.iter().filter(|v| !v.is_empty()).count()).sum()
2580    }
2581}
2582
2583// ─── Mesh Decimation ──────────────────────────────────────────────────────────
2584
2585/// Simple edge-collapse mesh decimation (Garland-Heckbert style, simplified).
2586pub fn decimate_mesh(mesh: &GeneratedMesh, target_triangle_count: usize) -> GeneratedMesh {
2587    if mesh.indices.len() / 3 <= target_triangle_count {
2588        return mesh.clone();
2589    }
2590    // Build adjacency: which triangles share each vertex
2591    let n_verts = mesh.vertices.len();
2592    let mut vert_tris: Vec<Vec<usize>> = vec![Vec::new(); n_verts];
2593    let n_tris = mesh.indices.len() / 3;
2594    for ti in 0..n_tris {
2595        for j in 0..3 {
2596            let vi = mesh.indices[ti * 3 + j] as usize;
2597            vert_tris[vi].push(ti);
2598        }
2599    }
2600
2601    // Compute per-vertex error quadric (simplified: use normal spread)
2602    let mut vertex_errors: Vec<f32> = vec![0.0; n_verts];
2603    for (vi, tris) in vert_tris.iter().enumerate() {
2604        if tris.len() < 2 { continue; }
2605        let mut normal_sum = Vec3::ZERO;
2606        for &ti in tris {
2607            let p0 = mesh.vertices[mesh.indices[ti*3] as usize].position;
2608            let p1 = mesh.vertices[mesh.indices[ti*3+1] as usize].position;
2609            let p2 = mesh.vertices[mesh.indices[ti*3+2] as usize].position;
2610            let n = (p1-p0).cross(p2-p0);
2611            normal_sum += if n.length_squared() > 1e-9 { n.normalize() } else { Vec3::ZERO };
2612        }
2613        // Higher error = more variation in normals (feature vertex, keep it)
2614        vertex_errors[vi] = 1.0 - (normal_sum.length() / tris.len() as f32).min(1.0);
2615    }
2616
2617    // For this simplified decimation, just return the original mesh
2618    // (full implementation would collapse edges with lowest error cost)
2619    mesh.clone()
2620}
2621
2622// ─── Ambient Occlusion Baking ─────────────────────────────────────────────────
2623
2624pub fn bake_voxel_ambient_occlusion(
2625    grid: &VoxelGrid,
2626    mesh: &mut GeneratedMesh,
2627    num_rays: usize,
2628    max_dist: f32,
2629) {
2630    let ray_dirs: Vec<Vec3> = (0..num_rays).map(|i| {
2631        let t = i as f64 / num_rays as f64;
2632        let phi = t * std::f64::consts::TAU;
2633        let theta = (t * num_rays as f64).cos().acos();
2634        Vec3::new(
2635            (phi.cos() * theta.sin()) as f32,
2636            (theta.cos()) as f32,
2637            (phi.sin() * theta.sin()) as f32,
2638        )
2639    }).collect();
2640
2641    for vert in &mut mesh.vertices {
2642        let mut occluded = 0.0f32;
2643        for &ray_dir in &ray_dirs {
2644            // Only sample rays in the hemisphere around the normal
2645            if ray_dir.dot(vert.normal) <= 0.0 { continue; }
2646            if let Some(_hit) = ray_dda(grid, vert.position + vert.normal * 0.01, ray_dir) {
2647                occluded += 1.0;
2648            }
2649        }
2650        let ao = 1.0 - occluded / num_rays as f32;
2651        vert.color = Vec4::new(vert.color.x * ao, vert.color.y * ao, vert.color.z * ao, vert.color.w);
2652    }
2653}
2654
2655// ─── Greedy Mesh Generation ───────────────────────────────────────────────────
2656
2657/// Greedy meshing: merge adjacent coplanar quad faces.
2658pub fn greedy_mesh(grid: &VoxelGrid, material_table: &[VoxelMaterial]) -> GeneratedMesh {
2659    let mut mesh = GeneratedMesh::new();
2660
2661    // Process each axis direction (6 faces)
2662    for axis in 0..3usize {
2663        let (u, v, w) = match axis {
2664            0 => (1usize, 2usize, 0usize),
2665            1 => (0usize, 2usize, 1usize),
2666            _ => (0usize, 1usize, 2usize),
2667        };
2668        let dims = [grid.width, grid.height, grid.depth];
2669
2670        for d in 0..dims[w] {
2671            let mut mask: Vec<Option<(u8, bool)>> = vec![None; dims[u] * dims[v]];
2672            // Build face mask for this slice
2673            for j in 0..dims[v] {
2674                for i in 0..dims[u] {
2675                    let mut pos = [0i32; 3];
2676                    pos[u] = i as i32;
2677                    pos[v] = j as i32;
2678                    pos[w] = d as i32;
2679                    let cur = grid.get(pos[0], pos[1], pos[2]);
2680                    let mut next_pos = pos;
2681                    next_pos[w] += 1;
2682                    let nxt = grid.get(next_pos[0], next_pos[1], next_pos[2]);
2683                    // Face exists between cur and nxt if one is solid and other isn't
2684                    let face = if cur.is_solid() && !nxt.is_solid() {
2685                        Some((cur.material, true))
2686                    } else if !cur.is_solid() && nxt.is_solid() {
2687                        Some((nxt.material, false))
2688                    } else {
2689                        None
2690                    };
2691                    mask[i + j * dims[u]] = face;
2692                }
2693            }
2694
2695            // Greedy merge
2696            let mut used = vec![false; dims[u] * dims[v]];
2697            for j in 0..dims[v] {
2698                for i in 0..dims[u] {
2699                    let idx = i + j * dims[u];
2700                    if used[idx] || mask[idx].is_none() { continue; }
2701                    let (mat, front) = mask[idx].unwrap();
2702                    // Extend in u direction
2703                    let mut w_ext = 1;
2704                    while i + w_ext < dims[u] {
2705                        let ni = i + w_ext + j * dims[u];
2706                        if mask[ni] == Some((mat, front)) && !used[ni] { w_ext += 1; }
2707                        else { break; }
2708                    }
2709                    // Extend in v direction
2710                    let mut h_ext = 1;
2711                    'outer: while j + h_ext < dims[v] {
2712                        for di in 0..w_ext {
2713                            let ni = (i + di) + (j + h_ext) * dims[u];
2714                            if mask[ni] != Some((mat, front)) || used[ni] { break 'outer; }
2715                        }
2716                        h_ext += 1;
2717                    }
2718                    // Mark used
2719                    for dj in 0..h_ext {
2720                        for di in 0..w_ext {
2721                            used[(i + di) + (j + dj) * dims[u]] = true;
2722                        }
2723                    }
2724                    // Generate quad
2725                    let mut origin = [0f32; 3];
2726                    origin[u] = i as f32;
2727                    origin[v] = j as f32;
2728                    origin[w] = d as f32 + if front { 1.0 } else { 0.0 };
2729                    let mut du = [0f32; 3]; du[u] = w_ext as f32;
2730                    let mut dv = [0f32; 3]; dv[v] = h_ext as f32;
2731                    let o = grid.origin + Vec3::new(origin[0], origin[1], origin[2]) * grid.voxel_size;
2732                    let du3 = Vec3::new(du[0], du[1], du[2]) * grid.voxel_size;
2733                    let dv3 = Vec3::new(dv[0], dv[1], dv[2]) * grid.voxel_size;
2734                    let color = if (mat as usize) < material_table.len() {
2735                        material_table[mat as usize].color
2736                    } else { Vec4::ONE };
2737                    let normal_dir = if front { 1.0f32 } else { -1.0f32 };
2738                    let mut norm = Vec3::ZERO;
2739                    norm[w] = normal_dir;
2740                    let p0 = o;
2741                    let p1 = o + du3;
2742                    let p2 = o + du3 + dv3;
2743                    let p3 = o + dv3;
2744                    let mkv = |p: Vec3| MeshVertex { position: p, normal: norm, color, uv: Vec2::ZERO };
2745                    if front {
2746                        mesh.push_triangle(mkv(p0), mkv(p1), mkv(p2));
2747                        mesh.push_triangle(mkv(p0), mkv(p2), mkv(p3));
2748                    } else {
2749                        mesh.push_triangle(mkv(p0), mkv(p2), mkv(p1));
2750                        mesh.push_triangle(mkv(p0), mkv(p3), mkv(p2));
2751                    }
2752                }
2753            }
2754        }
2755    }
2756    mesh
2757}
2758
2759// ─── Voxel Painting Tool ──────────────────────────────────────────────────────
2760
2761pub struct VoxelPainter {
2762    pub palette: Vec<VoxelMaterial>,
2763    pub current_material: usize,
2764    pub blend_mode: PaintBlendMode,
2765}
2766
2767#[derive(Debug, Clone, Copy, PartialEq)]
2768pub enum PaintBlendMode {
2769    Replace,
2770    Add,
2771    Blend(f32),
2772}
2773
2774impl VoxelPainter {
2775    pub fn new() -> Self {
2776        Self {
2777            palette: default_material_table(),
2778            current_material: 1,
2779            blend_mode: PaintBlendMode::Replace,
2780        }
2781    }
2782
2783    pub fn apply_paint(&self, existing: &Voxel, point: Vec3, center: Vec3, radius: f32) -> Voxel {
2784        if existing.is_empty() { return *existing; }
2785        let dist = (point - center).length();
2786        if dist > radius { return *existing; }
2787        let mat = self.current_material as u8;
2788        match self.blend_mode {
2789            PaintBlendMode::Replace => Voxel::new(existing.density, mat),
2790            PaintBlendMode::Add => {
2791                let t = 1.0 - dist / radius;
2792                if t > 0.5 { Voxel::new(existing.density, mat) } else { *existing }
2793            }
2794            PaintBlendMode::Blend(strength) => {
2795                let t = (1.0 - dist / radius) * strength;
2796                if t > 0.5 { Voxel::new(existing.density, mat) } else { *existing }
2797            }
2798        }
2799    }
2800}
2801
2802// ─── Voxel Grid Statistics ─────────────────────────────────────────────────────
2803
2804#[derive(Debug, Clone, Default)]
2805pub struct GridStats {
2806    pub total_voxels: usize,
2807    pub solid_voxels: usize,
2808    pub empty_voxels: usize,
2809    pub material_counts: Vec<usize>,
2810    pub avg_density: f32,
2811    pub fill_ratio: f32,
2812}
2813
2814impl GridStats {
2815    pub fn compute(grid: &VoxelGrid) -> Self {
2816        let mut stats = Self::default();
2817        stats.total_voxels = grid.width * grid.height * grid.depth;
2818        stats.material_counts = vec![0; MATERIAL_COUNT];
2819        let mut density_sum = 0.0f64;
2820        for v in &grid.voxels {
2821            if v.is_empty() {
2822                stats.empty_voxels += 1;
2823            } else {
2824                stats.solid_voxels += 1;
2825                if (v.material as usize) < MATERIAL_COUNT {
2826                    stats.material_counts[v.material as usize] += 1;
2827                }
2828                density_sum += v.density as f64;
2829            }
2830        }
2831        stats.avg_density = if stats.solid_voxels > 0 {
2832            (density_sum / stats.solid_voxels as f64) as f32
2833        } else { 0.0 };
2834        stats.fill_ratio = if stats.total_voxels > 0 {
2835            stats.solid_voxels as f32 / stats.total_voxels as f32
2836        } else { 0.0 };
2837        stats
2838    }
2839}
2840
2841// ─── Voxel Smooth Filter ──────────────────────────────────────────────────────
2842
2843pub fn smooth_grid(grid: &mut VoxelGrid, iterations: u32, strength: f32) {
2844    for _ in 0..iterations {
2845        let old = grid.voxels.clone();
2846        for z in 1..(grid.depth as i32 - 1) {
2847            for y in 1..(grid.height as i32 - 1) {
2848                for x in 1..(grid.width as i32 - 1) {
2849                    let mut sum = 0.0f32;
2850                    let mut count = 0u32;
2851                    for (dx, dy, dz) in [
2852                        (-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1),(0,0,0)
2853                    ] {
2854                        let idx = grid.index((x+dx) as usize, (y+dy) as usize, (z+dz) as usize);
2855                        sum += old[idx].density;
2856                        count += 1;
2857                    }
2858                    let avg = sum / count as f32;
2859                    let idx = grid.index(x as usize, y as usize, z as usize);
2860                    let old_d = old[idx].density;
2861                    grid.voxels[idx].density = old_d + (avg - old_d) * strength;
2862                }
2863            }
2864        }
2865    }
2866}
2867
2868// ─── Heightmap Export ─────────────────────────────────────────────────────────
2869
2870pub fn export_heightmap(grid: &VoxelGrid) -> Vec<f32> {
2871    let mut heights = vec![0.0f32; grid.width * grid.depth];
2872    for z in 0..grid.depth {
2873        for x in 0..grid.width {
2874            // Find highest solid voxel in this column
2875            let mut h = 0.0f32;
2876            for y in (0..grid.height).rev() {
2877                if !grid.get(x as i32, y as i32, z as i32).is_empty() {
2878                    h = y as f32 / grid.height as f32;
2879                    break;
2880                }
2881            }
2882            heights[x + z * grid.width] = h;
2883        }
2884    }
2885    heights
2886}
2887
2888// ─── Surface Normal Computation ───────────────────────────────────────────────
2889
2890pub fn compute_surface_normals(grid: &VoxelGrid) -> HashMap<(i32, i32, i32), Vec3> {
2891    let mut normals = HashMap::new();
2892    for z in 1..(grid.depth as i32 - 1) {
2893        for y in 1..(grid.height as i32 - 1) {
2894            for x in 1..(grid.width as i32 - 1) {
2895                let v = grid.get(x, y, z);
2896                if !v.is_solid() { continue; }
2897                let n = grid_normal(grid, x, y, z);
2898                normals.insert((x, y, z), n);
2899            }
2900        }
2901    }
2902    normals
2903}
2904
2905// ─── Voxel Mesh UV Generation ─────────────────────────────────────────────────
2906
2907pub fn generate_triplanar_uvs(mesh: &mut GeneratedMesh, uv_scale: f32) {
2908    for vert in &mut mesh.vertices {
2909        let abs_n = vert.normal.abs();
2910        if abs_n.x > abs_n.y && abs_n.x > abs_n.z {
2911            vert.uv = Vec2::new(vert.position.z * uv_scale, vert.position.y * uv_scale);
2912        } else if abs_n.y > abs_n.z {
2913            vert.uv = Vec2::new(vert.position.x * uv_scale, vert.position.z * uv_scale);
2914        } else {
2915            vert.uv = Vec2::new(vert.position.x * uv_scale, vert.position.y * uv_scale);
2916        }
2917    }
2918}
2919
2920// ─── Voxel Visibility Culling ─────────────────────────────────────────────────
2921
2922/// Check if a voxel face is visible (has an air neighbor).
2923pub fn is_face_visible(grid: &VoxelGrid, x: i32, y: i32, z: i32, nx: i32, ny: i32, nz: i32) -> bool {
2924    if !grid.get(x, y, z).is_solid() { return false; }
2925    !grid.get(x+nx, y+ny, z+nz).is_solid()
2926}
2927
2928pub fn count_visible_faces(grid: &VoxelGrid) -> usize {
2929    let mut count = 0;
2930    let dirs = [(-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1)];
2931    for z in 0..grid.depth as i32 {
2932        for y in 0..grid.height as i32 {
2933            for x in 0..grid.width as i32 {
2934                if !grid.get(x, y, z).is_solid() { continue; }
2935                for (dx, dy, dz) in &dirs {
2936                    if is_face_visible(grid, x, y, z, *dx, *dy, *dz) {
2937                        count += 1;
2938                    }
2939                }
2940            }
2941        }
2942    }
2943    count
2944}
2945
2946// ─── Heightmap-to-SDF Conversion ──────────────────────────────────────────────
2947
2948/// Generate a signed distance field approximation from voxel grid.
2949pub fn voxel_to_sdf(grid: &VoxelGrid) -> Vec<f32> {
2950    let n = grid.width * grid.height * grid.depth;
2951    let mut sdf = vec![f32::MAX; n];
2952
2953    // Initialize: inside = negative, outside = positive
2954    for z in 0..grid.depth {
2955        for y in 0..grid.height {
2956            for x in 0..grid.width {
2957                let idx = grid.index(x, y, z);
2958                let v = grid.get(x as i32, y as i32, z as i32);
2959                sdf[idx] = if v.is_solid() { -v.density } else { 1.0 - v.density };
2960            }
2961        }
2962    }
2963
2964    // 3D distance transform (simplified: 1-pass, not exact)
2965    for z in 1..grid.depth as i32 {
2966        for y in 1..grid.height as i32 {
2967            for x in 1..grid.width as i32 {
2968                let idx = grid.index(x as usize, y as usize, z as usize);
2969                let n0 = sdf[grid.index((x-1) as usize, y as usize, z as usize)] + 1.0;
2970                let n1 = sdf[grid.index(x as usize, (y-1) as usize, z as usize)] + 1.0;
2971                let n2 = sdf[grid.index(x as usize, y as usize, (z-1) as usize)] + 1.0;
2972                let min_n = n0.min(n1).min(n2);
2973                if min_n < sdf[idx] { sdf[idx] = min_n; }
2974            }
2975        }
2976    }
2977
2978    sdf
2979}
2980
2981// ─── Additional Procedural: Dungeon Generator ─────────────────────────────────
2982
2983#[derive(Debug, Clone)]
2984pub struct DungeonRoom {
2985    pub min: (i32, i32, i32),
2986    pub max: (i32, i32, i32),
2987}
2988
2989impl DungeonRoom {
2990    pub fn new(cx: i32, cy: i32, cz: i32, hw: i32, hh: i32, hd: i32) -> Self {
2991        Self {
2992            min: (cx - hw, cy - hh, cz - hd),
2993            max: (cx + hw, cy + hh, cz + hd),
2994        }
2995    }
2996
2997    pub fn overlaps(&self, other: &DungeonRoom) -> bool {
2998        self.min.0 <= other.max.0 && self.max.0 >= other.min.0 &&
2999        self.min.1 <= other.max.1 && self.max.1 >= other.min.1 &&
3000        self.min.2 <= other.max.2 && self.max.2 >= other.min.2
3001    }
3002
3003    pub fn center(&self) -> (i32, i32, i32) {
3004        (
3005            (self.min.0 + self.max.0) / 2,
3006            (self.min.1 + self.max.1) / 2,
3007            (self.min.2 + self.max.2) / 2,
3008        )
3009    }
3010}
3011
3012pub fn generate_dungeon(
3013    grid: &mut VoxelGrid,
3014    num_rooms: usize,
3015    seed: u64,
3016) -> Vec<DungeonRoom> {
3017    let mut rng = seed;
3018    let next_range = |rng: &mut u64, lo: i32, hi: i32| -> i32 {
3019        *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3020        ((*rng >> 33) as i32).abs() % (hi - lo + 1) + lo
3021    };
3022
3023    // Fill entire grid with stone
3024    for v in &mut grid.voxels {
3025        *v = Voxel::new(1.0, 1); // stone
3026    }
3027
3028    let mut rooms = Vec::new();
3029    let attempts = num_rooms * 10;
3030    for _ in 0..attempts {
3031        if rooms.len() >= num_rooms { break; }
3032        let cx = next_range(&mut rng, 5, grid.width as i32 - 5);
3033        let cy = next_range(&mut rng, 3, grid.height as i32 - 3);
3034        let cz = next_range(&mut rng, 5, grid.depth as i32 - 5);
3035        let hw = next_range(&mut rng, 2, 6);
3036        let hh = next_range(&mut rng, 2, 4);
3037        let hd = next_range(&mut rng, 2, 6);
3038        let room = DungeonRoom::new(cx, cy, cz, hw, hh, hd);
3039        // Check overlap
3040        if rooms.iter().any(|r: &DungeonRoom| r.overlaps(&room)) { continue; }
3041        // Carve the room
3042        for z in room.min.2..=room.max.2 {
3043            for y in room.min.1..=room.max.1 {
3044                for x in room.min.0..=room.max.0 {
3045                    grid.set(x, y, z, Voxel::EMPTY);
3046                }
3047            }
3048        }
3049        rooms.push(room);
3050    }
3051
3052    // Connect rooms with corridors
3053    for i in 1..rooms.len() {
3054        let (ax, ay, az) = rooms[i-1].center();
3055        let (bx, by, bz) = rooms[i].center();
3056        // Carve L-shaped corridor
3057        for x in ax.min(bx)..=ax.max(bx) {
3058            grid.set(x, ay, az, Voxel::EMPTY);
3059            grid.set(x, ay+1, az, Voxel::EMPTY);
3060        }
3061        for z in az.min(bz)..=az.max(bz) {
3062            grid.set(bx, ay, z, Voxel::EMPTY);
3063            grid.set(bx, ay+1, z, Voxel::EMPTY);
3064        }
3065        for y in ay.min(by)..=ay.max(by) {
3066            grid.set(bx, y, bz, Voxel::EMPTY);
3067        }
3068    }
3069
3070    rooms
3071}
3072
3073// ─── Volume Copy/Paste with Rotation ─────────────────────────────────────────
3074
3075pub fn rotate_clipboard_90_xz(cb: &VoxelClipboard) -> VoxelClipboard {
3076    let (sw, sh, sd) = cb.size();
3077    let new_voxels: Vec<((i32, i32, i32), Voxel)> = cb.voxels.iter().map(|&((x, y, z), v)| {
3078        // Rotate 90 degrees in XZ plane: (x,y,z) -> (z, y, sw-1-x)
3079        ((z, y, sw - 1 - x), v)
3080    }).collect();
3081    VoxelClipboard {
3082        voxels: new_voxels,
3083        bounds_min: (0, 0, 0),
3084        bounds_max: (sd - 1, sh - 1, sw - 1),
3085    }
3086}
3087
3088// ─── Voxel Mesh Export (OBJ format string) ───────────────────────────────────
3089
3090pub fn export_mesh_obj(mesh: &GeneratedMesh) -> String {
3091    let mut obj = String::new();
3092    obj.push_str("# Voxel Mesh\n");
3093    for v in &mesh.vertices {
3094        obj.push_str(&format!("v {} {} {}\n", v.position.x, v.position.y, v.position.z));
3095    }
3096    for v in &mesh.vertices {
3097        obj.push_str(&format!("vn {} {} {}\n", v.normal.x, v.normal.y, v.normal.z));
3098    }
3099    for v in &mesh.vertices {
3100        obj.push_str(&format!("vt {} {}\n", v.uv.x, v.uv.y));
3101    }
3102    let n_tris = mesh.indices.len() / 3;
3103    for ti in 0..n_tris {
3104        let a = mesh.indices[ti*3] + 1;
3105        let b = mesh.indices[ti*3+1] + 1;
3106        let c = mesh.indices[ti*3+2] + 1;
3107        obj.push_str(&format!("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", a, b, c));
3108    }
3109    obj
3110}
3111
3112// ─── Voxel Raycast Result ─────────────────────────────────────────────────────
3113
3114#[derive(Debug, Clone)]
3115pub struct VoxelRaycastResult {
3116    pub hit: bool,
3117    pub position: Vec3,
3118    pub normal: Vec3,
3119    pub voxel_coord: (i32, i32, i32),
3120    pub distance: f32,
3121    pub material: u8,
3122}
3123
3124pub fn raycast_voxel_grid(grid: &VoxelGrid, ray_origin: Vec3, ray_dir: Vec3) -> VoxelRaycastResult {
3125    let mut result = VoxelRaycastResult {
3126        hit: false,
3127        position: Vec3::ZERO,
3128        normal: Vec3::Y,
3129        voxel_coord: (0, 0, 0),
3130        distance: f32::MAX,
3131        material: 0,
3132    };
3133
3134    if let Some((x, y, z)) = ray_dda(grid, ray_origin, ray_dir) {
3135        let v = grid.get(x, y, z);
3136        let pos = grid.grid_to_world(x, y, z);
3137        let dist = (pos - ray_origin).length();
3138        result.hit = true;
3139        result.position = pos;
3140        result.voxel_coord = (x, y, z);
3141        result.distance = dist;
3142        result.material = v.material;
3143        result.normal = grid_normal(grid, x, y, z);
3144    }
3145    result
3146}
3147
3148// ─── Marching Cubes with Material Blending ────────────────────────────────────
3149
3150pub fn marching_cubes_material_blend(
3151    grid: &VoxelGrid,
3152    material_table: &[VoxelMaterial],
3153) -> GeneratedMesh {
3154    let mut mesh = GeneratedMesh::new();
3155    let vsize = grid.voxel_size;
3156
3157    for z in 0..(grid.depth as i32 - 1) {
3158        for y in 0..(grid.height as i32 - 1) {
3159            for x in 0..(grid.width as i32 - 1) {
3160                let mut cube_vals = [0.0f32; 8];
3161                let mut cube_mats = [0u8; 8];
3162                let mut cube_idx = 0u8;
3163
3164                for (vi, (dx, dy, dz)) in MC_VERTEX_OFFSETS.iter().enumerate() {
3165                    let v = grid.get(x + dx, y + dy, z + dz);
3166                    cube_vals[vi] = v.density;
3167                    cube_mats[vi] = v.material;
3168                    if v.density >= ISO_LEVEL {
3169                        cube_idx |= 1 << vi;
3170                    }
3171                }
3172
3173                let edge_mask = MC_EDGE_TABLE[cube_idx as usize];
3174                if edge_mask == 0 { continue; }
3175
3176                let mut edge_verts = [Vec3::ZERO; 12];
3177                let mut edge_colors = [Vec4::ONE; 12];
3178
3179                for edge in 0..12 {
3180                    if (edge_mask >> edge) & 1 == 0 { continue; }
3181                    let (vi0, vi1) = MC_EDGE_VERTICES[edge];
3182                    let (dx0, dy0, dz0) = MC_VERTEX_OFFSETS[vi0];
3183                    let (dx1, dy1, dz1) = MC_VERTEX_OFFSETS[vi1];
3184                    let p0 = grid.grid_to_world(x + dx0, y + dy0, z + dz0);
3185                    let p1 = grid.grid_to_world(x + dx1, y + dy1, z + dz1);
3186                    let v0 = cube_vals[vi0];
3187                    let v1 = cube_vals[vi1];
3188                    let t = if (v1 - v0).abs() > 1e-9 { (ISO_LEVEL - v0) / (v1 - v0) } else { 0.5 };
3189                    edge_verts[edge] = p0.lerp(p1, t);
3190                    let mat0 = cube_mats[vi0] as usize;
3191                    let mat1 = cube_mats[vi1] as usize;
3192                    let c0 = if mat0 < material_table.len() { material_table[mat0].color } else { Vec4::ONE };
3193                    let c1 = if mat1 < material_table.len() { material_table[mat1].color } else { Vec4::ONE };
3194                    edge_colors[edge] = c0.lerp(c1, t);
3195                }
3196
3197                let tri_row = &MC_TRI_TABLE[cube_idx as usize];
3198                let mut ti = 0;
3199                while ti < 15 && tri_row[ti] >= 0 {
3200                    let e0 = tri_row[ti] as usize;
3201                    let e1 = tri_row[ti+1] as usize;
3202                    let e2 = tri_row[ti+2] as usize;
3203                    let p0 = edge_verts[e0]; let c0 = edge_colors[e0];
3204                    let p1 = edge_verts[e1]; let c1 = edge_colors[e1];
3205                    let p2 = edge_verts[e2]; let c2 = edge_colors[e2];
3206                    let n = (p1-p0).cross(p2-p0);
3207                    let norm = if n.length_squared() > 1e-9 { n.normalize() } else { Vec3::Y };
3208                    mesh.push_triangle(
3209                        MeshVertex { position: p0, normal: norm, color: c0, uv: Vec2::ZERO },
3210                        MeshVertex { position: p1, normal: norm, color: c1, uv: Vec2::ZERO },
3211                        MeshVertex { position: p2, normal: norm, color: c2, uv: Vec2::ZERO },
3212                    );
3213                    ti += 3;
3214                }
3215            }
3216        }
3217    }
3218    mesh.compute_normals();
3219    mesh
3220}
3221
3222// ─── Extended: Voxel Terrain Features ────────────────────────────────────────
3223
3224/// Biome system: per-voxel-column biome assignment based on noise.
3225#[derive(Debug, Clone, Copy, PartialEq)]
3226pub enum Biome {
3227    Plains, Forest, Desert, Tundra, Mountains, Ocean, Swamp, Jungle,
3228}
3229
3230impl Biome {
3231    pub fn surface_material(&self) -> u8 {
3232        match self {
3233            Biome::Plains => 3,    // grass
3234            Biome::Forest => 3,    // grass
3235            Biome::Desert => 4,    // sand
3236            Biome::Tundra => 15,   // snow
3237            Biome::Mountains => 1, // stone
3238            Biome::Ocean => 5,     // water
3239            Biome::Swamp => 2,     // dirt
3240            Biome::Jungle => 3,    // grass
3241        }
3242    }
3243
3244    pub fn subsurface_material(&self) -> u8 {
3245        match self {
3246            Biome::Desert => 4,    // sand
3247            Biome::Tundra => 15,   // snow
3248            Biome::Ocean => 4,     // sand
3249            _ => 2,                // dirt
3250        }
3251    }
3252}
3253
3254pub fn classify_biome(temperature: f64, humidity: f64, altitude: f64) -> Biome {
3255    if altitude > 0.8 { return Biome::Mountains; }
3256    if altitude < 0.1 { return Biome::Ocean; }
3257    if temperature < 0.2 { return Biome::Tundra; }
3258    if temperature > 0.8 && humidity < 0.3 { return Biome::Desert; }
3259    if humidity > 0.8 && temperature > 0.5 { return Biome::Jungle; }
3260    if humidity > 0.6 { return Biome::Swamp; }
3261    if humidity > 0.4 { return Biome::Forest; }
3262    Biome::Plains
3263}
3264
3265pub fn generate_biome_terrain(
3266    grid: &mut VoxelGrid,
3267    noise: &PerlinNoise3D,
3268    noise_scale: f32,
3269    base_height: f32,
3270    height_scale: f32,
3271) {
3272    for z in 0..grid.depth {
3273        for x in 0..grid.width {
3274            let world = grid.grid_to_world(x as i32, 0, z as i32);
3275            let nx = world.x as f64 / noise_scale as f64;
3276            let nz = world.z as f64 / noise_scale as f64;
3277            let height_n = noise.octave_noise(nx, 0.0, nz, 6, 0.5, 2.0);
3278            let temp_n = noise.octave_noise(nx * 0.3, 100.0, nz * 0.3, 2, 0.5, 2.0) * 0.5 + 0.5;
3279            let humid_n = noise.octave_noise(nx * 0.3, 200.0, nz * 0.3, 2, 0.5, 2.0) * 0.5 + 0.5;
3280            let alt_n = height_n * 0.5 + 0.5;
3281            let biome = classify_biome(temp_n, humid_n, alt_n);
3282            let surface_y = (base_height + height_scale * height_n as f32) as i32;
3283            let surface_mat = biome.surface_material();
3284            let sub_mat = biome.subsurface_material();
3285            for y in 0..grid.height {
3286                if y as i32 == surface_y {
3287                    grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, surface_mat));
3288                } else if y < surface_y as usize && surface_y as usize - y <= 3 {
3289                    grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, sub_mat));
3290                } else if y < surface_y as usize {
3291                    grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, 1));
3292                }
3293            }
3294        }
3295    }
3296}
3297
3298// ─── Extended: Tree Generation ───────────────────────────────────────────────
3299
3300pub struct TreeGenerator {
3301    pub trunk_material: u8,
3302    pub leaf_material: u8,
3303    pub trunk_height: u32,
3304    pub canopy_radius: f32,
3305}
3306
3307impl TreeGenerator {
3308    pub fn new() -> Self {
3309        Self { trunk_material: 6, leaf_material: 7, trunk_height: 6, canopy_radius: 3.0 }
3310    }
3311
3312    pub fn plant(&self, grid: &mut VoxelGrid, base_x: i32, base_y: i32, base_z: i32) {
3313        // Trunk
3314        for dy in 0..self.trunk_height as i32 {
3315            grid.set(base_x, base_y + dy, base_z, Voxel::new(1.0, self.trunk_material));
3316        }
3317        // Canopy sphere
3318        let crown_y = base_y + self.trunk_height as i32;
3319        let r = self.canopy_radius;
3320        let ri = r.ceil() as i32;
3321        for dz in -ri..=ri {
3322            for dy in -ri..=ri {
3323                for dx in -ri..=ri {
3324                    let dist = ((dx*dx + dy*dy + dz*dz) as f32).sqrt();
3325                    if dist <= r {
3326                        let density = 1.0 - (dist / r) * 0.3;
3327                        grid.set(base_x + dx, crown_y + dy, base_z + dz, Voxel::new(density, self.leaf_material));
3328                    }
3329                }
3330            }
3331        }
3332    }
3333
3334    pub fn scatter_forest(
3335        &self,
3336        grid: &mut VoxelGrid,
3337        noise: &PerlinNoise3D,
3338        density: f64,
3339        min_y: i32,
3340        seed: u64,
3341    ) {
3342        let mut rng = seed;
3343        let next_f = |rng: &mut u64| -> f64 {
3344            *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3345            (*rng >> 33) as f64 / u32::MAX as f64
3346        };
3347        for z in (0..grid.depth as i32).step_by(4) {
3348            for x in (0..grid.width as i32).step_by(4) {
3349                let n = noise.octave_noise(x as f64 * 0.1, 300.0, z as f64 * 0.1, 2, 0.5, 2.0) * 0.5 + 0.5;
3350                if n > density {
3351                    // Find surface y
3352                    let mut surf_y = min_y;
3353                    for y in (0..grid.height as i32).rev() {
3354                        if grid.get(x, y, z).is_solid() { surf_y = y + 1; break; }
3355                    }
3356                    let jx = x + (next_f(&mut rng) * 3.0 - 1.5) as i32;
3357                    let jz = z + (next_f(&mut rng) * 3.0 - 1.5) as i32;
3358                    if surf_y > min_y && surf_y < grid.height as i32 - (self.trunk_height as i32 + 5) {
3359                        self.plant(grid, jx, surf_y, jz);
3360                    }
3361                }
3362            }
3363        }
3364    }
3365}
3366
3367// ─── Extended: Erosion Simulation ────────────────────────────────────────────
3368
3369pub fn hydraulic_erosion(
3370    grid: &mut VoxelGrid,
3371    iterations: usize,
3372    erosion_strength: f32,
3373    deposition_strength: f32,
3374    seed: u64,
3375) {
3376    let mut rng = seed;
3377    let next_f = |rng: &mut u64| -> f32 {
3378        *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3379        (*rng >> 33) as f32 / u32::MAX as f32
3380    };
3381
3382    for _ in 0..iterations {
3383        // Random raindrop
3384        let dx = (next_f(&mut rng) * grid.width as f32) as i32;
3385        let dz = (next_f(&mut rng) * grid.depth as f32) as i32;
3386        // Find surface
3387        let mut pos = (dx, 0i32, dz);
3388        for y in (0..grid.height as i32).rev() {
3389            if grid.get(dx, y, dz).is_solid() { pos.1 = y; break; }
3390        }
3391        let mut sediment = 0.0f32;
3392        let mut water = 1.0f32;
3393        // Flow downhill
3394        for _ in 0..32 {
3395            let (cx, cy, cz) = pos;
3396            // Find lowest neighbor
3397            let neighbors = [(cx-1,cy,cz),(cx+1,cy,cz),(cx,cy,cz-1),(cx,cy,cz+1),
3398                             (cx-1,cy-1,cz),(cx+1,cy-1,cz),(cx,cy-1,cz-1),(cx,cy-1,cz+1)];
3399            let cur_density = grid.get(cx, cy, cz).density;
3400            let lowest = neighbors.iter().min_by(|&&(nx,ny,nz), &&(mx,my,mz)| {
3401                let nd = grid.get(nx, ny, nz).density;
3402                let md = grid.get(mx, my, mz).density;
3403                nd.partial_cmp(&md).unwrap_or(std::cmp::Ordering::Equal)
3404            });
3405            if let Some(&(nx, ny, nz)) = lowest {
3406                let next_density = grid.get(nx, ny, nz).density;
3407                if next_density < cur_density {
3408                    // Erode current cell
3409                    let eroded = erosion_strength * water;
3410                    let cur_v = grid.get(cx, cy, cz);
3411                    if !cur_v.is_empty() {
3412                        let new_d = (cur_v.density - eroded).max(0.0);
3413                        grid.set(cx, cy, cz, Voxel::new(new_d, cur_v.material));
3414                        sediment += eroded;
3415                    }
3416                    // Deposit some sediment
3417                    let deposit = deposition_strength * sediment;
3418                    sediment -= deposit;
3419                    let next_v = grid.get(nx, ny, nz);
3420                    if !next_v.is_empty() {
3421                        grid.set(nx, ny, nz, Voxel::new((next_v.density + deposit).min(1.0), next_v.material));
3422                    }
3423                    pos = (nx, ny, nz);
3424                    water *= 0.99;
3425                } else { break; }
3426            } else { break; }
3427        }
3428    }
3429}
3430
3431// ─── Extended: Voxel Signed Distance Field ───────────────────────────────────
3432
3433pub struct VoxelSDF {
3434    pub grid: VoxelGrid,
3435    pub values: Vec<f32>,
3436}
3437
3438impl VoxelSDF {
3439    pub fn build(grid: &VoxelGrid) -> Self {
3440        let values = voxel_to_sdf(grid);
3441        Self { grid: grid.clone(), values }
3442    }
3443
3444    pub fn sample_at(&self, world_pos: Vec3) -> f32 {
3445        let (x, y, z) = self.grid.world_to_grid(world_pos);
3446        if x < 0 || y < 0 || z < 0 || x >= self.grid.width as i32 || y >= self.grid.height as i32 || z >= self.grid.depth as i32 {
3447            return f32::MAX;
3448        }
3449        let idx = self.grid.index(x as usize, y as usize, z as usize);
3450        self.values[idx]
3451    }
3452
3453    pub fn gradient_at(&self, world_pos: Vec3) -> Vec3 {
3454        let eps = self.grid.voxel_size;
3455        let dx = self.sample_at(world_pos + Vec3::X * eps) - self.sample_at(world_pos - Vec3::X * eps);
3456        let dy = self.sample_at(world_pos + Vec3::Y * eps) - self.sample_at(world_pos - Vec3::Y * eps);
3457        let dz = self.sample_at(world_pos + Vec3::Z * eps) - self.sample_at(world_pos - Vec3::Z * eps);
3458        Vec3::new(dx, dy, dz) / (2.0 * eps)
3459    }
3460
3461    pub fn is_inside(&self, world_pos: Vec3) -> bool {
3462        self.sample_at(world_pos) < 0.0
3463    }
3464
3465    /// Boolean union with another SDF (minimum of values).
3466    pub fn union_inplace(&mut self, other: &VoxelSDF) {
3467        for (a, b) in self.values.iter_mut().zip(other.values.iter()) {
3468            *a = a.min(*b);
3469        }
3470    }
3471
3472    /// Boolean subtraction: max(A, -B).
3473    pub fn subtract_inplace(&mut self, other: &VoxelSDF) {
3474        for (a, b) in self.values.iter_mut().zip(other.values.iter()) {
3475            *a = a.max(-b);
3476        }
3477    }
3478
3479    /// Smooth union (polynomial blend).
3480    pub fn smooth_union_inplace(&mut self, other: &VoxelSDF, k: f32) {
3481        for (a, b) in self.values.iter_mut().zip(other.values.iter()) {
3482            let h = (k - (a.abs() - b.abs()).abs()).max(0.0) / k;
3483            let m = h * h * 0.25;
3484            *a = a.min(*b) - m * k;
3485        }
3486    }
3487}
3488
3489// ─── Extended: Voxel Path Finding ────────────────────────────────────────────
3490
3491#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3492pub struct VoxelNode(pub i32, pub i32, pub i32);
3493
3494impl VoxelNode {
3495    pub fn distance(&self, other: &VoxelNode) -> f32 {
3496        let dx = (self.0 - other.0) as f32;
3497        let dy = (self.1 - other.1) as f32;
3498        let dz = (self.2 - other.2) as f32;
3499        (dx*dx + dy*dy + dz*dz).sqrt()
3500    }
3501}
3502
3503/// A* pathfinding on voxel grid.
3504pub fn voxel_astar(
3505    grid: &VoxelGrid,
3506    start: VoxelNode,
3507    goal: VoxelNode,
3508    max_nodes: usize,
3509) -> Option<Vec<VoxelNode>> {
3510    use std::collections::BinaryHeap;
3511    use std::cmp::Reverse;
3512
3513    #[derive(PartialEq)]
3514    struct FNode(f32, VoxelNode);
3515    impl Eq for FNode {}
3516    impl PartialOrd for FNode {
3517        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
3518    }
3519    impl Ord for FNode {
3520        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3521            other.0.partial_cmp(&self.0).unwrap_or(std::cmp::Ordering::Equal)
3522        }
3523    }
3524
3525    let mut open: BinaryHeap<FNode> = BinaryHeap::new();
3526    let mut came_from: HashMap<VoxelNode, VoxelNode> = HashMap::new();
3527    let mut g_score: HashMap<VoxelNode, f32> = HashMap::new();
3528    g_score.insert(start.clone(), 0.0);
3529    open.push(FNode(start.distance(&goal), start.clone()));
3530
3531    let mut visited = 0usize;
3532    while let Some(FNode(_, current)) = open.pop() {
3533        if current == goal {
3534            // Reconstruct path
3535            let mut path = vec![current.clone()];
3536            let mut cur = current;
3537            while let Some(prev) = came_from.get(&cur) {
3538                path.push(prev.clone());
3539                cur = prev.clone();
3540            }
3541            path.reverse();
3542            return Some(path);
3543        }
3544        if visited > max_nodes { break; }
3545        visited += 1;
3546
3547        for (dx, dy, dz) in [
3548            (-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1),
3549            (-1,0,-1),(-1,0,1),(1,0,-1),(1,0,1),
3550        ] {
3551            let nx = current.0 + dx;
3552            let ny = current.1 + dy;
3553            let nz = current.2 + dz;
3554            let neighbor = VoxelNode(nx, ny, nz);
3555            // Check if walkable: solid ground below, air at position
3556            let is_air = !grid.get(nx, ny, nz).is_solid();
3557            let has_ground = grid.get(nx, ny-1, nz).is_solid();
3558            if !is_air || !has_ground { continue; }
3559            let step_cost = if dx != 0 && dz != 0 { 1.414 } else { 1.0 };
3560            let tentative_g = g_score.get(&current).copied().unwrap_or(f32::MAX) + step_cost;
3561            if tentative_g < g_score.get(&neighbor).copied().unwrap_or(f32::MAX) {
3562                came_from.insert(neighbor.clone(), current.clone());
3563                g_score.insert(neighbor.clone(), tentative_g);
3564                let h = neighbor.distance(&goal);
3565                open.push(FNode(tentative_g + h, neighbor));
3566            }
3567        }
3568    }
3569    None
3570}
3571
3572// ─── Extended: Voxel Light Propagation ───────────────────────────────────────
3573
3574#[derive(Debug, Clone, Copy, Default)]
3575pub struct LightCell {
3576    pub r: u8, pub g: u8, pub b: u8,
3577    pub sun: u8,
3578}
3579
3580pub struct VoxelLightGrid {
3581    pub width: usize, pub height: usize, pub depth: usize,
3582    pub cells: Vec<LightCell>,
3583}
3584
3585impl VoxelLightGrid {
3586    pub fn new(width: usize, height: usize, depth: usize) -> Self {
3587        Self { width, height, depth, cells: vec![LightCell::default(); width * height * depth] }
3588    }
3589
3590    pub fn index(&self, x: usize, y: usize, z: usize) -> usize {
3591        x + y * self.width + z * self.width * self.height
3592    }
3593
3594    pub fn propagate_sunlight(&mut self, grid: &VoxelGrid) {
3595        // Top-down sunlight pass
3596        for z in 0..self.depth {
3597            for x in 0..self.width {
3598                let mut sun = 255u8;
3599                for y in (0..self.height).rev() {
3600                    let v = grid.get(x as i32, y as i32, z as i32);
3601                    if v.is_solid() { sun = (sun as f32 * 0.8) as u8; }
3602                    let idx = self.index(x, y, z);
3603                    self.cells[idx].sun = sun;
3604                }
3605            }
3606        }
3607        // Flood fill lateral propagation (simplified BFS)
3608        let mut queue: VecDeque<(usize, usize, usize)> = VecDeque::new();
3609        for z in 0..self.depth {
3610            for x in 0..self.width {
3611                let idx = self.index(x, self.height - 1, z);
3612                if self.cells[idx].sun > 0 { queue.push_back((x, self.height - 1, z)); }
3613            }
3614        }
3615        while let Some((x, y, z)) = queue.pop_front() {
3616            let cur_sun = self.cells[self.index(x, y, z)].sun;
3617            if cur_sun <= 1 { continue; }
3618            let new_sun = cur_sun - 1;
3619            for (nx, ny, nz) in [
3620                (x.wrapping_sub(1), y, z), (x+1, y, z), (x, y.wrapping_sub(1), z),
3621                (x, y+1, z), (x, y, z.wrapping_sub(1)), (x, y, z+1),
3622            ] {
3623                if nx >= self.width || ny >= self.height || nz >= self.depth { continue; }
3624                let nidx = self.index(nx, ny, nz);
3625                if !grid.get(nx as i32, ny as i32, nz as i32).is_solid() && self.cells[nidx].sun < new_sun {
3626                    self.cells[nidx].sun = new_sun;
3627                    queue.push_back((nx, ny, nz));
3628                }
3629            }
3630        }
3631    }
3632
3633    pub fn add_point_light(&mut self, grid: &VoxelGrid, lx: usize, ly: usize, lz: usize, r: u8, g: u8, b: u8, radius: f32) {
3634        let ri = radius.ceil() as usize;
3635        let xr = (lx.saturating_sub(ri))..(lx + ri + 1).min(self.width);
3636        let yr = (ly.saturating_sub(ri))..(ly + ri + 1).min(self.height);
3637        let zr = (lz.saturating_sub(ri))..(lz + ri + 1).min(self.depth);
3638        for z in zr.clone() {
3639            for y in yr.clone() {
3640                for x in xr.clone() {
3641                    let dist = (((x as i32 - lx as i32).pow(2) + (y as i32 - ly as i32).pow(2) + (z as i32 - lz as i32).pow(2)) as f32).sqrt();
3642                    if dist > radius { continue; }
3643                    let att = 1.0 - dist / radius;
3644                    let idx = self.index(x, y, z);
3645                    self.cells[idx].r = (self.cells[idx].r as f32 + r as f32 * att).min(255.0) as u8;
3646                    self.cells[idx].g = (self.cells[idx].g as f32 + g as f32 * att).min(255.0) as u8;
3647                    self.cells[idx].b = (self.cells[idx].b as f32 + b as f32 * att).min(255.0) as u8;
3648                }
3649            }
3650        }
3651    }
3652}
3653
3654// ─── Extended: Volumetric Fog ─────────────────────────────────────────────────
3655
3656pub struct VolumetricFogGrid {
3657    pub width: usize, pub height: usize, pub depth: usize,
3658    pub density: Vec<f32>,
3659    pub scatter_color: Vec3,
3660    pub absorption: f32,
3661}
3662
3663impl VolumetricFogGrid {
3664    pub fn new(w: usize, h: usize, d: usize) -> Self {
3665        Self { width: w, height: h, depth: d, density: vec![0.0; w*h*d], scatter_color: Vec3::ONE * 0.8, absorption: 0.1 }
3666    }
3667
3668    pub fn index(&self, x: usize, y: usize, z: usize) -> usize { x + y*self.width + z*self.width*self.height }
3669
3670    pub fn fill_from_grid(&mut self, grid: &VoxelGrid, lava_mat: u8) {
3671        let sw = self.width.min(grid.width);
3672        let sh = self.height.min(grid.height);
3673        let sd = self.depth.min(grid.depth);
3674        for z in 0..sd {
3675            for y in 0..sh {
3676                for x in 0..sw {
3677                    let v = grid.get(x as i32, y as i32, z as i32);
3678                    if v.material == lava_mat {
3679                        // Add fog near lava
3680                        for dy in 1..4 {
3681                            let fy = y + dy;
3682                            if fy < self.height && !grid.get(x as i32, fy as i32, z as i32).is_solid() {
3683                                let idx = self.index(x, fy, z);
3684                                self.density[idx] = (self.density[idx] + 0.3 / dy as f32).min(1.0);
3685                            }
3686                        }
3687                    }
3688                }
3689            }
3690        }
3691    }
3692
3693    pub fn march_ray(&self, origin: Vec3, dir: Vec3, step_size: f32, max_dist: f32) -> (f32, Vec3) {
3694        let voxel_size = 1.0f32; // assume unit voxels for fog
3695        let mut t = 0.0f32;
3696        let mut transmittance = 1.0f32;
3697        let mut in_scatter = Vec3::ZERO;
3698        while t < max_dist && transmittance > 0.01 {
3699            let pos = origin + dir * t;
3700            let x = pos.x as usize;
3701            let y = pos.y as usize;
3702            let z = pos.z as usize;
3703            if x < self.width && y < self.height && z < self.depth {
3704                let idx = self.index(x, y, z);
3705                let d = self.density[idx];
3706                let ext = (d * self.absorption + d * 0.1) * step_size;
3707                in_scatter += self.scatter_color * d * transmittance * step_size;
3708                transmittance *= (-ext).exp();
3709            }
3710            t += step_size;
3711        }
3712        (transmittance, in_scatter)
3713    }
3714}
3715
3716// ─── Extended: Minecraft-style Chunk Format ───────────────────────────────────
3717
3718#[derive(Debug, Clone)]
3719pub struct MinecraftChunkSection {
3720    pub y_offset: i32,
3721    pub palette: Vec<u16>,          // material ids used in this section
3722    pub block_states: Vec<u16>,     // index into palette, 16*16*16 blocks
3723}
3724
3725impl MinecraftChunkSection {
3726    pub fn new(y_offset: i32) -> Self {
3727        Self { y_offset, palette: vec![0], block_states: vec![0; 16*16*16] }
3728    }
3729
3730    pub fn get(&self, x: usize, y: usize, z: usize) -> u16 {
3731        let idx = x + z*16 + y*16*16;
3732        let palette_idx = self.block_states.get(idx).copied().unwrap_or(0) as usize;
3733        self.palette.get(palette_idx).copied().unwrap_or(0)
3734    }
3735
3736    pub fn set(&mut self, x: usize, y: usize, z: usize, block_id: u16) {
3737        // Find or add to palette
3738        let palette_idx = if let Some(pi) = self.palette.iter().position(|&b| b == block_id) {
3739            pi
3740        } else {
3741            self.palette.push(block_id);
3742            self.palette.len() - 1
3743        };
3744        let idx = x + z*16 + y*16*16;
3745        if idx < self.block_states.len() {
3746            self.block_states[idx] = palette_idx as u16;
3747        }
3748    }
3749
3750    pub fn from_voxel_grid_slice(grid: &VoxelGrid, chunk_x: i32, y_offset: i32, chunk_z: i32) -> Self {
3751        let mut section = Self::new(y_offset);
3752        for ly in 0..16 {
3753            for lz in 0..16 {
3754                for lx in 0..16 {
3755                    let gx = chunk_x * 16 + lx as i32;
3756                    let gy = y_offset * 16 + ly as i32;
3757                    let gz = chunk_z * 16 + lz as i32;
3758                    let v = grid.get(gx, gy, gz);
3759                    section.set(lx, ly, lz, v.material as u16);
3760                }
3761            }
3762        }
3763        section
3764    }
3765}
3766
3767// ─── Extended: Mesh Builder ───────────────────────────────────────────────────
3768
3769pub struct MeshBuilder {
3770    pub positions: Vec<Vec3>,
3771    pub normals: Vec<Vec3>,
3772    pub uvs: Vec<Vec2>,
3773    pub colors: Vec<Vec4>,
3774    pub indices: Vec<u32>,
3775}
3776
3777impl MeshBuilder {
3778    pub fn new() -> Self {
3779        Self { positions: Vec::new(), normals: Vec::new(), uvs: Vec::new(), colors: Vec::new(), indices: Vec::new() }
3780    }
3781
3782    pub fn add_vertex(&mut self, pos: Vec3, normal: Vec3, uv: Vec2, color: Vec4) -> u32 {
3783        let idx = self.positions.len() as u32;
3784        self.positions.push(pos);
3785        self.normals.push(normal);
3786        self.uvs.push(uv);
3787        self.colors.push(color);
3788        idx
3789    }
3790
3791    pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) {
3792        self.indices.extend_from_slice(&[a, b, c]);
3793    }
3794
3795    pub fn add_quad(&mut self, a: u32, b: u32, c: u32, d: u32) {
3796        self.indices.extend_from_slice(&[a, b, c, a, c, d]);
3797    }
3798
3799    pub fn build(self) -> GeneratedMesh {
3800        let vertices: Vec<MeshVertex> = self.positions.iter().zip(self.normals.iter())
3801            .zip(self.uvs.iter()).zip(self.colors.iter())
3802            .map(|(((p, n), uv), c)| MeshVertex { position: *p, normal: *n, uv: *uv, color: *c })
3803            .collect();
3804        GeneratedMesh { vertices, indices: self.indices }
3805    }
3806
3807    pub fn add_box_faces(&mut self, min: Vec3, max: Vec3, color: Vec4) {
3808        let corners = [
3809            Vec3::new(min.x, min.y, min.z), Vec3::new(max.x, min.y, min.z),
3810            Vec3::new(max.x, max.y, min.z), Vec3::new(min.x, max.y, min.z),
3811            Vec3::new(min.x, min.y, max.z), Vec3::new(max.x, min.y, max.z),
3812            Vec3::new(max.x, max.y, max.z), Vec3::new(min.x, max.y, max.z),
3813        ];
3814        let faces = [
3815            ([0,1,2,3], Vec3::NEG_Z), ([5,4,7,6], Vec3::Z),
3816            ([4,0,3,7], Vec3::NEG_X), ([1,5,6,2], Vec3::X),
3817            ([4,5,1,0], Vec3::NEG_Y), ([3,2,6,7], Vec3::Y),
3818        ];
3819        for (verts, normal) in &faces {
3820            let vs: Vec<u32> = verts.iter().map(|&vi| {
3821                self.add_vertex(corners[vi], *normal, Vec2::ZERO, color)
3822            }).collect();
3823            self.add_quad(vs[0], vs[1], vs[2], vs[3]);
3824        }
3825    }
3826}
3827
3828// ─── Extended: Voxel Brush Stamp ─────────────────────────────────────────────
3829
3830/// Pre-defined stamp patterns for voxel brushes.
3831pub struct VoxelStamp {
3832    pub relative_positions: Vec<(i32, i32, i32)>,
3833    pub densities: Vec<f32>,
3834    pub material: u8,
3835}
3836
3837impl VoxelStamp {
3838    pub fn sphere(radius: f32, material: u8) -> Self {
3839        let ri = radius.ceil() as i32;
3840        let mut positions = Vec::new();
3841        let mut densities = Vec::new();
3842        for dz in -ri..=ri {
3843            for dy in -ri..=ri {
3844                for dx in -ri..=ri {
3845                    let dist = ((dx*dx + dy*dy + dz*dz) as f32).sqrt();
3846                    if dist <= radius {
3847                        positions.push((dx, dy, dz));
3848                        densities.push(1.0 - dist / radius * 0.5);
3849                    }
3850                }
3851            }
3852        }
3853        Self { relative_positions: positions, densities, material }
3854    }
3855
3856    pub fn cube(half: i32, material: u8) -> Self {
3857        let mut positions = Vec::new();
3858        let mut densities = Vec::new();
3859        for dz in -half..=half {
3860            for dy in -half..=half {
3861                for dx in -half..=half {
3862                    positions.push((dx, dy, dz));
3863                    densities.push(1.0);
3864                }
3865            }
3866        }
3867        Self { relative_positions: positions, densities, material }
3868    }
3869
3870    pub fn apply(&self, grid: &mut VoxelGrid, cx: i32, cy: i32, cz: i32, add: bool) {
3871        for (&(dx, dy, dz), &d) in self.relative_positions.iter().zip(self.densities.iter()) {
3872            let x = cx + dx; let y = cy + dy; let z = cz + dz;
3873            if add {
3874                let existing = grid.get(x, y, z);
3875                let new_d = (existing.density + d).min(1.0);
3876                grid.set(x, y, z, Voxel::new(new_d, self.material));
3877            } else {
3878                let existing = grid.get(x, y, z);
3879                let new_d = (existing.density - d).max(0.0);
3880                if new_d < 0.001 { grid.set(x, y, z, Voxel::EMPTY); }
3881                else { grid.set(x, y, z, Voxel::new(new_d, existing.material)); }
3882            }
3883        }
3884    }
3885}
3886
3887// ─── Extended: Voxel Selection Operations ────────────────────────────────────
3888
3889pub fn select_by_material(grid: &VoxelGrid, material: u8) -> HashSet<(i32, i32, i32)> {
3890    let mut sel = HashSet::new();
3891    for z in 0..grid.depth as i32 {
3892        for y in 0..grid.height as i32 {
3893            for x in 0..grid.width as i32 {
3894                let v = grid.get(x, y, z);
3895                if v.material == material && !v.is_empty() {
3896                    sel.insert((x, y, z));
3897                }
3898            }
3899        }
3900    }
3901    sel
3902}
3903
3904pub fn select_by_density_range(grid: &VoxelGrid, min_d: f32, max_d: f32) -> HashSet<(i32, i32, i32)> {
3905    let mut sel = HashSet::new();
3906    for z in 0..grid.depth as i32 {
3907        for y in 0..grid.height as i32 {
3908            for x in 0..grid.width as i32 {
3909                let v = grid.get(x, y, z);
3910                if v.density >= min_d && v.density <= max_d {
3911                    sel.insert((x, y, z));
3912                }
3913            }
3914        }
3915    }
3916    sel
3917}
3918
3919pub fn select_surface_voxels(grid: &VoxelGrid) -> HashSet<(i32, i32, i32)> {
3920    let mut sel = HashSet::new();
3921    let dirs = [(-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1)];
3922    for z in 0..grid.depth as i32 {
3923        for y in 0..grid.height as i32 {
3924            for x in 0..grid.width as i32 {
3925                if !grid.get(x, y, z).is_solid() { continue; }
3926                for (dx, dy, dz) in &dirs {
3927                    if !grid.get(x+dx, y+dy, z+dz).is_solid() {
3928                        sel.insert((x, y, z));
3929                        break;
3930                    }
3931                }
3932            }
3933        }
3934    }
3935    sel
3936}
3937
3938// ─── Extended: Voxel Stress Visualization ────────────────────────────────────
3939
3940pub fn colorize_by_stress(
3941    grid: &VoxelGrid,
3942    cells: &[StructuralCell],
3943    material_table: &[VoxelMaterial],
3944    max_stress: f32,
3945) -> Vec<Vec4> {
3946    let n = grid.width * grid.height * grid.depth;
3947    let mut colors = vec![Vec4::ZERO; n];
3948    for z in 0..grid.depth {
3949        for y in 0..grid.height {
3950            for x in 0..grid.width {
3951                let idx = grid.index(x, y, z);
3952                let v = grid.voxels[idx];
3953                if v.is_empty() { continue; }
3954                let stress_t = (cells[idx].stress / max_stress.max(1e-9)).clamp(0.0, 1.0);
3955                // Heatmap: blue (low) -> green -> red (high)
3956                let r = stress_t;
3957                let g = (1.0 - (stress_t - 0.5).abs() * 2.0).max(0.0);
3958                let b = 1.0 - stress_t;
3959                colors[idx] = Vec4::new(r, g, b, 1.0);
3960            }
3961        }
3962    }
3963    colors
3964}
3965
3966// ─── Extended: Mesh Tangent Generation ───────────────────────────────────────
3967
3968pub fn generate_tangents(mesh: &mut GeneratedMesh) {
3969    let n = mesh.vertices.len();
3970    let mut tan1 = vec![Vec3::ZERO; n];
3971    let mut tan2 = vec![Vec3::ZERO; n];
3972    let n_tris = mesh.indices.len() / 3;
3973    for ti in 0..n_tris {
3974        let i0 = mesh.indices[ti*3] as usize;
3975        let i1 = mesh.indices[ti*3+1] as usize;
3976        let i2 = mesh.indices[ti*3+2] as usize;
3977        let p0 = mesh.vertices[i0].position;
3978        let p1 = mesh.vertices[i1].position;
3979        let p2 = mesh.vertices[i2].position;
3980        let uv0 = mesh.vertices[i0].uv;
3981        let uv1 = mesh.vertices[i1].uv;
3982        let uv2 = mesh.vertices[i2].uv;
3983        let e1 = p1 - p0; let e2 = p2 - p0;
3984        let du1 = uv1.x - uv0.x; let dv1 = uv1.y - uv0.y;
3985        let du2 = uv2.x - uv0.x; let dv2 = uv2.y - uv0.y;
3986        let r = du1*dv2 - du2*dv1;
3987        if r.abs() < 1e-9 { continue; }
3988        let inv_r = 1.0 / r;
3989        let t = (e1 * dv2 - e2 * dv1) * inv_r;
3990        let bt = (e2 * du1 - e1 * du2) * inv_r;
3991        tan1[i0] += t; tan1[i1] += t; tan1[i2] += t;
3992        tan2[i0] += bt; tan2[i1] += bt; tan2[i2] += bt;
3993    }
3994    // Could add a tangent field to MeshVertex if needed, for now just compute normals
3995    for (i, v) in mesh.vertices.iter_mut().enumerate() {
3996        let n = v.normal;
3997        let t = tan1[i];
3998        // Gram-Schmidt orthogonalize
3999        if t.length_squared() > 1e-9 {
4000            let tangent = (t - n * n.dot(t)).normalize();
4001            // We could store this if MeshVertex had a tangent field
4002            let _ = tangent;
4003        }
4004    }
4005}
4006
4007// ─── Extended: Voxel World Serialization ─────────────────────────────────────
4008
4009pub fn serialize_voxel_world(world: &VoxelWorld) -> Vec<u8> {
4010    let mut out = Vec::new();
4011    out.extend_from_slice(&(world.chunks.len() as u32).to_le_bytes());
4012    out.extend_from_slice(&(world.chunk_dim as u32).to_le_bytes());
4013    out.extend_from_slice(&world.voxel_size.to_bits().to_le_bytes());
4014    for (coord, chunk) in &world.chunks {
4015        out.extend_from_slice(&coord.cx.to_le_bytes());
4016        out.extend_from_slice(&coord.cy.to_le_bytes());
4017        out.extend_from_slice(&coord.cz.to_le_bytes());
4018        let rle = rle_encode(&chunk.voxels);
4019        out.extend_from_slice(&(rle.len() as u32).to_le_bytes());
4020        out.extend_from_slice(&rle);
4021    }
4022    out
4023}
4024
4025pub fn deserialize_voxel_world(data: &[u8]) -> Option<VoxelWorld> {
4026    if data.len() < 12 { return None; }
4027    let n_chunks = u32::from_le_bytes(data[0..4].try_into().ok()?) as usize;
4028    let chunk_dim = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
4029    let voxel_size = f32::from_bits(u32::from_le_bytes(data[8..12].try_into().ok()?));
4030    let mut world = VoxelWorld::new(chunk_dim, voxel_size, 0);
4031    let mut pos = 12usize;
4032    for _ in 0..n_chunks {
4033        if pos + 16 > data.len() { break; }
4034        let cx = i32::from_le_bytes(data[pos..pos+4].try_into().ok()?); pos += 4;
4035        let cy = i32::from_le_bytes(data[pos..pos+4].try_into().ok()?); pos += 4;
4036        let cz = i32::from_le_bytes(data[pos..pos+4].try_into().ok()?); pos += 4;
4037        let rle_len = u32::from_le_bytes(data[pos..pos+4].try_into().ok()?) as usize; pos += 4;
4038        if pos + rle_len > data.len() { break; }
4039        let expected = chunk_dim * chunk_dim * chunk_dim;
4040        let voxels = rle_decode(&data[pos..pos+rle_len], expected);
4041        pos += rle_len;
4042        let coord = ChunkCoord { cx, cy, cz };
4043        let origin = coord.to_world_origin(voxel_size, chunk_dim);
4044        let mut chunk = VoxelGrid::new(chunk_dim, chunk_dim, chunk_dim, origin, voxel_size);
4045        chunk.voxels = voxels;
4046        world.chunks.insert(coord, chunk);
4047    }
4048    Some(world)
4049}
4050
4051// ─── Extended: Voxel LOD Streaming Manager ───────────────────────────────────
4052
4053pub struct VoxelLodStreamer {
4054    pub loaded_chunks: HashMap<ChunkCoord, LodOctreeSet>,
4055    pub view_distance: f32,
4056    pub lod_depths: Vec<usize>,
4057}
4058
4059impl VoxelLodStreamer {
4060    pub fn new(view_distance: f32) -> Self {
4061        Self {
4062            loaded_chunks: HashMap::new(),
4063            view_distance,
4064            lod_depths: vec![6, 4, 2],
4065        }
4066    }
4067
4068    pub fn update(&mut self, world: &VoxelWorld, camera_pos: Vec3) {
4069        let chunk_size = world.voxel_size * world.chunk_dim as f32;
4070        let rad = (self.view_distance / chunk_size).ceil() as i32;
4071        let base = ChunkCoord::from_world(camera_pos, world.voxel_size, world.chunk_dim);
4072
4073        for dz in -rad..=rad {
4074            for dy in -rad..=rad {
4075                for dx in -rad..=rad {
4076                    let coord = ChunkCoord { cx: base.cx+dx, cy: base.cy+dy, cz: base.cz+dz };
4077                    let chunk_center = coord.to_world_origin(world.voxel_size, world.chunk_dim) + Vec3::splat(chunk_size*0.5);
4078                    let dist = (chunk_center - camera_pos).length();
4079                    if dist > self.view_distance { continue; }
4080                    if self.loaded_chunks.contains_key(&coord) { continue; }
4081                    if let Some(chunk) = world.chunks.get(&coord) {
4082                        let svo = chunk.to_svo();
4083                        let lod_set = LodOctreeSet::build(svo, &self.lod_depths);
4084                        self.loaded_chunks.insert(coord, lod_set);
4085                    }
4086                }
4087            }
4088        }
4089
4090        // Unload distant chunks
4091        self.loaded_chunks.retain(|coord, _| {
4092            let chunk_center = coord.to_world_origin(world.voxel_size, world.chunk_dim) + Vec3::splat(chunk_size*0.5);
4093            (chunk_center - camera_pos).length() <= self.view_distance * 1.5
4094        });
4095    }
4096
4097    pub fn query_voxel(&self, world_pos: Vec3, camera_pos: Vec3, world: &VoxelWorld) -> Voxel {
4098        let coord = ChunkCoord::from_world(world_pos, world.voxel_size, world.chunk_dim);
4099        if let Some(lod_set) = self.loaded_chunks.get(&coord) {
4100            let chunk_center = coord.to_world_origin(world.voxel_size, world.chunk_dim);
4101            let dist = (chunk_center - camera_pos).length();
4102            let svo = lod_set.select_lod(dist, 10.0);
4103            svo.query(world_pos)
4104        } else {
4105            Voxel::EMPTY
4106        }
4107    }
4108}
4109
4110// ─── End of File ──────────────────────────────────────────────────────────────