GameCult.Geometry.Csg/src/tree.rs
2026-05-07 01:32:51 +01:00

306 lines
8.6 KiB
Rust

use crate::{Assembler, Brush, BrushId, BrushOp, MaterialId, Primitive};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CsgNodeId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CsgTreeBrush {
pub node: CsgNodeId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CsgTreeBranch {
pub node: CsgNodeId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CsgTree {
pub root: CsgNodeId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CsgOperationType {
Additive = 0,
Subtractive = 1,
Intersecting = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CsgBranchOp {
Addition,
Subtraction,
Common,
}
#[derive(Clone, Debug)]
pub enum CsgNode {
Brush(Brush),
Branch {
name: String,
op: CsgBranchOp,
children: Vec<CsgNodeId>,
},
}
#[derive(Clone, Debug, Default)]
pub struct CsgTreeArena {
nodes: Vec<CsgNode>,
}
impl CsgTreeArena {
pub fn new() -> Self {
Self::default()
}
pub fn nodes(&self) -> &[CsgNode] {
&self.nodes
}
pub fn node(&self, id: CsgNodeId) -> Option<&CsgNode> {
self.nodes.get(id.0 as usize)
}
pub fn node_mut(&mut self, id: CsgNodeId) -> Option<&mut CsgNode> {
self.nodes.get_mut(id.0 as usize)
}
pub fn brush_count(&self) -> usize {
self.nodes
.iter()
.filter(|node| matches!(node, CsgNode::Brush(_)))
.count()
}
pub fn branch_count(&self) -> usize {
self.nodes
.iter()
.filter(|node| matches!(node, CsgNode::Branch { .. }))
.count()
}
pub fn generate_brush(
&mut self,
name: impl Into<String>,
operation: CsgOperationType,
primitive: Primitive,
material: MaterialId,
) -> CsgTreeBrush {
let id = CsgNodeId(self.nodes.len() as u32);
let brush = Brush::new(
BrushId(id.0),
name,
operation.into_brush_op(),
primitive,
material,
);
self.nodes.push(CsgNode::Brush(brush));
CsgTreeBrush { node: id }
}
pub fn generate_branch(
&mut self,
name: impl Into<String>,
op: CsgBranchOp,
children: impl IntoIterator<Item = CsgNodeId>,
) -> CsgTreeBranch {
let id = CsgNodeId(self.nodes.len() as u32);
self.nodes.push(CsgNode::Branch {
name: name.into(),
op,
children: children.into_iter().collect(),
});
CsgTreeBranch { node: id }
}
pub fn generate_tree(&self, root: CsgNodeId) -> CsgTree {
CsgTree { root }
}
pub fn set_brush_operation_type(
&mut self,
brush: CsgTreeBrush,
operation: CsgOperationType,
) -> bool {
let Some(CsgNode::Brush(node)) = self.node_mut(brush.node) else {
return false;
};
node.op = operation.into_brush_op();
node.set_dirty();
true
}
pub fn set_branch_operation_type(&mut self, branch: CsgTreeBranch, op: CsgBranchOp) -> bool {
let Some(CsgNode::Branch { op: current_op, .. }) = self.node_mut(branch.node) else {
return false;
};
*current_op = op;
true
}
pub fn set_child_nodes(
&mut self,
branch: CsgTreeBranch,
children: impl IntoIterator<Item = CsgNodeId>,
) -> bool {
let Some(CsgNode::Branch {
children: current_children,
..
}) = self.node_mut(branch.node)
else {
return false;
};
*current_children = children.into_iter().collect();
true
}
pub fn child_nodes(&self, branch: CsgTreeBranch) -> Option<&[CsgNodeId]> {
let Some(CsgNode::Branch { children, .. }) = self.node(branch.node) else {
return None;
};
Some(children)
}
pub fn compile_tree_to_assembler(&self, tree: CsgTree) -> Assembler {
let mut assembler = Assembler::new();
self.compile_node(tree.root, &mut assembler, None);
assembler
}
fn compile_node(
&self,
id: CsgNodeId,
assembler: &mut Assembler,
operation_override: Option<CsgOperationType>,
) {
let Some(node) = self.node(id) else {
return;
};
match node {
CsgNode::Brush(brush) => {
let op = operation_override
.map(CsgOperationType::into_brush_op)
.unwrap_or(brush.op);
assembler.add_brush(
brush.name.clone(),
op,
brush.primitive.clone(),
brush.material,
);
}
CsgNode::Branch { op, children, .. } => match op {
CsgBranchOp::Addition => {
for child in children {
self.compile_node(*child, assembler, operation_override);
}
}
CsgBranchOp::Subtraction => {
if let Some((first, rest)) = children.split_first() {
self.compile_node(*first, assembler, operation_override);
for child in rest {
self.compile_node(
*child,
assembler,
Some(CsgOperationType::Subtractive),
);
}
}
}
CsgBranchOp::Common => {
for child in children {
self.compile_node(*child, assembler, Some(CsgOperationType::Intersecting));
}
}
},
}
}
}
impl CsgOperationType {
pub fn into_brush_op(self) -> BrushOp {
match self {
Self::Additive => BrushOp::Add,
Self::Subtractive => BrushOp::Subtract,
Self::Intersecting => BrushOp::Intersect,
}
}
}
impl From<BrushOp> for CsgOperationType {
fn from(value: BrushOp) -> Self {
match value {
BrushOp::Add => Self::Additive,
BrushOp::Subtract => Self::Subtractive,
BrushOp::Intersect => Self::Intersecting,
}
}
}
#[cfg(test)]
mod tests {
use bevy_math::Vec3;
use super::*;
use crate::{Aabb, Primitive};
#[test]
fn tree_subtraction_compiles_to_ordered_brush_stream() {
let mut arena = CsgTreeArena::new();
let solid = arena.generate_brush(
"solid",
CsgOperationType::Additive,
Primitive::Box {
bounds: Aabb::from_center_size(Vec3::ZERO, Vec3::splat(4.0)),
},
MaterialId(1),
);
let void = arena.generate_brush(
"void",
CsgOperationType::Additive,
Primitive::Box {
bounds: Aabb::from_center_size(Vec3::ZERO, Vec3::splat(2.0)),
},
MaterialId(0),
);
let branch = arena.generate_branch(
"solid minus void",
CsgBranchOp::Subtraction,
[solid.node, void.node],
);
let assembler = arena.compile_tree_to_assembler(arena.generate_tree(branch.node));
assert_eq!(assembler.brushes().len(), 2);
assert_eq!(assembler.brushes()[0].op, BrushOp::Add);
assert_eq!(assembler.brushes()[1].op, BrushOp::Subtract);
assert_eq!(assembler.build().report.emitted_convex_fragments, 6);
}
#[test]
fn tree_edit_api_replaces_children_and_operations() {
let mut arena = CsgTreeArena::new();
let a = arena.generate_brush(
"a",
CsgOperationType::Additive,
Primitive::Box {
bounds: Aabb::from_center_size(Vec3::ZERO, Vec3::splat(2.0)),
},
MaterialId(1),
);
let b = arena.generate_brush(
"b",
CsgOperationType::Additive,
Primitive::Box {
bounds: Aabb::from_center_size(Vec3::X, Vec3::splat(1.0)),
},
MaterialId(1),
);
let branch = arena.generate_branch("branch", CsgBranchOp::Addition, [a.node]);
assert!(arena.set_child_nodes(branch, [a.node, b.node]));
assert_eq!(arena.child_nodes(branch).expect("branch").len(), 2);
assert!(arena.set_branch_operation_type(branch, CsgBranchOp::Subtraction));
assert!(arena.set_brush_operation_type(b, CsgOperationType::Subtractive));
assert_eq!(arena.brush_count(), 2);
assert_eq!(arena.branch_count(), 1);
}
}