Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions inc/cvc/ariadne/scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ struct SceneNode {
std::string source_primitive;
float plane_size = 1.0f; // "plane": full edge length

// A VOLUME computed as a signed distance field from a MESH (for volren/volslice), instead of
// loading a pre-baked volume with source: { file }. `source: { sdf: { mesh: <uri>, dim: N } }` —
// the realizer loads the mesh and runs cvc::sdf over an N³ grid framed to the mesh extents. Empty
// = not an SDF source. Needs a CVC_ENABLE_SDF build (the realizer warns and skips the node
// without it). This is how a demo turns a mesh (e.g. stanford.bunny) into a volume with no asset
// file.
std::string source_sdf_mesh;
int sdf_dim = 64;

bool has_material = false;
float color[3] = {0.8f, 0.8f, 0.9f};
float ambient = 0.2f;
Expand Down
12 changes: 12 additions & 0 deletions src/cvc/ariadne/components/INDEX.ari
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,15 @@ catalog:
for: "a live image/raster viewer (nav grids, textures) with a colormap menu"
args: []
note: "needs a host that publishes named cvc::images (ImGuiBackend::set_image) each frame"
- component: volren_controls
file: volren_controls.ari
unit: volren_controls
binds: "<prefix>.graphics.root.children.bunny_volume.volren.*" # cvc::volren::state_settings
for: "a `type: volren` node (steps / ambient / supersample / volumetric shadows)"
args: [] # convention: node id `bunny_volume`; copy + rename for another
- component: volslice_controls
file: volslice_controls.ari
unit: volslice_controls
binds: "<prefix>.graphics.root.children.bunny_volume.volslice.*" # cvc::volslice::state_settings
for: "a `type: volslice` node (quality / near-plane peel / max planes)"
args: [] # convention: node id `bunny_volume`; copy + rename for another
35 changes: 35 additions & 0 deletions src/cvc/ariadne/components/volren_controls.ari
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# volren_controls.ari — a REUSABLE Ariadne component: live controls for a `type: volren` node.
# Binds the two-way cvc::volren::state_settings keys (state at
# "<scene>.graphics.root.children.bunny_volume.volren.*"), so a slider drives the software raycaster
# with NO host C++ — the declarative replacement for the volren_bunny.cpp "Volume raycast" panel.
#
# Convention: it binds a volren node named `bunny_volume`. To drive a differently-named node, copy
# this file and change the node id in the bind paths.
#
# Use it:
# import: components/volren_controls.ari
# windows:
# - include: volren_controls

units:
volren_controls:
window: Volume raycast
id: volren_controls
children:
- slider_int: Steps
bind: graphics.root.children.bunny_volume.volren.steps
lo: 64
hi: 1024
def: 384
- slider_float: Ambient
bind: graphics.root.children.bunny_volume.volren.ambient
lo: 0.0
hi: 1.0
def: 0.35
- slider_int: Supersample (AA)
bind: graphics.root.children.bunny_volume.volren.supersample
lo: 1
hi: 4
def: 1
- checkbox: Volumetric shadows
bind: graphics.root.children.bunny_volume.volren.shadows.enabled
33 changes: 33 additions & 0 deletions src/cvc/ariadne/components/volslice_controls.ari
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# volslice_controls.ari — a REUSABLE Ariadne component: live controls for a `type: volslice` node.
# Binds the two-way cvc::volslice::state_settings keys (state at
# "<scene>.graphics.root.children.bunny_volume.volslice.*"), so a slider drives the slice renderer
# with NO host C++ — the declarative replacement for volslice_bunny.cpp's "Volume slices" panel.
#
# Convention: it binds a volslice node named `bunny_volume` (the id the volume demos use). To drive a
# differently-named node, copy this file and change the node id in the bind paths.
#
# Use it:
# import: components/volslice_controls.ari
# windows:
# - include: volslice_controls

units:
volslice_controls:
window: Volume slices
id: volslice_controls
children:
- slider_float: Quality
bind: graphics.root.children.bunny_volume.volslice.quality
lo: 0.1
hi: 1.0
def: 0.6
- slider_float: Near-plane peel
bind: graphics.root.children.bunny_volume.volslice.near_plane
lo: 0.0
hi: 2.0
def: 0.0
- slider_int: Max planes
bind: graphics.root.children.bunny_volume.volslice.max_planes
lo: 32
hi: 2000
def: 1000
5 changes: 5 additions & 0 deletions src/cvc/ariadne/loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,11 @@ SceneNode parse_scene_node(const YAML::Node &n) {
sn.plane_size = static_cast<float>(num(plane, "size", sn.plane_size));
else if (plane.IsScalar())
sn.plane_size = static_cast<float>(plane.as<double>());
} else if (const YAML::Node sdf = src["sdf"]) {
// source: { sdf: { mesh: <uri>, dim: N } } — a volren/volslice volume computed as the
// signed distance field of a mesh (no pre-baked volume asset).
sn.source_sdf_mesh = str(sdf, "mesh", str(sdf, "file"));
sn.sdf_dim = static_cast<int>(num(sdf, "dim", sn.sdf_dim));
} else {
sn.source_file = str(src, "uri", str(src, "file"));
}
Expand Down
28 changes: 28 additions & 0 deletions src/cvc/tests/ariadne_loader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,34 @@ meta: { min_libcvc: "0.0.0" }
EXPECT_TRUE(b->fit_up_y);
}

