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
8 changes: 4 additions & 4 deletions docs/WORLD_UNITS_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ double metres_to_world(double metres) const; // metres / scale (scale > 0
### Canonical SI ↔ the display regime

```cpp
double to_display(double si_value, dimension d) const; // SI -> regime base unit
double to_display(double canonical_value, dimension d) const; // SI -> regime base unit
double from_display(double display_value, dimension d) const; // regime base unit -> SI
std::string unit_symbol(dimension d) const; // "m"/"ft", "kg"/"lb", ...
std::string unit_symbol(dimension d) const; // "m"/"ft", "kg"/"lb", ...
```

`to_display`/`from_display` use the regime's **base** unit (metre/foot,
Expand All @@ -161,7 +161,7 @@ kilogram/pound, m/s / mph, …). They do **not** promote by magnitude — use

```cpp
struct measurement { double value; std::string unit; };
measurement format(double si_value, dimension d) const;
measurement format(double canonical_value, dimension d) const;

struct coordinate { double x, y, z; std::string unit; };
coordinate world_point_to_real(double wx, double wy, double wz) const;
Expand Down Expand Up @@ -245,7 +245,7 @@ package). `world_units` is what a future Jolt bridge reads:
velocities and forces cross the engine boundary in canonical SI with **no
scaling**. Only `metres_per_world_unit` is applied when importing world-space
geometry into the physics world.
- Report engine outputs to the user with `format(si_value, dimension::force)`
- Report engine outputs to the user with `format(canonical_value, dimension::force)`
etc., so a change of regime converts every reported quantity through the same
table.
- Keep conversions **out** of the bit-identical nav kernels (they are compiled
Expand Down
4 changes: 2 additions & 2 deletions inc/cvc/core/world_units.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class world_units {
// of the active regime expects, in that regime's BASE unit (metre/foot,
// kilogram/pound, second, m/s / mph, ...), and back. No magnitude-based
// promotion happens here; use format() for that.
double to_display(double si_value, dimension d) const;
double to_display(double canonical_value, dimension d) const;
double from_display(double display_value, dimension d) const;

// The base unit label for a dimension in the active regime, e.g. "m"/"ft",
Expand All @@ -160,7 +160,7 @@ class world_units {
double value = 0.0;
std::string unit;
};
measurement format(double si_value, dimension d) const;
measurement format(double canonical_value, dimension d) const;

// --- coordinate mapping ------------------------------------------------

Expand Down
7 changes: 5 additions & 2 deletions inc/cvc/utility/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ static inline bool ends_with(const std::string &haystack, const std::string &nee
// in the .cpp file but the mac was complaining
const uint64 VM_VERSION = 0x00010206;

template <class T> const T &MIN(const T &a, const T &b) { return std::min(a, b); }
template <class T> const T &MAX(const T &a, const T &b) { return std::max(a, b); }
// The names are parenthesized so a function-like MIN(a,b)/MAX(a,b) macro (glibc and
// macOS <sys/param.h>, GLib) cannot expand the declarations when such a header is
// included first.
template <class T> const T &(MIN)(const T &a, const T &b) { return std::min(a, b); }
template <class T> const T &(MAX)(const T &a, const T &b) { return std::max(a, b); }

// trilinear interpolation function
static inline double getTriVal(double val[8], double x, double y, double z, double resX,
Expand Down
12 changes: 6 additions & 6 deletions src/cvc/core/world_units.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ double world_units::metres_to_world(double metres) const {
return metres / scale;
}

double world_units::to_display(double si_value, dimension d) const {
double world_units::to_display(double canonical_value, dimension d) const {
system regime;
{
std::lock_guard<std::mutex> lk(_mutex);
regime = _cfg.regime;
}
return si_value / si_per_display_unit(d, regime);
return canonical_value / si_per_display_unit(d, regime);
}

double world_units::from_display(double display_value, dimension d) const {
Expand All @@ -190,19 +190,19 @@ std::string world_units::unit_symbol(dimension d) const {
return base_unit_symbol(d, regime);
}

world_units::measurement world_units::format(double si_value, dimension d) const {
world_units::measurement world_units::format(double canonical_value, dimension d) const {
system regime;
{
std::lock_guard<std::mutex> lk(_mutex);
regime = _cfg.regime;
}
measurement m;
if (d == dimension::length) {
const length_tier tier = pick_length_tier(si_value, regime);
m.value = si_value / tier.metres_per_unit;
const length_tier tier = pick_length_tier(canonical_value, regime);
m.value = canonical_value / tier.metres_per_unit;
m.unit = tier.unit;
} else {
m.value = si_value / si_per_display_unit(d, regime);
m.value = canonical_value / si_per_display_unit(d, regime);
m.unit = base_unit_symbol(d, regime);
}
return m;
Expand Down
11 changes: 11 additions & 0 deletions src/cvc/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ add_executable(state_hash_partition_test state_hash_partition_test.cpp)
add_executable(state_hybrid_time_test state_hybrid_time_test.cpp)
add_executable(world_clock_test world_clock_test.cpp)
add_executable(world_units_test world_units_test.cpp)
add_executable(system_macro_headers_test system_macro_headers_test.cpp)
add_executable(state_brick_frustum_test state_brick_frustum_test.cpp)
add_executable(state_conflict_ring_test state_conflict_ring_test.cpp)
add_executable(state_zstd_round_trip_test state_zstd_round_trip_test.cpp)
Expand Down Expand Up @@ -222,6 +223,7 @@ set(TEST_TARGETS
state_brick_manifest_test
world_clock_test
world_units_test
system_macro_headers_test
state_data_hydrator_test
state_node_telemetry_test
state_telemetry_aggregator_test
Expand Down Expand Up @@ -1337,6 +1339,13 @@ target_link_libraries(world_units_test
GTest::gtest_main
)

target_link_libraries(system_macro_headers_test
PRIVATE
cvc
GTest::gtest
GTest::gtest_main
)

target_link_libraries(state_brick_frustum_test
PRIVATE
cvc
Expand Down Expand Up @@ -1511,6 +1520,7 @@ target_compile_features(state_hash_partition_test PRIVATE cxx_std_17)
target_compile_features(state_hybrid_time_test PRIVATE cxx_std_17)
target_compile_features(world_clock_test PRIVATE cxx_std_17)
target_compile_features(world_units_test PRIVATE cxx_std_17)
target_compile_features(system_macro_headers_test PRIVATE cxx_std_20)
target_compile_features(state_brick_frustum_test PRIVATE cxx_std_17)
target_compile_features(state_conflict_ring_test PRIVATE cxx_std_17)
target_compile_features(state_zstd_round_trip_test PRIVATE cxx_std_17)
Expand Down Expand Up @@ -1812,6 +1822,7 @@ cvc_discover_tests(state_hash_partition_test)
cvc_discover_tests(state_hybrid_time_test)
cvc_discover_tests(world_clock_test)
cvc_discover_tests(world_units_test)
cvc_discover_tests(system_macro_headers_test)
cvc_discover_tests(state_brick_frustum_test)
cvc_discover_tests(state_conflict_ring_test)
cvc_discover_tests(state_zstd_round_trip_test)
Expand Down
87 changes: 87 additions & 0 deletions src/cvc/tests/system_macro_headers_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
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.
*/

// Header-hygiene regression: libcvc's public headers must compile when a
// consumer includes system headers FIRST. Those headers define lowercase
// macros that silently rewrite any identifier of the same name, e.g. glibc's
// <signal.h> has `#define si_value _sifields._rt.si_sigval`, which turned the
// parameter in `double to_display(double si_value, dimension d) const;` into a
// syntax error for every TU that included <signal.h> before
// <cvc/core/world_units.h> (seen in volrover3's dialogs against libcvc 3.5.0).
// The test is that this TU compiles; the TEST below only makes it a real
// executable that links and runs.
//
// The system headers stay in their own preprocessor block, ahead of the libcvc
// includes, so include sorting can never move them after the headers they guard.

