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
80 changes: 80 additions & 0 deletions inc/cvc/gl/LodGraphicsNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2026 The University of Texas at Austin

This file is part of libcvc.

libcvc is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 2.1 as published by the Free Software Foundation.

libcvc is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

// LodGraphicsNode.h -- a scene-graph node that owns a mesh LOD ladder and draws
// exactly one rung, chosen each frame by camera distance.
//
// It holds one GeometryNode child per rung (rung 0 = finest) plus the pyramid's
// monotone world_error_m ladder, and is itself a prop-less container. select(view)
// runs the pure math in <cvc/lod/select.h> -- bound-nearest distance to the node's
// world bounding sphere, then the hysteretic select_rung over the ladder -- and
// sets exactly the chosen rung's child visible. A SceneGraph-level pass (or the
// caller) invokes select() per frame; nothing here draws, so the selection is
// testable with no GL context.
//
// LOD is a render proxy only: a rung is never read by a nav/material/RF path.

#ifndef __CVC_GL_LOD_GRAPHICS_NODE_H__
#define __CVC_GL_LOD_GRAPHICS_NODE_H__

#include <cvc/gl/GraphicsNode.h>
#include <cvc/lod/pyramid.h>
#include <cvc/lod/select.h>
#include <memory>
#include <vector>

namespace cvc {
namespace gl {

class GeometryNode;

class LodGraphicsNode : public GraphicsNode {
public:
LodGraphicsNode(cvc::app &ctx, const std::string &statePath, const std::string &name = "lod");
~LodGraphicsNode() override;

// Populate from a built mesh pyramid: one GeometryNode child per rung, and the
// ladder select() uses. Replaces any prior rungs. Rung 0 starts visible.
void setPyramid(const cvc::lod::mesh_pyramid &pyr);

int rungCount() const { return static_cast<int>(m_rungs.size()); }
int selectedRung() const { return m_current; }
double rungError(int k) const;

// Choose the affordable rung for `view` and show only it. Returns the rung
// index (0 = finest). A no-op returning 0 when empty.
int select(const cvc::lod::view_params &view);

// Untransformed extent of rung 0 (the full-detail mesh) -- what distance and
// culling should see regardless of which rung currently draws.
cvc::bounding_box getBoundingBox() const override;

protected:
vtkProp *getProp() override; // prop-less container; the rungs are children

private:
std::vector<std::shared_ptr<GeometryNode>> m_rungs;
std::vector<double> m_worldError; // monotone; matches m_rungs
int m_current = 0;
};

} // namespace gl
} // namespace cvc

#endif // __CVC_GL_LOD_GRAPHICS_NODE_H__
10 changes: 10 additions & 0 deletions inc/cvc/gl/SceneGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class app;
class geometry;
class volume;
class state;
namespace lod {
struct view_params;
}
namespace gl {
class state_publisher;
class ShadowSettings;
Expand All @@ -35,6 +38,7 @@ class NullGraphicNode;
class GridNode;
class AxisNode;
class BBoxNode;
class LodGraphicsNode;

class SceneGraph {
public:
Expand Down Expand Up @@ -279,6 +283,12 @@ class SceneGraph {
}
size_t getGeometryGraphicsCount() const { return getAllGeometryGraphics().size(); }

// Per-frame LOD pass: choose the rung for every LodGraphicsNode in the scene
// from this camera view (cvc::lod::view_params: eye, viewport height, fov, error
// budget), toggling each node's visible rung. Call once per frame before render.
// Returns the number of LOD nodes visited. A no-op (0) when there are none.
int selectLOD(const cvc::lod::view_params &view);

// Multi-volume rendering control
void enableMultiVolumeRendering(bool enable);
bool isMultiVolumeRenderingEnabled() const;
Expand Down
10 changes: 10 additions & 0 deletions inc/cvc/lod/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ std::vector<lod_index_entry> read_lod_index(app &ctx, const std::string &h5file)
bool has_pyramid(app &ctx, const std::string &h5file, const std::string &name,
const std::string &source_hash);

// ── bake ────────────────────────────────────────────────────────────────────
// Build a mesh's LOD pyramid and write it into `h5file` under `name`, SKIPPING the
// work when a current pyramid (one whose source_hash matches this mesh) is already
// present -- unless `force`. `pool` fans the pyramid build over the app workers.
// Returns true if it (re)baked, false if it skipped an up-to-date pyramid. This is
// the reusable core of the cvc-lod-bake tool.
bool bake_mesh_asset(app &ctx, const std::string &h5file, const std::string &name,
const geometry &src, const pyramid_params &params = pyramid_params(),
bool force = false, thread_pool *pool = nullptr);

} // namespace lod
} // namespace cvc