// A volren/volslice volume declared as the signed distance field of a MESH:
// source: { sdf: { mesh: <uri>, dim: N } } — no pre-baked volume asset.
TEST(AriadneScene, VolumeSdfMeshSource) {
SKIP_WITHOUT_YAML();
LoadResult r = load_string(R"(
meta: { min_libcvc: "0.0.0" }
scene:
nodes:
- node: bunny_volume
type: volren
source: { sdf: { mesh: stanford.bunny, dim: 48 } }
fit: { height: 100, up: y }
volren: { distance_field: true, isosurfaces: [ { value: 0.0, color: [0.8, 0.8, 0.9] } ] }
)");
ASSERT_TRUE(r.ok) << r.error;
ASSERT_EQ(r.scene.nodes.size(), 1u);
const SceneNode *v = find_scene_node(r.scene.nodes, "bunny_volume");
ASSERT_NE(v, nullptr);
EXPECT_EQ(v->type, "volren");
EXPECT_EQ(v->source_sdf_mesh, "stanford.bunny"); // the mesh the SDF is computed from
EXPECT_EQ(v->sdf_dim, 48);
EXPECT_TRUE(v->source_file.empty()); // an sdf source is not a file
ASSERT_TRUE(v->has_fit); // fit is applied to the mesh BEFORE the SDF (framing parity)
EXPECT_FLOAT_EQ(v->fit_height, 100.0f);
ASSERT_TRUE(v->has_volren);
ASSERT_EQ(v->volren.isosurfaces.size(), 1u);
}

