Expand grammar and add CSG parity fixtures

This commit is contained in:
Meta Vens 2026-05-07 01:22:14 +01:00
commit cc4bbaa635
5 changed files with 94 additions and 1 deletions

View file

@ -265,6 +265,12 @@ Algorithm migration steps:
## Acceptance Tests To Add
- Where `vg_csg` exposes the same behavior as public RealtimeCSG/demo APIs,
maintain exact observable parity fixtures. These should compare public
behavior such as category vocabulary, brush plane/polygon counts, split
counts, visible/reversed semantics, bounds early-outs, and mesh output
invariants. Do not use vague "looks right" tests where a public seam has a
countable contract.
- Two overlapping additive boxes produce no duplicate coplanar surface.
- Subtraction is implemented as `!(!A || B)` and matches direct cases.
- Intersection is implemented as `!(!A || !B)` and matches direct cases.

View file

@ -13,6 +13,14 @@ pub enum BrushOp {
Intersect,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PolygonCategory {
Inside,
Outside,
Aligned,
ReverseAligned,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Aabb {
pub min: Vec3,

View file

@ -330,6 +330,35 @@ fn polygon_normal(vertices: &[Vec3]) -> Vec3 {
mod tests {
use super::*;
fn assert_box_parity(solid: &ConvexSolid, material: MaterialId) {
assert_eq!(solid.polygons.len(), 6);
assert_eq!(solid.clip_planes.len(), 6);
assert!(
solid
.polygons
.iter()
.all(|polygon| polygon.vertices.len() == 4)
);
assert!(
solid
.polygons
.iter()
.all(|polygon| polygon.material == material)
);
}
#[test]
fn parity_box_brush_from_planes_has_six_quad_polygons() {
let solid = ConvexSolid::box_from_center_size(
Vec3::ZERO,
Vec3::splat(2.0),
Quat::IDENTITY,
MaterialId(9),
);
assert_box_parity(&solid, MaterialId(9));
}
#[test]
fn subtract_center_box_emits_six_convex_fragments() {
let source = ConvexSolid::box_from_center_size(

View file

@ -14,7 +14,7 @@ mod mesh;
mod primitives;
pub use assembler::{Assembler, BuildOutput, BuildReport, BuildWarning};
pub use brush::{Aabb, Brush, BrushId, BrushOp, MaterialId, Primitive};
pub use brush::{Aabb, Brush, BrushId, BrushOp, MaterialId, PolygonCategory, Primitive};
pub use convex::{ConvexPolygon, ConvexSolid, Plane};
pub use dsl::LevelDsl;
pub use mesh::TriangleMesh;

View file

@ -0,0 +1,50 @@
use bevy_math::{Quat, Vec3};
use vg_csg::{Aabb, Assembler, ConvexSolid, MaterialId, PolygonCategory};
#[test]
fn parity_polygon_category_vocabulary_matches_public_demo() {
let categories = [
PolygonCategory::Inside,
PolygonCategory::Outside,
PolygonCategory::Aligned,
PolygonCategory::ReverseAligned,
];
assert_eq!(categories.len(), 4);
}
#[test]
fn parity_box_brush_observable_counts_match_realtime_csg_demo() {
let solid = ConvexSolid::box_from_center_size(
Vec3::ZERO,
Vec3::splat(2.0),
Quat::IDENTITY,
MaterialId(3),
);
assert_eq!(solid.clip_planes.len(), 6);
assert_eq!(solid.polygons.len(), 6);
assert_eq!(
solid
.polygons
.iter()
.map(|polygon| polygon.vertices.len())
.sum::<usize>(),
24
);
}
#[test]
fn parity_center_box_subtraction_has_six_surviving_regions() {
let mut asm = Assembler::new();
asm.solid_box(
"source",
Aabb::from_center_size(Vec3::ZERO, Vec3::splat(4.0)),
MaterialId(1),
);
asm.cut_box("void", Aabb::from_center_size(Vec3::ZERO, Vec3::splat(2.0)));
let output = asm.build();
assert_eq!(output.report.emitted_convex_fragments, 6);
assert_eq!(output.report.warnings.len(), 0);
}