Expand Down
7 changes: 7 additions & 0 deletions src/cvc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,13 @@ if(CVC_BUILD_LSYS_TOOLS)
add_executable(cvc-worldgen ${CMAKE_CURRENT_SOURCE_DIR}/tools/cvc_worldgen_main.cpp)
target_link_libraries(cvc-worldgen PRIVATE cvc)
target_compile_features(cvc-worldgen PRIVATE cxx_std_17)
# cvc-lod-bake caches a mesh's LOD pyramid in a scene.cvch5 (needs the HDF5 store).
if(CVC_USING_HDF5)
add_executable(cvc-lod-bake ${CMAKE_CURRENT_SOURCE_DIR}/tools/cvc_lod_bake_main.cpp)
target_link_libraries(cvc-lod-bake PRIVATE cvc)
target_include_directories(cvc-lod-bake PRIVATE ${HDF5_INCLUDE_DIRS})
target_compile_features(cvc-lod-bake PRIVATE cxx_std_17)
endif()
endif()

# Windows-specific batch file
Expand Down
21 changes: 21 additions & 0 deletions src/cvc/lod/store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <cvc/core/state_blob_store.h>
#include <cvc/lod/store.h>
#include <cvc/volume/hdf5_utils.h>
#include <fstream>

namespace cvc {
namespace lod {
Expand Down Expand Up @@ -416,5 +417,25 @@ bool has_pyramid(app &ctx, const std::string &h5file, const std::string &name,
}
}

bool bake_mesh_asset(app &ctx, const std::string &h5file, const std::string &name,
const geometry &src, const pyramid_params &params, bool force,
thread_pool *pool) {
const std::string hash = mesh_content_hash(src);
if (!force) {
std::ifstream probe(h5file.c_str(), std::ios::binary);
if (probe.good()) {
probe.close();
try {
if (scene_reader(ctx, h5file).has(name, hash))
return false; // an up-to-date pyramid is already baked
} catch (...) {
}
}
}
mesh_pyramid pyr = build_mesh_pyramid(src, params, pool);
scene_writer(ctx, h5file).write_mesh_pyramid(name, pyr, hash);
return true;
}

} // namespace lod
} // namespace cvc
19 changes: 19 additions & 0 deletions src/cvc/tests/lod_store_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,22 @@ TEST(LodStore, InMemoryBlobRoundTrips) {
EXPECT_TRUE(r.has("buildings", cvc::lod::mesh_content_hash(m)));
EXPECT_FALSE(r.has("buildings", "nope"));
}

TEST(LodStore, BakeMeshAssetSkipsWhenCurrent) {
cvc::app ctx;
geometry m = bumpy_grid(ctx, 20);
const std::string file = tmp_h5();
std::remove(file.c_str());

EXPECT_TRUE(cvc::lod::bake_mesh_asset(ctx, file, "buildings", m)); // first -> baked
EXPECT_TRUE(cvc::lod::has_pyramid(ctx, file, "buildings", cvc::lod::mesh_content_hash(m)));
EXPECT_GE(cvc::lod::read_mesh_pyramid(ctx, file, "buildings").rungs.size(), 2u);

EXPECT_FALSE(cvc::lod::bake_mesh_asset(ctx, file, "buildings", m)); // same mesh -> skipped
EXPECT_TRUE(cvc::lod::bake_mesh_asset(ctx, file, "buildings", m, cvc::lod::pyramid_params(),
/*force=*/true)); // forced -> rebaked

geometry m2 = bumpy_grid(ctx, 26); // different content
EXPECT_TRUE(cvc::lod::bake_mesh_asset(ctx, file, "buildings", m2)); // new hash -> rebaked
std::remove(file.c_str());
}
116 changes: 116 additions & 0 deletions src/cvc/tools/cvc_lod_bake_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2026 The University of Texas at Austin

This file is part of libcvc.

libcvc is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 2.1 as published by the Free Software Foundation.

libcvc is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

