mod common;
use bevy::prelude::*;
use bevy_isomesh::MeshBuilder;
use common::{Capture, CommonPlugin, DemoMesh, DemoStats, OrbitCamera, ViewFlags};
use isomesh::dual_contouring::{Clamp, DualContouring};
use isomesh::fields::{BoxExact, FbmTerrain, ReferenceField, capped_gyroid, csg_difference};
use isomesh::{RuntimeShape3, Sdf};
const FIELDS: [&str; 4] = ["box_exact", "csg_difference", "gyroid", "fbm_terrain"];
const MIN_LAMBDA: f64 = 1.0e-6;
const MAX_LAMBDA: f64 = 1.0;
const LAMBDA_STEP: f64 = 2.0;
#[derive(Resource)]
struct Demo {
field: usize,
samples: u32,
lambda: f64,
clamp: bool,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "isomesh — E-109 sharp features".into(),
..default()
}),
..default()
}))
.add_plugins(CommonPlugin)
.insert_resource(Demo {
field: 0,
samples: common::samples_override().unwrap_or(25),
lambda: 0.01,
clamp: false,
})
.add_systems(Startup, setup)
.add_systems(Update, (controls, remesh))
.run();
}
fn setup(
mut commands: Commands,
mut materials: ResMut<Assets<StandardMaterial>>,
mut camera: Query<&mut OrbitCamera>,
mut demo: ResMut<Demo>,
flags: Res<ViewFlags>,
) {
demo.field = flags.field.min(FIELDS.len() - 1);
for mut orbit in &mut camera {
orbit.yaw = 0.7;
orbit.pitch = 0.35;
orbit.radius = 7.0;
}
commands.insert_resource(Surface(materials.add(StandardMaterial {
base_color: Color::srgb(0.80, 0.78, 0.72),
perceptual_roughness: 0.45,
double_sided: true,
cull_mode: None,
..default()
})));
}
#[derive(Resource)]
struct Surface(Handle<StandardMaterial>);
fn controls(
keys: Res<ButtonInput<KeyCode>>,
capture: Res<Capture>,
mut demo: ResMut<Demo>,
mut flags: ResMut<ViewFlags>,
) {
if capture.is_active() {
const STEPS: u32 = 24;
let phase = capture.taken % (STEPS * 2);
let step = if phase < STEPS {
phase
} else {
STEPS * 2 - phase - 1
};
let t = f64::from(step) / f64::from(STEPS - 1);
demo.lambda = MIN_LAMBDA * (MAX_LAMBDA / MIN_LAMBDA).powf(t);
return;
}
for (key, index) in [
(KeyCode::Digit1, 0),
(KeyCode::Digit2, 1),
(KeyCode::Digit3, 2),
(KeyCode::Digit4, 3),
] {
if keys.just_pressed(key) {
demo.field = index;
}
}
if keys.just_pressed(KeyCode::Equal) {
demo.lambda = (demo.lambda * LAMBDA_STEP).min(MAX_LAMBDA);
}
if keys.just_pressed(KeyCode::Minus) {
demo.lambda = (demo.lambda / LAMBDA_STEP).max(MIN_LAMBDA);
}
if keys.just_pressed(KeyCode::KeyC) {
demo.clamp = !demo.clamp;
}
if keys.just_pressed(KeyCode::BracketRight) {
demo.samples = (demo.samples + 4).min(49);
}
if keys.just_pressed(KeyCode::BracketLeft) {
demo.samples = demo.samples.saturating_sub(4).max(9);
}
if keys.just_pressed(KeyCode::KeyR) {
flags.remesh_requested = true;
}
}
fn extract<F: Sdf<Scalar = f32> + ReferenceField>(
field: &F,
demo: &Demo,
) -> Option<(MeshBuilder, f32, f32)> {
let (min, max) = field.domain();
let cell = (max[0] - min[0]) / (demo.samples - 1) as f32;
let shape = RuntimeShape3::new([demo.samples; 3]).ok()?;
let mut dc = DualContouring::<f32>::new();
dc.set_lambda(Some(demo.lambda));
dc.set_clamp(if demo.clamp {
Clamp::ToCell
} else {
Clamp::None
});
let mut out = MeshBuilder::new();
dc.extract(field, &shape, min, cell, &mut out).ok()?;
let mut rounding = 0.0f32;
for p in out.positions() {
let off = (field.sample(*p) / cell).abs();
if off.is_finite() && off > rounding {
rounding = off;
}
}
let mut clamped = DualContouring::<f32>::new();
clamped.set_lambda(Some(demo.lambda));
clamped.set_clamp(Clamp::ToCell);
let mut reference = MeshBuilder::new();
clamped
.extract(field, &shape, min, cell, &mut reference)
.ok()?;
let mut runaway = 0.0f32;
if reference.vertex_count() == out.vertex_count() {
for (loose, held) in out.positions().iter().zip(reference.positions()) {
let d = [loose[0] - held[0], loose[1] - held[1], loose[2] - held[2]];
let moved = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt() / cell;
if moved.is_finite() && moved > runaway {
runaway = moved;
}
}
}
Some((out, rounding, runaway))
}
#[allow(clippy::too_many_arguments)]
fn remesh(
demo: Res<Demo>,
mut stats: ResMut<DemoStats>,
mut meshes: ResMut<Assets<Mesh>>,
surface: Res<Surface>,
mut commands: Commands,
mut query: Query<&mut Mesh3d, With<DemoMesh>>,
mut flags: ResMut<ViewFlags>,
mut last: Local<Option<(usize, u32, u64, bool)>>,
) {
let key = (demo.field, demo.samples, demo.lambda.to_bits(), demo.clamp);
if *last == Some(key) && !flags.remesh_requested {
return;
}
*last = Some(key);
flags.remesh_requested = false;
let built = match demo.field {
0 => extract(&BoxExact::<f32>::canonical(), &demo),
1 => extract(&csg_difference::<f32>(), &demo),
2 => extract(&capped_gyroid::<f32>(), &demo),
_ => extract(&FbmTerrain::<f32>::canonical(), &demo),
};
let Some((builder, rounding, runaway)) = built else {
return;
};
let verdict = if runaway > 1.0 {
"RUNAWAY -- a vertex has left its own cell entirely"
} else if rounding > 0.15 {
"rounded -- lambda is pulling vertices off the surface"
} else if runaway > 0.1 {
"sharp, but vertices are starting to wander"
} else {
"sharp and stable -- the useful range"
};
stats.title = format!(
"E-109 sharp features lambda {:.0e} clamp {} field {} ({})",
demo.lambda,
if demo.clamp { "on" } else { "OFF" },
demo.field + 1,
FIELDS[demo.field]
);
stats.vertices = builder.vertex_count();
stats.triangles = builder.triangle_count();
stats.extra = vec![
format!(
"{:<18} {:>10.0e} [-] softer, [=] sharper",
"lambda", demo.lambda
),
format!(
"{:<18} {:>10} [C] toggles -- the crate defaults to on",
"cell clamp",
if demo.clamp { "on" } else { "OFF" }
),
format!(
"{:<18} {:>10.3} cells off the surface -- the ROUNDING end",
"worst |f| / h", rounding
),
format!(
"{:<18} {:>10.3} cells out of its cell -- the RUNAWAY end",
"worst clamp move", runaway
),
format!("{:<18} {:>10}", "samples/axis", demo.samples),
String::new(),
verdict.to_string(),
String::new(),
"lambda is the Tikhonov regularizer in the vertex solve, and the whole".into(),
"sharpness trade in one number. the two failures need two numbers: |f|/h".into(),
"sees the rounding, and it is BLIND to the runaway, because a flat cell's".into(),
"unconstrained directions lie *within* the surface -- a vertex with".into(),
"nothing holding it slides along the plane and stays on it. so the second".into(),
"number is M-30's: how far the clamp had to drag it back.".into(),
String::new(),
"and the two ends live on different fields. M-30 measured the runaway at".into(),
"3.18 cells on gyroid and 2.17 on fbm_terrain, and *zero* on box_exact --".into(),
"press 3 or 4 to see it. box_exact is where the rounding shows.".into(),
String::new(),
"with the clamp ON you cannot see any of this: A-009 confines each".into(),
"vertex to its own cell, so the failure never becomes geometry. that is".into(),
"why it is the default, and why it is off here.".into(),
];
let mesh = meshes.add(builder.into_mesh());
if query.is_empty() {
commands.spawn((
Mesh3d(mesh),
MeshMaterial3d(surface.0.clone()),
Transform::default(),
DemoMesh,
));
} else {
for mut handle in &mut query {
handle.0 = mesh.clone();
}
}
}