// §9 enrichments: a tuned StageLighting rig and shadow resolution/update-interval.
TEST(AriadneScene, RigTuningAndShadowResolution) {
SKIP_WITHOUT_YAML();
Expand Down
60 changes: 48 additions & 12 deletions src/cvcGL/ariadne/scene_realize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cvc/gl/VolSliceNode.h>
#include <cvc/gl/VolumeNode.h>
#include <cvc/gl/ariadne/scene_realize.h>
#include <cvc/utility/algorithm.h> // cvc::sdf — source: { sdf: { mesh:, dim: } } volume source
#include <cvc/volren/settings.h>
#include <cvc/volslice/settings.h>
#include <cvc/volume/bounding_box.h>
Expand Down Expand Up @@ -245,6 +246,47 @@ void configure_volslice(VolSliceNode &vn, const cvc::ariadne::SceneVolSlice &v,
vn.setConfig(s);
}

#if CVC_ENABLE_SDF
// A dim³ signed-distance-field volume of a mesh, framed like the volume demos' bunny_sdf: the grid
// spans the mesh extents padded 10% (cubic), SDF_V2. Lets a volren/volslice node declare
// `source: { sdf: { mesh:, dim: } }` instead of a pre-baked volume file.
cvc::volume sdf_volume_from_mesh(cvc::app &app, const std::string &mesh_uri, int dim,
bool apply_fit, bool fit_up_y, float fit_height) {
cvc::geometry mesh = cvc::read_geometry(mesh_uri);
if (apply_fit)
mesh = fit_to_ground(mesh, fit_up_y, fit_height); // stand/centre/scale the mesh before the SDF,
// so the volume is framed like the geometry
const cvc::bounding_box e = mesh.extents();
const double ext = std::max({e.maxx - e.minx, e.maxy - e.miny, e.maxz - e.minz});
const double half = ext * 1.1 * 0.5;
const double cx = 0.5 * (e.minx + e.maxx), cy = 0.5 * (e.miny + e.maxy),
cz = 0.5 * (e.minz + e.maxz);
const cvc::bounding_box bbox(cx - half, cy - half, cz - half, cx + half, cy + half, cz + half);
const int d = dim > 0 ? dim : 64;
return cvc::sdf(app, mesh, cvc::dimension(d, d, d), bbox, cvc::SDF_V2);
}
#endif

// The volume a volren/volslice node draws: loaded from `source: { file }`, or computed as the SDF
// of a mesh (`source: { sdf: { mesh:, dim: } }`). Throws on failure — the caller's try/catch warns
// and skips the node.
cvc::volume load_node_volume(SceneGraph &sg, const cvc::ariadne::SceneNode &n,
std::vector<std::string> *warnings) {
if (!n.source_sdf_mesh.empty()) {
#if CVC_ENABLE_SDF
return sdf_volume_from_mesh(sg.appContext(), n.source_sdf_mesh, n.sdf_dim, n.has_fit,
n.fit_up_y, n.fit_height);
#else
(void)warnings;
throw std::runtime_error("source: { sdf } needs a CVC_ENABLE_SDF build");
#endif
}
cvc::ariadne::ResolvedFile src = resolve_source(n, warnings);
if (!src.ok)
throw std::runtime_error("unresolved source");
return cvc::volume(sg.appContext(), src.path);
}

// Realize one node under `parent` (null = a top-level node parented to the graphics
// root). A nested node is created via parent->createChild/addGraphicsChild, which
// gives it the hierarchical state path `{parent}.children.{id}` AND makes its
Expand Down Expand Up @@ -337,16 +379,13 @@ void realize_node(SceneGraph &sg, const cvc::ariadne::SceneNode &n, const std::s
vnode->setDiffuse(n.diffuse);
}
} else if (n.type == "volren") {
if (n.source_file.empty()) {
warn(warnings, "ari: scene node '" + n.id + "' (volren) has no source.file");
if (n.source_file.empty() && n.source_sdf_mesh.empty()) {
warn(warnings, "ari: scene node '" + n.id + "' (volren) has no source (file or sdf)");
return;
}
cvc::ariadne::ResolvedFile src = resolve_source(n, warnings);
if (!src.ok)
return;
std::shared_ptr<VolRenNode> vn;
try {
cvc::volume vol(sg.appContext(), src.path); // reads on construct; throws on a bad file
cvc::volume vol = load_node_volume(sg, n, warnings); // file, or an SDF of a mesh
// No sg.addGraphics overload for VolRenNode: create under the parent (or root).
GraphicsNode *pr = parent ? parent : sg.getGraphicsRoot().get();
// Last-wins parity with sg.addGraphics: registerGraphics only reassigns the name
Expand All @@ -365,16 +404,13 @@ void realize_node(SceneGraph &sg, const cvc::ariadne::SceneNode &n, const std::s
out.volren_ticks.push_back(vn); // needs a per-frame tick() (tick_scene)
node = vn;
} else if (n.type == "volslice") {
if (n.source_file.empty()) {
warn(warnings, "ari: scene node '" + n.id + "' (volslice) has no source.file");
if (n.source_file.empty() && n.source_sdf_mesh.empty()) {
warn(warnings, "ari: scene node '" + n.id + "' (volslice) has no source (file or sdf)");
return;
}
cvc::ariadne::ResolvedFile src = resolve_source(n, warnings);
if (!src.ok)
return;
std::shared_ptr<VolSliceNode> vn;
try {
cvc::volume vol(sg.appContext(), src.path);
cvc::volume vol = load_node_volume(sg, n, warnings);
GraphicsNode *pr = parent ? parent : sg.getGraphicsRoot().get();
if (!parent && sg.hasGraphics(n.id)) // last-wins parity with sg.addGraphics (see volren)
sg.removeGraphics(n.id);
Expand Down
51 changes: 51 additions & 0 deletions src/cvcGL/examples/volren_bunny.ari
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# volren_bunny.ari — the cvcGL volume-raycast bunny, ported to Ariadne. The volume is the signed
# distance field of the embedded Stanford bunny MESH, declared with `source: { sdf: { mesh:, dim } }`
# (no asset file). Run by the generic ariadne_hello host:
#
# ariadne_hello volren_bunny.ari --component-path src/cvc/ariadne/components
# ariadne_hello volren_bunny.ari --offscreen --png volren.png --frames 200 --component-path …

meta:
name: Volren bunny (Ariadne)
author: cvc
min_libcvc: 3.4.0

# Reusable control components (shared with the other bunny demos): the lighting rig + camera
# navigation, plus the volume-raycast panel. All declarative — no host C++ panels.
import:
- cvc://components/stage_lighting.ari
- cvc://components/camera_controls.ari
- cvc://components/volren_controls.ari

menubar:
- menu: Sim
items:
- menu_item: Quit
on: quit

windows:
- include: stage_lighting
- include: camera_controls
- include: volren_controls

scene:
chrome: false
background: { top: [0.10, 0.12, 0.16], bottom: [0.02, 0.02, 0.03] }
nodes:
- node: bunny_volume
type: volren
source: { sdf: { mesh: stanford.bunny, dim: 64 } }
fit: { height: 100, up: y }
volren:
distance_field: true
ambient: 0.35
steps: 384
resolution_scale: 0.6
isosurfaces:
- { value: 0.0, color: [0.85, 0.82, 0.88], opacity: 1.0, shininess: 24 }
lights:
- { color: [1.0, 1.0, 1.0], direction: [-0.4, -0.3, -0.85] }
- node: ground
type: geometry
source: { plane: { size: 440 } }
material: { color: [0.18, 0.19, 0.22], ambient: 0.3, diffuse: 0.8 }
58 changes: 58 additions & 0 deletions src/cvcGL/examples/volslice_bunny.ari
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# volslice_bunny.ari — the cvcGL volume-slice bunny, ported to Ariadne. The volume is the signed
# distance field of the embedded Stanford bunny MESH (source: { sdf: { mesh:, dim } }); the transfer
# function paints a window around the zero level set (the surface). Run by the generic ariadne_hello:
#
# ariadne_hello volslice_bunny.ari --component-path src/cvc/ariadne/components
# ariadne_hello volslice_bunny.ari --offscreen --png volslice.png --frames 8 --component-path …

meta:
name: Volslice bunny (Ariadne)
author: cvc
min_libcvc: 3.4.0

# Reusable control components (shared with the other bunny demos): the lighting rig + camera
# navigation, plus the volume-slice panel. All declarative — no host C++ panels.
import:
- cvc://components/stage_lighting.ari
- cvc://components/camera_controls.ari
- cvc://components/volslice_controls.ari

menubar:
- menu: Sim
items:
- menu_item: Quit
on: quit

windows:
- include: stage_lighting
- include: camera_controls
- include: volslice_controls

scene:
chrome: false
background: { top: [0.10, 0.12, 0.16], bottom: [0.02, 0.02, 0.03] }
nodes:
# The bunny as a sliced volume. Slices are unlit by design — the transfer function supplies the
# colour; opacity is highest inside the surface (negative SDF) and fades to transparent air.
- node: bunny_volume
type: volslice
source: { sdf: { mesh: stanford.bunny, dim: 96 } }
fit: { height: 100, up: y }
volslice:
quality: 0.6
opacity_correction: true
transfer_function:
window: [-6, 6]
points:
- { value: -6.0, color: [0.90, 0.60, 0.20, 0.85] }
- { value: -0.5, color: [1.00, 0.85, 0.60, 0.45] }
- { value: 0.5, color: [1.00, 0.95, 0.85, 0.12] }
- { value: 6.0, color: [1.00, 1.00, 1.00, 0.00] }
- node: ground
type: geometry
source: { plane: { size: 440 } }
material: { color: [0.18, 0.19, 0.22], ambient: 0.3, diffuse: 0.8 }

lights:
- rig: three_point
key: { intensity: 1.9, azimuth: -38, elevation: 52 }
Loading