// cvc-lod-bake -- build a mesh's LOD pyramid and cache it in a scene.cvch5.
//
// cvc-lod-bake INPUT.{off,raw} OUTPUT.cvch5 [--name NAME] [--rungs N]
// [--ratio R] [--force]
//
// INPUT is read with cvc::geometry (OFF / cvc-raw). A pyramid is decimated with
// the app compute pool and written under NAME (default: the input basename),
// skipping the work when an up-to-date pyramid is already present unless --force.
// Model formats (glTF/OBJ) are baked from code via cvc::lod::bake_mesh_asset on a
// mesh the caller has already loaded (that is how the demo bundle bake works);
// this CLI stays hermetic and covers the native mesh formats. Requires HDF5.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cvc/core/app.h>
#include <cvc/geometry/geometry.h>
#include <cvc/lod/pyramid.h>
#include <cvc/lod/store.h>
#include <string>

namespace {

std::string basename_noext(const std::string &path) {
std::size_t slash = path.find_last_of("/\\");
std::string base = (slash == std::string::npos) ? path : path.substr(slash + 1);
std::size_t dot = base.find_last_of('.');
return (dot == std::string::npos) ? base : base.substr(0, dot);
}

void usage() {
std::fprintf(stderr, "usage: cvc-lod-bake INPUT.{off,raw} OUTPUT.cvch5 "
"[--name NAME] [--rungs N] [--ratio R] [--force]\n");
}

} // namespace

int main(int argc, char **argv) {
std::string input, output, name;
cvc::lod::pyramid_params pp;
bool force = false;

for (int i = 1; i < argc; ++i) {
const std::string a = argv[i];
auto next = [&]() -> std::string { return (i + 1 < argc) ? argv[++i] : std::string(); };
if (a == "-h" || a == "--help") {
usage();
return 0;
} else if (a == "--name") {
name = next();
} else if (a == "--rungs") {
pp.max_rungs = std::atoi(next().c_str());
} else if (a == "--ratio") {
pp.mesh_ratio = std::atof(next().c_str());
} else if (a == "--force") {
force = true;
} else if (input.empty()) {
input = a;
} else if (output.empty()) {
output = a;
} else {
std::fprintf(stderr, "cvc-lod-bake: unexpected argument '%s'\n", a.c_str());
usage();
return 2;
}
}
if (input.empty() || output.empty()) {
usage();
return 2;
}
if (name.empty())
name = basename_noext(input);

cvc::app app;
cvc::geometry g(app);
try {
g.read(input);
} catch (const std::exception &e) {
std::fprintf(stderr, "cvc-lod-bake: cannot read '%s': %s\n", input.c_str(), e.what());
return 1;
}
if (g.num_tris() == 0) {
std::fprintf(stderr, "cvc-lod-bake: '%s' has no triangles\n", input.c_str());
return 1;
}

try {
const bool baked =
cvc::lod::bake_mesh_asset(app, output, name, g, pp, force, &app.computePool());
std::printf("%s '%s' (%llu tris) -> %s\n", baked ? "baked" : "up to date, skipped",
name.c_str(), static_cast<unsigned long long>(g.num_tris()), output.c_str());
} catch (const std::exception &e) {
std::fprintf(stderr, "cvc-lod-bake: bake failed: %s\n", e.what());
return 1;
}
return 0;
}
7 changes: 7 additions & 0 deletions src/cvcGL/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ add_executable(cvcgl_smoke test/cvcgl_smoke.cpp)
target_link_libraries(cvcgl_smoke PRIVATE cvcGL)
add_test(NAME cvcgl_smoke COMMAND cvcgl_smoke)

# LodGraphicsNode distance-based rung selection (the cvc::lod -> scene-graph seam).
# Headless: null renderer, no GL context; asserts close->finest, far->coarsest,
# monotone coarsening with distance.
add_executable(cvcgl_lod_node test/cvcgl_lod_node.cpp)
target_link_libraries(cvcgl_lod_node PRIVATE cvcGL)
add_test(NAME cvcgl_lod_node COMMAND cvcgl_lod_node)

# Spectral GPU FFT ocean (the ABYSSAL port used by lsystem_coast). Checks the
# invariants a broken butterfly IFFT / bit-reversal / conjugate-symmetry would
# violate: zero-mean height, unit-mean Jacobian, determinism, time evolution and
Expand Down
Loading
Loading