diff --git a/docs/research/realtime-csg-bevy-assembler.md b/docs/research/realtime-csg-bevy-assembler.md index 4408e27..9bba816 100644 --- a/docs/research/realtime-csg-bevy-assembler.md +++ b/docs/research/realtime-csg-bevy-assembler.md @@ -82,6 +82,10 @@ fragment-splitting implementation is a stepping stone; the faster mature kernel should preserve brush polygons, classify them through the tree, and emit only the visible categories. +The expanded doctrine lives in +`docs/research/realtime-csg-doctrine.md`; keep this file focused on the current +crate shape. + Procedural non-CSG primitives are additive: - `CylinderZ` for tethers, shafts, columns, conduits, and ring anchors diff --git a/docs/research/realtime-csg-doctrine.md b/docs/research/realtime-csg-doctrine.md new file mode 100644 index 0000000..4815fa0 --- /dev/null +++ b/docs/research/realtime-csg-doctrine.md @@ -0,0 +1,290 @@ +# Realtime CSG Doctrine + +## Provenance + +This note distills public material around Sander van Rossen and Matthew +Baranowski's "Real-Time Constructive Solid Geometry" article and the later +RealtimeCSG ecosystem. It excludes closed native kernel internals. + +Primary clean inputs: + +- Sander van Rossen, [Realtime CSG - Part 1](https://sandervanrossen.blogspot.com/2009/12/realtime-csg-part-1.html) +- Sander van Rossen, [Realtime CSG - Part 5](https://sandervanrossen.blogspot.com/2010/05/realtime-csg-part-5.html) +- Sander van Rossen, [Realtime CSG - Optimizations](https://sandervanrossen.blogspot.com/2010/05/realtime-csg-optimizations.html) +- [`LogicalError/Realtime-CSG-demo`](https://github.com/LogicalError/Realtime-CSG-demo) +- Patrick Kaster, [BSO3](https://patrickkaster.github.io/BSO3/index.html) +- [Geometry in Milliseconds - Real-Time Constructive Solid Geometry](https://www.classcentral.com/course/youtube-geometry-in-milliseconds-real-time-constructive-solid-geometry-158033) +- [three.js CSG support discussion](https://bleepingcoder.com/three-js/427091361/constructive-solid-geometry-csg-support) + +## Core Model + +Realtime CSG is not primarily a triangle boolean problem. It is a brush polygon +visibility problem. + +The article/blog/demo model starts with convex brushes, cuts source brush +polygons only when needed, classifies the resulting polygon pieces through a +CSG tree, and emits the pieces that remain visible. It does not treat the final +mesh as the authority. The brush tree is the authority; the mesh is cache and +evidence. + +The highest-value shift for `vg_csg` is therefore: + +```text +from: subtract cutter -> produce new convex fragments -> triangulate fragments +to: source brush polygons -> split only at crossing planes -> classify through tree -> emit visible polygon pieces +``` + +Our current convex splitter is useful scaffolding, but it is still a carver. +The target kernel is a classifier. + +## Doctrines + +### 1. Consistency Beats Accuracy + +Floating point geometry fails first through inconsistent decisions, not through +small absolute error. Every plane test needs one shared epsilon policy: + +- distance `< -epsilon` is inside +- distance `> epsilon` is outside +- values between are coplanar + +Do not mix inclusive/exclusive comparisons casually. The same point must not be +inside for one subsystem and coplanar for another because someone got cute with +`>=`. + +For intersections, compute from a canonical direction. If an edge crosses a +plane, pick one input order for interpolation and use it everywhere. Otherwise +adjacent polygons will receive near-identical but non-identical vertices, and +the mesh will die by a thousand decimal-place paper cuts. + +### 2. Brushes Are Convex Plane Sets + +A brush should be authored and cached as a set of planes, not merely as a +triangle soup. Brush mesh creation is the act of finding valid intersections +between those planes and linking the resulting topology. + +Implication for `vg_csg`: add a `ConvexBrush` authored by planes, then derive +polygons, half-edges, bounds, and render buffers from that. Boxes, cylinders, +wedges, stairs, panels, and dome approximations should become constructors for +plane sets where possible. + +### 3. Half-Edges Are Working Topology, Not Render Data + +Rendering buffers are bad workbenches for CSG. Downstream discussions around +three.js make the same point from another angle: render-oriented geometries +often lose topology, indexing, material grouping, or edit intent. + +Use half-edge-like topology while cutting and classifying: + +- split one edge and update both adjacent polygons consistently +- preserve polygon identity, plane identity, material, and visibility +- convert to Bevy mesh buffers only at the final boundary + +This does not require a literal pointer-heavy half-edge graph forever. The +important thing is that the working structure carries adjacency and polygon +ownership better than a pile of final triangles. + +### 4. Boolean Operations Are Routing Tables + +The elegant part of the public demo is category routing. A polygon piece is +classified as: + +- inside +- outside +- aligned/touching inside +- reverse-aligned/touching outside + +Union, intersection, and subtraction can then be expressed through the same +logical OR machinery with category parameter reversal: + +- union: `A || B` +- intersection: `!(!A || !B)` +- subtraction: `!(!A || B)` + +This matters because subtraction stops being a special monster. It becomes +category routing plus orientation reversal. Less bespoke logic, fewer places +for edge cases to hide and invoice us later. + +### 5. Visible Polygons Are Source Polygons With Rejections + +The target algorithm should avoid inventing new surface area except at cuts. +It starts with all brush polygons and removes or hides the pieces that the tree +rejects. + +Top-level output policy: + +- pieces inside the final tree are invisible +- pieces outside the final tree are invisible +- aligned pieces stay visible +- reverse-aligned pieces need winding reversal +- overlapping coplanar surfaces are resolved by tree/order policy so duplicate + faces do not z-fight + +Implication for `vg_csg`: material propagation should be natural if source +polygons remain source polygons. Generated cut faces need explicit policy: +inherit cutter material, source material, or a domain material such as "raw +concrete cut." Do not leave that as accidental array order. + +### 6. Bounds Are Part Of The Algorithm + +Realtime performance comes from refusing work. Branch bounds and brush bounds +allow early-outs before cutting: + +- if polygons cannot touch a branch in union, route to the other side or + outside immediately +- if polygons are outside either side of an intersection, they are outside +- if subtraction cannot touch the right/cutter branch, categorize only the left + +The optimization post reports a major speedup from branch-specific bounds +checks alone. This is not a garnish. Bounds are semantic gates. + +Implication for `vg_csg`: every brush, polygon batch, and branch needs cheap +bounds. Later BVH, hashed grid, or sweep-and-prune should be measured against +the built-in tree culling instead of assumed superior by vibes in a lab coat. + +### 7. Realtime Means Incremental, Not Global + +The public notes frame the algorithm as fast for updating affected brushes, not +as a promise to rebuild the whole world every frame. + +Interactive loop doctrine: + +- base brush meshes are cacheable +- when a brush moves, reprocess it and the brushes it touches +- brush independence creates parallel work units +- global rebuild is a debugging tool and benchmark, not the intended editor + path + +Implication for Bevy: store authoring brushes as ECS/editor state, maintain a +dirty set, find touching brushes through bounds/spatial index, then rebuild +only affected render meshes. + +### 8. CPU Cache Is A First-Class Design Constraint + +Sander explicitly calls out cache-aware rewrite potential and half-edges as the +largest pain point. The three.js discussion arrives at the same pressure from a +different ecosystem: representation conversions, de-indexing, and optimization +passes dominate runtime when data layout is wrong. + +Rust doctrine: + +- favor index-based arenas over pointer graphs +- store hot plane, vertex, edge, polygon fields in compact arrays +- avoid temporary list storms in the inner classifier +- use flags/ranges/scratch arenas before heap allocation +- keep render buffers as output, not internal truth + +The first correct implementation can be plain. The fast implementation should +be built with data layout in mind before the code ossifies into a shapely little +performance tax. + +### 9. Generated Mesh Optimization Is A Separate Pass + +The public material notes remaining T-junctions, unmerged polygons, and +unoptimized output. Downstream engine discussions agree: CSG and final render +mesh optimization are related but distinct jobs. + +Keep the kernel responsible for correct categorized polygon output. Then run +separate passes for: + +- polygon merge +- T-junction repair +- vertex welding +- material grouping +- Bevy mesh/index-buffer packing +- normal/UV generation or preservation + +Do not contaminate the boolean kernel with every render-buffer cleanup trick. +That way lies soup with comments. + +### 10. CSG Belongs Beside Procedural Grammars + +BSO3 is useful philosophically: it ports the article algorithm into a growth +grammar modeller by making boolean set operations a node type. For VibeGeometry, +this means the Rust CSG assembler should not replace procedural generation. It +should be one grammar operator among many. + +Use CSG for: + +- hard architectural voids +- interlocking modular parts +- tunnels, hatches, shafts, sockets, stairs, cut floors +- level-design iteration where negative space is authored + +Use procedural fields and meshes for: + +- cities, settlements, organic detail, clouds, fields, decoration +- high-frequency surface elaboration +- forms whose identity is distribution rather than volume claim + +The level DSL should let these coexist. Boolean nodes cut space; procedural +nodes grow matter and detail around that space. + +## Implementation Target For `vg_csg` + +Near-term kernel shape: + +```text +ConvexBrush { + planes + polygons + half_edges + vertices + bounds +} + +Branch { + op: Union | Intersect | Subtract + left + right + bounds +} + +Process brush: + clone or borrow cached base polygons + classify polygon pieces through root + mark visible / invisible / reversed + emit visible pieces into render mesh +``` + +Algorithm migration steps: + +1. Add polygon categories and visible/reversed flags to `vg_csg`. +2. Add plane-set brush construction independent of `Aabb`. +3. Add a topology arena with vertices, directed edges, polygons, and polygon + plane/material ids. +4. Implement polygon-plane split with one epsilon policy and canonical + intersection direction. +5. Implement brush classification against convex brush planes. +6. Implement boolean branch routing using category list swizzles. +7. Add bounds early-outs before every expensive classification. +8. Add dirty-set and touching-brush discovery. +9. Convert categorized visible polygons to Bevy mesh buffers. +10. Benchmark global rebuild, affected-brush rebuild, and worst-case overlap + scenes before making performance claims. + +## Acceptance Tests To Add + +- 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. +- Reverse-aligned output flips winding exactly once. +- A moved cutter only dirties itself and brushes whose bounds overlap it. +- Polygon split creates shared vertices for adjacent polygons on the same cut. +- Repeated cuts do not create strict-inside triangle centers in the void. +- Material ids survive classification on source faces and follow an explicit + policy on cut faces. +- Global rebuild and affected-brush rebuild produce identical render buffers + for a deterministic scene. + +## Open Questions + +- Should cut faces inherit source material, cutter material, or a DSL-specified + cut material? +- Should Bevy output preserve large n-gons for later optimization, or triangulate + at the boundary immediately? +- What is the best initial spatial rejection structure after branch bounds: + loose grid, BVH, sweep-and-prune, or no extra structure until profiling says + the tree culling is insufficient? +- How much topology should live in reusable library code versus Bevy ECS + components? diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 43cd9c3..d83bfd3 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -50,3 +50,4 @@ {"ts": "2026-05-05T03:31:22+00:00", "type": "implementation", "status": "accepted", "note": "Scaled Lucent external ray florets to Citadel-like utility arms: alternating solar/radiator panels now dwarf the dome and span far along the tether, so a folded defensive posture can plausibly reach toward the media-eye end."} {"ts": "2026-05-06T23:06:51+00:00", "type": "implementation", "status": "accepted", "note": "Added vg_csg, a Rust workspace crate for the new interactive Bevy-compatible CSG path: RealtimeCSG-inspired brush ordering and generations, exact AABB box subtraction, additive cylinder/dome/floret primitives, mesh-array output, DSL, tests, clippy, example, and research doctrine. Public RealtimeCSG source informed the shape but not the hidden native kernel."} {"ts": "2026-05-06T23:20:20+00:00", "type": "implementation", "status": "accepted", "note": "Upgraded vg_csg from AABB-only subtraction to a convex-brush stepping-stone kernel: oriented-box brushes, plane splitting, convex fragment emission, angled-cut tests, and updated RealtimeCSG doctrine. Public LogicalError Realtime-CSG-demo and Sander van Rossen blog/article material are now preferred clean research inputs for the faster target model: polygon classification through boolean trees, not decompiled native internals."} +{"ts": "2026-05-06T23:31:47+00:00", "type": "research", "status": "accepted", "note": "Distilled public Realtime CSG ecosystem doctrine from Sander van Rossen blog posts, LogicalError/Realtime-CSG-demo, BSO3, GDC talk listing, and downstream engine discussions: target vg_csg should migrate from fragment carving toward polygon classification through boolean trees with explicit categories, half-edge/topology working data, bounds early-outs, dirty affected-brush rebuilds, separate mesh optimization, and CSG as one procedural grammar operator."} diff --git a/state/map.yaml b/state/map.yaml index fd8c3c2..65d0430 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -71,6 +71,7 @@ current_artifacts: - notes/vibegeometry-implementation-plan.md - docs/architecture/pipeline-overview.md - docs/research/procedural-doctrine.md + - docs/research/realtime-csg-doctrine.md - docs/research/geometry-script-fork-notes.md - docs/research/translations/ircss-french-houses-system-map.md - docs/research/translations/ircss-french-houses-node-atlas.md