Build industrial maintenance loop fixture
This commit is contained in:
parent
d7f203f233
commit
9847e1dc91
9 changed files with 43911 additions and 1 deletions
922
crates/vg_csg/examples/industrial_maintenance_loop.rs
Normal file
922
crates/vg_csg/examples/industrial_maintenance_loop.rs
Normal file
|
|
@ -0,0 +1,922 @@
|
|||
use std::{
|
||||
fmt::Write as _,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use bevy_math::Vec3;
|
||||
use vg_csg::{LevelDsl, MaterialId, TriangleMesh};
|
||||
|
||||
const DECK: MaterialId = MaterialId(21);
|
||||
const SHELL: MaterialId = MaterialId(22);
|
||||
const DARK: MaterialId = MaterialId(23);
|
||||
const MACHINE: MaterialId = MaterialId(24);
|
||||
const PIPE: MaterialId = MaterialId(25);
|
||||
const AMBER: MaterialId = MaterialId(26);
|
||||
const BLUE: MaterialId = MaterialId(27);
|
||||
const GLASS: MaterialId = MaterialId(28);
|
||||
const HAZARD: MaterialId = MaterialId(29);
|
||||
const GEAR: MaterialId = MaterialId(30);
|
||||
|
||||
const INNER_LOOP_RADIUS: f32 = 6.6;
|
||||
const OUTER_LOOP_RADIUS: f32 = 16.0;
|
||||
const ROOM_HEIGHT: f32 = 7.2;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut level = LevelDsl::new();
|
||||
let mut shell_mesh = TriangleMesh::new();
|
||||
|
||||
append_curved_room_shell(&mut shell_mesh);
|
||||
append_machinery_island_shroud(&mut shell_mesh);
|
||||
add_central_machinery_island(&mut level);
|
||||
add_inner_manifold_faces(&mut level);
|
||||
add_outer_staging_zone(&mut level);
|
||||
add_supervisor_gallery(&mut level);
|
||||
add_service_arteries_and_harness_gear(&mut level);
|
||||
add_ribs_and_panel_language(&mut level);
|
||||
|
||||
let mut output = level.assemble();
|
||||
let index_offset = output.mesh.positions.len() as u32;
|
||||
output.mesh.positions.extend(shell_mesh.positions);
|
||||
output.mesh.normals.extend(shell_mesh.normals);
|
||||
output.mesh.uvs.extend(shell_mesh.uvs);
|
||||
output.mesh.indices.extend(
|
||||
shell_mesh
|
||||
.indices
|
||||
.into_iter()
|
||||
.map(|index| index + index_offset),
|
||||
);
|
||||
output
|
||||
.mesh
|
||||
.triangle_materials
|
||||
.extend(shell_mesh.triangle_materials);
|
||||
|
||||
let out_dir = output_dir();
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
write_obj_bundle(&out_dir, "industrial_maintenance_loop", &output.mesh)?;
|
||||
fs::write(
|
||||
out_dir.join("industrial_maintenance_loop_preview.svg"),
|
||||
svg_preview(&output.mesh),
|
||||
)?;
|
||||
|
||||
println!(
|
||||
"industrial_maintenance_loop brushes={} convex_fragments={} vertices={} triangles={} candidate_pairs={} rejected_pairs={} warnings={} obj={} preview={}",
|
||||
output.report.input_brushes,
|
||||
output.report.emitted_convex_fragments,
|
||||
output.mesh.vertex_count(),
|
||||
output.mesh.triangle_count(),
|
||||
output.report.candidate_pairs,
|
||||
output.report.rejected_pairs,
|
||||
output.report.warnings.len(),
|
||||
out_dir.join("industrial_maintenance_loop.obj").display(),
|
||||
out_dir
|
||||
.join("industrial_maintenance_loop_preview.svg")
|
||||
.display(),
|
||||
);
|
||||
|
||||
if !output.report.warnings.is_empty() {
|
||||
eprintln!("warnings: {:?}", output.report.warnings);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct LoopFrame {
|
||||
angle: f32,
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
impl LoopFrame {
|
||||
fn at(angle: f32, radius: f32) -> Self {
|
||||
Self { angle, radius }
|
||||
}
|
||||
|
||||
fn tangent(self) -> Vec3 {
|
||||
Vec3::new(-self.angle.sin(), self.angle.cos(), 0.0)
|
||||
}
|
||||
|
||||
fn radial(self) -> Vec3 {
|
||||
Vec3::new(self.angle.cos(), self.angle.sin(), 0.0)
|
||||
}
|
||||
|
||||
fn point(self, tangent: f32, radial: f32, z: f32) -> Vec3 {
|
||||
self.radial() * (self.radius + radial) + self.tangent() * tangent + Vec3::Z * z
|
||||
}
|
||||
|
||||
fn yaw(self) -> bevy_math::Quat {
|
||||
bevy_math::Quat::from_rotation_z(self.angle + std::f32::consts::FRAC_PI_2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct LoopBox {
|
||||
tangent: f32,
|
||||
radial: f32,
|
||||
z: f32,
|
||||
size_tangent: f32,
|
||||
size_radial: f32,
|
||||
size_z: f32,
|
||||
material: MaterialId,
|
||||
}
|
||||
|
||||
fn loop_box(level: &mut LevelDsl, name: impl Into<String>, frame: LoopFrame, spec: LoopBox) {
|
||||
level.solid_oriented_box(
|
||||
name,
|
||||
frame.point(spec.tangent, spec.radial, spec.z),
|
||||
Vec3::new(spec.size_tangent, spec.size_radial, spec.size_z),
|
||||
frame.yaw(),
|
||||
spec.material,
|
||||
);
|
||||
}
|
||||
|
||||
fn append_curved_room_shell(mesh: &mut TriangleMesh) {
|
||||
append_annular_band(
|
||||
mesh,
|
||||
ArcBandSpec::new(INNER_LOOP_RADIUS, OUTER_LOOP_RADIUS, -2.85, 2.85, 0.0, DECK).segments(56),
|
||||
);
|
||||
append_annular_band(
|
||||
mesh,
|
||||
ArcBandSpec::new(
|
||||
INNER_LOOP_RADIUS - 0.35,
|
||||
OUTER_LOOP_RADIUS + 0.25,
|
||||
-2.85,
|
||||
2.85,
|
||||
ROOM_HEIGHT,
|
||||
SHELL,
|
||||
)
|
||||
.segments(56),
|
||||
);
|
||||
append_vertical_arc_wall(
|
||||
mesh,
|
||||
ArcWallSpec::new(
|
||||
OUTER_LOOP_RADIUS + 0.1,
|
||||
-2.85,
|
||||
2.85,
|
||||
0.0,
|
||||
ROOM_HEIGHT,
|
||||
SHELL,
|
||||
)
|
||||
.segments(56),
|
||||
);
|
||||
append_vertical_arc_wall(
|
||||
mesh,
|
||||
ArcWallSpec::new(
|
||||
INNER_LOOP_RADIUS - 0.25,
|
||||
-2.85,
|
||||
2.85,
|
||||
0.0,
|
||||
ROOM_HEIGHT,
|
||||
DARK,
|
||||
)
|
||||
.segments(56),
|
||||
);
|
||||
|
||||
for index in 0..28 {
|
||||
let a0 = -2.85 + index as f32 * (5.70 / 28.0);
|
||||
let a1 = a0 + 5.70 / 28.0 * 0.82;
|
||||
append_annular_band(
|
||||
mesh,
|
||||
ArcBandSpec::new(
|
||||
INNER_LOOP_RADIUS + 0.4,
|
||||
OUTER_LOOP_RADIUS - 0.55,
|
||||
a0,
|
||||
a1,
|
||||
0.035,
|
||||
if index % 5 == 0 { HAZARD } else { DECK },
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_central_machinery_island(level: &mut LevelDsl) {
|
||||
for index in 0..24 {
|
||||
let angle = index as f32 * std::f32::consts::TAU / 24.0;
|
||||
let frame = LoopFrame::at(angle, 5.45);
|
||||
let height = 2.0 + (index % 4) as f32 * 0.75;
|
||||
loop_box(
|
||||
level,
|
||||
format!("machinery vertical casing {index}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.0,
|
||||
z: height * 0.5 + 0.35,
|
||||
size_tangent: 0.85,
|
||||
size_radial: 0.7,
|
||||
size_z: height,
|
||||
material: if index % 3 == 0 { PIPE } else { MACHINE },
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("amber island screen {index}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.38,
|
||||
z: 1.55 + (index % 3) as f32 * 0.55,
|
||||
size_tangent: 0.48,
|
||||
size_radial: 0.08,
|
||||
size_z: 0.42,
|
||||
material: AMBER,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn append_machinery_island_shroud(mesh: &mut TriangleMesh) {
|
||||
append_vertical_arc_wall(
|
||||
mesh,
|
||||
ArcWallSpec::new(
|
||||
5.25,
|
||||
-std::f32::consts::PI,
|
||||
std::f32::consts::PI,
|
||||
0.15,
|
||||
ROOM_HEIGHT,
|
||||
DARK,
|
||||
)
|
||||
.segments(72),
|
||||
);
|
||||
append_vertical_arc_wall(
|
||||
mesh,
|
||||
ArcWallSpec::new(
|
||||
5.72,
|
||||
-std::f32::consts::PI,
|
||||
std::f32::consts::PI,
|
||||
0.0,
|
||||
0.45,
|
||||
MACHINE,
|
||||
)
|
||||
.segments(72),
|
||||
);
|
||||
append_vertical_arc_wall(
|
||||
mesh,
|
||||
ArcWallSpec::new(
|
||||
5.72,
|
||||
-std::f32::consts::PI,
|
||||
std::f32::consts::PI,
|
||||
ROOM_HEIGHT - 0.45,
|
||||
ROOM_HEIGHT,
|
||||
MACHINE,
|
||||
)
|
||||
.segments(72),
|
||||
);
|
||||
}
|
||||
|
||||
fn add_inner_manifold_faces(level: &mut LevelDsl) {
|
||||
for bay in 0..18 {
|
||||
let angle = -2.55 + bay as f32 * (5.10 / 17.0);
|
||||
let frame = LoopFrame::at(angle, INNER_LOOP_RADIUS);
|
||||
loop_box(
|
||||
level,
|
||||
format!("gasket-rimmed manifold panel {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: -0.35,
|
||||
z: 1.45,
|
||||
size_tangent: 1.05,
|
||||
size_radial: 0.22,
|
||||
size_z: 1.9,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("round access hatch square proxy {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: -0.18,
|
||||
radial: -0.52,
|
||||
z: 1.25,
|
||||
size_tangent: 0.52,
|
||||
size_radial: 0.12,
|
||||
size_z: 0.52,
|
||||
material: DARK,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("diagnostic amber strip {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.26,
|
||||
radial: -0.55,
|
||||
z: 1.95,
|
||||
size_tangent: 0.36,
|
||||
size_radial: 0.08,
|
||||
size_z: 0.58,
|
||||
material: AMBER,
|
||||
},
|
||||
);
|
||||
|
||||
if bay % 2 == 0 {
|
||||
add_pipe_bundle(level, frame, bay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_pipe_bundle(level: &mut LevelDsl, frame: LoopFrame, index: usize) {
|
||||
for pipe in 0..3 {
|
||||
loop_box(
|
||||
level,
|
||||
format!("inner manifold cable bundle {index}-{pipe}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: -0.45 + pipe as f32 * 0.45,
|
||||
radial: -0.78,
|
||||
z: 3.2 + pipe as f32 * 0.42,
|
||||
size_tangent: 0.16,
|
||||
size_radial: 0.28,
|
||||
size_z: 2.3,
|
||||
material: PIPE,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_outer_staging_zone(level: &mut LevelDsl) {
|
||||
for bay in 0..20 {
|
||||
let angle = -2.65 + bay as f32 * (5.30 / 19.0);
|
||||
let frame = LoopFrame::at(angle, OUTER_LOOP_RADIUS - 1.15);
|
||||
loop_box(
|
||||
level,
|
||||
format!("bolted outer anchor rail {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.0,
|
||||
z: 0.55,
|
||||
size_tangent: 1.25,
|
||||
size_radial: 0.18,
|
||||
size_z: 0.18,
|
||||
material: HAZARD,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("sliding rail block {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: if bay % 2 == 0 { -0.32 } else { 0.34 },
|
||||
radial: -0.2,
|
||||
z: 0.78,
|
||||
size_tangent: 0.28,
|
||||
size_radial: 0.42,
|
||||
size_z: 0.32,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
|
||||
if bay % 4 == 1 {
|
||||
add_tool_locker(level, frame, bay);
|
||||
}
|
||||
if bay % 5 == 2 {
|
||||
add_cart_and_rescue_line(level, frame, bay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tool_locker(level: &mut LevelDsl, frame: LoopFrame, bay: usize) {
|
||||
loop_box(
|
||||
level,
|
||||
format!("outer tool locker {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.78,
|
||||
z: 1.05,
|
||||
size_tangent: 0.72,
|
||||
size_radial: 0.55,
|
||||
size_z: 1.8,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("blue locker readout {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.18,
|
||||
radial: 0.48,
|
||||
z: 1.45,
|
||||
size_tangent: 0.26,
|
||||
size_radial: 0.06,
|
||||
size_z: 0.36,
|
||||
material: BLUE,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn add_cart_and_rescue_line(level: &mut LevelDsl, frame: LoopFrame, bay: usize) {
|
||||
loop_box(
|
||||
level,
|
||||
format!("low maintenance cart {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: -0.18,
|
||||
radial: -0.72,
|
||||
z: 0.38,
|
||||
size_tangent: 0.85,
|
||||
size_radial: 0.55,
|
||||
size_z: 0.55,
|
||||
material: GEAR,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("clipped rescue line {bay}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.36,
|
||||
radial: -0.62,
|
||||
z: 0.88,
|
||||
size_tangent: 0.1,
|
||||
size_radial: 1.15,
|
||||
size_z: 0.1,
|
||||
material: PIPE,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn add_supervisor_gallery(level: &mut LevelDsl) {
|
||||
let frame = LoopFrame::at(-1.85, OUTER_LOOP_RADIUS - 1.0);
|
||||
loop_box(
|
||||
level,
|
||||
"single supervisor gallery deck",
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.1,
|
||||
z: 3.65,
|
||||
size_tangent: 5.4,
|
||||
size_radial: 1.35,
|
||||
size_z: 0.28,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
"supervisor gallery glass face",
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: -0.65,
|
||||
z: 4.55,
|
||||
size_tangent: 5.2,
|
||||
size_radial: 0.12,
|
||||
size_z: 1.35,
|
||||
material: GLASS,
|
||||
},
|
||||
);
|
||||
for step in 0..9 {
|
||||
loop_box(
|
||||
level,
|
||||
format!("visible gallery stair step {step}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: -3.2 + step as f32 * 0.34,
|
||||
radial: -1.6 + step as f32 * 0.12,
|
||||
z: 0.42 + step as f32 * 0.33,
|
||||
size_tangent: 0.54,
|
||||
size_radial: 0.72,
|
||||
size_z: 0.12,
|
||||
material: HAZARD,
|
||||
},
|
||||
);
|
||||
}
|
||||
for panel in 0..4 {
|
||||
loop_box(
|
||||
level,
|
||||
format!("blue supervisor console {panel}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: -1.8 + panel as f32 * 1.2,
|
||||
radial: 0.08,
|
||||
z: 4.15,
|
||||
size_tangent: 0.75,
|
||||
size_radial: 0.18,
|
||||
size_z: 0.42,
|
||||
material: BLUE,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_service_arteries_and_harness_gear(level: &mut LevelDsl) {
|
||||
for mouth in 0..7 {
|
||||
let angle = -0.55 + mouth as f32 * 0.18;
|
||||
let frame = LoopFrame::at(angle, INNER_LOOP_RADIUS);
|
||||
loop_box(
|
||||
level,
|
||||
format!("dark service artery mouth {mouth}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: -0.72,
|
||||
z: 1.0 + (mouth % 3) as f32 * 0.55,
|
||||
size_tangent: 0.5,
|
||||
size_radial: 0.32,
|
||||
size_z: 0.42,
|
||||
material: DARK,
|
||||
},
|
||||
);
|
||||
loop_box(
|
||||
level,
|
||||
format!("pipe tunnel throat {mouth}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: -1.35,
|
||||
z: 1.0 + (mouth % 3) as f32 * 0.55,
|
||||
size_tangent: 0.32,
|
||||
size_radial: 1.2,
|
||||
size_z: 0.28,
|
||||
material: PIPE,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let rack_frame = LoopFrame::at(0.78, OUTER_LOOP_RADIUS - 0.8);
|
||||
loop_box(
|
||||
level,
|
||||
"dry-operation mobility gear wall rack",
|
||||
rack_frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.65,
|
||||
z: 1.65,
|
||||
size_tangent: 2.1,
|
||||
size_radial: 0.24,
|
||||
size_z: 2.2,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
for item in 0..8 {
|
||||
loop_box(
|
||||
level,
|
||||
format!("folded harness loop and cartridge {item}"),
|
||||
rack_frame,
|
||||
LoopBox {
|
||||
tangent: -0.85 + item as f32 * 0.24,
|
||||
radial: 0.38,
|
||||
z: 0.8 + (item % 4) as f32 * 0.42,
|
||||
size_tangent: 0.12,
|
||||
size_radial: 0.18,
|
||||
size_z: 0.62,
|
||||
material: if item % 3 == 0 { BLUE } else { GEAR },
|
||||
},
|
||||
);
|
||||
}
|
||||
for tube in 0..5 {
|
||||
loop_box(
|
||||
level,
|
||||
format!("coiled oxygenation tube proxy {tube}"),
|
||||
rack_frame,
|
||||
LoopBox {
|
||||
tangent: -0.75 + tube as f32 * 0.38,
|
||||
radial: -0.42,
|
||||
z: 0.46,
|
||||
size_tangent: 0.28,
|
||||
size_radial: 0.28,
|
||||
size_z: 0.24,
|
||||
material: PIPE,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_ribs_and_panel_language(level: &mut LevelDsl) {
|
||||
for rib in 0..18 {
|
||||
let angle = -2.75 + rib as f32 * (5.50 / 17.0);
|
||||
let frame = LoopFrame::at(angle, (INNER_LOOP_RADIUS + OUTER_LOOP_RADIUS) * 0.5);
|
||||
loop_box(
|
||||
level,
|
||||
format!("manufactured shell rib {rib}"),
|
||||
frame,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.0,
|
||||
z: ROOM_HEIGHT - 0.28,
|
||||
size_tangent: 0.18,
|
||||
size_radial: OUTER_LOOP_RADIUS - INNER_LOOP_RADIUS + 0.8,
|
||||
size_z: 0.48,
|
||||
material: MACHINE,
|
||||
},
|
||||
);
|
||||
let outer = LoopFrame::at(angle, OUTER_LOOP_RADIUS - 0.15);
|
||||
loop_box(
|
||||
level,
|
||||
format!("outer shell pressure housing {rib}"),
|
||||
outer,
|
||||
LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.0,
|
||||
z: 3.15,
|
||||
size_tangent: 0.75,
|
||||
size_radial: 0.42,
|
||||
size_z: 1.7,
|
||||
material: if rib % 4 == 0 { AMBER } else { SHELL },
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ArcBandSpec {
|
||||
inner: f32,
|
||||
outer: f32,
|
||||
start: f32,
|
||||
end: f32,
|
||||
segments: usize,
|
||||
z: f32,
|
||||
material: MaterialId,
|
||||
}
|
||||
|
||||
impl ArcBandSpec {
|
||||
fn new(inner: f32, outer: f32, start: f32, end: f32, z: f32, material: MaterialId) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
outer,
|
||||
start,
|
||||
end,
|
||||
segments: 1,
|
||||
z,
|
||||
material,
|
||||
}
|
||||
}
|
||||
|
||||
fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ArcWallSpec {
|
||||
radius: f32,
|
||||
start: f32,
|
||||
end: f32,
|
||||
segments: usize,
|
||||
z_min: f32,
|
||||
z_max: f32,
|
||||
material: MaterialId,
|
||||
}
|
||||
|
||||
impl ArcWallSpec {
|
||||
fn new(
|
||||
radius: f32,
|
||||
start: f32,
|
||||
end: f32,
|
||||
z_min: f32,
|
||||
z_max: f32,
|
||||
material: MaterialId,
|
||||
) -> Self {
|
||||
Self {
|
||||
radius,
|
||||
start,
|
||||
end,
|
||||
segments: 1,
|
||||
z_min,
|
||||
z_max,
|
||||
material,
|
||||
}
|
||||
}
|
||||
|
||||
fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn append_annular_band(mesh: &mut TriangleMesh, spec: ArcBandSpec) {
|
||||
let segments = spec.segments.max(1);
|
||||
for index in 0..segments {
|
||||
let a0 = spec.start + (spec.end - spec.start) * index as f32 / segments as f32;
|
||||
let a1 = spec.start + (spec.end - spec.start) * (index + 1) as f32 / segments as f32;
|
||||
let p0 = polar_point(spec.inner, a0, spec.z);
|
||||
let p1 = polar_point(spec.outer, a0, spec.z);
|
||||
let p2 = polar_point(spec.outer, a1, spec.z);
|
||||
let p3 = polar_point(spec.inner, a1, spec.z);
|
||||
let normal = if spec.z > ROOM_HEIGHT * 0.5 {
|
||||
Vec3::NEG_Z
|
||||
} else {
|
||||
Vec3::Z
|
||||
};
|
||||
mesh.append_quad([p0, p1, p2, p3], normal, spec.material);
|
||||
}
|
||||
}
|
||||
|
||||
fn append_vertical_arc_wall(mesh: &mut TriangleMesh, spec: ArcWallSpec) {
|
||||
let segments = spec.segments.max(1);
|
||||
for index in 0..segments {
|
||||
let a0 = spec.start + (spec.end - spec.start) * index as f32 / segments as f32;
|
||||
let a1 = spec.start + (spec.end - spec.start) * (index + 1) as f32 / segments as f32;
|
||||
let p0 = polar_point(spec.radius, a0, spec.z_min);
|
||||
let p1 = polar_point(spec.radius, a1, spec.z_min);
|
||||
let p2 = polar_point(spec.radius, a1, spec.z_max);
|
||||
let p3 = polar_point(spec.radius, a0, spec.z_max);
|
||||
let normal = Vec3::new(((a0 + a1) * 0.5).cos(), ((a0 + a1) * 0.5).sin(), 0.0);
|
||||
mesh.append_quad([p0, p1, p2, p3], normal, spec.material);
|
||||
}
|
||||
}
|
||||
|
||||
fn polar_point(radius: f32, angle: f32, z: f32) -> Vec3 {
|
||||
Vec3::new(angle.cos() * radius, angle.sin() * radius, z)
|
||||
}
|
||||
|
||||
fn output_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("experiments")
|
||||
.join("generated")
|
||||
.join("industrial_maintenance_loop")
|
||||
}
|
||||
|
||||
fn write_obj_bundle(dir: &Path, name: &str, mesh: &TriangleMesh) -> io::Result<()> {
|
||||
fs::write(dir.join(format!("{name}.mtl")), material_library())?;
|
||||
fs::write(dir.join(format!("{name}.obj")), obj_text(name, mesh))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn obj_text(name: &str, mesh: &TriangleMesh) -> String {
|
||||
let mut out = String::new();
|
||||
let _ = writeln!(out, "mtllib {name}.mtl");
|
||||
let _ = writeln!(out, "o {name}");
|
||||
|
||||
for position in &mesh.positions {
|
||||
let _ = writeln!(out, "v {} {} {}", position.x, position.y, position.z);
|
||||
}
|
||||
for uv in &mesh.uvs {
|
||||
let _ = writeln!(out, "vt {} {}", uv.x, uv.y);
|
||||
}
|
||||
for normal in &mesh.normals {
|
||||
let _ = writeln!(out, "vn {} {} {}", normal.x, normal.y, normal.z);
|
||||
}
|
||||
|
||||
let mut active_material = None;
|
||||
for (tri_index, face) in mesh.indices.chunks_exact(3).enumerate() {
|
||||
let material = mesh
|
||||
.triangle_materials
|
||||
.get(tri_index)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
if active_material != Some(material) {
|
||||
let _ = writeln!(out, "usemtl {}", material_name(material));
|
||||
active_material = Some(material);
|
||||
}
|
||||
let a = face[0] + 1;
|
||||
let b = face[1] + 1;
|
||||
let c = face[2] + 1;
|
||||
let _ = writeln!(out, "f {a}/{a}/{a} {b}/{b}/{b} {c}/{c}/{c}");
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn material_library() -> String {
|
||||
[
|
||||
material_text(DECK, "0.21 0.20 0.18", "0.015 0.012 0.010"),
|
||||
material_text(SHELL, "0.28 0.26 0.22", "0.015 0.012 0.010"),
|
||||
material_text(DARK, "0.045 0.043 0.040", "0.0 0.0 0.0"),
|
||||
material_text(MACHINE, "0.18 0.17 0.15", "0.018 0.015 0.012"),
|
||||
material_text(PIPE, "0.09 0.085 0.075", "0.01 0.008 0.006"),
|
||||
material_text(AMBER, "0.95 0.48 0.12", "0.70 0.26 0.035"),
|
||||
material_text(BLUE, "0.08 0.55 0.95", "0.03 0.28 0.60"),
|
||||
material_text(GLASS, "0.30 0.65 0.90", "0.04 0.20 0.36"),
|
||||
material_text(HAZARD, "0.85 0.58 0.12", "0.20 0.09 0.02"),
|
||||
material_text(GEAR, "0.18 0.22 0.24", "0.02 0.025 0.03"),
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn material_text(id: MaterialId, kd: &str, ke: &str) -> String {
|
||||
format!("newmtl {}\nKd {kd}\nKe {ke}\nNs 65\n", material_name(id))
|
||||
}
|
||||
|
||||
fn material_name(id: MaterialId) -> &'static str {
|
||||
match id {
|
||||
DECK => "mat_scuffed_deck",
|
||||
SHELL => "mat_curved_shell",
|
||||
DARK => "mat_dark_substrate",
|
||||
MACHINE => "mat_machinery",
|
||||
PIPE => "mat_pipe_trunks",
|
||||
AMBER => "mat_amber_screens",
|
||||
BLUE => "mat_blue_diagnostics",
|
||||
GLASS => "mat_gallery_glass",
|
||||
HAZARD => "mat_anchor_hazard",
|
||||
GEAR => "mat_mobility_gear",
|
||||
_ => "mat_default",
|
||||
}
|
||||
}
|
||||
|
||||
fn svg_preview(mesh: &TriangleMesh) -> String {
|
||||
let width = 1600.0_f32;
|
||||
let height = 1050.0_f32;
|
||||
let margin = 34.0_f32;
|
||||
let mut projected = Vec::with_capacity(mesh.positions.len());
|
||||
|
||||
for point in &mesh.positions {
|
||||
let x = (point.x - point.y) * 0.92;
|
||||
let y = (point.x + point.y) * 0.38 - point.z * 1.35;
|
||||
let depth = point.x + point.y + point.z * 0.75;
|
||||
projected.push((x, y, depth));
|
||||
}
|
||||
|
||||
let mut min_x = f32::INFINITY;
|
||||
let mut max_x = f32::NEG_INFINITY;
|
||||
let mut min_y = f32::INFINITY;
|
||||
let mut max_y = f32::NEG_INFINITY;
|
||||
for (x, y, _) in &projected {
|
||||
min_x = min_x.min(*x);
|
||||
max_x = max_x.max(*x);
|
||||
min_y = min_y.min(*y);
|
||||
max_y = max_y.max(*y);
|
||||
}
|
||||
|
||||
let span_x = (max_x - min_x).max(1.0);
|
||||
let span_y = (max_y - min_y).max(1.0);
|
||||
let scale = ((width - margin * 2.0) / span_x).min((height - margin * 2.0) / span_y);
|
||||
let ox = (width - span_x * scale) * 0.5 - min_x * scale;
|
||||
let oy = (height - span_y * scale) * 0.5 - min_y * scale;
|
||||
|
||||
let mut tris = Vec::with_capacity(mesh.triangle_count());
|
||||
for (tri_index, face) in mesh.indices.chunks_exact(3).enumerate() {
|
||||
let a = projected[face[0] as usize];
|
||||
let b = projected[face[1] as usize];
|
||||
let c = projected[face[2] as usize];
|
||||
let depth = (a.2 + b.2 + c.2) / 3.0;
|
||||
let points = [
|
||||
(a.0 * scale + ox, a.1 * scale + oy),
|
||||
(b.0 * scale + ox, b.1 * scale + oy),
|
||||
(c.0 * scale + ox, c.1 * scale + oy),
|
||||
];
|
||||
let material = mesh
|
||||
.triangle_materials
|
||||
.get(tri_index)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
tris.push((depth, material, points));
|
||||
}
|
||||
tris.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
|
||||
let mut out = String::new();
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 {width} {height}\">"
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<rect width=\"100%\" height=\"100%\" fill=\"#090908\"/>"
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<g stroke=\"#050504\" stroke-width=\"0.42\" stroke-linejoin=\"round\">"
|
||||
);
|
||||
for (_, material, points) in tris {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<polygon points=\"{:.2},{:.2} {:.2},{:.2} {:.2},{:.2}\" fill=\"{}\" opacity=\"{}\"/>",
|
||||
points[0].0,
|
||||
points[0].1,
|
||||
points[1].0,
|
||||
points[1].1,
|
||||
points[2].0,
|
||||
points[2].1,
|
||||
svg_fill(material),
|
||||
svg_opacity(material),
|
||||
);
|
||||
}
|
||||
let _ = writeln!(out, "</g>");
|
||||
let _ = writeln!(out, "</svg>");
|
||||
out
|
||||
}
|
||||
|
||||
fn svg_fill(id: MaterialId) -> &'static str {
|
||||
match id {
|
||||
DECK => "#35322d",
|
||||
SHELL => "#474239",
|
||||
DARK => "#10100f",
|
||||
MACHINE => "#2c2924",
|
||||
PIPE => "#181612",
|
||||
AMBER => "#e88b23",
|
||||
BLUE => "#1796dd",
|
||||
GLASS => "#68b9df",
|
||||
HAZARD => "#d79827",
|
||||
GEAR => "#29363b",
|
||||
_ => "#808080",
|
||||
}
|
||||
}
|
||||
|
||||
fn svg_opacity(id: MaterialId) -> &'static str {
|
||||
match id {
|
||||
SHELL => "0.28",
|
||||
DECK => "0.84",
|
||||
DARK => "0.72",
|
||||
GLASS => "0.36",
|
||||
AMBER | BLUE => "0.92",
|
||||
_ => "1.0",
|
||||
}
|
||||
}
|
||||
79
docs/research/builds/industrial-maintenance-loop.md
Normal file
79
docs/research/builds/industrial-maintenance-loop.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Industrial Maintenance Loop
|
||||
|
||||
Grounded fixture generated from the maintenance-loop reference prompt and image.
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cargo run -p vg_csg --example industrial_maintenance_loop
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `experiments/generated/industrial_maintenance_loop/industrial_maintenance_loop.obj`
|
||||
- `experiments/generated/industrial_maintenance_loop/industrial_maintenance_loop.mtl`
|
||||
- `experiments/generated/industrial_maintenance_loop/industrial_maintenance_loop_preview.svg`
|
||||
- `experiments/generated/industrial_maintenance_loop/industrial_maintenance_loop_preview.png`
|
||||
|
||||
Current build stats:
|
||||
|
||||
- brushes: 266
|
||||
- convex fragments: 266
|
||||
- vertices: 11,448
|
||||
- triangles: 4,128
|
||||
- candidate pairs: 0
|
||||
- rejected pairs: 0
|
||||
- CSG warnings: 0
|
||||
|
||||
## Composition
|
||||
|
||||
The scene is a single-level industrial maintenance loop built into a curved
|
||||
habitat shell. It includes:
|
||||
|
||||
- annular scuffed deck, curved outer shell wall, inner island shroud, and
|
||||
continuous ceiling previewed as x-ray shell material
|
||||
- one floor-to-ceiling central machinery island that blocks the far loop
|
||||
- inner manifold faces with gasket panel proxies, access hatches, amber
|
||||
diagnostic strips, and cable bundles
|
||||
- outer staging zone with anchor rail, sliding rail blocks, lockers, carts,
|
||||
clipped rescue lines, and hazard markings
|
||||
- one raised supervisor gallery segment behind glass with a visible stair
|
||||
- service artery mouths and pipe-tunnel throats entering the machinery island
|
||||
- dry-operation mobility gear rack with folded harness loops, cartridges,
|
||||
oxygenation tube proxies, and charging/detail blocks
|
||||
|
||||
## DSL Lesson
|
||||
|
||||
This fixture is mostly about coordinate systems. The example introduces a local
|
||||
`LoopFrame` helper:
|
||||
|
||||
```rust
|
||||
let frame = LoopFrame::at(angle, OUTER_LOOP_RADIUS - 1.15);
|
||||
loop_box(level, "bolted outer anchor rail", frame, LoopBox {
|
||||
tangent: 0.0,
|
||||
radial: 0.0,
|
||||
z: 0.55,
|
||||
size_tangent: 1.25,
|
||||
size_radial: 0.18,
|
||||
size_z: 0.18,
|
||||
material: HAZARD,
|
||||
});
|
||||
```
|
||||
|
||||
The move is simple and important: author each surface in its own domain. The
|
||||
outer staging zone thinks in tangent/radial/up. The inner manifold face uses
|
||||
the same axes at a different radius. The gallery owns one angular segment. The
|
||||
shell is generated as annular and vertical arc bands. The world-space brush
|
||||
placement becomes a compilation step instead of the authoring language.
|
||||
|
||||
That is the shape the DSL should grow toward: named local coordinate contracts,
|
||||
small repeated detail organs, and late mapping into CSG brushes or direct mesh
|
||||
panels depending on whether the surface needs boolean behavior.
|
||||
|
||||
## Caveat
|
||||
|
||||
The SVG preview is an inspection cutaway, not a physical render. The model
|
||||
contains a continuous ceiling and shell, but the preview makes shell materials
|
||||
transparent so the internal fixture can be judged without a renderer. A Bevy or
|
||||
Blender pass should turn this into a lit interior view with proper occlusion,
|
||||
glass, emissive panels, and worn materials.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
newmtl mat_scuffed_deck
|
||||
Kd 0.21 0.20 0.18
|
||||
Ke 0.015 0.012 0.010
|
||||
Ns 65
|
||||
|
||||
newmtl mat_curved_shell
|
||||
Kd 0.28 0.26 0.22
|
||||
Ke 0.015 0.012 0.010
|
||||
Ns 65
|
||||
|
||||
newmtl mat_dark_substrate
|
||||
Kd 0.045 0.043 0.040
|
||||
Ke 0.0 0.0 0.0
|
||||
Ns 65
|
||||
|
||||
newmtl mat_machinery
|
||||
Kd 0.18 0.17 0.15
|
||||
Ke 0.018 0.015 0.012
|
||||
Ns 65
|
||||
|
||||
newmtl mat_pipe_trunks
|
||||
Kd 0.09 0.085 0.075
|
||||
Ke 0.01 0.008 0.006
|
||||
Ns 65
|
||||
|
||||
newmtl mat_amber_screens
|
||||
Kd 0.95 0.48 0.12
|
||||
Ke 0.70 0.26 0.035
|
||||
Ns 65
|
||||
|
||||
newmtl mat_blue_diagnostics
|
||||
Kd 0.08 0.55 0.95
|
||||
Ke 0.03 0.28 0.60
|
||||
Ns 65
|
||||
|
||||
newmtl mat_gallery_glass
|
||||
Kd 0.30 0.65 0.90
|
||||
Ke 0.04 0.20 0.36
|
||||
Ns 65
|
||||
|
||||
newmtl mat_anchor_hazard
|
||||
Kd 0.85 0.58 0.12
|
||||
Ke 0.20 0.09 0.02
|
||||
Ns 65
|
||||
|
||||
newmtl mat_mobility_gear
|
||||
Kd 0.18 0.22 0.24
|
||||
Ke 0.02 0.025 0.03
|
||||
Ns 65
|
||||
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 693 KiB |
File diff suppressed because it is too large
Load diff
|
After Width: | Height: | Size: 372 KiB |
|
|
@ -74,6 +74,15 @@ cut foundation nave, central glass dome, anchor-city towers, giant alternating
|
|||
solar/radiator florets, looping cargo arteries, and material partitions. Build
|
||||
note: `docs/research/builds/dream-machine-level.md`.
|
||||
|
||||
Grounded fixture example: `cargo run -p vg_csg --example
|
||||
industrial_maintenance_loop` emits
|
||||
`experiments/generated/industrial_maintenance_loop/` with OBJ, MTL, SVG, and PNG
|
||||
preview artifacts. It builds a curved habitat-shell maintenance loop from local
|
||||
tangent/radial/up frames: central floor-to-ceiling machinery island, inner
|
||||
manifold faces, outer staging rail/tools, one supervisor gallery segment,
|
||||
service artery mouths, and dry-operation mobility gear. Build note:
|
||||
`docs/research/builds/industrial-maintenance-loop.md`.
|
||||
|
||||
`crates/vg_grammar` is the Dream Machine grammar layer above CSG. Rules emit
|
||||
semantic solid/void claims with tags, then compile those claims into `vg_csg`
|
||||
brushes. Current features: local `Frame`s, oriented claims, seeded variation,
|
||||
|
|
|
|||
|
|
@ -73,3 +73,4 @@
|
|||
{"ts": "2026-05-07T14:55:58+00:00", "type": "implementation", "status": "ok", "note": "Added explicit rebuild-strategy fields to vg_csg BuildReport and perf JSON: used_checkpoint, checkpoint_rebuild_from, and direct_dirty_rebuild. Tests now pin direct box fallback, general convex checkpoint replay, and reused_mesh for rejected general convex edits so future timings cannot hide which path ran."}
|
||||
{"ts": "2026-05-07T15:08:41+00:00", "type": "implementation", "status": "ok", "note": "Rust CSG vertical slice added: dream_machine_level example emits a named multi-part machine-cathedral OBJ/MTL/SVG artifact, proving LevelDsl can carry visible showpiece blockouts before Bevy renderer integration."}
|
||||
{"ts": "2026-05-07T15:42:22+00:00", "type": "review", "status": "revised", "note": "Dream Machine preview rejected the first CSG showpiece silhouette: rectangular nave, roof ribs, and flat petals overpowered the dome. Revised toward circular hub, visible dome, lowered city mass, orbiting arteries, and segmented curved ray panels; thumbnail silhouette must read before detail budget matters."}
|
||||
{"ts": "2026-05-07T17:36:47+00:00", "type": "implementation", "status": "ok", "note": "Added industrial_maintenance_loop Rust CSG fixture from grounded reference prompt. Durable DSL lesson: curved habitat room details should be authored in local tangent/radial/up frames per domain, then compiled into world brushes or direct mesh bands."}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ current_artifacts:
|
|||
- docs/research/builds/aetheria-bloom-habitat.md
|
||||
- docs/research/builds/lucent-tether-habitat.md
|
||||
- docs/research/builds/dream-machine-level.md
|
||||
- docs/research/builds/industrial-maintenance-loop.md
|
||||
- docs/research/realtime-csg-bevy-assembler.md
|
||||
- crates/vg_csg/
|
||||
- crates/vg_grammar/
|
||||
|
|
@ -111,7 +112,7 @@ data_flow:
|
|||
- If state grows faster than understanding, cut it back.
|
||||
|
||||
current_status:
|
||||
summary: VibeGeometry has verified Geometry Script translations for local samples, official Blender demos, and IRCSS French Houses footholds, plus from-scratch Aetheria Bloom and Lucent tether habitat builds. Durable lessons now driving the pipeline: Python orchestrates intent and batching; Geometry Script emits inspectable node groups; source-bound scenes need explicit coordinate contracts before ornament; spin-gravity habitats need ground planes orthogonal to their tether/rotation axis; utility petals, radiators, and solar arrays belong outside pressure glass and may need Citadel-scale presence; large habitat parts need visible load paths across coordinate frames; organic city maps need street fields, irregular parcels, parks, and land-use gradients rather than grids; dense detail must be batched; institutional spaces should model power as surfaces, rails, overlays, thresholds, shader behavior, and camera evidence; and visual acceptance needs domain-facing inspection renders plus verifier checks, not only overview renders. The first reusable `vibegeometry/` helper library now carries coordinate frames, fields, batching, Blender scene primitives, and verifier helpers used by the Bloom and Lucent builds. The Rust crate `vg_csg` now has a RealtimeCSG-shaped API skeleton plus a convex-brush stepping-stone kernel: CSG operation ordinals, branch vocabulary, tree/branch/brush handles, child and operation mutation, exact parity fixtures, ordered assembler compilation, AABB/oriented-box subtraction/intersection via convex plane splitting, crossing polygon category buckets, primitive/solid/tree bounds, additive cylinder/dome/floret primitives, JSONL performance fixtures with stable, dirty, and incremental dirty modes, mesh-array output, stable-generation output caching, dirty prefix checkpoints for general convex builders, conservative mesh reuse for rejected dirty suffixes, explicit rebuild-strategy report fields, tests, clippy, and examples. The new `dream_machine_level` example proves the Rust side can already emit a named, inspectable, multi-part showpiece as OBJ/MTL/SVG: cut machine nave, central dome, anchor city, giant florets, cargo arteries, and material partitions without waiting for Bevy. The `vg_grammar` crate is the Dream Machine authoring layer: semantic solid/void claims with tags compile into flat `vg_csg` brushes or generated CSG tree intent; it supports local frames, oriented claims, deterministic seeded variation, rule composition, and generated gallery chains. Public RealtimeCSG demo/source research points the target away from fragment carving and toward faster polygon classification through boolean trees. A standalone RealtimeCSG C++ P/Invoke bridge now mirrors the managed wrapper surface, verifies a native health cube, extracts generated mesh descriptions, and appends direct native timing records. Current perf pass: `vg_csg` has a fast axis-aligned direct path, cached compiled brush geometry, owned convex splitting, convex containment shortcuts, stable-tree output caching, valid-prefix incremental dirty caching for general convex edits, and report flags for `reused_mesh`, `used_checkpoint`, `checkpoint_rebuild_from`, and `direct_dirty_rebuild`; box candidate edits use the direct builder after an honest dirty baseline showed checkpoint replay is slower without real mesh patching. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
summary: VibeGeometry has verified Geometry Script translations for local samples, official Blender demos, and IRCSS French Houses footholds, plus from-scratch Aetheria Bloom and Lucent tether habitat builds. Durable lessons now driving the pipeline: Python orchestrates intent and batching; Geometry Script emits inspectable node groups; source-bound scenes need explicit coordinate contracts before ornament; spin-gravity habitats need ground planes orthogonal to their tether/rotation axis; utility petals, radiators, and solar arrays belong outside pressure glass and may need Citadel-scale presence; large habitat parts need visible load paths across coordinate frames; organic city maps need street fields, irregular parcels, parks, and land-use gradients rather than grids; dense detail must be batched; institutional spaces should model power as surfaces, rails, overlays, thresholds, shader behavior, and camera evidence; and visual acceptance needs domain-facing inspection renders plus verifier checks, not only overview renders. The first reusable `vibegeometry/` helper library now carries coordinate frames, fields, batching, Blender scene primitives, and verifier helpers used by the Bloom and Lucent builds. The Rust crate `vg_csg` now has a RealtimeCSG-shaped API skeleton plus a convex-brush stepping-stone kernel: CSG operation ordinals, branch vocabulary, tree/branch/brush handles, child and operation mutation, exact parity fixtures, ordered assembler compilation, AABB/oriented-box subtraction/intersection via convex plane splitting, crossing polygon category buckets, primitive/solid/tree bounds, additive cylinder/dome/floret primitives, JSONL performance fixtures with stable, dirty, and incremental dirty modes, mesh-array output, stable-generation output caching, dirty prefix checkpoints for general convex builders, conservative mesh reuse for rejected dirty suffixes, explicit rebuild-strategy report fields, tests, clippy, and examples. The `dream_machine_level` and `industrial_maintenance_loop` examples prove the Rust side can emit named, inspectable scene fixtures as OBJ/MTL/SVG/PNG artifacts; the maintenance loop adds local tangent/radial/up authoring frames for curved habitat rooms, with manifold, staging, gallery, and service-artery subdomains compiled into world brushes. The `vg_grammar` crate is the Dream Machine authoring layer: semantic solid/void claims with tags compile into flat `vg_csg` brushes or generated CSG tree intent; it supports local frames, oriented claims, deterministic seeded variation, rule composition, and generated gallery chains. Public RealtimeCSG demo/source research points the target away from fragment carving and toward faster polygon classification through boolean trees. A standalone RealtimeCSG C++ P/Invoke bridge now mirrors the managed wrapper surface, verifies a native health cube, extracts generated mesh descriptions, and appends direct native timing records. Current perf pass: `vg_csg` has a fast axis-aligned direct path, cached compiled brush geometry, owned convex splitting, convex containment shortcuts, stable-tree output caching, valid-prefix incremental dirty caching for general convex edits, and report flags for `reused_mesh`, `used_checkpoint`, `checkpoint_rebuild_from`, and `direct_dirty_rebuild`; box candidate edits use the direct builder after an honest dirty baseline showed checkpoint replay is slower without real mesh patching. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
next_action: Build the first Bevy inspection shell for Rust-side CSG artifacts: load or generate the Dream Machine level mesh, show orbit camera/material groups, and keep dirty edit hooks visible so later mesh-patching work is judged against an actual interactive object rather than isolated timing fixtures.
|
||||
remotes:
|
||||
vibegeometry: https://github.com/GameCult/VibeGeometry
|
||||
|
|
|
|||
Loading…
Reference in a new issue