From 26bde533e3132e14670be69c1b4d65ce148049ac Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 10:04:16 +0100 Subject: [PATCH 1/6] fix(Model): add API helper function to create a surface BRep from a given BoundingBox3D --- .../model/helpers/simplicial_brep_creator.hpp | 5 ++ .../model/helpers/simplicial_brep_creator.cpp | 86 +++++++++++++++++++ tests/model/test-model-creator.cpp | 39 ++++++--- 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/include/geode/model/helpers/simplicial_brep_creator.hpp b/include/geode/model/helpers/simplicial_brep_creator.hpp index 97a4bac97..a6c4895fe 100644 --- a/include/geode/model/helpers/simplicial_brep_creator.hpp +++ b/include/geode/model/helpers/simplicial_brep_creator.hpp @@ -33,7 +33,9 @@ namespace geode { FORWARD_DECLARATION_DIMENSION_CLASS( Point ); + FORWARD_DECLARATION_DIMENSION_CLASS( BoundingBox ); ALIAS_3D( Point ); + ALIAS_3D( BoundingBox ); class BRep; struct uuid; } // namespace geode @@ -76,4 +78,7 @@ namespace geode private: IMPLEMENTATION_MEMBER( impl_ ); }; + + BRep opengeode_model_api create_model_from_bounding_box( + const BoundingBox3D& box ); } // namespace geode \ No newline at end of file diff --git a/src/geode/model/helpers/simplicial_brep_creator.cpp b/src/geode/model/helpers/simplicial_brep_creator.cpp index 731875109..5fb4054ad 100644 --- a/src/geode/model/helpers/simplicial_brep_creator.cpp +++ b/src/geode/model/helpers/simplicial_brep_creator.cpp @@ -25,6 +25,9 @@ #include +#include +#include + #include #include @@ -33,6 +36,73 @@ #include #include +namespace +{ + std::vector< geode::Point3D > create_bbox_points( + const geode::BoundingBox3D& bbox ) + { + std::vector< geode::Point3D > points; + points.reserve( 8 ); + points.emplace_back( bbox.min() ); + points.emplace_back( geode::Point3D{ { bbox.max().value( 0 ), + bbox.min().value( 1 ), bbox.min().value( 2 ) } } ); + points.emplace_back( geode::Point3D{ { bbox.max().value( 0 ), + bbox.max().value( 1 ), bbox.min().value( 2 ) } } ); + points.emplace_back( geode::Point3D{ { bbox.min().value( 0 ), + bbox.max().value( 1 ), bbox.min().value( 2 ) } } ); + points.emplace_back( geode::Point3D{ { bbox.min().value( 0 ), + bbox.min().value( 1 ), bbox.max().value( 2 ) } } ); + points.emplace_back( geode::Point3D{ { bbox.max().value( 0 ), + bbox.min().value( 1 ), bbox.max().value( 2 ) } } ); + points.emplace_back( bbox.max() ); + points.emplace_back( geode::Point3D{ { bbox.min().value( 0 ), + bbox.max().value( 1 ), bbox.max().value( 2 ) } } ); + return points; + } + + std::vector< geode::CornerDefinition > create_corner_definitions() + { + return std::vector< geode::CornerDefinition >{ { 0 }, { 1 }, { 2 }, + { 3 }, { 4 }, { 5 }, { 6 }, { 7 } }; + } + + std::vector< geode::LineDefinition > create_line_definitions() + { + return std::vector< geode::LineDefinition >{ + { { 0, 1 } }, + { { 1, 2 } }, + { { 2, 3 } }, + { { 3, 0 } }, + { { 4, 5 } }, + { { 5, 6 } }, + { { 6, 7 } }, + { { 7, 4 } }, + { { 0, 4 } }, + { { 1, 5 } }, + { { 2, 6 } }, + { { 3, 7 } }, + }; + } + + std::vector< geode::SurfaceDefinition > create_surface_definitions() + { + return std::vector< geode::SurfaceDefinition >{ + { { 0, 1, 2, 3 }, { 0, 1, 2, 0, 2, 3 }, { 0, 1, 2, 3 }, {}, {} }, + { { 4, 5, 6, 7 }, { 0, 1, 2, 0, 2, 3 }, { 4, 5, 6, 7 }, {}, {} }, + { { 0, 1, 5, 4 }, { 0, 1, 2, 0, 2, 3 }, { 0, 9, 4, 8 }, {}, {} }, + { { 1, 2, 6, 5 }, { 0, 1, 2, 0, 2, 3 }, { 1, 10, 5, 9 }, {}, {} }, + { { 2, 3, 7, 6 }, { 0, 1, 2, 0, 2, 3 }, { 2, 11, 6, 10 }, {}, {} }, + { { 3, 0, 4, 7 }, { 0, 1, 2, 0, 2, 3 }, { 3, 8, 7, 11 }, {}, {} }, + }; + } + + std::vector< geode::BlockDefinition > create_block_definitions() + { + return std::vector< geode::BlockDefinition >{ { {}, {}, + { 0, 1, 2, 3, 4, 5 }, {}, {}, {} } }; + } +} // namespace + namespace geode { class SimplicialBRepCreator::Impl @@ -191,4 +261,20 @@ namespace geode { return impl_->create_model_boundaries( surfaces, definitions ); } + + BRep create_model_from_bounding_box( const BoundingBox3D& box ) + { + BRep brep; + const auto points = create_bbox_points( box ); + SimplicialBRepCreator creator{ brep, points }; + const auto corners = + creator.create_corners( create_corner_definitions() ); + const auto lines = + creator.create_lines( corners, create_line_definitions() ); + const auto surfaces = + creator.create_surfaces( lines, create_surface_definitions() ); + const auto blocks = + creator.create_blocks( surfaces, create_block_definitions() ); + return brep; + } } // namespace geode \ No newline at end of file diff --git a/tests/model/test-model-creator.cpp b/tests/model/test-model-creator.cpp index 89845ff17..f9d0363c6 100644 --- a/tests/model/test-model-creator.cpp +++ b/tests/model/test-model-creator.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -39,9 +40,20 @@ #include #include #include +#include +#include #include +void test_create_brep_from_bbox() +{ + geode::BoundingBox3D box; + box.add_point( geode::Point3D{ { 0, 0, 0 } } ); + box.add_point( geode::Point3D{ { 1, 1, 1 } } ); + const auto brep = geode::create_model_from_bounding_box( box ); + geode::save_brep( brep, "brep_from_bbox.og_brep" ); +} + void test_create_brep_with_dangling_components() { std::vector< geode::Point3D > points{ @@ -100,12 +112,12 @@ void test_create_brep_with_dangling_components() const auto line_uuids = creator.create_lines( corner_uuids, lines ); std::vector< geode::SurfaceDefinition > surfaces{ - { { 0, 1, 2, 3 }, { 0, 1, 2, 0, 3, 2 }, { 0, 1, 2, 3 }, {}, {} }, - { { 0, 1, 5, 4 }, { 0, 1, 2, 0, 3, 2 }, { 0, 4, 8, 9 }, {}, {} }, - { { 1, 2, 6, 5 }, { 0, 1, 2, 0, 3, 2 }, { 1, 5, 9, 10 }, {}, {} }, - { { 2, 3, 7, 6 }, { 0, 1, 2, 0, 3, 2 }, { 2, 6, 10, 11 }, {}, {} }, - { { 0, 3, 7, 4 }, { 0, 1, 2, 0, 3, 2 }, { 3, 7, 11, 8 }, {}, {} }, - { { 4, 5, 6, 7 }, { 0, 1, 2, 0, 3, 2 }, {}, {}, {} }, + { { 0, 1, 2, 3 }, { 0, 1, 2, 0, 2, 3 }, { 0, 1, 2, 3 }, {}, {} }, + { { 0, 1, 5, 4 }, { 0, 1, 2, 0, 2, 3 }, { 0, 4, 8, 9 }, {}, {} }, + { { 1, 2, 6, 5 }, { 0, 1, 2, 0, 2, 3 }, { 1, 5, 9, 10 }, {}, {} }, + { { 2, 3, 7, 6 }, { 0, 1, 2, 0, 2, 3 }, { 2, 6, 10, 11 }, {}, {} }, + { { 0, 3, 7, 4 }, { 0, 1, 2, 0, 2, 3 }, { 3, 7, 11, 8 }, {}, {} }, + { { 4, 5, 6, 7 }, { 0, 1, 2, 0, 2, 3 }, {}, {}, {} }, { { 5, 6, 10, 9, 12 }, { 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4 }, { 5, 12, 13, 14 }, {}, { 12 } }, }; @@ -129,11 +141,11 @@ void test_create_brep_with_dangling_components() "[Test] Wrong number of corner embedding blocks" ); OPENGEODE_EXCEPTION( nb_embedding_surfaces == 1, "[Test] Wrong number of corner embedding surfaces" ); + geode::save_brep( brep, "test.og_brep" ); } -void test() +void test_section() { - geode::OpenGeodeModelLibrary::initialize(); std::vector< geode::Point2D > points{ geode::Point2D{ { 0, 0 } }, geode::Point2D{ { 1, 0 } }, @@ -198,8 +210,8 @@ void test() std::vector< geode::SurfaceDefinition > surface_definitions{ { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10 }, - { 0, 4, 7, 7, 4, 1, 1, 4, 8, 4, 10, 8, 8, 10, 5, 8, 5, 2, 2, 5, 9, - 9, 5, 3, 3, 5, 6, 6, 5, 11, 6, 11, 4, 6, 4, 0 }, + { 0, 7, 4, 7, 1, 4, 1, 8, 4, 4, 8, 10, 8, 5, 10, 8, 2, 5, 2, 9, 5, + 9, 3, 5, 3, 6, 5, 6, 11, 5, 6, 4, 11, 6, 0, 4 }, { 0, 1, 2, 3 }, { 4 }, {} }, }; const auto surfaces = creator.create_surfaces( lines, surface_definitions ); @@ -273,8 +285,15 @@ void test() found, "[Test] Missing Line in ModelBoundary" ); } } + geode::save_section( section, "test_section.og_sctn" ); +} +void test() +{ + geode::OpenGeodeModelLibrary::initialize(); + test_create_brep_from_bbox(); test_create_brep_with_dangling_components(); + test_section(); } OPENGEODE_TEST( "model-creator" ) \ No newline at end of file From 76720862792f0096d7fe719c53bee9055a9a1154 Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 15:48:57 +0100 Subject: [PATCH 2/6] fix(Geometry): define BoundingBox epsilon_contains and epsilon_intersects methods --- include/geode/geometry/bounding_box.hpp | 28 ++ include/geode/geometry/detail/aabb_impl.hpp | 17 +- src/geode/geometry/bounding_box.cpp | 377 +++++++++++++++++--- 3 files changed, 361 insertions(+), 61 deletions(-) diff --git a/include/geode/geometry/bounding_box.hpp b/include/geode/geometry/bounding_box.hpp index 651ffee14..13cc8e3b0 100644 --- a/include/geode/geometry/bounding_box.hpp +++ b/include/geode/geometry/bounding_box.hpp @@ -63,6 +63,10 @@ namespace geode bool contains( const BoundingBox< dimension >& bbox ) const; + bool epsilon_contains( const Point< dimension >& point ) const; + + bool epsilon_contains( const BoundingBox< dimension >& bbox ) const; + [[nodiscard]] bool intersects( const BoundingBox< dimension >& bbox ) const; @@ -94,6 +98,30 @@ namespace geode [[nodiscard]] typename std::enable_if< T == 3, bool >::type intersects( const Tetrahedron& tetra ) const; + /*! + * Epsilon intersection methods are equivalent to intersects ones, but + * considering an epsilon extension of the bbox + */ + [[nodiscard]] bool epsilon_intersects( + const BoundingBox< dimension >& bbox ) const; + + [[nodiscard]] bool epsilon_intersects( + const Ray< dimension >& ray ) const; + + [[nodiscard]] bool epsilon_intersects( + const InfiniteLine< dimension >& line ) const; + + [[nodiscard]] bool epsilon_intersects( + const Segment< dimension >& segment ) const; + + template < index_t T = dimension > + [[nodiscard]] typename std::enable_if< T == 2 || T == 3, bool >::type + epsilon_intersects( const Triangle< T >& triangle ) const; + + template < index_t T = dimension > + [[nodiscard]] typename std::enable_if< T == 3, bool >::type + epsilon_intersects( const Tetrahedron& tetra ) const; + /*! * Returns the distance between the point and the box. * If the point is inside the box, the distance is negative. diff --git a/include/geode/geometry/detail/aabb_impl.hpp b/include/geode/geometry/detail/aabb_impl.hpp index 6ef4a2184..f20b8bfce 100644 --- a/include/geode/geometry/detail/aabb_impl.hpp +++ b/include/geode/geometry/detail/aabb_impl.hpp @@ -301,7 +301,7 @@ namespace geode } // The acceleration is here: - if( !node( node_index1 ).intersects( node( node_index2 ) ) ) + if( !node( node_index1 ).epsilon_intersects( node( node_index2 ) ) ) { return false; } @@ -382,7 +382,8 @@ namespace geode // The acceleration is here: if( !node( node_index1 ) - .intersects( other_tree.impl_->node( node_index2 ) ) ) + .epsilon_intersects( + other_tree.impl_->node( node_index2 ) ) ) { return false; } @@ -527,7 +528,7 @@ namespace geode node_index < tree_.size(), "Node index out of tree" ); OPENGEODE_ASSERT( element_begin != element_end, "Begin and End indices should be different" ); - if( !node( node_index ).contains( query ) ) + if( !node( node_index ).epsilon_contains( query ) ) { return; } @@ -588,7 +589,7 @@ namespace geode const BoundingBox< dimension >& box, EvalIntersection& action ) const { const auto box_filter = [&box]( const auto& inner_box ) { - return inner_box.intersects( box ); + return inner_box.epsilon_intersects( box ); }; compute_generic_element_bbox_intersections( box_filter, action ); } @@ -627,7 +628,7 @@ namespace geode const Ray< dimension >& ray, EvalIntersection& action ) const { const auto box_filter = [&ray]( const auto& box ) { - return box.intersects( ray ); + return box.epsilon_intersects( ray ); }; compute_generic_element_bbox_intersections( box_filter, action ); } @@ -638,7 +639,7 @@ namespace geode const InfiniteLine< dimension >& line, EvalIntersection& action ) const { const auto box_filter = [&line]( const auto& box ) { - return box.intersects( line ); + return box.epsilon_intersects( line ); }; compute_generic_element_bbox_intersections( box_filter, action ); } @@ -662,7 +663,7 @@ namespace geode const Triangle< dimension >& triangle, EvalIntersection& action ) const { const auto box_filter = [&triangle]( const auto& box ) { - return box.intersects( triangle ); + return box.epsilon_intersects( triangle ); }; compute_generic_element_bbox_intersections( box_filter, action ); } @@ -673,7 +674,7 @@ namespace geode const Segment< dimension >& segment, EvalIntersection& action ) const { const auto box_filter = [&segment]( const auto& box ) { - return box.intersects( segment ); + return box.epsilon_intersects( segment ); }; compute_generic_element_bbox_intersections( box_filter, action ); } diff --git a/src/geode/geometry/bounding_box.cpp b/src/geode/geometry/bounding_box.cpp index 2940d13b2..2d99c0c8b 100644 --- a/src/geode/geometry/bounding_box.cpp +++ b/src/geode/geometry/bounding_box.cpp @@ -35,13 +35,84 @@ namespace { + geode::BoundingBox1D triangle_1d_projection( + const geode::Triangle3D& triangle, const geode::Vector3D& normal ) + { + const auto& vertices = triangle.vertices(); + geode::BoundingBox1D interval; + interval.add_point( geode::Point1D{ + { normal.dot( geode::Vector3D{ vertices[0].get() } ) } } ); + interval.add_point( geode::Point1D{ + { normal.dot( geode::Vector3D{ vertices[1].get() } ) } } ); + interval.add_point( geode::Point1D{ + { normal.dot( geode::Vector3D{ vertices[2].get() } ) } } ); + return interval; + } + + geode::BoundingBox1D bbox_1d_projection( + const geode::BoundingBox3D& box, const geode::Vector3D& normal ) + { + const auto box_diagonal = box.diagonal(); + const auto origin = normal.dot( geode::Vector3D{ box.center() } ); + const auto maximum_extent = + ( std::fabs( normal.value( 0 ) * box_diagonal.value( 0 ) ) + + std::fabs( normal.value( 1 ) * box_diagonal.value( 1 ) ) + + std::fabs( normal.value( 2 ) * box_diagonal.value( 2 ) ) ) + / 2.; + return geode::BoundingBox1D{ geode::Point1D{ + { origin - maximum_extent } }, + geode::Point1D{ { origin + maximum_extent } } }; + } + template < geode::index_t dimension > - bool line_intersects( const geode::BoundingBox< dimension >& box, + bool ray_quick_skip( const geode::BoundingBox< dimension >& box, + const geode::Ray< dimension >& ray ) + { + const auto box_half_extent = box.diagonal() / 2.; + const auto ray_translated_origin = ray.origin() - box.center(); + for( const auto i : geode::LRange{ dimension } ) + { + if( std::fabs( ray_translated_origin.value( i ) ) + - box_half_extent.value( i ) + > geode::GLOBAL_EPSILON + && ray_translated_origin.value( i ) * ray.direction().value( i ) + > geode::GLOBAL_EPSILON ) + { + return true; + } + } + return false; + } + + template < geode::index_t dimension > + bool segment_quick_skip( const geode::BoundingBox< dimension >& box, + const geode::Segment< dimension >& segment ) + { + const auto box_extent = box.diagonal() / 2.; + const auto segment_origin = segment.barycenter() - box.center(); + const auto segment_extent = segment.length() / 2.; + const auto segment_direction = segment.normalized_direction(); + for( const auto i : geode::LRange{ dimension } ) + { + const auto lhs = std::fabs( segment_origin.value( i ) ); + const auto rhs = + box_extent.value( i ) + + segment_extent * std::fabs( segment_direction.value( i ) ); + if( lhs - rhs > geode::GLOBAL_EPSILON ) + { + return true; + } + } + return false; + } + + template < geode::index_t dimension > + bool line_epsilon_intersects( const geode::BoundingBox< dimension >& box, const geode::GenericLine< geode::RefPoint< dimension >, dimension >& line ); template <> - bool line_intersects< 3 >( const geode::BoundingBox< 3 >& box, + bool line_epsilon_intersects< 3 >( const geode::BoundingBox< 3 >& box, const geode::GenericLine< geode::RefPoint< 3 >, 3 >& line ) { const auto box_half_extent = box.diagonal() / 2.; @@ -72,7 +143,7 @@ namespace } template <> - bool line_intersects< 2 >( const geode::BoundingBox< 2 >& box, + bool line_epsilon_intersects< 2 >( const geode::BoundingBox< 2 >& box, const geode::GenericLine< geode::RefPoint< 2 >, 2 >& line ) { const auto box_center = box.center(); @@ -88,11 +159,43 @@ namespace return lhs - rhs <= geode::GLOBAL_EPSILON; } + template <> + bool line_epsilon_intersects< 1 >( const geode::BoundingBox< 1 >& box, + const geode::GenericLine< geode::RefPoint< 1 >, 1 >& line ) + { + if( line.direction().value( 0 ) > 0 ) + { + return line.origin().value( 0 ) + < box.min().value( 0 ) - geode::GLOBAL_EPSILON; + } + return line.origin().value( 0 ) + > box.max().value( 0 ) + geode::GLOBAL_EPSILON; + } + + template < geode::index_t dimension > + bool line_intersects( const geode::BoundingBox< dimension >& box, + const geode::GenericLine< geode::RefPoint< dimension >, dimension >& + line ); + + template <> + bool line_intersects< 3 >( const geode::BoundingBox< 3 >& box, + const geode::GenericLine< geode::RefPoint< 3 >, 3 >& line ) + { + return line_epsilon_intersects< 3 >( box, line ); + } + + template <> + bool line_intersects< 2 >( const geode::BoundingBox< 2 >& box, + const geode::GenericLine< geode::RefPoint< 2 >, 2 >& line ) + { + return line_epsilon_intersects< 2 >( box, line ); + } + template <> bool line_intersects< 1 >( const geode::BoundingBox< 1 >& box, const geode::GenericLine< geode::RefPoint< 1 >, 1 >& line ) { - if( box.diagonal().dot( line.direction() ) > 0 ) + if( line.direction().value( 0 ) > 0 ) { return line.origin().value( 0 ) < box.min().value( 0 ); } @@ -216,6 +319,28 @@ namespace geode return contains( bbox.min_ ) && contains( bbox.max_ ); } + template < index_t dimension > + bool BoundingBox< dimension >::epsilon_contains( + const Point< dimension >& point ) const + { + for( const auto i : LRange{ dimension } ) + { + if( point.value( i ) < min_.value( i ) - GLOBAL_EPSILON + || point.value( i ) > max_.value( i ) + GLOBAL_EPSILON ) + { + return false; + } + } + return true; + } + + template < index_t dimension > + bool BoundingBox< dimension >::epsilon_contains( + const BoundingBox< dimension >& bbox ) const + { + return epsilon_contains( bbox.min_ ) && epsilon_contains( bbox.max_ ); + } + template < index_t dimension > bool BoundingBox< dimension >::intersects( const BoundingBox< dimension >& box ) const @@ -232,25 +357,42 @@ namespace geode } template < index_t dimension > - bool BoundingBox< dimension >::intersects( - const Ray< dimension >& ray ) const + bool BoundingBox< dimension >::epsilon_intersects( + const BoundingBox< dimension >& box ) const { - const auto box_half_extent = diagonal() / 2.; - const auto ray_translated_origin = ray.origin() - center(); for( const auto i : LRange{ dimension } ) { - if( std::fabs( ray_translated_origin.value( i ) ) - - box_half_extent.value( i ) - > GLOBAL_EPSILON - && ray_translated_origin.value( i ) * ray.direction().value( i ) - > GLOBAL_EPSILON ) + if( max_.value( i ) < box.min_.value( i ) - GLOBAL_EPSILON + || min_.value( i ) > box.max_.value( i ) + GLOBAL_EPSILON ) { return false; } } + return true; + } + + template < index_t dimension > + bool BoundingBox< dimension >::intersects( + const Ray< dimension >& ray ) const + { + if( ray_quick_skip( *this, ray ) ) + { + return false; + } return line_intersects( *this, ray ); } + template < index_t dimension > + bool BoundingBox< dimension >::epsilon_intersects( + const Ray< dimension >& ray ) const + { + if( ray_quick_skip( *this, ray ) ) + { + return false; + } + return line_epsilon_intersects( *this, ray ); + } + template < index_t dimension > bool BoundingBox< dimension >::intersects( const InfiniteLine< dimension >& line ) const @@ -258,6 +400,13 @@ namespace geode return line_intersects( *this, line ); } + template < index_t dimension > + bool BoundingBox< dimension >::epsilon_intersects( + const InfiniteLine< dimension >& line ) const + { + return line_epsilon_intersects( *this, line ); + } + template < index_t dimension > bool BoundingBox< dimension >::intersects( const Segment< dimension >& segment ) const @@ -278,23 +427,38 @@ namespace geode { return false; } - const auto box_center = center(); - const auto box_extent = diagonal() / 2.; - const auto segment_origin = segment.barycenter() - box_center; - const auto segment_extent = segment.length() / 2.; - const auto segment_direction = segment.normalized_direction(); - for( const auto i : LRange{ dimension } ) + if( segment_quick_skip( *this, segment ) ) { - const auto lhs = std::fabs( segment_origin.value( i ) ); - const auto rhs = - box_extent.value( i ) - + segment_extent * std::fabs( segment_direction.value( i ) ); - if( lhs - rhs > geode::GLOBAL_EPSILON ) + return false; + } + return this->intersects( InfiniteLine< dimension >{ segment } ); + } + + template < index_t dimension > + bool BoundingBox< dimension >::epsilon_intersects( + const Segment< dimension >& segment ) const + { + const auto& vertices = segment.vertices(); + for( const auto v : LRange{ 2 } ) + { + if( epsilon_contains( vertices[v].get() ) ) { - return false; + return true; } } - return this->intersects( InfiniteLine< dimension >{ segment } ); + if( segment.length() < GLOBAL_EPSILON ) + { + return false; + } + if( !epsilon_intersects( segment.bounding_box() ) ) + { + return false; + } + if( segment_quick_skip( *this, segment ) ) + { + return false; + } + return this->epsilon_intersects( InfiniteLine< dimension >{ segment } ); } template <> @@ -314,29 +478,8 @@ namespace geode { return false; } - const auto triangle_projection = [&vertices]( const Vector3D& normal ) { - BoundingBox1D interval; - interval.add_point( - Point1D{ { normal.dot( Vector3D{ vertices[0].get() } ) } } ); - interval.add_point( - Point1D{ { normal.dot( Vector3D{ vertices[1].get() } ) } } ); - interval.add_point( - Point1D{ { normal.dot( Vector3D{ vertices[2].get() } ) } } ); - return interval; - }; const Vector3D box_center{ center() }; const auto box_diagonal = diagonal(); - const auto bbox_projection = [&box_center, &box_diagonal]( - const Vector3D& normal ) { - const auto origin = normal.dot( box_center ); - const auto maximum_extent = - ( std::fabs( normal.value( 0 ) * box_diagonal.value( 0 ) ) - + std::fabs( normal.value( 1 ) * box_diagonal.value( 1 ) ) - + std::fabs( normal.value( 2 ) * box_diagonal.value( 2 ) ) ) - / 2.; - return BoundingBox1D{ Point1D{ { origin - maximum_extent } }, - Point1D{ { origin + maximum_extent } } }; - }; const std::array< Vector3D, 3 > edges{ Vector3D{ vertices[0].get(), vertices[1].get() }, Vector3D{ vertices[0].get(), vertices[2].get() }, @@ -344,7 +487,7 @@ namespace geode // Test direction of triangle normal. const auto triangle_normal = edges[0].cross( edges[1] ); - if( !bbox_projection( triangle_normal ) + if( !bbox_1d_projection( *this, triangle_normal ) .contains( Point1D{ { triangle_normal.dot( Vector3D{ vertices[0].get() } ) } } ) ) { @@ -356,7 +499,8 @@ namespace geode { Vector3D axis; axis.set_value( i, 1 ); - const auto triangle_interval = triangle_projection( axis ); + const auto triangle_interval = + triangle_1d_projection( triangle, axis ); const auto center_value = box_center.value( i ); const auto extent = box_diagonal.value( i ) / 2.; const BoundingBox1D box_interval{ Point1D{ @@ -376,8 +520,9 @@ namespace geode Vector3D axis; axis.set_value( i1, 1 ); const auto normal = edges[i0].cross( axis ); - const auto triangle_interval = triangle_projection( normal ); - const auto box_interval = bbox_projection( normal ); + const auto triangle_interval = + triangle_1d_projection( triangle, normal ); + const auto box_interval = bbox_1d_projection( *this, normal ); if( !triangle_interval.intersects( box_interval ) ) { return false; @@ -387,6 +532,77 @@ namespace geode return true; } + template <> + template <> + bool opengeode_geometry_api BoundingBox< 3 >::epsilon_intersects< 3 >( + const Triangle< 3 >& triangle ) const + { + const auto& vertices = triangle.vertices(); + for( const auto v : LRange{ 3 } ) + { + if( epsilon_contains( vertices[v].get() ) ) + { + return true; + } + } + if( !epsilon_intersects( triangle.bounding_box() ) ) + { + return false; + } + const Vector3D box_center{ center() }; + const auto box_diagonal = diagonal(); + const std::array< Vector3D, 3 > edges{ Vector3D{ vertices[0].get(), + vertices[1].get() }, + Vector3D{ vertices[0].get(), vertices[2].get() }, + Vector3D{ vertices[1].get(), vertices[2].get() } }; + + // Test direction of triangle normal. + const auto triangle_normal = edges[0].cross( edges[1] ); + if( !bbox_1d_projection( *this, triangle_normal ) + .epsilon_contains( Point1D{ { triangle_normal.dot( + Vector3D{ vertices[0].get() } ) } } ) ) + { + return false; + } + + // Test direction of box faces. + for( const auto i : LRange{ 3 } ) + { + Vector3D axis; + axis.set_value( i, 1 ); + const auto triangle_interval = + triangle_1d_projection( triangle, axis ); + const auto center_value = box_center.value( i ); + const auto extent = box_diagonal.value( i ) / 2.; + const BoundingBox1D box_interval{ Point1D{ + { center_value - extent } }, + Point1D{ { center_value + extent } } }; + if( !triangle_interval.epsilon_intersects( box_interval ) ) + { + return false; + } + } + + // Test direction of triangle-box edge cross products. + for( const auto i0 : LRange{ 3 } ) + { + for( const auto i1 : LRange{ 3 } ) + { + Vector3D axis; + axis.set_value( i1, 1 ); + const auto normal = edges[i0].cross( axis ); + const auto triangle_interval = + triangle_1d_projection( triangle, normal ); + const auto box_interval = bbox_1d_projection( *this, normal ); + if( !triangle_interval.epsilon_intersects( box_interval ) ) + { + return false; + } + } + } + return true; + } + template <> template <> bool opengeode_geometry_api BoundingBox< 2 >::intersects< 2 >( @@ -411,6 +627,31 @@ namespace geode Segment2D{ vertices[1].get(), vertices[2].get() } ); } + template <> + template <> + bool opengeode_geometry_api BoundingBox< 2 >::epsilon_intersects< 2 >( + const Triangle< 2 >& triangle ) const + { + if( point_triangle_position( center(), triangle ) == POSITION::inside ) + { + return true; + } + const auto& vertices = triangle.vertices(); + for( const auto v : LRange{ 3 } ) + { + if( epsilon_contains( vertices[v].get() ) ) + { + return true; + } + } + return epsilon_intersects( + Segment2D{ vertices[0].get(), vertices[1].get() } ) + || epsilon_intersects( + Segment2D{ vertices[0].get(), vertices[2].get() } ) + || epsilon_intersects( + Segment2D{ vertices[1].get(), vertices[2].get() } ); + } + template < index_t dimension > template < index_t T > typename std::enable_if< T == 3, bool >::type @@ -438,6 +679,34 @@ namespace geode vertices[3].get() } ); } + template < index_t dimension > + template < index_t T > + typename std::enable_if< T == 3, bool >::type + BoundingBox< dimension >::epsilon_intersects( + const Tetrahedron& tetra ) const + { + if( point_tetrahedron_position( center(), tetra ) == POSITION::inside ) + { + return true; + } + const auto& vertices = tetra.vertices(); + for( const auto v : LRange{ 4 } ) + { + if( epsilon_contains( vertices[v].get() ) ) + { + return true; + } + } + return epsilon_intersects( + { vertices[0].get(), vertices[1].get(), vertices[2].get() } ) + || epsilon_intersects( + { vertices[0].get(), vertices[1].get(), vertices[3].get() } ) + || epsilon_intersects( + { vertices[0].get(), vertices[2].get(), vertices[3].get() } ) + || epsilon_intersects( { vertices[1].get(), vertices[2].get(), + vertices[3].get() } ); + } + template < index_t dimension > Point< dimension > BoundingBox< dimension >::center() const { @@ -499,7 +768,7 @@ namespace geode const auto Pmin = point - min_; const auto Pmax = point - max_; auto inner_distance = std::numeric_limits< double >::max(); - for( const auto c : geode::LRange{ dimension } ) + for( const auto c : LRange{ dimension } ) { const auto local_distance = std::min( std::fabs( Pmin.value( c ) ), std::fabs( Pmax.value( c ) ) ); @@ -513,7 +782,7 @@ namespace geode { double volume{ 1.0 }; const auto box_extent = diagonal(); - for( const auto c : geode::LRange{ dimension } ) + for( const auto c : LRange{ dimension } ) { volume *= ( box_extent.value( c ) ); } @@ -533,4 +802,6 @@ namespace geode template opengeode_geometry_api bool BoundingBox< 3 >::intersects< 3 >( const Tetrahedron& ) const; + template opengeode_geometry_api bool + BoundingBox< 3 >::epsilon_intersects< 3 >( const Tetrahedron& ) const; } // namespace geode From 644816b1ae1b659e0777ffa49d27e83445c982d6 Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 15:49:15 +0100 Subject: [PATCH 3/6] fix(Geometry): AABB tree requests use epsilon methods --- src/geode/mesh/helpers/aabb_surface_helpers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/geode/mesh/helpers/aabb_surface_helpers.cpp b/src/geode/mesh/helpers/aabb_surface_helpers.cpp index fab928a1e..157ca2e6b 100644 --- a/src/geode/mesh/helpers/aabb_surface_helpers.cpp +++ b/src/geode/mesh/helpers/aabb_surface_helpers.cpp @@ -55,6 +55,7 @@ namespace geode bbox.add_point( mesh.point( mesh.polygon_vertex( { p, v } ) ) ); } + // bbox.extends( GLOBAL_EPSILON ); box_vector[p] = std::move( bbox ); } ); return AABBTree< dimension >{ box_vector }; From 380807163d74d645d1ae7cbd08f451fb6a2edde6 Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 17:07:27 +0100 Subject: [PATCH 4/6] bindings for new functions --- bindings/python/src/model/CMakeLists.txt | 1 + .../model/helpers/simplicial_brep_creator.cpp | 35 +++++++++++++++++++ bindings/python/src/model/model.cpp | 2 ++ 3 files changed, 38 insertions(+) create mode 100644 bindings/python/src/model/helpers/simplicial_brep_creator.cpp diff --git a/bindings/python/src/model/CMakeLists.txt b/bindings/python/src/model/CMakeLists.txt index 383339303..5ba3fd4a3 100644 --- a/bindings/python/src/model/CMakeLists.txt +++ b/bindings/python/src/model/CMakeLists.txt @@ -30,6 +30,7 @@ add_geode_python_binding( "helpers/model_concatener.cpp" "helpers/model_crs.cpp" "helpers/ray_tracing.cpp" + "helpers/simplicial_brep_creator.cpp" "mixin/builder/blocks_builder.cpp" "mixin/builder/block_collections_builder.cpp" "mixin/builder/component_registry_builder.cpp" diff --git a/bindings/python/src/model/helpers/simplicial_brep_creator.cpp b/bindings/python/src/model/helpers/simplicial_brep_creator.cpp new file mode 100644 index 000000000..55b3a6d8c --- /dev/null +++ b/bindings/python/src/model/helpers/simplicial_brep_creator.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 - 2025 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "../../common.hpp" + +#include + +namespace geode +{ + void define_simplicial_brep_creator( pybind11::module& module ) + { + module.def( + "create_brep_from_bounding_box", &create_model_from_bounding_box ); + } +} // namespace geode diff --git a/bindings/python/src/model/model.cpp b/bindings/python/src/model/model.cpp index 201e60264..c69a816c0 100644 --- a/bindings/python/src/model/model.cpp +++ b/bindings/python/src/model/model.cpp @@ -87,6 +87,7 @@ namespace geode void define_model_concatener( pybind11::module& ); void define_model_coordinate_reference_system( pybind11::module& ); void define_model_ray_tracing( pybind11::module& ); + void define_simplicial_brep_creator( pybind11::module& ); } // namespace geode PYBIND11_MODULE( opengeode_py_model, module ) @@ -164,4 +165,5 @@ PYBIND11_MODULE( opengeode_py_model, module ) geode::define_model_concatener( module ); geode::define_model_coordinate_reference_system( module ); geode::define_model_ray_tracing( module ); + geode::define_simplicial_brep_creator( module ); } \ No newline at end of file From 64d8756ac3b65e96465a7b34206a7cb3c4e209ff Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 17:24:41 +0100 Subject: [PATCH 5/6] try fix python --- bindings/python/src/model/helpers/simplicial_brep_creator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/python/src/model/helpers/simplicial_brep_creator.cpp b/bindings/python/src/model/helpers/simplicial_brep_creator.cpp index 55b3a6d8c..893ee3f3f 100644 --- a/bindings/python/src/model/helpers/simplicial_brep_creator.cpp +++ b/bindings/python/src/model/helpers/simplicial_brep_creator.cpp @@ -23,7 +23,10 @@ #include "../../common.hpp" +#include + #include +#include namespace geode { From fa4d9fcde5ad3e57e08252b3cc5c66f3a3ef2c73 Mon Sep 17 00:00:00 2001 From: Pierre Anquez Date: Tue, 6 Jan 2026 18:04:47 +0100 Subject: [PATCH 6/6] clean --- src/geode/mesh/helpers/aabb_surface_helpers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/geode/mesh/helpers/aabb_surface_helpers.cpp b/src/geode/mesh/helpers/aabb_surface_helpers.cpp index 157ca2e6b..fab928a1e 100644 --- a/src/geode/mesh/helpers/aabb_surface_helpers.cpp +++ b/src/geode/mesh/helpers/aabb_surface_helpers.cpp @@ -55,7 +55,6 @@ namespace geode bbox.add_point( mesh.point( mesh.polygon_vertex( { p, v } ) ) ); } - // bbox.extends( GLOBAL_EPSILON ); box_vector[p] = std::move( bbox ); } ); return AABBTree< dimension >{ box_vector };