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
6const 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; const MAX_DENSITY: f32 = 1.0;
21const MIN_DENSITY: f32 = 0.0;
22const ISO_LEVEL: f32 = 0.5;
23
24#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct VoxelMaterial {
28 pub id: u8,
29 pub color: Vec4, pub density_factor: f32, pub emission: Vec3, pub hardness: f32, 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 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 table.push(VoxelMaterial { id: 1, color: Vec4::new(0.5, 0.5, 0.5, 1.0), hardness: 0.9, ..Default::default() });
59 table.push(VoxelMaterial { id: 2, color: Vec4::new(0.4, 0.3, 0.2, 1.0), hardness: 0.3, ..Default::default() });
61 table.push(VoxelMaterial { id: 3, color: Vec4::new(0.2, 0.7, 0.2, 1.0), hardness: 0.2, ..Default::default() });
63 table.push(VoxelMaterial { id: 4, color: Vec4::new(0.9, 0.8, 0.6, 1.0), hardness: 0.1, ..Default::default() });
65 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 table.push(VoxelMaterial { id: 6, color: Vec4::new(0.6, 0.4, 0.2, 1.0), hardness: 0.6, ..Default::default() });
69 table.push(VoxelMaterial { id: 7, color: Vec4::new(0.1, 0.6, 0.1, 0.9), hardness: 0.1, ..Default::default() });
71 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 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 table.push(VoxelMaterial { id: 10, color: Vec4::new(0.1, 0.1, 0.1, 1.0), hardness: 0.8, ..Default::default() });
77 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 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 table.push(VoxelMaterial { id: 13, color: Vec4::new(0.6, 0.6, 0.5, 1.0), hardness: 0.25, ..Default::default() });
83 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 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 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 table.push(VoxelMaterial { id: 17, color: Vec4::new(0.7, 0.65, 0.55, 1.0), hardness: 0.2, ..Default::default() });
91 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 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 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
111pub 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#[derive(Debug, Clone, Copy, PartialEq)]
128pub struct Voxel {
129 pub density: f32, 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#[derive(Debug, Clone)]
153pub enum OctreeNode {
154 Leaf(Voxel),
155 Branch {
156 children: Box<[Option<Box<OctreeNode>>; 8]>,
157 },
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
187pub 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, }
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 pub fn leaf_size(&self) -> f32 {
220 self.size / (1 << self.depth) as f32
221 }
222
223 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 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 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 pub fn delete(&mut self, pos: Vec3) {
264 self.insert(pos, Voxel::EMPTY);
265 }
266
267 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 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 pub fn optimize(&mut self) {
297 optimize_node(&mut self.root);
298 }
299
300 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 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 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 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 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 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#[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#[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 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 let signed_dist = plane_normal.dot(world) - plane_d;
720 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
739pub 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, 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
778pub const MC_TRI_TABLE: [[i8; 16]; 256] = generate_mc_tri_table();
781
782const fn generate_mc_tri_table() -> [[i8; 16]; 256] {
783 [
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], [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
1045pub 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
1052pub 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
1108pub 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 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 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 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 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#[derive(Debug, Clone, Default)]
1192pub struct QEF {
1193 pub ata: [[f64; 3]; 3], pub atb: [f64; 3], 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 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 pub fn solve(&self) -> Vec3 {
1231 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 if self.num_points > 0 {
1243 return self.mass_point / self.num_points as f32;
1244 }
1245 return Vec3::ZERO;
1246 }
1247
1248 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 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
1293pub 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
1302pub 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 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 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 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 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
1384fn 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 let mut p = [0u8; 256];
1412 for i in 0..256 {
1413 p[i] = i as u8;
1414 }
1415 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
1475pub 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; let sub_mat = 2u8; 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; };
1506 grid.set(x as i32, y as i32, z as i32, Voxel::new(1.0, mat));
1507 }
1508 }
1509 }
1510}
1511
1512pub 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 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 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 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
1558pub 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#[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 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 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 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 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; cells[idx].stress = weight / (hardness * 10.0 + 0.001);
1637 }
1638 }
1639 }
1640
1641 cells
1642}
1643
1644pub 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#[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
1711pub 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 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 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)>, }
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
1802pub 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 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 for v in &grid.voxels {
1814 data.push(v.material);
1815 }
1816 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
1839pub 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
1878pub 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
1904pub 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
1930pub 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#[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#[derive(Debug, Clone, Copy, PartialEq)]
2019pub enum MirrorAxis {
2020 X,
2021 Y,
2022 Z,
2023 XY,
2024 XZ,
2025 YZ,
2026 XYZ,
2027}
2028
2029pub 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#[derive(Debug, Clone)]
2111pub struct VoxelUndoState {
2112 pub modified_voxels: Vec<((i32, i32, i32), Voxel, Voxel)>, }
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 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#[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 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 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 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
2432pub 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#[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
2583pub 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 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 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 vertex_errors[vi] = 1.0 - (normal_sum.length() / tris.len() as f32).min(1.0);
2615 }
2616
2617 mesh.clone()
2620}
2621
2622pub 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 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
2655pub fn greedy_mesh(grid: &VoxelGrid, material_table: &[VoxelMaterial]) -> GeneratedMesh {
2659 let mut mesh = GeneratedMesh::new();
2660
2661 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 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 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 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 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 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 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 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
2759pub 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#[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
2841pub 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
2868pub 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 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
2888pub 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
2905pub 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
2920pub 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
2946pub 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 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 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#[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 for v in &mut grid.voxels {
3025 *v = Voxel::new(1.0, 1); }
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 if rooms.iter().any(|r: &DungeonRoom| r.overlaps(&room)) { continue; }
3041 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 for i in 1..rooms.len() {
3054 let (ax, ay, az) = rooms[i-1].center();
3055 let (bx, by, bz) = rooms[i].center();
3056 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
3073pub 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 ((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
3088pub 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#[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
3148pub 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#[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, Biome::Forest => 3, Biome::Desert => 4, Biome::Tundra => 15, Biome::Mountains => 1, Biome::Ocean => 5, Biome::Swamp => 2, Biome::Jungle => 3, }
3242 }
3243
3244 pub fn subsurface_material(&self) -> u8 {
3245 match self {
3246 Biome::Desert => 4, Biome::Tundra => 15, Biome::Ocean => 4, _ => 2, }
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
3298pub 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 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 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 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
3367pub 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 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 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 for _ in 0..32 {
3395 let (cx, cy, cz) = pos;
3396 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 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 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
3431pub 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 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 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 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#[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
3503pub 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 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 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(¤t).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#[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 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 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
3654pub 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 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; 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#[derive(Debug, Clone)]
3719pub struct MinecraftChunkSection {
3720 pub y_offset: i32,
3721 pub palette: Vec<u16>, pub block_states: Vec<u16>, }
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 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
3767pub 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
3828pub 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
3887pub 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
3938pub 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 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
3966pub 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 for (i, v) in mesh.vertices.iter_mut().enumerate() {
3996 let n = v.normal;
3997 let t = tan1[i];
3998 if t.length_squared() > 1e-9 {
4000 let tangent = (t - n * n.dot(t)).normalize();
4001 let _ = tangent;
4003 }
4004 }
4005}
4006
4007pub 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
4051pub 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 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