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
12 changes: 12 additions & 0 deletions .github/workflows/conan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,27 @@ jobs:
- name: Install Conan dependencies
run: conan install . --output-folder=build/conan --build=missing

- name: Select GCC 15 for Meson
run: |
cat > build/compiler.ini <<'EOF'
[binaries]
c = 'gcc-15'
cpp = 'g++-15'
EOF

- name: Configure
run: >
meson setup build
--wrap-mode=forcefallback
--native-file build/conan/conan_meson_native.ini
--native-file build/compiler.ini
-Dvisualization=false
-Dqt=false
-Dexamples=false

- name: Build and test
shell: bash
run: |
meson compile -C build
source build/conan/conanrun.sh
meson test -C build -v
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class Balsa(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "PkgConfigDeps"
generators = "PkgConfigDeps", "VirtualRunEnv"

requires = [
"eigen/3.4.0",
Expand Down
168 changes: 102 additions & 66 deletions geometry/include/balsa/geometry/BoundingBox.hpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
#if !defined(BALSA_GEOMETRY_BOUNDINGBOX)
#define BALSA_GEOMETRY_BOUNDINGBOX
#pragma once

// ============================================================================
// BoundingBox — thin wrapper around quiver::spatial::AABB
// BoundingBox<T, Dim> — thin wrapper around quiver::spatial::AABB<T, Dim>
// ============================================================================
//
// BoundingBox<Dim> is always double-precision, matching quiver's KDOP
// internals. It exposes a zipper-vector-based API (min(), max(),
// range(), corner(), expand(), contains()) on top of the underlying
// AABB<Dim>.
// Provides a zipper-vector-based API (min(), max(), range(), corner(),
// expand(), contains()) on top of quiver's AABB. The scalar type T is
// explicit — no default — matching quiver's convention.
//
// The old BoundingBox<T, Dim> was templated on the scalar type; this
// version drops the T parameter (always double) but adds a conversion
// constructor from the AABB so that code can freely pass between the
// two representations.
// Per-axis accessors: min(axis), max(axis), range(axis).
// Convenience names: width(), height(), depth().

#include <array>
#include <bitset>
Expand All @@ -27,84 +23,127 @@

namespace balsa::geometry {

template<::zipper::rank_type Dim>
template <typename T, ::zipper::rank_type Dim>
class BoundingBox {
public:
using aabb_type = quiver::spatial::AABB<double, static_cast<int8_t>(Dim)>;
using value_type = T;
using aabb_type = quiver::spatial::AABB<T, static_cast<int8_t>(Dim)>;

// ── Construction ────────────────────────────────────────────────

/// Default: empty box (ready for expand()).
BoundingBox() : m_aabb(aabb_type::empty()) {}

/// From two zipper vectors (min corner, max corner).
template<::zipper::concepts::Vector MinVec,
::zipper::concepts::Vector MaxVec>
BoundingBox(const MinVec &lo, const MaxVec &hi) : m_aabb(aabb_type::empty()) {
std::array<double, Dim> lo_arr, hi_arr;
template <::zipper::concepts::Vector MinVec,
::zipper::concepts::Vector MaxVec>
BoundingBox(const MinVec &lo, const MaxVec &hi)
: m_aabb(aabb_type::empty()) {
std::array<T, Dim> lo_arr, hi_arr;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
lo_arr[j] = static_cast<double>(lo(j));
hi_arr[j] = static_cast<double>(hi(j));
lo_arr[j] = static_cast<T>(lo(j));
hi_arr[j] = static_cast<T>(hi(j));
}
m_aabb.expand(std::span<const double, Dim>(lo_arr));
m_aabb.expand(std::span<const double, Dim>(hi_arr));
m_aabb.expand(std::span<const T, Dim>(lo_arr));
m_aabb.expand(std::span<const T, Dim>(hi_arr));
}

/// From a single point (degenerate box).
template<::zipper::concepts::Vector Vec>
template <::zipper::concepts::Vector Vec>
explicit BoundingBox(const Vec &pt) : m_aabb(aabb_type::empty()) {
std::array<double, Dim> arr;
std::array<T, Dim> arr;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
arr[j] = static_cast<double>(pt(j));
arr[j] = static_cast<T>(pt(j));
}
m_aabb.expand(std::span<const double, Dim>(arr));
m_aabb.expand(std::span<const T, Dim>(arr));
}

/// From explicit min/max values (for integral types like uint32_t).
/// Only available when Dim == 2.
BoundingBox(T x_min, T y_min, T x_max, T y_max)
requires(Dim == 2)
: m_aabb(aabb_type::empty()) {
std::array<T, 2> lo_arr{x_min, y_min};
std::array<T, 2> hi_arr{x_max, y_max};
m_aabb.expand(std::span<const T, 2>(lo_arr));
m_aabb.expand(std::span<const T, 2>(hi_arr));
}

/// From an existing AABB (implicit conversion).
BoundingBox(const aabb_type &aabb) : m_aabb(aabb) {}

BoundingBox(BoundingBox &&) = default;
BoundingBox(const BoundingBox &) = default;
BoundingBox &operator=(BoundingBox &&) = default;
BoundingBox &operator=(const BoundingBox &) = default;
auto operator=(BoundingBox &&) -> BoundingBox & = default;
auto operator=(const BoundingBox &) -> BoundingBox & = default;

// ── Access to underlying AABB ───────────────────────────────────

const aabb_type &aabb() const { return m_aabb; }
aabb_type &aabb() { return m_aabb; }
auto aabb() const -> const aabb_type & { return m_aabb; }
auto aabb() -> aabb_type & { return m_aabb; }

// ── min / max (return zipper vectors) ───────────────────────────
// ── min / max (full vector) ─────────────────────────────────────

auto min() const -> ::zipper::Vector<double, Dim> {
::zipper::Vector<double, Dim> v;
auto min() const -> ::zipper::Vector<T, Dim> {
::zipper::Vector<T, Dim> v;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
v(j) = m_aabb.min(static_cast<int8_t>(j));
}
return v;
}

auto max() const -> ::zipper::Vector<double, Dim> {
::zipper::Vector<double, Dim> v;
auto max() const -> ::zipper::Vector<T, Dim> {
::zipper::Vector<T, Dim> v;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
v(j) = m_aabb.max(static_cast<int8_t>(j));
}
return v;
}

// ── range (max - min) ───────────────────────────────────────────
// ── Per-axis accessors ──────────────────────────────────────────

auto min(::zipper::rank_type axis) const -> T {
return m_aabb.min(static_cast<int8_t>(axis));
}

auto max(::zipper::rank_type axis) const -> T {
return m_aabb.max(static_cast<int8_t>(axis));
}

auto range(::zipper::rank_type axis) const -> T {
return m_aabb.width(static_cast<int8_t>(axis));
}

// ── range (full vector) ─────────────────────────────────────────

auto range() const -> ::zipper::Vector<double, Dim> {
::zipper::Vector<double, Dim> v;
auto range() const -> ::zipper::Vector<T, Dim> {
::zipper::Vector<T, Dim> v;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
v(j) = m_aabb.width(static_cast<int8_t>(j));
}
return v;
}

// ── Convenience dimension names ─────────────────────────────────

auto width() const -> T { return range(0); }

auto height() const -> T
requires(Dim >= 2)
{
return range(1);
}

auto depth() const -> T
requires(Dim >= 3)
{
return range(2);
}

// ── corner ──────────────────────────────────────────────────────

auto corner(const std::bitset<Dim> &c) const -> ::zipper::Vector<double, Dim> {
::zipper::Vector<double, Dim> v;
auto corner(const std::bitset<Dim> &c) const -> ::zipper::Vector<T, Dim> {
::zipper::Vector<T, Dim> v;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
v(j) = c[j] ? m_aabb.max(static_cast<int8_t>(j))
: m_aabb.min(static_cast<int8_t>(j));
Expand All @@ -114,34 +153,31 @@ class BoundingBox {

// ── expand ──────────────────────────────────────────────────────

template<::zipper::concepts::Vector Vec>
void expand(const Vec &pt) {
std::array<double, Dim> arr;
template <::zipper::concepts::Vector Vec>
auto expand(const Vec &pt) -> void {
std::array<T, Dim> arr;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
arr[j] = static_cast<double>(pt(j));
arr[j] = static_cast<T>(pt(j));
}
m_aabb.expand(std::span<const double, Dim>(arr));
m_aabb.expand(std::span<const T, Dim>(arr));
}

void expand(const BoundingBox &other) {
auto expand(const BoundingBox &other) -> void {
m_aabb.merge_in_place(other.m_aabb);
}

// ── contains ────────────────────────────────────────────────────

template<::zipper::concepts::Vector Vec>
bool contains(const Vec &pt) const {
std::array<double, Dim> arr;
template <::zipper::concepts::Vector Vec>
auto contains(const Vec &pt) const -> bool {
std::array<T, Dim> arr;
for (::zipper::rank_type j = 0; j < Dim; ++j) {
arr[j] = static_cast<double>(pt(j));
arr[j] = static_cast<T>(pt(j));
}
return m_aabb.contains(std::span<const double, Dim>(arr));
return m_aabb.contains(std::span<const T, Dim>(arr));
}

bool contains(const BoundingBox &other) const {
// A contains B iff merging B into A doesn't change A.
// Equivalently: A.min(j) <= B.min(j) && A.max(j) >= B.max(j)
// for all j.
auto contains(const BoundingBox &other) const -> bool {
for (::zipper::rank_type j = 0; j < Dim; ++j) {
auto axis = static_cast<int8_t>(j);
if (m_aabb.min(axis) > other.m_aabb.min(axis)) return false;
Expand All @@ -152,25 +188,25 @@ class BoundingBox {

// ── is_empty ────────────────────────────────────────────────────

bool is_empty() const { return m_aabb.is_empty(); }
auto is_empty() const -> bool { return m_aabb.is_empty(); }

private:
aabb_type m_aabb;
};

// ── CTAD deduction guides ───────────────────────────────────────────

template<::zipper::concepts::Vector MinVec,
::zipper::concepts::Vector MaxVec>
requires(std::is_same_v<typename MinVec::value_type, typename MaxVec::value_type>)
template <::zipper::concepts::Vector MinVec, ::zipper::concepts::Vector MaxVec>
requires(std::is_same_v<typename MinVec::value_type,
typename MaxVec::value_type>)
BoundingBox(const MinVec &, const MaxVec &)
-> BoundingBox<MinVec::extents_type::static_extent(0) == std::dynamic_extent
? MaxVec::extents_type::static_extent(0)
: MinVec::extents_type::static_extent(0)>;

template<::zipper::concepts::Vector Vec>
BoundingBox(const Vec &) -> BoundingBox<Vec::extents_type::static_extent(0)>;
-> BoundingBox<typename MinVec::value_type,
MinVec::extents_type::static_extent(0) == std::dynamic_extent
? MaxVec::extents_type::static_extent(0)
: MinVec::extents_type::static_extent(0)>;

}// namespace balsa::geometry
template <::zipper::concepts::Vector Vec>
BoundingBox(const Vec &) -> BoundingBox<typename Vec::value_type,
Vec::extents_type::static_extent(0)>;

#endif
} // namespace balsa::geometry
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AlternatingDigitalTree {
public:
using Scalar = double;
constexpr static int Dim = D;
using BoundingBox = geometry::BoundingBox<D>;
using BoundingBox = geometry::BoundingBox<Scalar, D>;


// void insert(AlignedBox);
Expand Down
22 changes: 11 additions & 11 deletions geometry/include/balsa/geometry/bounding_box.hpp
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
#if !defined(BALSA_GEOMETRY_BOUNDING_BOX)
#define BALSA_GEOMETRY_BOUNDING_BOX
#include <Eigen/Geometry>
#include "balsa/eigen/types.hpp"
#include "BoundingBox.hpp"
#include "balsa/eigen/concepts/shape_types.hpp"
#include "balsa/eigen/types.hpp"
#include <Eigen/Geometry>
#include <zipper/Matrix.hpp>
#include "BoundingBox.hpp"


namespace balsa::geometry {


template<eigen::concepts::ColVecsDCompatible VType>
template <eigen::concepts::ColVecsDCompatible VType>
auto bounding_box(const VType &V) {
using BBox = Eigen::AlignedBox<typename VType::Scalar, VType::RowsAtCompileTime>;
using BBox =
Eigen::AlignedBox<typename VType::Scalar, VType::RowsAtCompileTime>;
if (V.cols() > 0) {
return BBox{ V.rowwise().minCoeff(), V.rowwise().maxCoeff() };
return BBox{V.rowwise().minCoeff(), V.rowwise().maxCoeff()};
}
return BBox{};
}

template<::zipper::concepts::Matrix VType>
template <::zipper::concepts::Matrix VType>
auto bounding_box(const VType &V) {
using T = typename VType::value_type;
constexpr auto Dim = VType::extents_type::static_extent(0);
BoundingBox<Dim> bb;
BoundingBox<T, Dim> bb;
for (zipper::index_type j = 0; j < V.extent(1); ++j) {
bb.expand(V.col(j));
}
return bb;
}

}// namespace balsa::geometry
} // namespace balsa::geometry

#endif
8 changes: 4 additions & 4 deletions visualization/include/balsa/scene_graph/BVHData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace balsa::scene_graph {
// render pass; the viewer calls apply_pending_update() before the
// next render pass to rebuild BVH / update wireframe child.

class MeshData;// forward
class MeshData; // forward

class BVHData : public AbstractFeature {
public:
Expand All @@ -46,7 +46,7 @@ class BVHData : public AbstractFeature {

// BVH build strategy.
quiver::spatial::BVHBuildStrategy strategy =
quiver::spatial::BVHBuildStrategy::sah;
quiver::spatial::BVHBuildStrategy::sah;

// Maximum number of primitives per leaf node.
uint16_t max_leaf_size = 4;
Expand All @@ -55,7 +55,7 @@ class BVHData : public AbstractFeature {
int display_depth = 0;

// Overlay wireframe color (RGBA).
float color[4] = { 0.1f, 0.8f, 0.2f, 1.0f };
float color[4] = {0.1f, 0.8f, 0.2f, 1.0f};

// Whether the BVH overlay is enabled (visible).
bool enabled = true;
Expand Down Expand Up @@ -132,6 +132,6 @@ class BVHData : public AbstractFeature {
void update_overlay();
};

}// namespace balsa::scene_graph
} // namespace balsa::scene_graph

#endif
Loading