Distill realtime CSG doctrine
This commit is contained in:
parent
6185f94140
commit
efe1558e29
4 changed files with 296 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
290
docs/research/realtime-csg-doctrine.md
Normal file
290
docs/research/realtime-csg-doctrine.md
Normal file
|
|
@ -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?
|
||||
|
|
@ -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."}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue