From 21a6672b2ef4c4ded0df33ac0786ec8dd75618b0 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Mon, 16 Dec 2024 14:23:19 -0500 Subject: [PATCH] Add BoundingCylinder --- .../ImplicitTilingUtilities.h | 24 ++++ .../TileBoundingVolumes.h | 27 ++++ .../src/ImplicitTilingUtilities.cpp | 27 ++++ .../src/TileBoundingVolumes.cpp | 41 ++++++ .../Cesium3DTilesSelection/BoundingVolume.h | 5 +- Cesium3DTilesSelection/src/BoundingVolume.cpp | 19 +++ .../include/CesiumGeometry/BoundingCylinder.h | 125 ++++++++++++++++++ .../CesiumGeometry/OrientedBoundingBox.h | 11 ++ CesiumGeometry/src/BoundingCylinder.cpp | 31 +++++ CesiumGeometry/src/OrientedBoundingBox.cpp | 12 ++ CesiumGeometry/test/TestBoundingCylinder.cpp | 34 +++++ 11 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 CesiumGeometry/include/CesiumGeometry/BoundingCylinder.h create mode 100644 CesiumGeometry/src/BoundingCylinder.cpp create mode 100644 CesiumGeometry/test/TestBoundingCylinder.cpp diff --git a/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h b/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h index b941ef334..364c46487 100644 --- a/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h +++ b/Cesium3DTilesContent/include/Cesium3DTilesContent/ImplicitTilingUtilities.h @@ -399,6 +399,30 @@ class ImplicitTilingUtilities { const CesiumGeometry::OctreeTileID& tileID, const CesiumGeospatial::Ellipsoid& ellipsoid CESIUM_DEFAULT_ELLIPSOID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit quadtree tile + * with the given ID as a bounding cylinder. + * + * @param rootBoundingVolume The oriented bounding box of the root tile. + * @param tileID The tile ID for which to compute the oriented bounding box. + * @return The oriented bounding box for the given implicit tile. + */ + static CesiumGeometry::BoundingCylinder computeBoundingVolume( + const CesiumGeometry::BoundingCylinder& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept; + + /** + * @brief Computes the bounding volume for an implicit octree tile with + * the given ID as a bounding cylinder. + * + * @param rootBoundingVolume The bounding cylinder of the root tile. + * @param tileID The tile ID for which to compute the bounding cylinder. + * @return The bounding cylinder for the given implicit tile. + */ + static CesiumGeometry::BoundingCylinder computeBoundingVolume( + const CesiumGeometry::BoundingCylinder& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept; }; } // namespace Cesium3DTilesContent diff --git a/Cesium3DTilesContent/include/Cesium3DTilesContent/TileBoundingVolumes.h b/Cesium3DTilesContent/include/Cesium3DTilesContent/TileBoundingVolumes.h index feb2aa26a..cd3c40c18 100644 --- a/Cesium3DTilesContent/include/Cesium3DTilesContent/TileBoundingVolumes.h +++ b/Cesium3DTilesContent/include/Cesium3DTilesContent/TileBoundingVolumes.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -122,6 +123,32 @@ class TileBoundingVolumes { static void setS2CellBoundingVolume( Cesium3DTiles::BoundingVolume& boundingVolume, const CesiumGeospatial::S2CellBoundingVolume& s2BoundingVolume); + + /** + * @brief Gets the bounding cylinder defined in the + * `3DTILES_bounding_volume_cylinder` extension of a + * {@link Cesium3DTiles::BoundingVolume}, if any. + * + * @param boundingVolume The bounding volume from which to get the cylinder. + * @return The cylinder, or `std::nullopt` if the bounding volume does not + * define a cylinder. The cylinder is defined in the tile's coordinate system. + */ + static std::optional + getBoundingCylinder(const Cesium3DTiles::BoundingVolume& boundingVolume); + + /** + * @brief Adds the `3DTILES_bounding_volume_cylinder` extension to a + * {@link Cesium3DTiles::BoundingVolume} based on a + * {@link CesiumGeometry::BoundingCylinder}. + * + * Other bounding volume types, if any, are not modified. + * + * @param boundingVolume The bounding volume to set. + * @param boundingCylinder The bounding cylinder with which to set the property. + */ + static void setBoundingCylinder( + Cesium3DTiles::BoundingVolume& boundingVolume, + const CesiumGeometry::BoundingCylinder& boundingCylinder); }; } // namespace Cesium3DTilesContent diff --git a/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp b/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp index 5dd06b1fe..b3c952ac4 100644 --- a/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp +++ b/Cesium3DTilesContent/src/ImplicitTilingUtilities.cpp @@ -140,6 +140,7 @@ Cesium3DTiles::BoundingVolume computeBoundingVolumeInternal( std::optional maybeBox = TileBoundingVolumes::getOrientedBoundingBox(rootBoundingVolume); + if (maybeBox) { OrientedBoundingBox obb = ImplicitTilingUtilities::computeBoundingVolume(*maybeBox, tileID); @@ -168,6 +169,14 @@ Cesium3DTiles::BoundingVolume computeBoundingVolumeInternal( TileBoundingVolumes::setS2CellBoundingVolume(result, s2); } + std::optional maybeCylinder = + TileBoundingVolumes::getBoundingCylinder(rootBoundingVolume, ellipsoid); + if (maybeCylinder) { + BoundingCylinder cylinder = + ImplicitTilingUtilities::computeBoundingVolume(*maybeCylinder tileID); + TileBoundingVolumes::setBoundingCylinder(result, maybeCylinder); + } + return result; } } // namespace @@ -334,6 +343,24 @@ ImplicitTilingUtilities::computeBoundingVolume( ellipsoid); } +CesiumGeometry::BoundingCylinder ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeometry::BoundingCylinder& rootBoundingVolume, + const CesiumGeometry::QuadtreeTileID& tileID) noexcept { + CesiumGeometry::OrientedBoundingBox result = computeBoundingVolume( + CesiumGeometry::OrientedBoundingBox::fromCylinder(rootBoundingVolume), + tileID); + return result.toCylinder(); +} + +CesiumGeometry::BoundingCylinder ImplicitTilingUtilities::computeBoundingVolume( + const CesiumGeometry::BoundingCylinder& rootBoundingVolume, + const CesiumGeometry::OctreeTileID& tileID) noexcept { + CesiumGeometry::OrientedBoundingBox result = computeBoundingVolume( + CesiumGeometry::OrientedBoundingBox::fromCylinder(rootBoundingVolume), + tileID); + return result.toCylinder(); +} + double ImplicitTilingUtilities::computeLevelDenominator(uint32_t level) noexcept { return static_cast(1 << level); diff --git a/Cesium3DTilesContent/src/TileBoundingVolumes.cpp b/Cesium3DTilesContent/src/TileBoundingVolumes.cpp index ca5654e16..a75c120ad 100644 --- a/Cesium3DTilesContent/src/TileBoundingVolumes.cpp +++ b/Cesium3DTilesContent/src/TileBoundingVolumes.cpp @@ -1,5 +1,6 @@ #include #include +#include #include using namespace Cesium3DTiles; @@ -110,4 +111,44 @@ void TileBoundingVolumes::setS2CellBoundingVolume( extension.maximumHeight = s2BoundingVolume.getMaximumHeight(); } +std::optional TileBoundingVolumes::getBoundingCylinder( + const BoundingVolume& boundingVolume) { + const Extension3dTilesBoundingVolumeCylinder* pExtension = + boundingVolume.getExtension(); + if (!pExtension) + return std::nullopt; + + if (pExtension->cylinder.size() < 12) + return std::nullopt; + + const std::vector& a = pExtension->cylinder; + return CesiumGeometry::BoundingCylinder( + glm::dvec3(a[0], a[1], a[2]), + glm::dmat3(a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])); +} + +void TileBoundingVolumes::setBoundingCylinder( + Cesium3DTiles::BoundingVolume& boundingVolume, + const CesiumGeometry::BoundingCylinder& boundingCylinder) { + Extension3dTilesBoundingVolumeCylinder& extension = + boundingVolume.addExtension(); + + const glm::dvec3& center = boundingCylinder.getCenter(); + const glm::dmat3& halfAxes = boundingCylinder.getHalfAxes(); + + extension.cylinder = { + center.x, + center.y, + center.z, + halfAxes[0].x, + halfAxes[0].y, + halfAxes[0].z, + halfAxes[1].x, + halfAxes[1].y, + halfAxes[1].z, + halfAxes[2].x, + halfAxes[2].y, + halfAxes[2].z}; +} + } // namespace Cesium3DTilesContent diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/BoundingVolume.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/BoundingVolume.h index b55823bfa..6475b0afa 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/BoundingVolume.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/BoundingVolume.h @@ -2,6 +2,7 @@ #include "Library.h" +#include #include #include #include @@ -25,13 +26,15 @@ namespace Cesium3DTilesSelection { * @see CesiumGeospatial::BoundingRegion * @see CesiumGeospatial::BoundingRegionWithLooseFittingHeights * @see CesiumGeospatial::S2CellBoundingVolume + * @see CesiumGeometry::BoundingCylinder */ typedef std::variant< CesiumGeometry::BoundingSphere, CesiumGeometry::OrientedBoundingBox, CesiumGeospatial::BoundingRegion, CesiumGeospatial::BoundingRegionWithLooseFittingHeights, - CesiumGeospatial::S2CellBoundingVolume> + CesiumGeospatial::S2CellBoundingVolume, + CesiumGeometry::BoundingCylinder> BoundingVolume; /** diff --git a/Cesium3DTilesSelection/src/BoundingVolume.cpp b/Cesium3DTilesSelection/src/BoundingVolume.cpp index c1a046e94..e6c4bea36 100644 --- a/Cesium3DTilesSelection/src/BoundingVolume.cpp +++ b/Cesium3DTilesSelection/src/BoundingVolume.cpp @@ -40,6 +40,11 @@ BoundingVolume transformBoundingVolume( // S2 Cells are not transformed. return s2CellBoundingVolume; } + + BoundingVolume + operator()(const BoundingCylinder& boundingCylinder) noexcept { + return boundingCylinder.transform(transform); + } }; return std::visit(Operation{transform}, boundingVolume); @@ -67,6 +72,10 @@ glm::dvec3 getBoundingVolumeCenter(const BoundingVolume& boundingVolume) { glm::dvec3 operator()(const S2CellBoundingVolume& s2Cell) noexcept { return s2Cell.getCenter(); } + + glm::dvec3 operator()(const BoundingCylinder& boundingCylinder) noexcept { + return boundingCylinder.getCenter(); + } }; return std::visit(Operation{}, boundingVolume); @@ -190,6 +199,11 @@ std::optional estimateGlobeRectangle( operator()(const S2CellBoundingVolume& s2Cell) { return s2Cell.getCellID().computeBoundingRectangle(); } + + std::optional + operator()(const BoundingCylinder& boundingCylinder) { + return operator()(OrientedBoundingBox::fromCylinder(boundingCylinder)); + } }; return std::visit(Operation{ellipsoid}, boundingVolume); @@ -238,6 +252,11 @@ OrientedBoundingBox getOrientedBoundingBoxFromBoundingVolume( operator()(const CesiumGeospatial::S2CellBoundingVolume& s2) const { return s2.computeBoundingRegion(ellipsoid).getBoundingBox(); } + + OrientedBoundingBox + operator()(const CesiumGeometry::BoundingCylinder& cylinder) const { + return OrientedBoundingBox::fromCylinder(cylinder); + } }; return std::visit(Operation{ellipsoid}, boundingVolume); diff --git a/CesiumGeometry/include/CesiumGeometry/BoundingCylinder.h b/CesiumGeometry/include/CesiumGeometry/BoundingCylinder.h new file mode 100644 index 000000000..ef110c4f6 --- /dev/null +++ b/CesiumGeometry/include/CesiumGeometry/BoundingCylinder.h @@ -0,0 +1,125 @@ +#pragma once + +#include "CullingResult.h" +#include "Library.h" +#include "OrientedBoundingBox.h" + +namespace CesiumGeometry { + +class Plane; + +/** + * @brief A bounding volume defined as a closed and convex cylinder with any + * orientation. + * + * NOTE: This uses a {@link OrientedBoundingBox} underneath the hood to + * approximate the result, similar to the way CesiumJS approximates cylinders. + * The output will not be accurate to the actual cylinder itself. + * + * TODO: Update this to more accurately represent a cylinder, and thus return + * more accurate results. + * + * @see OrientedBoundingBox + */ +class CESIUMGEOMETRY_API BoundingCylinder final { +public: + /** + * @brief Construct a new instance. + * + * @param center The center of the cylinder. + * @param halfAxes The three orthogonal half-axes of the bounding cylinder. + * Equivalently, the transformation matrix to rotate and scale a cylinder with + * a radius and height of 1 that is centered at the origin. + * + * @snippet TestBoundingCylinder.cpp Constructor + */ + BoundingCylinder( + const glm::dvec3& center, + const glm::dmat3& halfAxes) noexcept + : _box(center, halfAxes), + _radius(glm::length(halfAxes[0])), + _height(2.0 * glm::length(halfAxes[2])) {} + + /** + * @brief Gets the center of the cylinder. + */ + constexpr const glm::dvec3& getCenter() const noexcept { + return this->_box.getCenter(); + } + + /** + * @brief Gets the three orthogonal half-axes of the cylinder. + * Equivalently, the transformation matrix to rotate and scale a cylinder with + * a radius and height of 1 that is centered at the origin. + */ + constexpr const glm::dmat3& getHalfAxes() const noexcept { + return this->_box.getHalfAxes(); + } + + /** + * @brief Gets the radius of the cylinder. + */ + constexpr double getRadius() const noexcept { return this->_radius; } + + /** + * @brief Gets the height of the cylinder. + */ + constexpr double getHeight() const noexcept { return this->_height; } + + /** + * @brief Gets the inverse transformation matrix, to rotate from world space + * to local space relative to the cylinder. + */ + constexpr const glm::dmat3& getInverseHalfAxes() const noexcept { + return this->_box.getInverseHalfAxes(); + } + + /** + * @brief Determines on which side of a plane the bounding cylinder is + * located. + * + * @param plane The plane to test against. + * @return The {@link CullingResult}: + * * `Inside` if the entire cylinder is on the side of the plane the normal + * is pointing. + * * `Outside` if the entire cylinder is on the opposite side. + * * `Intersecting` if the cylinder intersects the plane. + */ + CullingResult intersectPlane(const Plane& plane) const noexcept; + + /** + * @brief Computes the distance squared from a given position to the closest + * point on the bounding volume. The bounding volume and the position must be + * expressed in the same coordinate system. + * + * @param position The position + * @return The estimated distance squared from the bounding box to the point. + */ + double + computeDistanceSquaredToPosition(const glm::dvec3& position) const noexcept; + + /** + * @brief Computes whether the given position is contained within the bounding + * cylinder. + * + * @param position The position. + * @return Whether the position is contained within the bounding cylinder. + */ + bool contains(const glm::dvec3& position) const noexcept; + + /** + * @brief Transforms this bounding cylinder to another coordinate system using + * a 4x4 matrix. + * + * @param transformation The transformation. + * @return The bounding cylinder in the new coordinate system. + */ + BoundingCylinder transform(const glm::dmat4& transformation) const noexcept; + +private: + OrientedBoundingBox _box; + double _radius; + double _height; +}; + +} // namespace CesiumGeometry diff --git a/CesiumGeometry/include/CesiumGeometry/OrientedBoundingBox.h b/CesiumGeometry/include/CesiumGeometry/OrientedBoundingBox.h index 983d06ba4..631466240 100644 --- a/CesiumGeometry/include/CesiumGeometry/OrientedBoundingBox.h +++ b/CesiumGeometry/include/CesiumGeometry/OrientedBoundingBox.h @@ -11,6 +11,7 @@ namespace CesiumGeometry { class Plane; +class BoundingCylinder; /** * @brief A bounding volume defined as a closed and convex cuboid with any @@ -128,6 +129,11 @@ class CESIUMGEOMETRY_API OrientedBoundingBox final { */ BoundingSphere toSphere() const noexcept; + /** + * @brief Converts this oriented bounding box to a bounding cylinder. + */ + BoundingCylinder toCylinder() const noexcept; + /** * @brief Creates an oriented bounding box from the given axis-aligned * bounding box. @@ -140,6 +146,11 @@ class CESIUMGEOMETRY_API OrientedBoundingBox final { */ static OrientedBoundingBox fromSphere(const BoundingSphere& sphere) noexcept; + /** + * @brief Creates an oriented bounding box from the given bounding cylinder. + */ + static OrientedBoundingBox fromCylinder(const BoundingCylinder& cylinder) noexcept; + private: glm::dvec3 _center; glm::dmat3 _halfAxes; diff --git a/CesiumGeometry/src/BoundingCylinder.cpp b/CesiumGeometry/src/BoundingCylinder.cpp new file mode 100644 index 000000000..46151ba24 --- /dev/null +++ b/CesiumGeometry/src/BoundingCylinder.cpp @@ -0,0 +1,31 @@ +#include "CesiumGeometry/BoundingCylinder.h" + +#include "CesiumGeometry/Plane.h" + +#include +#include + +namespace CesiumGeometry { + +CullingResult +BoundingCylinder::intersectPlane(const Plane& plane) const noexcept { + return this->_box.intersectPlane(plane); +} + +double BoundingCylinder::computeDistanceSquaredToPosition( + const glm::dvec3& position) const noexcept { + return this->_box.computeDistanceSquaredToPosition(position); +} + +bool BoundingCylinder::contains(const glm::dvec3& position) const noexcept { + return this->_box.contains(position); +} + +BoundingCylinder +BoundingCylinder::transform(const glm::dmat4& transformation) const noexcept { + return BoundingCylinder( + glm::dvec3(transformation * glm::dvec4(this->_box.getCenter(), 1.0)), + glm::dmat3(transformation) * this->_box.getHalfAxes()); +} + +} // namespace CesiumGeometry diff --git a/CesiumGeometry/src/OrientedBoundingBox.cpp b/CesiumGeometry/src/OrientedBoundingBox.cpp index 6ee147b70..c4eb1616d 100644 --- a/CesiumGeometry/src/OrientedBoundingBox.cpp +++ b/CesiumGeometry/src/OrientedBoundingBox.cpp @@ -1,5 +1,6 @@ #include "CesiumGeometry/OrientedBoundingBox.h" +#include "CesiumGeometry/BoundingCylinder.h" #include "CesiumGeometry/Plane.h" #include @@ -131,6 +132,10 @@ BoundingSphere OrientedBoundingBox::toSphere() const noexcept { return BoundingSphere(this->_center, sphereRadius); } +BoundingCylinder OrientedBoundingBox::toCylinder() const noexcept { + return BoundingCylinder(this->_center, this->_halfAxes); +} + /*static*/ OrientedBoundingBox OrientedBoundingBox::fromAxisAligned( const AxisAlignedBox& axisAligned) noexcept { return OrientedBoundingBox( @@ -148,4 +153,11 @@ OrientedBoundingBox::fromSphere(const BoundingSphere& sphere) noexcept { return OrientedBoundingBox(center, halfAxes); } +/*static*/ OrientedBoundingBox +OrientedBoundingBox::fromCylinder(const BoundingCylinder& cylinder) noexcept { + glm::dvec3 center = cylinder.getCenter(); + glm::dmat3 halfAxes = cylinder.getHalfAxes(); + return OrientedBoundingBox(center, halfAxes); +} + } // namespace CesiumGeometry diff --git a/CesiumGeometry/test/TestBoundingCylinder.cpp b/CesiumGeometry/test/TestBoundingCylinder.cpp new file mode 100644 index 000000000..6c0ab984b --- /dev/null +++ b/CesiumGeometry/test/TestBoundingCylinder.cpp @@ -0,0 +1,34 @@ +#include "CesiumGeometry/BoundingCylinder.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +using namespace CesiumGeometry; +using namespace Cesium3DTilesSelection; +using namespace CesiumUtility; + +TEST_CASE("BoundingCylinder constructor example") { + //! [Constructor] + // Create an Bounding Cylinder using a transformation matrix, a position + // where the cylinder will be translated, and a scale. + glm::dvec3 center = glm::dvec3(1.0, 0.0, 0.0); + glm::dmat3 halfAxes = glm::dmat3( + glm::dvec3(2.0, 0.0, 0.0), + glm::dvec3(0.0, 2.0, 0.0), + glm::dvec3(0.0, 0.0, 1.5)); + + auto cylinder = BoundingCylinder(center, halfAxes); + //! [Constructor] + (void)cylinder; + + CHECK(cylinder.getRadius() == 2.0); + CHECK(cylinder.getHeight() == 3.0); +}