#ifndef _WIN32
#include <signal.h> // si_value, si_pid, si_addr, ... (glibc/musl siginfo_t accessors)
#include <sys/param.h> // function-like MIN(a,b) / MAX(a,b)
#include <sys/stat.h> // st_atime / st_mtime / st_ctime
#include <sys/types.h> // major / minor / makedev on macOS and older glibc
#else
// Same guards the library uses around <windows.h> (see src/cvc/image/magick_io.cpp).
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h> // ERROR, DELETE, IN, OUT, OPTIONAL, near, far, RGB(), ...
#endif

// A representative public header from every module in the cvc target. Not
// included: cvc/gl/* (the optional cvcGL library, needs VTK/OpenGL),
// cvc/volume/hdf5_utils.h (HDF5), cvc/geometry/project_verts.h (CGAL) and
// cvc/utility/cuda_utils.h / cvc/volren/raycaster_cuda.h (CUDA).
#include <cvc/ariadne/widget.h>
#include <cvc/core/app.h>
#include <cvc/core/async_lane.h>
#include <cvc/core/async_task.h>
#include <cvc/core/exception.h>
#include <cvc/core/text.h>
#include <cvc/core/thread_pool.h>
#include <cvc/core/types.h>
#include <cvc/core/world_clock.h>
#include <cvc/core/world_units.h>
#include <cvc/geometry/geometry.h>
#include <cvc/geometry/geometry_file_io.h>
#include <cvc/image/image.h>
#include <cvc/lod/select.h>
#include <cvc/lsys/grammar.h>
#include <cvc/model/model.h>
#include <cvc/nav/grid_nav.h>
#include <cvc/nav/sim_world.h>
#include <cvc/state/state.h>
#include <cvc/utility/utility.h>
#include <cvc/vis/visible_set.h>
#include <cvc/volren/raycaster.h>
#include <cvc/volslice/slicer.h>
#include <cvc/volume/bounding_box.h>
#include <cvc/volume/volume.h>
#include <cvc/volume/volume_file_io.h>
#include <cvc/volume/volume_ops.h>
#include <cvc/world/heightfield.h>
#include <gtest/gtest.h>

TEST(SystemMacroHeaders, WorldUnitsUsableAfterSystemHeaders) {
cvc::world_units u;
EXPECT_DOUBLE_EQ(u.to_display(1.5, cvc::world_units::dimension::length), 1.5);
const cvc::world_units::measurement m = u.format(2500.0, cvc::world_units::dimension::length);
EXPECT_DOUBLE_EQ(m.value, 2.5);
EXPECT_EQ(m.unit, "km");
}

TEST(SystemMacroHeaders, UtilityMinMaxUsableAfterSysParam) {
// <sys/param.h> may define function-like MIN/MAX macros in this TU, so call
// the cvc templates the macro-proof way.
EXPECT_EQ((cvc::MIN)(3, 5), 3);
EXPECT_EQ((cvc::MAX)(3, 5), 5);
}
Loading