From 509deaa8597e1deb2ce53562fe50fef68d4f195b Mon Sep 17 00:00:00 2001 From: Joseph Kaile Date: Tue, 2 Jan 2024 14:43:21 -0500 Subject: [PATCH 01/65] update and comment intersection functions --- Cesium3DTilesSelection/src/TileUtilities.cpp | 37 ---- Cesium3DTilesSelection/src/TileUtilities.h | 6 - .../CesiumGeometry/IntersectionTests.h | 51 +++--- CesiumGeometry/include/CesiumGeometry/Ray.h | 9 +- CesiumGeometry/src/IntersectionTests.cpp | 91 +++++---- CesiumGeometry/src/Ray.cpp | 5 +- CesiumGeometry/test/TestIntersectionTests.cpp | 7 +- .../include/CesiumGltfContent/GltfUtilities.h | 17 +- CesiumGltfContent/src/GltfUtilities.cpp | 173 ++++++++++++------ .../test/TestIntersectRayGltf.cpp | 4 +- 10 files changed, 225 insertions(+), 175 deletions(-) diff --git a/Cesium3DTilesSelection/src/TileUtilities.cpp b/Cesium3DTilesSelection/src/TileUtilities.cpp index 509267161..118ad4c27 100644 --- a/Cesium3DTilesSelection/src/TileUtilities.cpp +++ b/Cesium3DTilesSelection/src/TileUtilities.cpp @@ -44,43 +44,6 @@ bool outsidePolygons( cartographicPolygons); } -bool rayIntersectsBoundingVolume( - const BoundingVolume& boundingVolume, - const Ray& ray) { - struct Operation { - const Ray& ray; - - bool operator()(const OrientedBoundingBox& boundingBox) noexcept { - return IntersectionTests::rayOBBParametric(ray, boundingBox) >= 0; - } - - bool operator()(const BoundingRegion& boundingRegion) noexcept { - return IntersectionTests::rayOBBParametric( - ray, - boundingRegion.getBoundingBox()) >= 0; - } - - bool operator()(const BoundingSphere& boundingSphere) noexcept { - return IntersectionTests::raySphereParametric(ray, boundingSphere) >= 0; - } - - bool operator()( - const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept { - return IntersectionTests::rayOBBParametric( - ray, - boundingRegion.getBoundingRegion().getBoundingBox()) >= 0; - } - - bool operator()(const S2CellBoundingVolume& s2Cell) noexcept { - return IntersectionTests::rayOBBParametric( - ray, - s2Cell.computeBoundingRegion().getBoundingBox()) >= 0; - } - }; - - return std::visit(Operation{ray}, boundingVolume); -} - } // namespace CesiumImpl } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/TileUtilities.h b/Cesium3DTilesSelection/src/TileUtilities.h index 5c3dd5032..bfd0c90bd 100644 --- a/Cesium3DTilesSelection/src/TileUtilities.h +++ b/Cesium3DTilesSelection/src/TileUtilities.h @@ -34,11 +34,5 @@ bool outsidePolygons( const std::vector& cartographicPolygons) noexcept; -/** - * @brief Returns whether the ray intersects the bounding volume. - */ -bool rayIntersectsBoundingVolume( - const BoundingVolume& boundingVolume, - const CesiumGeometry::Ray& ray); } // namespace CesiumImpl } // namespace Cesium3DTilesSelection diff --git a/CesiumGeometry/include/CesiumGeometry/IntersectionTests.h b/CesiumGeometry/include/CesiumGeometry/IntersectionTests.h index 52836082d..0e6ea6e8e 100644 --- a/CesiumGeometry/include/CesiumGeometry/IntersectionTests.h +++ b/CesiumGeometry/include/CesiumGeometry/IntersectionTests.h @@ -31,6 +31,28 @@ class CESIUMGEOMETRY_API IntersectionTests final { static std::optional rayPlane(const Ray& ray, const Plane& plane) noexcept; + /** + * @brief Determines whether the point is completely inside the triangle. + * + * @param point The point to check. + * @param triangleVertA The first vertex of the triangle. + * @param triangleVertB The second vertex of the triangle. + * @param triangleVertC The third vertex of the triangle. + * @return Whether the point is within the triangle. + */ + static bool pointInTriangle2D( + const glm::dvec2& point, + const glm::dvec2& triangleVertA, + const glm::dvec2& triangleVertB, + const glm::dvec2& triangleVertC) noexcept; + + /** + * The parametric functions below return a boolean value indicating whether a + * ray and a volume intersect and take a parameter t by reference. The + * parameter t is positive if the intersection point is in front of the ray + * origin, negative if it is behind it, or zero if the two points coincide. + */ + /** * @brief Tests if a ray hits a triangle and returns the hit point, if any. * @@ -49,11 +71,12 @@ class CESIUMGEOMETRY_API IntersectionTests final { const glm::dvec3& p2, bool cullBackFaces = false); - static double rayTriangleParametric( + static bool rayTriangleParametric( const Ray& ray, const glm::dvec3& p0, const glm::dvec3& p1, const glm::dvec3& p2, + double& t, bool cullBackFaces = false); /** @@ -67,7 +90,8 @@ class CESIUMGEOMETRY_API IntersectionTests final { static std::optional rayAABB(const Ray& ray, const AxisAlignedBox& aabb); - static double rayAABBParametric(const Ray& ray, const AxisAlignedBox& aabb); + static bool + rayAABBParametric(const Ray& ray, const AxisAlignedBox& aabb, double& t); /** * @brief Computes the intersection of a ray and an oriented bounding box. @@ -80,8 +104,8 @@ class CESIUMGEOMETRY_API IntersectionTests final { static std::optional rayOBB(const Ray& ray, const OrientedBoundingBox& obb); - static double - rayOBBParametric(const Ray& ray, const OrientedBoundingBox& obb); + static bool + rayOBBParametric(const Ray& ray, const OrientedBoundingBox& obb, double& t); /** * @brief Computes the intersection of a ray and a bounding sphere. @@ -94,23 +118,8 @@ class CESIUMGEOMETRY_API IntersectionTests final { static std::optional raySphere(const Ray& ray, const BoundingSphere& sphere); - static double - raySphereParametric(const Ray& ray, const BoundingSphere& sphere); - - /** - * @brief Determines whether the point is completely inside the triangle. - * - * @param point The point to check. - * @param triangleVertA The first vertex of the triangle. - * @param triangleVertB The second vertex of the triangle. - * @param triangleVertC The third vertex of the triangle. - * @return Whether the point is within the triangle. - */ - static bool pointInTriangle2D( - const glm::dvec2& point, - const glm::dvec2& triangleVertA, - const glm::dvec2& triangleVertB, - const glm::dvec2& triangleVertC) noexcept; + static bool + raySphereParametric(const Ray& ray, const BoundingSphere& sphere, double& t); }; } // namespace CesiumGeometry diff --git a/CesiumGeometry/include/CesiumGeometry/Ray.h b/CesiumGeometry/include/CesiumGeometry/Ray.h index a357441f8..1cfe9e107 100644 --- a/CesiumGeometry/include/CesiumGeometry/Ray.h +++ b/CesiumGeometry/include/CesiumGeometry/Ray.h @@ -5,8 +5,6 @@ #include #include -#include - namespace CesiumGeometry { /** @@ -37,12 +35,12 @@ class CESIUMGEOMETRY_API Ray final { /** * @brief Calculates the point on the ray that corresponds to the given - * parameter `t`. If `t` is negative, the function returns std::nullopt. + * parameter `t`. * * @param t The parameter value used in the ray equation. - * @return The point along the ray, or std::nullopt if t is negative. + * @return The point along the ray. */ - std::optional getPointAlongRay(double t) const noexcept; + glm::dvec3 getPointAlongRay(double t) const noexcept; /** * @brief Transforms the ray using a given 4x4 transformation matrix. @@ -51,7 +49,6 @@ class CESIUMGEOMETRY_API Ray final { * ray. * @return The transformed ray. */ - Ray transform(const glm::dmat4x4& transformation) const noexcept; /** diff --git a/CesiumGeometry/src/IntersectionTests.cpp b/CesiumGeometry/src/IntersectionTests.cpp index f630b999f..539a6c97f 100644 --- a/CesiumGeometry/src/IntersectionTests.cpp +++ b/CesiumGeometry/src/IntersectionTests.cpp @@ -11,6 +11,8 @@ #include #include +#include + using namespace CesiumUtility; namespace CesiumGeometry { @@ -41,15 +43,18 @@ std::optional IntersectionTests::rayTriangle( const glm::dvec3& v1, const glm::dvec3& v2, bool cullBackFaces) { - return ray.getPointAlongRay( - rayTriangleParametric(ray, v0, v1, v2, cullBackFaces)); + double t; + return rayTriangleParametric(ray, v0, v1, v2, t, cullBackFaces) && t >= 0 + ? std::make_optional(ray.getPointAlongRay(t)) + : std::nullopt; } -double IntersectionTests::rayTriangleParametric( +bool IntersectionTests::rayTriangleParametric( const Ray& ray, const glm::dvec3& p0, const glm::dvec3& p1, const glm::dvec3& p2, + double& t, bool cullBackFaces) { const glm::dvec3& origin = ray.getOrigin(); @@ -62,60 +67,66 @@ double IntersectionTests::rayTriangleParametric( double det = glm::dot(edge0, p); if (cullBackFaces) { if (det < Math::Epsilon6) - return -1.0; + return false; glm::dvec3 tvec = origin - p0; double u = glm::dot(tvec, p); if (u < 0.0 || u > det) - return -1.0; + return false; glm::dvec3 q = glm::cross(tvec, edge0); double v = glm::dot(direction, q); if (v < 0.0 || u + v > det) - return -1.0; + return false; - return glm::dot(edge1, q) / det; + t = glm::dot(edge1, q) / det; + return true; } else { - if (std::abs(det) < Math::Epsilon6) - return -1.0; + if (glm::abs(det) < Math::Epsilon6) + return false; double invDet = 1.0 / det; glm::dvec3 tvec = origin - p0; double u = glm::dot(tvec, p) * invDet; if (u < 0.0 || u > 1.0) - return -1.0; + return false; glm::dvec3 q = glm::cross(tvec, edge0); double v = glm::dot(direction, q) * invDet; if (v < 0.0 || u + v > 1.0) - return -1.0; + return false; - return glm::dot(edge1, q) * invDet; + t = glm::dot(edge1, q) * invDet; + return true; } } std::optional IntersectionTests::rayAABB(const Ray& ray, const AxisAlignedBox& aabb) { - return ray.getPointAlongRay(rayAABBParametric(ray, aabb)); + double t; + return rayAABBParametric(ray, aabb, t) && t >= 0 + ? std::make_optional(ray.getPointAlongRay(t)) + : std::nullopt; } -double IntersectionTests::rayAABBParametric( +bool IntersectionTests::rayAABBParametric( const Ray& ray, - const AxisAlignedBox& aabb) { + const AxisAlignedBox& aabb, + double& t) { const glm::dvec3& dir = ray.getDirection(); const glm::dvec3& origin = ray.getOrigin(); const glm::dvec3* min = reinterpret_cast(&aabb.minimumX); const glm::dvec3* max = reinterpret_cast(&aabb.maximumX); - double greatestMin = -DBL_MAX; - double smallestMax = DBL_MAX; - double tmin = -DBL_MAX; - double tmax = DBL_MAX; + double greatestMin = -std::numeric_limits::max(); + double smallestMax = std::numeric_limits::max(); + double tmin = greatestMin; + double tmax = smallestMax; - for (int i = 0; i < 3; ++i) { - if (abs(dir[i]) < Math::Epsilon6) { + for (uint32_t i = 0; i < 3; ++i) { + if (glm::abs(dir[i]) < Math::Epsilon6) { continue; } else { tmin = ((*min)[i] - origin[i]) / dir[i]; @@ -128,19 +139,24 @@ double IntersectionTests::rayAABBParametric( smallestMax = glm::min(tmax, smallestMax); } if (smallestMax < 0.0 || greatestMin > smallestMax) { - return -1.0; + return false; } - return greatestMin < 0.0 ? smallestMax : greatestMin; + t = greatestMin < 0.0 ? smallestMax : greatestMin; + return true; } std::optional IntersectionTests::rayOBB(const Ray& ray, const OrientedBoundingBox& obb) { - return ray.getPointAlongRay(rayOBBParametric(ray, obb)); + double t; + return rayOBBParametric(ray, obb, t) && t >= 0 + ? std::make_optional(ray.getPointAlongRay(t)) + : std::nullopt; } -double IntersectionTests::rayOBBParametric( +bool IntersectionTests::rayOBBParametric( const Ray& ray, - const OrientedBoundingBox& obb) { + const OrientedBoundingBox& obb, + double& t) { const glm::dmat3x3& inverseHalfAxis = obb.getInverseHalfAxes(); glm::dmat4x4 transformation( @@ -157,12 +173,16 @@ double IntersectionTests::rayOBBParametric( return rayAABBParametric( ray.transform(transformation), - AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z)); + AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z), + t); } std::optional IntersectionTests::raySphere(const Ray& ray, const BoundingSphere& sphere) { - return ray.getPointAlongRay(raySphereParametric(ray, sphere)); + double t; + return raySphereParametric(ray, sphere, t) && t >= 0 + ? std::make_optional(ray.getPointAlongRay(t)) + : std::nullopt; } namespace { @@ -197,9 +217,10 @@ bool solveQuadratic( } } // namespace -double IntersectionTests::raySphereParametric( +bool IntersectionTests::raySphereParametric( const Ray& ray, - const BoundingSphere& sphere) { + const BoundingSphere& sphere, + double& t) { const glm::dvec3& origin = ray.getOrigin(); const glm::dvec3& direction = ray.getDirection(); @@ -212,13 +233,11 @@ double IntersectionTests::raySphereParametric( const double c = glm::dot(diff, diff) - radiusSquared; double t0, t1; - solveQuadratic(1.0, b, c, t0, t1); - - if (t0 >= 0) { - return t1 < 0 ? t0 : t0 < t1 ? t0 : t1; - } else { - return t1 >= 0 ? t1 : -1; + if (solveQuadratic(1.0, b, c, t0, t1)) { + t = t0 < 0 ? t1 : t0; + return true; } + return false; } bool IntersectionTests::pointInTriangle2D( diff --git a/CesiumGeometry/src/Ray.cpp b/CesiumGeometry/src/Ray.cpp index 47e9c2505..06b0d443a 100644 --- a/CesiumGeometry/src/Ray.cpp +++ b/CesiumGeometry/src/Ray.cpp @@ -20,9 +20,8 @@ Ray::Ray(const glm::dvec3& origin, const glm::dvec3& direction) //>>includeEnd('debug'); } -std::optional Ray::getPointAlongRay(double t) const noexcept { - return t < 0 ? std::optional() - : std::make_optional(_origin + t * _direction); +glm::dvec3 Ray::getPointAlongRay(double t) const noexcept { + return _origin + t * _direction; } Ray Ray::transform(const glm::dmat4x4& transformation) const noexcept { diff --git a/CesiumGeometry/test/TestIntersectionTests.cpp b/CesiumGeometry/test/TestIntersectionTests.cpp index 867e004d7..f2f7e2d88 100644 --- a/CesiumGeometry/test/TestIntersectionTests.cpp +++ b/CesiumGeometry/test/TestIntersectionTests.cpp @@ -312,9 +312,10 @@ TEST_CASE("IntersectionTests::raySphere") { BoundingSphere(glm::dvec3(200.0, 0.0, 0.0), 1.0), -1.0}); - double t = - IntersectionTests::raySphereParametric(testCase.ray, testCase.sphere); - + double t; + t = IntersectionTests::raySphereParametric(testCase.ray, testCase.sphere, t) + ? t < 0 ? -1.0 : t + : -1.0; CHECK(CesiumUtility::Math::equalsEpsilon( t, testCase.t, diff --git a/CesiumGltfContent/include/CesiumGltfContent/GltfUtilities.h b/CesiumGltfContent/include/CesiumGltfContent/GltfUtilities.h index 582a24c7f..96cde663b 100644 --- a/CesiumGltfContent/include/CesiumGltfContent/GltfUtilities.h +++ b/CesiumGltfContent/include/CesiumGltfContent/GltfUtilities.h @@ -136,17 +136,28 @@ struct CESIUMGLTFCONTENT_API GltfUtilities { * @param ray The ray. * @param gltf The glTF model. * @param cullBackFaces An optional boolean flag to indicate whether to cull + * @param modelToWorld An optional 4x4 matrix to transform from model to world + * space. If this parameter is not provided, the ray is assumed to be already + * in model space. * backfaces or not. Defaults to true. * @param return The intersection point along the ray, if any. */ static std::optional intersectRayGltfModel( const CesiumGeometry::Ray& ray, const CesiumGltf::Model& gltf, - bool cullBackFaces = true); + bool cullBackFaces = true, + const glm::dmat4x4& modelToWorld = glm::dmat4(1.0)); - static double intersectRayGltfModelParametric( + static bool intersectRayGltfModelParametric( const CesiumGeometry::Ray& ray, const CesiumGltf::Model& gltf, - bool cullBackFaces = true); + double& t, + bool cullBackFaces = true, + const glm::dmat4x4& modelToWorld = glm::dmat4(1.0)); + + static bool rayIntersectsGltfPrimitiveAABBs( + const CesiumGeometry::Ray& ray, + const CesiumGltf::Model& gltf, + const glm::dmat4x4& modelToWorld); }; } // namespace CesiumGltfContent diff --git a/CesiumGltfContent/src/GltfUtilities.cpp b/CesiumGltfContent/src/GltfUtilities.cpp index 9539de649..cf703ae01 100644 --- a/CesiumGltfContent/src/GltfUtilities.cpp +++ b/CesiumGltfContent/src/GltfUtilities.cpp @@ -8,6 +8,7 @@ #include #include +#include #include using namespace CesiumGltf; @@ -216,131 +217,189 @@ GltfUtilities::parseGltfCopyright(const CesiumGltf::Model& gltf) { } namespace { + +double signAwareMin(double n1, double n2) { + if (n2 < n1) + std::swap(n1, n2); + return n1 < 0 ? n2 : n1; +} + template -static void intersectRayPrimitive( - CesiumGeometry::Ray ray, +bool intersectRayPrimitiveParametric( + const CesiumGeometry::Ray& ray, const CesiumGltf::Model& model, const CesiumGltf::MeshPrimitive& primitive, - const glm::dmat4& nodeTransform, bool cullBackFaces, double& tMin) { - if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && - primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP) { - return; - } - auto positionAccessorIt = primitive.attributes.find("POSITION"); if (positionAccessorIt == primitive.attributes.end()) { - return; + return false; } int positionAccessorID = positionAccessorIt->second; const Accessor* pPositionAccessor = Model::getSafe(&model.accessors, positionAccessorID); if (!pPositionAccessor) { - return; + return false; + } + + const std::vector& min = pPositionAccessor->min; + const std::vector& max = pPositionAccessor->max; + + double t; + if (!CesiumGeometry::IntersectionTests::rayAABBParametric( + ray, + CesiumGeometry::AxisAlignedBox( + min[0], + min[1], + min[2], + max[0], + max[1], + max[2]), + t)) { + return false; } AccessorView indicesView(model, primitive.indices); AccessorView positionView(model, *pPositionAccessor); - double tCurr; - ray = ray.transform(glm::inverse(nodeTransform)); + tMin = -std::numeric_limits::max(); + double tCurr; + bool intersected = false; if (primitive.mode == CesiumGltf::MeshPrimitive::Mode::TRIANGLES) { for (int32_t i = 0; i < indicesView.size(); i += 3) { - tCurr = CesiumGeometry::IntersectionTests::rayTriangleParametric( - ray, - glm::dvec3(positionView[static_cast(indicesView[i])]), - glm::dvec3(positionView[static_cast(indicesView[i + 1])]), - glm::dvec3(positionView[static_cast(indicesView[i + 2])]), - cullBackFaces); - if (tCurr >= 0 && tCurr < tMin) { - tMin = tCurr; + if (CesiumGeometry::IntersectionTests::rayTriangleParametric( + ray, + glm::dvec3(positionView[static_cast(indicesView[i])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 1])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 2])]), + tCurr, + cullBackFaces)) { + intersected = true; + tMin = signAwareMin(tMin, tCurr); } } } else { for (int32_t i = 0; i < indicesView.size() - 2; ++i) { if (i % 2) { - tCurr = CesiumGeometry::IntersectionTests::rayTriangleParametric( - ray, - glm::dvec3(positionView[static_cast(indicesView[i])]), - glm::dvec3(positionView[static_cast(indicesView[i + 2])]), - glm::dvec3(positionView[static_cast(indicesView[i + 1])]), - true); + if (CesiumGeometry::IntersectionTests::rayTriangleParametric( + ray, + glm::dvec3(positionView[static_cast(indicesView[i])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 2])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 1])]), + tCurr, + true)) { + intersected = true; + tMin = signAwareMin(tMin, tCurr); + } } else { - tCurr = CesiumGeometry::IntersectionTests::rayTriangleParametric( - ray, - glm::dvec3(positionView[static_cast(indicesView[i])]), - glm::dvec3(positionView[static_cast(indicesView[i + 1])]), - glm::dvec3(positionView[static_cast(indicesView[i + 2])]), - true); - } - if (tCurr >= 0 && tCurr < tMin) { - tMin = tCurr; + if (CesiumGeometry::IntersectionTests::rayTriangleParametric( + ray, + glm::dvec3(positionView[static_cast(indicesView[i])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 1])]), + glm::dvec3( + positionView[static_cast(indicesView[i + 2])]), + tCurr, + true)) { + intersected = true; + tMin = signAwareMin(tMin, tCurr); + } } } } + return intersected; } } // namespace -double GltfUtilities::intersectRayGltfModelParametric( +bool GltfUtilities::intersectRayGltfModelParametric( const CesiumGeometry::Ray& ray, const CesiumGltf::Model& gltf, - bool cullBackFaces) { - double t = DBL_MAX; + double& tMin, + bool cullBackFaces, + const glm::dmat4x4& modelToWorld) { + glm::dmat4x4 rootTransform = applyRtcCenter(gltf, modelToWorld); + rootTransform = applyGltfUpAxisTransform(gltf, rootTransform); + + tMin = -std::numeric_limits::max(); + bool intersected = false; + gltf.forEachPrimitiveInScene( -1, - [ray, cullBackFaces, &t]( + [ray, cullBackFaces, rootTransform, &intersected, &tMin]( const CesiumGltf::Model& model, const CesiumGltf::Node& /*node*/, const CesiumGltf::Mesh& /*mesh*/, const CesiumGltf::MeshPrimitive& primitive, const glm::dmat4& nodeTransform) { - if (primitive.mode < CesiumGltf::MeshPrimitive::Mode::TRIANGLES) + if (primitive.mode != MeshPrimitive::Mode::TRIANGLES && + primitive.mode != MeshPrimitive::Mode::TRIANGLE_STRIP) { return; + } + + glm::dmat4x4 worldToPrimitive = + glm::inverse(rootTransform * nodeTransform); - switch (model.accessors[primitive.indices].componentType) { + double tCurr = -std::numeric_limits::max(); + bool intersectedPrimitive = false; + + switch (model.accessors[static_cast(primitive.indices)] + .componentType) { case Accessor::ComponentType::UNSIGNED_BYTE: - intersectRayPrimitive( - ray, + intersectedPrimitive = intersectRayPrimitiveParametric( + ray.transform(worldToPrimitive), model, primitive, - nodeTransform, cullBackFaces, - t); + tCurr); break; case Accessor::ComponentType::UNSIGNED_SHORT: - intersectRayPrimitive( - ray, + intersectedPrimitive = intersectRayPrimitiveParametric( + ray.transform(worldToPrimitive), model, primitive, - nodeTransform, cullBackFaces, - t); + tCurr); break; case Accessor::ComponentType::UNSIGNED_INT: - intersectRayPrimitive( - ray, + intersectedPrimitive = intersectRayPrimitiveParametric( + ray.transform(worldToPrimitive), model, primitive, - nodeTransform, cullBackFaces, - t); + tCurr); break; } + if (intersectedPrimitive) { + intersected = true; + tMin = signAwareMin(tMin, tCurr); + } }); - return t != DBL_MAX ? t : -1; + return intersected; } std::optional GltfUtilities::intersectRayGltfModel( const CesiumGeometry::Ray& ray, const CesiumGltf::Model& gltf, - bool cullBackFaces) { - return ray.getPointAlongRay( - intersectRayGltfModelParametric(ray, gltf, cullBackFaces)); + bool cullBackFaces, + const glm::dmat4x4& modelToWorld) { + double t; + return intersectRayGltfModelParametric( + ray, + gltf, + t, + cullBackFaces, + modelToWorld) && + t >= 0 + ? std::make_optional(ray.getPointAlongRay(t)) + : std::nullopt; } } // namespace CesiumGltfContent diff --git a/CesiumGltfContent/test/TestIntersectRayGltf.cpp b/CesiumGltfContent/test/TestIntersectRayGltf.cpp index f1582d1be..729c4737d 100644 --- a/CesiumGltfContent/test/TestIntersectRayGltf.cpp +++ b/CesiumGltfContent/test/TestIntersectRayGltf.cpp @@ -66,9 +66,7 @@ TEST_CASE("GltfUtilities::intersectRayGltfModel") { glm::dvec3(-1.0 / glm::sqrt(2.0), -1.0 / glm::sqrt(2.0), 0.0)), cube); CHECK(glm::all(glm::lessThan( - glm::abs( - *intersectionPoint - - glm::dvec3(1.0, 1.0, 0.0)), + glm::abs(*intersectionPoint - glm::dvec3(1.0, 1.0, 0.0)), glm::dvec3(CesiumUtility::Math::Epsilon6)))); // works with a translated/rotated gltf From 35eb018cc7701c09f914096e7a02da80987a1a73 Mon Sep 17 00:00:00 2001 From: Joseph Kaile Date: Tue, 2 Jan 2024 14:53:16 -0500 Subject: [PATCH 02/65] initial commit for height query class --- .../include/Cesium3DTilesSelection/Tileset.h | 6 + Cesium3DTilesSelection/src/Tileset.cpp | 46 +++-- .../src/TilesetHeightFinder.cpp | 189 ++++++++++++++++++ .../src/TilesetHeightFinder.h | 55 +++++ 4 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 Cesium3DTilesSelection/src/TilesetHeightFinder.cpp create mode 100644 Cesium3DTilesSelection/src/TilesetHeightFinder.h diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index f8bb44fb7..625b2754f 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -23,6 +23,7 @@ namespace Cesium3DTilesSelection { class TilesetContentManager; class TilesetMetadata; +class TilesetHeightFinder; /** * @brief A loadMetadata(); + CesiumAsync::Future> getHeightsAtCoordinates( + const std::vector& coordinates); + private: /** * @brief The result of traversing one branch of the tile hierarchy. @@ -497,6 +501,8 @@ class CESIUM3DTILESSELECTION_API Tileset final { CesiumUtility::IntrusivePointer _pTilesetContentManager; + std::unique_ptr _pTilesetHeightFinder; + void addTileToLoadQueue( Tile& tile, TileLoadPriorityGroup priorityGroup, diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 1ae7b6015..2368a9f99 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1,5 +1,6 @@ #include "TileUtilities.h" #include "TilesetContentManager.h" +#include "TilesetHeightFinder.h" #include #include @@ -44,13 +45,17 @@ Tileset::Tileset( _previousFrameNumber(0), _distances(), _childOcclusionProxies(), - _pTilesetContentManager{new TilesetContentManager( - _externals, - _options, - RasterOverlayCollection{_loadedTiles, externals}, - std::vector{}, - std::move(pCustomLoader), - std::move(pRootTile))} {} + _pTilesetContentManager{ + new TilesetContentManager( + _externals, + _options, + RasterOverlayCollection{_loadedTiles, externals}, + std::vector{}, + std::move(pCustomLoader), + std::move(pRootTile)), + }, + _pTilesetHeightFinder{ + new TilesetHeightFinder(this, _pTilesetContentManager)} {} Tileset::Tileset( const TilesetExternals& externals, @@ -62,11 +67,15 @@ Tileset::Tileset( _previousFrameNumber(0), _distances(), _childOcclusionProxies(), - _pTilesetContentManager{new TilesetContentManager( - _externals, - _options, - RasterOverlayCollection{_loadedTiles, externals}, - url)} {} + _pTilesetContentManager{ + new TilesetContentManager( + _externals, + _options, + RasterOverlayCollection{_loadedTiles, externals}, + url), + }, + _pTilesetHeightFinder{ + new TilesetHeightFinder(this, _pTilesetContentManager)} {} Tileset::Tileset( const TilesetExternals& externals, @@ -86,7 +95,9 @@ Tileset::Tileset( RasterOverlayCollection{_loadedTiles, externals}, ionAssetID, ionAccessToken, - ionAssetEndpointUrl)} {} + ionAssetEndpointUrl)}, + _pTilesetHeightFinder{ + new TilesetHeightFinder(this, _pTilesetContentManager)} {} Tileset::~Tileset() noexcept { this->_pTilesetContentManager->unloadAll(); @@ -327,6 +338,10 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { return result; } + if (_pTilesetHeightFinder->_heightRequests.size() != 0) { + _pTilesetHeightFinder->_processHeightRequests(); + } + for (const std::shared_ptr& pExcluder : this->_options.excluders) { pExcluder->startNewFrame(); @@ -523,6 +538,11 @@ CesiumAsync::Future Tileset::loadMetadata() { }); } +CesiumAsync::Future> +Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { + return _pTilesetHeightFinder->_getHeightsAtCoordinates(coordinates); +} + static void markTileNonRendered( TileSelectionState::Result lastResult, Tile& tile, diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp new file mode 100644 index 000000000..75aab45eb --- /dev/null +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -0,0 +1,189 @@ +#include "TilesetHeightFinder.h" + +#include "TileUtilities.h" +#include "TilesetContentManager.h" +#include +#include + +#include + +using namespace Cesium3DTilesSelection; +using namespace CesiumGeospatial; +using namespace CesiumGeometry; +using namespace CesiumUtility; +using namespace CesiumAsync; + +namespace { +bool boundingVolumeContainsCoordinate( + const BoundingVolume& boundingVolume, + const Ray& ray, + const Cartographic& coordinate) { + struct Operation { + const Ray& ray; + const Cartographic& coordinate; + + bool operator()(const OrientedBoundingBox& boundingBox) noexcept { + double t; + return IntersectionTests::rayOBBParametric(ray, boundingBox, t); + } + + bool operator()(const BoundingRegion& boundingRegion) noexcept { + return boundingRegion.getRectangle().contains(coordinate); + } + + bool operator()(const BoundingSphere& boundingSphere) noexcept { + double t; + return IntersectionTests::raySphereParametric(ray, boundingSphere, t); + } + + bool operator()( + const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept { + return boundingRegion.getBoundingRegion().getRectangle().contains(coordinate); + } + + bool operator()(const S2CellBoundingVolume& s2Cell) noexcept { + return s2Cell.computeBoundingRegion().getRectangle().contains(coordinate); + } + }; + + return std::visit(Operation{ray, coordinate}, boundingVolume); +} + +Ray createRay(Cartographic cartographic) { + cartographic.height = 100000.0; + return Ray( + Ellipsoid::WGS84.cartographicToCartesian(cartographic), + -Ellipsoid::WGS84.geodeticSurfaceNormal(cartographic)); +} +} // namespace + +bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { + if (pTile->getChildren().size() != 0) { + return false; + } + const TilesetOptions& options = _pTileset->getOptions(); + switch (pTile->getState()) { + case TileLoadState::Unloaded: + case TileLoadState::FailedTemporarily: + if (_pTilesetContentManager->getNumberOfTilesLoading() < + static_cast(options.maximumSimultaneousTileLoads)) + _pTilesetContentManager->loadTileContent(*pTile, options); + return true; + case TileLoadState::ContentLoading: + case TileLoadState::Unloading: + return true; + case TileLoadState::ContentLoaded: + case TileLoadState::Done: + case TileLoadState::Failed: + return false; + } + return false; +} + +void TilesetHeightFinder::_intersectLeafTile(Tile* pTile, RayInfo& rayInfo) { + const Ray& ray = rayInfo.ray; + double& tMin = rayInfo.tMin; + + TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); + if (pRenderContent) { + CesiumGltf::Model& gltf = pRenderContent->getModel(); + double t; + if (CesiumGltfContent::GltfUtilities::intersectRayGltfModelParametric( + ray, + gltf, + t, + true, + pTile->getTransform()) && + t >= 0) { + tMin = tMin < 0 ? t : std::min(tMin, t); + } + } +} + +void TilesetHeightFinder::_findAndIntersectLeafTiles( + Tile* pTile, + RayInfo& rayInfo, + std::vector& newTilesToLoad) { + if (pTile->getState() == TileLoadState::Failed) { + return; + } + if (pTile->getChildren().empty()) { + _intersectLeafTile(pTile, rayInfo); + } else { + for (Tile& child : pTile->getChildren()) { + if (!boundingVolumeContainsCoordinate( + child.getBoundingVolume(), + rayInfo.ray, + rayInfo.coordinate)) + continue; + if (_loadTileIfNeeded(&child)) { + newTilesToLoad.push_back(&child); + } else { + _findAndIntersectLeafTiles(&child, rayInfo, newTilesToLoad); + } + } + } +} + +void TilesetHeightFinder::_processTilesLoadingQueue(RayInfo& rayInfo) { + std::vector newTilesToLoad; + for (auto it = rayInfo.tilesLoading.begin(); + it != rayInfo.tilesLoading.end();) { + Tile* pTile = *it; + if (_loadTileIfNeeded(pTile)) { + ++it; + } else { + it = rayInfo.tilesLoading.erase(it); + _findAndIntersectLeafTiles(pTile, rayInfo, newTilesToLoad); + } + } + rayInfo.tilesLoading.insert( + rayInfo.tilesLoading.end(), + newTilesToLoad.begin(), + newTilesToLoad.end()); +} + +void TilesetHeightFinder::_processHeightRequests() { + HeightRequests& requests = _heightRequests.front(); + RayInfo& current = requests.current; + _processTilesLoadingQueue(current); + while (current.tilesLoading.empty()) { + requests.heights[requests.numRaysDone++] = + current.tMin >= 0 ? 100000.0 - current.tMin : -9999.0; + if (requests.numRaysDone < requests.coordinates.size()) { + current.coordinate = requests.coordinates[requests.numRaysDone]; + current.ray = createRay(current.coordinate); + current.tMin = -1.0; + Tile* pRoot = _pTilesetContentManager->getRootTile(); + _findAndIntersectLeafTiles(pRoot, current, current.tilesLoading); + } else { + requests.promise.resolve(std::move(requests.heights)); + _heightRequests.erase(_heightRequests.begin()); + return; + } + } +} + +Future> +TilesetHeightFinder::_getHeightsAtCoordinates( + std::vector coordinates) { + Tile* pRoot = _pTilesetContentManager->getRootTile(); + if (pRoot == nullptr || coordinates.empty()) { + return _pTileset->getAsyncSystem() + .createResolvedFuture>(std::vector(coordinates.size(), -9999.0)); + } + Promise promise = + _pTileset->getAsyncSystem().createPromise>(); + _heightRequests.emplace_back( + HeightRequests{ + RayInfo{ + createRay(coordinates[0]), + coordinates[0], + -1.0, + {pRoot}}, + 0, + coordinates, + std::vector(coordinates.size()), + promise}); + return promise.getFuture(); +} diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h new file mode 100644 index 000000000..94eede9a0 --- /dev/null +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -0,0 +1,55 @@ +#include +#include +#include + +#include + +namespace Cesium3DTilesSelection { + +class TilesetHeightFinder { + friend class Tileset; + + struct RayInfo { + CesiumGeometry::Ray ray; + CesiumGeospatial::Cartographic coordinate; + double tMin; + std::vector tilesLoading; + }; + + struct HeightRequests { + RayInfo current; + uint32_t numRaysDone; + std::vector coordinates = { + CesiumGeospatial::Cartographic(0.0, 0.0)}; + std::vector heights; + CesiumAsync::Promise> promise; + }; + + TilesetHeightFinder( + Cesium3DTilesSelection::Tileset* pTileset, + CesiumUtility::IntrusivePointer + pTilesetContentManager) + : _pTileset(pTileset), _pTilesetContentManager(pTilesetContentManager){}; + + CesiumAsync::Future> _getHeightsAtCoordinates( + std::vector coordinates); + + bool _loadTileIfNeeded(Tile* pTile); + + void _intersectLeafTile(Tile* pTile, RayInfo& rayInfo); + + void _findAndIntersectLeafTiles( + Tile* pTile, + RayInfo& rayInfo, + std::vector& newTilesToLoad); + + void _processTilesLoadingQueue(RayInfo& rayInfo); + + void _processHeightRequests(); + + std::vector _heightRequests; + Cesium3DTilesSelection::Tileset* _pTileset; + CesiumUtility::IntrusivePointer + _pTilesetContentManager; +}; +} // namespace Cesium3DTilesSelection From 468595ad8ce9d07e4e44c8678090a69f40ea8416 Mon Sep 17 00:00:00 2001 From: Joseph Kaile Date: Tue, 2 Jan 2024 15:03:50 -0500 Subject: [PATCH 03/65] clang-format --- .../src/TilesetHeightFinder.cpp | 28 ++++++++----------- .../src/TilesetHeightFinder.h | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 75aab45eb..1dd65ec33 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -2,9 +2,9 @@ #include "TileUtilities.h" #include "TilesetContentManager.h" + #include #include - #include using namespace Cesium3DTilesSelection; @@ -38,7 +38,8 @@ bool boundingVolumeContainsCoordinate( bool operator()( const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept { - return boundingRegion.getBoundingRegion().getRectangle().contains(coordinate); + return boundingRegion.getBoundingRegion().getRectangle().contains( + coordinate); } bool operator()(const S2CellBoundingVolume& s2Cell) noexcept { @@ -164,26 +165,21 @@ void TilesetHeightFinder::_processHeightRequests() { } } -Future> -TilesetHeightFinder::_getHeightsAtCoordinates( +Future> TilesetHeightFinder::_getHeightsAtCoordinates( std::vector coordinates) { Tile* pRoot = _pTilesetContentManager->getRootTile(); if (pRoot == nullptr || coordinates.empty()) { return _pTileset->getAsyncSystem() - .createResolvedFuture>(std::vector(coordinates.size(), -9999.0)); + .createResolvedFuture>( + std::vector(coordinates.size(), -9999.0)); } Promise promise = _pTileset->getAsyncSystem().createPromise>(); - _heightRequests.emplace_back( - HeightRequests{ - RayInfo{ - createRay(coordinates[0]), - coordinates[0], - -1.0, - {pRoot}}, - 0, - coordinates, - std::vector(coordinates.size()), - promise}); + _heightRequests.emplace_back(HeightRequests{ + RayInfo{createRay(coordinates[0]), coordinates[0], -1.0, {pRoot}}, + 0, + coordinates, + std::vector(coordinates.size()), + promise}); return promise.getFuture(); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 94eede9a0..4e4c87c53 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -47,7 +47,7 @@ class TilesetHeightFinder { void _processHeightRequests(); - std::vector _heightRequests; + std::vector _heightRequests; Cesium3DTilesSelection::Tileset* _pTileset; CesiumUtility::IntrusivePointer _pTilesetContentManager; From 4a767e9d1e13731b5a59d9ce0eaee7bce7f334ce Mon Sep 17 00:00:00 2001 From: Joseph Kaile Date: Wed, 3 Jan 2024 14:06:51 -0500 Subject: [PATCH 04/65] add support for add-refine and cwt --- .../src/TilesetHeightFinder.cpp | 39 +++++++++++++------ .../src/TilesetHeightFinder.h | 4 +- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 1dd65ec33..1b4fc1ef2 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -59,7 +59,8 @@ Ray createRay(Cartographic cartographic) { } // namespace bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { - if (pTile->getChildren().size() != 0) { + if (pTile->getChildren().size() != 0 && + pTile->getRefine() != TileRefine::Add) { return false; } const TilesetOptions& options = _pTileset->getOptions(); @@ -74,6 +75,12 @@ bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { case TileLoadState::Unloading: return true; case TileLoadState::ContentLoaded: + if (!_pTilesetContentManager->getRasterOverlayCollection() + .getOverlays() + .empty()) { + _pTilesetContentManager->updateTileContent(*pTile, options); + } + return false; case TileLoadState::Done: case TileLoadState::Failed: return false; @@ -81,7 +88,7 @@ bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { return false; } -void TilesetHeightFinder::_intersectLeafTile(Tile* pTile, RayInfo& rayInfo) { +void TilesetHeightFinder::_intersectVisibleTile(Tile* pTile, RayInfo& rayInfo) { const Ray& ray = rayInfo.ray; double& tMin = rayInfo.tMin; @@ -101,26 +108,36 @@ void TilesetHeightFinder::_intersectLeafTile(Tile* pTile, RayInfo& rayInfo) { } } -void TilesetHeightFinder::_findAndIntersectLeafTiles( +void TilesetHeightFinder::_findAndIntersectVisibleTiles( Tile* pTile, RayInfo& rayInfo, std::vector& newTilesToLoad) { if (pTile->getState() == TileLoadState::Failed) { return; } + if (pTile->getChildren().empty()) { - _intersectLeafTile(pTile, rayInfo); + _intersectVisibleTile(pTile, rayInfo); } else { + if (pTile->getRefine() == TileRefine::Add) { + _intersectVisibleTile(pTile, rayInfo); + } for (Tile& child : pTile->getChildren()) { - if (!boundingVolumeContainsCoordinate( - child.getBoundingVolume(), - rayInfo.ray, - rayInfo.coordinate)) + if (child.getContentBoundingVolume()) { + if (!boundingVolumeContainsCoordinate( + *child.getContentBoundingVolume(), + rayInfo.ray, + rayInfo.coordinate)) + continue; + } else if (!boundingVolumeContainsCoordinate( + child.getBoundingVolume(), + rayInfo.ray, + rayInfo.coordinate)) continue; if (_loadTileIfNeeded(&child)) { newTilesToLoad.push_back(&child); } else { - _findAndIntersectLeafTiles(&child, rayInfo, newTilesToLoad); + _findAndIntersectVisibleTiles(&child, rayInfo, newTilesToLoad); } } } @@ -135,7 +152,7 @@ void TilesetHeightFinder::_processTilesLoadingQueue(RayInfo& rayInfo) { ++it; } else { it = rayInfo.tilesLoading.erase(it); - _findAndIntersectLeafTiles(pTile, rayInfo, newTilesToLoad); + _findAndIntersectVisibleTiles(pTile, rayInfo, newTilesToLoad); } } rayInfo.tilesLoading.insert( @@ -156,7 +173,7 @@ void TilesetHeightFinder::_processHeightRequests() { current.ray = createRay(current.coordinate); current.tMin = -1.0; Tile* pRoot = _pTilesetContentManager->getRootTile(); - _findAndIntersectLeafTiles(pRoot, current, current.tilesLoading); + _findAndIntersectVisibleTiles(pRoot, current, current.tilesLoading); } else { requests.promise.resolve(std::move(requests.heights)); _heightRequests.erase(_heightRequests.begin()); diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 4e4c87c53..88eee8bf7 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -36,9 +36,9 @@ class TilesetHeightFinder { bool _loadTileIfNeeded(Tile* pTile); - void _intersectLeafTile(Tile* pTile, RayInfo& rayInfo); + void _intersectVisibleTile(Tile* pTile, RayInfo& rayInfo); - void _findAndIntersectLeafTiles( + void _findAndIntersectVisibleTiles( Tile* pTile, RayInfo& rayInfo, std::vector& newTilesToLoad); From 530d4c260936953c52ce8cf2e2fcd2a542101661 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 16 May 2024 13:27:24 -0600 Subject: [PATCH 05/65] Updates from intersectRayGltfModelParametric changes --- .../src/TilesetHeightFinder.cpp | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 1b4fc1ef2..02f93da32 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -89,23 +89,24 @@ bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { } void TilesetHeightFinder::_intersectVisibleTile(Tile* pTile, RayInfo& rayInfo) { - const Ray& ray = rayInfo.ray; - double& tMin = rayInfo.tMin; - TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); - if (pRenderContent) { - CesiumGltf::Model& gltf = pRenderContent->getModel(); - double t; - if (CesiumGltfContent::GltfUtilities::intersectRayGltfModelParametric( - ray, - gltf, - t, - true, - pTile->getTransform()) && - t >= 0) { - tMin = tMin < 0 ? t : std::min(tMin, t); - } - } + if (!pRenderContent) + return; + + auto hitResult = + CesiumGltfContent::GltfUtilities::intersectRayGltfModelParametric( + rayInfo.ray, + pRenderContent->getModel(), + true, + pTile->getTransform()); + + if (!hitResult.has_value()) + return; + + // Set ray info to this hit if closer, or the first hit + assert(hitResult->t >= 0); + if (hitResult->t < rayInfo.tMin || rayInfo.tMin == -1) + rayInfo.tMin = hitResult->t; } void TilesetHeightFinder::_findAndIntersectVisibleTiles( From 565c255c1a2188066102115a7931fc080658b624 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Tue, 28 May 2024 11:53:47 -0600 Subject: [PATCH 06/65] Let ::getHeightsAtCoordinates return full Cartographic coordinates, not just height --- .../include/Cesium3DTilesSelection/Tileset.h | 3 ++- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- .../src/TilesetHeightFinder.cpp | 19 +++++++++++-------- .../src/TilesetHeightFinder.h | 8 ++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 625b2754f..f147467c9 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -259,7 +259,8 @@ class CESIUM3DTILESSELECTION_API Tileset final { */ CesiumAsync::Future loadMetadata(); - CesiumAsync::Future> getHeightsAtCoordinates( + CesiumAsync::Future> + getHeightsAtCoordinates( const std::vector& coordinates); private: diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index aa5909c61..0a167ef0b 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -539,7 +539,7 @@ CesiumAsync::Future Tileset::loadMetadata() { }); } -CesiumAsync::Future> +CesiumAsync::Future> Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { return _pTilesetHeightFinder->_getHeightsAtCoordinates(coordinates); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 02f93da32..dbbb96f44 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -167,7 +167,9 @@ void TilesetHeightFinder::_processHeightRequests() { RayInfo& current = requests.current; _processTilesLoadingQueue(current); while (current.tilesLoading.empty()) { - requests.heights[requests.numRaysDone++] = + CesiumGeospatial::Cartographic& currentCoordinate = + requests.coordinates[requests.numRaysDone++]; + currentCoordinate.height = current.tMin >= 0 ? 100000.0 - current.tMin : -9999.0; if (requests.numRaysDone < requests.coordinates.size()) { current.coordinate = requests.coordinates[requests.numRaysDone]; @@ -176,28 +178,29 @@ void TilesetHeightFinder::_processHeightRequests() { Tile* pRoot = _pTilesetContentManager->getRootTile(); _findAndIntersectVisibleTiles(pRoot, current, current.tilesLoading); } else { - requests.promise.resolve(std::move(requests.heights)); + requests.promise.resolve(std::move(requests.coordinates)); _heightRequests.erase(_heightRequests.begin()); return; } } } -Future> TilesetHeightFinder::_getHeightsAtCoordinates( - std::vector coordinates) { +Future> +TilesetHeightFinder::_getHeightsAtCoordinates( + const std::vector& coordinates) { Tile* pRoot = _pTilesetContentManager->getRootTile(); if (pRoot == nullptr || coordinates.empty()) { return _pTileset->getAsyncSystem() - .createResolvedFuture>( - std::vector(coordinates.size(), -9999.0)); + .createResolvedFuture>( + std::vector(coordinates.size(), {0, 0, 0})); } Promise promise = - _pTileset->getAsyncSystem().createPromise>(); + _pTileset->getAsyncSystem() + .createPromise>(); _heightRequests.emplace_back(HeightRequests{ RayInfo{createRay(coordinates[0]), coordinates[0], -1.0, {pRoot}}, 0, coordinates, - std::vector(coordinates.size()), promise}); return promise.getFuture(); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 88eee8bf7..b515bea32 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -21,8 +21,7 @@ class TilesetHeightFinder { uint32_t numRaysDone; std::vector coordinates = { CesiumGeospatial::Cartographic(0.0, 0.0)}; - std::vector heights; - CesiumAsync::Promise> promise; + CesiumAsync::Promise> promise; }; TilesetHeightFinder( @@ -31,8 +30,9 @@ class TilesetHeightFinder { pTilesetContentManager) : _pTileset(pTileset), _pTilesetContentManager(pTilesetContentManager){}; - CesiumAsync::Future> _getHeightsAtCoordinates( - std::vector coordinates); + CesiumAsync::Future> + _getHeightsAtCoordinates( + const std::vector& coordinates); bool _loadTileIfNeeded(Tile* pTile); From dd6dece5c30eb675221951ccab32a1b58f658d9f Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Fri, 31 May 2024 16:20:48 -0600 Subject: [PATCH 07/65] Update to new hit results, and rework height return data to show if the intersection succeeded --- .../include/Cesium3DTilesSelection/Tileset.h | 8 +- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- .../src/TilesetHeightFinder.cpp | 95 ++++++++++++------- .../src/TilesetHeightFinder.h | 19 ++-- 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index f147467c9..b07a6e5d2 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -259,8 +259,12 @@ class CESIUM3DTILESSELECTION_API Tileset final { */ CesiumAsync::Future loadMetadata(); - CesiumAsync::Future> - getHeightsAtCoordinates( + struct HeightResult { + bool heightAvailable = false; + CesiumGeospatial::Cartographic coordinate = {-1, -1, -1}; + }; + + CesiumAsync::Future> getHeightsAtCoordinates( const std::vector& coordinates); private: diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 0a167ef0b..9c945740a 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -539,7 +539,7 @@ CesiumAsync::Future Tileset::loadMetadata() { }); } -CesiumAsync::Future> +CesiumAsync::Future> Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { return _pTilesetHeightFinder->_getHeightsAtCoordinates(coordinates); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index dbbb96f44..7537a49e1 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -88,13 +88,15 @@ bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { return false; } -void TilesetHeightFinder::_intersectVisibleTile(Tile* pTile, RayInfo& rayInfo) { +void TilesetHeightFinder::_intersectVisibleTile( + Tile* pTile, + RayIntersect& rayInfo) { TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!pRenderContent) return; - auto hitResult = - CesiumGltfContent::GltfUtilities::intersectRayGltfModelParametric( + std::optional hitResult = + CesiumGltfContent::GltfUtilities::intersectRayGltfModel( rayInfo.ray, pRenderContent->getModel(), true, @@ -104,14 +106,16 @@ void TilesetHeightFinder::_intersectVisibleTile(Tile* pTile, RayInfo& rayInfo) { return; // Set ray info to this hit if closer, or the first hit - assert(hitResult->t >= 0); - if (hitResult->t < rayInfo.tMin || rayInfo.tMin == -1) - rayInfo.tMin = hitResult->t; + assert(hitResult->rayToWorldPointDistanceSq >= 0); + if (rayInfo.hitResult.rayToWorldPointDistanceSq == -1 || + hitResult->rayToWorldPointDistanceSq < + rayInfo.hitResult.rayToWorldPointDistanceSq) + rayInfo.hitResult = std::move(*hitResult); } void TilesetHeightFinder::_findAndIntersectVisibleTiles( Tile* pTile, - RayInfo& rayInfo, + RayIntersect& rayInfo, std::vector& newTilesToLoad) { if (pTile->getState() == TileLoadState::Failed) { return; @@ -144,7 +148,7 @@ void TilesetHeightFinder::_findAndIntersectVisibleTiles( } } -void TilesetHeightFinder::_processTilesLoadingQueue(RayInfo& rayInfo) { +void TilesetHeightFinder::_processTilesLoadingQueue(RayIntersect& rayInfo) { std::vector newTilesToLoad; for (auto it = rayInfo.tilesLoading.begin(); it != rayInfo.tilesLoading.end();) { @@ -164,43 +168,64 @@ void TilesetHeightFinder::_processTilesLoadingQueue(RayInfo& rayInfo) { void TilesetHeightFinder::_processHeightRequests() { HeightRequests& requests = _heightRequests.front(); - RayInfo& current = requests.current; - _processTilesLoadingQueue(current); - while (current.tilesLoading.empty()) { - CesiumGeospatial::Cartographic& currentCoordinate = - requests.coordinates[requests.numRaysDone++]; - currentCoordinate.height = - current.tMin >= 0 ? 100000.0 - current.tMin : -9999.0; - if (requests.numRaysDone < requests.coordinates.size()) { - current.coordinate = requests.coordinates[requests.numRaysDone]; - current.ray = createRay(current.coordinate); - current.tMin = -1.0; + RayIntersect* currentIntersect = + &requests.rayIntersects[requests.numRaysDone]; + _processTilesLoadingQueue(*currentIntersect); + + while (currentIntersect->tilesLoading.empty()) { + // Our ray is done loading and should have found a hit or not + // Go to next ray in this request batch + requests.numRaysDone++; + + // If there are more rays to process, set up the next one + if (requests.numRaysDone < requests.rayIntersects.size()) { + currentIntersect = &requests.rayIntersects[requests.numRaysDone]; Tile* pRoot = _pTilesetContentManager->getRootTile(); - _findAndIntersectVisibleTiles(pRoot, current, current.tilesLoading); - } else { - requests.promise.resolve(std::move(requests.coordinates)); - _heightRequests.erase(_heightRequests.begin()); - return; + _findAndIntersectVisibleTiles( + pRoot, + *currentIntersect, + currentIntersect->tilesLoading); + continue; + } + + // All rays are done, create results + std::vector results; + for (RayIntersect& ray : requests.rayIntersects) { + bool heightAvailable = ray.hitResult.rayToWorldPointDistanceSq != -1; + if (heightAvailable) { + ray.coordinate.height = + 100000.0 - glm::sqrt(ray.hitResult.rayToWorldPointDistanceSq); + } + + results.push_back(Tileset::HeightResult{heightAvailable, ray.coordinate}); } + requests.promise.resolve(std::move(results)); + _heightRequests.erase(_heightRequests.begin()); + return; } } -Future> +Future> TilesetHeightFinder::_getHeightsAtCoordinates( const std::vector& coordinates) { Tile* pRoot = _pTilesetContentManager->getRootTile(); if (pRoot == nullptr || coordinates.empty()) { return _pTileset->getAsyncSystem() - .createResolvedFuture>( - std::vector(coordinates.size(), {0, 0, 0})); + .createResolvedFuture>( + std::vector( + coordinates.size(), + {false, {-1, -1, -1}})); } - Promise promise = - _pTileset->getAsyncSystem() - .createPromise>(); - _heightRequests.emplace_back(HeightRequests{ - RayInfo{createRay(coordinates[0]), coordinates[0], -1.0, {pRoot}}, - 0, - coordinates, - promise}); + Promise promise = _pTileset->getAsyncSystem() + .createPromise>(); + + std::vector rayIntersects; + for (const CesiumGeospatial::Cartographic& coordinate : coordinates) + rayIntersects.push_back( + RayIntersect{createRay(coordinate), coordinate, {}, {pRoot}}); + + _heightRequests.emplace_back( + HeightRequests{std::move(rayIntersects), 0, promise}); + return promise.getFuture(); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index b515bea32..5825b90f9 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -9,19 +10,17 @@ namespace Cesium3DTilesSelection { class TilesetHeightFinder { friend class Tileset; - struct RayInfo { + struct RayIntersect { CesiumGeometry::Ray ray; CesiumGeospatial::Cartographic coordinate; - double tMin; + CesiumGltfContent::GltfUtilities::HitResult hitResult; std::vector tilesLoading; }; struct HeightRequests { - RayInfo current; + std::vector rayIntersects; uint32_t numRaysDone; - std::vector coordinates = { - CesiumGeospatial::Cartographic(0.0, 0.0)}; - CesiumAsync::Promise> promise; + CesiumAsync::Promise> promise; }; TilesetHeightFinder( @@ -30,20 +29,20 @@ class TilesetHeightFinder { pTilesetContentManager) : _pTileset(pTileset), _pTilesetContentManager(pTilesetContentManager){}; - CesiumAsync::Future> + CesiumAsync::Future> _getHeightsAtCoordinates( const std::vector& coordinates); bool _loadTileIfNeeded(Tile* pTile); - void _intersectVisibleTile(Tile* pTile, RayInfo& rayInfo); + void _intersectVisibleTile(Tile* pTile, RayIntersect& rayInfo); void _findAndIntersectVisibleTiles( Tile* pTile, - RayInfo& rayInfo, + RayIntersect& rayInfo, std::vector& newTilesToLoad); - void _processTilesLoadingQueue(RayInfo& rayInfo); + void _processTilesLoadingQueue(RayIntersect& rayInfo); void _processHeightRequests(); From 29250be39219b13873c16c99fb7ef986d94d2c83 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:23:21 -0600 Subject: [PATCH 08/65] Changes to support new IntersectResult --- .../src/TilesetHeightFinder.cpp | 21 +++++++++++-------- .../src/TilesetHeightFinder.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 7537a49e1..bc14c673f 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -95,22 +95,23 @@ void TilesetHeightFinder::_intersectVisibleTile( if (!pRenderContent) return; - std::optional hitResult = + auto intersectResult = CesiumGltfContent::GltfUtilities::intersectRayGltfModel( rayInfo.ray, pRenderContent->getModel(), true, pTile->getTransform()); - if (!hitResult.has_value()) + if (!intersectResult.hit.has_value()) return; // Set ray info to this hit if closer, or the first hit - assert(hitResult->rayToWorldPointDistanceSq >= 0); - if (rayInfo.hitResult.rayToWorldPointDistanceSq == -1 || - hitResult->rayToWorldPointDistanceSq < - rayInfo.hitResult.rayToWorldPointDistanceSq) - rayInfo.hitResult = std::move(*hitResult); + double prevDistSq = rayInfo.intersectResult.hit->rayToWorldPointDistanceSq; + double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; + + bool setClosest = prevDistSq == -1 || thisDistSq < prevDistSq; + if (setClosest) + rayInfo.intersectResult = std::move(intersectResult); } void TilesetHeightFinder::_findAndIntersectVisibleTiles( @@ -191,10 +192,12 @@ void TilesetHeightFinder::_processHeightRequests() { // All rays are done, create results std::vector results; for (RayIntersect& ray : requests.rayIntersects) { - bool heightAvailable = ray.hitResult.rayToWorldPointDistanceSq != -1; + bool heightAvailable = + ray.intersectResult.hit->rayToWorldPointDistanceSq != -1; if (heightAvailable) { ray.coordinate.height = - 100000.0 - glm::sqrt(ray.hitResult.rayToWorldPointDistanceSq); + 100000.0 - + glm::sqrt(ray.intersectResult.hit->rayToWorldPointDistanceSq); } results.push_back(Tileset::HeightResult{heightAvailable, ray.coordinate}); diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 5825b90f9..8a19f74ec 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -13,7 +13,7 @@ class TilesetHeightFinder { struct RayIntersect { CesiumGeometry::Ray ray; CesiumGeospatial::Cartographic coordinate; - CesiumGltfContent::GltfUtilities::HitResult hitResult; + CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; std::vector tilesLoading; }; From ce119c5f3785958042c94fcdbc0f7f57d4b577b6 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:51:58 -0600 Subject: [PATCH 09/65] Fix closest hit logix --- Cesium3DTilesSelection/src/TilesetHeightFinder.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index bc14c673f..8f418e327 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -106,12 +106,14 @@ void TilesetHeightFinder::_intersectVisibleTile( return; // Set ray info to this hit if closer, or the first hit - double prevDistSq = rayInfo.intersectResult.hit->rayToWorldPointDistanceSq; - double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; - - bool setClosest = prevDistSq == -1 || thisDistSq < prevDistSq; - if (setClosest) + if (!rayInfo.intersectResult.hit.has_value()) { rayInfo.intersectResult = std::move(intersectResult); + } else { + double prevDistSq = rayInfo.intersectResult.hit->rayToWorldPointDistanceSq; + double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; + if (thisDistSq < prevDistSq) + rayInfo.intersectResult = std::move(intersectResult); + } } void TilesetHeightFinder::_findAndIntersectVisibleTiles( From ec8f8ec5fda2f794a0fd5c6a8d789117aba70dfc Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:56:43 -0600 Subject: [PATCH 10/65] Modify return of _getHeightsAtCoordinates to support warnings --- .../include/Cesium3DTilesSelection/Tileset.h | 13 ++++-- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- .../src/TilesetHeightFinder.cpp | 44 ++++++++++--------- .../src/TilesetHeightFinder.h | 7 ++- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index b07a6e5d2..fe41003f6 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -259,12 +259,17 @@ class CESIUM3DTILESSELECTION_API Tileset final { */ CesiumAsync::Future loadMetadata(); - struct HeightResult { - bool heightAvailable = false; - CesiumGeospatial::Cartographic coordinate = {-1, -1, -1}; + struct HeightResults { + struct CoordinateResult { + bool heightAvailable = false; + CesiumGeospatial::Cartographic coordinate = {-1, -1, -1}; + std::vector warnings = {}; + }; + + std::vector coordinateResults; }; - CesiumAsync::Future> getHeightsAtCoordinates( + CesiumAsync::Future getHeightsAtCoordinates( const std::vector& coordinates); private: diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 9c945740a..e5febe254 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -539,7 +539,7 @@ CesiumAsync::Future Tileset::loadMetadata() { }); } -CesiumAsync::Future> +CesiumAsync::Future Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { return _pTilesetHeightFinder->_getHeightsAtCoordinates(coordinates); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 8f418e327..207a5a7f8 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -13,6 +13,11 @@ using namespace CesiumGeometry; using namespace CesiumUtility; using namespace CesiumAsync; +// 10,000 meters above ellisoid +// Highest point on ellipsoid is Mount Everest at 8,848 m +// Nothing intersectable should be above this +#define RAY_ORIGIN_HEIGHT 10000.0 + namespace { bool boundingVolumeContainsCoordinate( const BoundingVolume& boundingVolume, @@ -51,7 +56,7 @@ bool boundingVolumeContainsCoordinate( } Ray createRay(Cartographic cartographic) { - cartographic.height = 100000.0; + cartographic.height = RAY_ORIGIN_HEIGHT; return Ray( Ellipsoid::WGS84.cartographicToCartesian(cartographic), -Ellipsoid::WGS84.geodeticSurfaceNormal(cartographic)); @@ -135,12 +140,12 @@ void TilesetHeightFinder::_findAndIntersectVisibleTiles( if (!boundingVolumeContainsCoordinate( *child.getContentBoundingVolume(), rayInfo.ray, - rayInfo.coordinate)) + rayInfo.inputCoordinate)) continue; } else if (!boundingVolumeContainsCoordinate( child.getBoundingVolume(), rayInfo.ray, - rayInfo.coordinate)) + rayInfo.inputCoordinate)) continue; if (_loadTileIfNeeded(&child)) { newTilesToLoad.push_back(&child); @@ -192,42 +197,41 @@ void TilesetHeightFinder::_processHeightRequests() { } // All rays are done, create results - std::vector results; + Tileset::HeightResults results; for (RayIntersect& ray : requests.rayIntersects) { - bool heightAvailable = - ray.intersectResult.hit->rayToWorldPointDistanceSq != -1; - if (heightAvailable) { - ray.coordinate.height = - 100000.0 - + Tileset::HeightResults::CoordinateResult coordinateResult = { + ray.intersectResult.hit.has_value(), + std::move(ray.inputCoordinate), + std::move(ray.intersectResult.warnings)}; + + if (coordinateResult.heightAvailable) + coordinateResult.coordinate.height = + RAY_ORIGIN_HEIGHT - glm::sqrt(ray.intersectResult.hit->rayToWorldPointDistanceSq); - } - results.push_back(Tileset::HeightResult{heightAvailable, ray.coordinate}); + results.coordinateResults.push_back(coordinateResult); } + requests.promise.resolve(std::move(results)); _heightRequests.erase(_heightRequests.begin()); return; } } -Future> -TilesetHeightFinder::_getHeightsAtCoordinates( +Future TilesetHeightFinder::_getHeightsAtCoordinates( const std::vector& coordinates) { Tile* pRoot = _pTilesetContentManager->getRootTile(); if (pRoot == nullptr || coordinates.empty()) { return _pTileset->getAsyncSystem() - .createResolvedFuture>( - std::vector( - coordinates.size(), - {false, {-1, -1, -1}})); + .createResolvedFuture({}); } - Promise promise = _pTileset->getAsyncSystem() - .createPromise>(); + Promise promise = + _pTileset->getAsyncSystem().createPromise(); std::vector rayIntersects; for (const CesiumGeospatial::Cartographic& coordinate : coordinates) rayIntersects.push_back( - RayIntersect{createRay(coordinate), coordinate, {}, {pRoot}}); + RayIntersect{coordinate, createRay(coordinate), {}, {pRoot}}); _heightRequests.emplace_back( HeightRequests{std::move(rayIntersects), 0, promise}); diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 8a19f74ec..f0cee2488 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -11,8 +11,8 @@ class TilesetHeightFinder { friend class Tileset; struct RayIntersect { + CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; - CesiumGeospatial::Cartographic coordinate; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; std::vector tilesLoading; }; @@ -20,7 +20,7 @@ class TilesetHeightFinder { struct HeightRequests { std::vector rayIntersects; uint32_t numRaysDone; - CesiumAsync::Promise> promise; + CesiumAsync::Promise promise; }; TilesetHeightFinder( @@ -29,8 +29,7 @@ class TilesetHeightFinder { pTilesetContentManager) : _pTileset(pTileset), _pTilesetContentManager(pTilesetContentManager){}; - CesiumAsync::Future> - _getHeightsAtCoordinates( + CesiumAsync::Future _getHeightsAtCoordinates( const std::vector& coordinates); bool _loadTileIfNeeded(Tile* pTile); From 1076b4d9369b033a6033014391a768d5395ed022 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:20:06 -0600 Subject: [PATCH 11/65] fix format --- Cesium3DTilesSelection/src/Tileset.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 7bf6dcbba..f3ee0178e 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -49,7 +49,10 @@ Tileset::Tileset( new TilesetContentManager( _externals, _options, - RasterOverlayCollection{_loadedTiles, externals, options.ellipsoid}, + RasterOverlayCollection{ + _loadedTiles, + externals, + options.ellipsoid}, std::vector{}, std::move(pCustomLoader), std::move(pRootTile)), @@ -71,7 +74,10 @@ Tileset::Tileset( new TilesetContentManager( _externals, _options, - RasterOverlayCollection{_loadedTiles, externals, options.ellipsoid}, + RasterOverlayCollection{ + _loadedTiles, + externals, + options.ellipsoid}, url), }, _pTilesetHeightFinder{ From 82f56ebdfcd6840168e6ded13b92405d6abe635e Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:13:06 -0600 Subject: [PATCH 12/65] Rework ::_processHeightRequests to split load work and intersect work Also reuse the loading queue from the normal view tile selection Seems to have a significant speed improvement. My tests went 6-8 seconds to 3 secs. --- Cesium3DTilesSelection/src/Tileset.cpp | 27 +++- .../src/TilesetHeightFinder.cpp | 151 +++++++++--------- .../src/TilesetHeightFinder.h | 9 +- 3 files changed, 104 insertions(+), 83 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index f3ee0178e..6e82dc81d 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -344,10 +344,6 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { return result; } - if (_pTilesetHeightFinder->_heightRequests.size() != 0) { - _pTilesetHeightFinder->_processHeightRequests(); - } - for (const std::shared_ptr& pExcluder : this->_options.excluders) { pExcluder->startNewFrame(); @@ -378,6 +374,29 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { result = ViewUpdateResult(); } + if (_pTilesetHeightFinder->_heightRequests.size() != 0) { + std::vector tilesNeedingLoading; + _pTilesetHeightFinder->_processHeightRequests(tilesNeedingLoading); + + // Add a load request for tiles that haven't started + TileLoadPriorityGroup priorityGroup = TileLoadPriorityGroup::Urgent; + double priority = 0.0; + for (Tile* pTile : tilesNeedingLoading) { + TileLoadState loadState = pTile->getState(); + + CESIUM_ASSERT(loadState != TileLoadState::Done); + + // Push to appropriate queue based on state + // TO DO, check for overlap with view selection + if (pTile->getState() == TileLoadState::Unloaded) { + this->_workerThreadLoadQueue.push_back( + {pTile, priorityGroup, priority}); + } else if (pTile->getState() == TileLoadState::ContentLoaded) { + this->_mainThreadLoadQueue.push_back({pTile, priorityGroup, priority}); + } + } + } + result.workerThreadTileLoadQueueLength = static_cast(this->_workerThreadLoadQueue.size()); result.mainThreadTileLoadQueueLength = diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index 207a5a7f8..b5e37ad3b 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -121,101 +121,106 @@ void TilesetHeightFinder::_intersectVisibleTile( } } -void TilesetHeightFinder::_findAndIntersectVisibleTiles( +void TilesetHeightFinder::_findCandidateTiles( Tile* pTile, RayIntersect& rayInfo, - std::vector& newTilesToLoad) { - if (pTile->getState() == TileLoadState::Failed) { + std::vector& candidateTiles) { + + // If tile failed to load, this means we can't complete the intersection + // TODO, need to return warning, or abort the intersect + if (pTile->getState() == TileLoadState::Failed) + return; + + // If tile not done loading, add it to the list + if (pTile->getState() != TileLoadState::Done) { + candidateTiles.push_back(pTile); return; } if (pTile->getChildren().empty()) { - _intersectVisibleTile(pTile, rayInfo); + // This is a leaf node, add it to the list + candidateTiles.push_back(pTile); } else { - if (pTile->getRefine() == TileRefine::Add) { - _intersectVisibleTile(pTile, rayInfo); - } + // We have children + + // If additive refinement, add parent to the list with children + if (pTile->getRefine() == TileRefine::Add) + candidateTiles.push_back(pTile); + + // Traverse children for (Tile& child : pTile->getChildren()) { - if (child.getContentBoundingVolume()) { - if (!boundingVolumeContainsCoordinate( - *child.getContentBoundingVolume(), - rayInfo.ray, - rayInfo.inputCoordinate)) - continue; - } else if (!boundingVolumeContainsCoordinate( - child.getBoundingVolume(), - rayInfo.ray, - rayInfo.inputCoordinate)) + auto& contentBoundingVolume = child.getContentBoundingVolume(); + + // If content bounding volume exists and no intersection, we can skip it + if (contentBoundingVolume && !boundingVolumeContainsCoordinate( + *contentBoundingVolume, + rayInfo.ray, + rayInfo.inputCoordinate)) + continue; + + // if bounding volume doesn't intersect this ray, we can skip it + if (!boundingVolumeContainsCoordinate( + child.getBoundingVolume(), + rayInfo.ray, + rayInfo.inputCoordinate)) continue; - if (_loadTileIfNeeded(&child)) { - newTilesToLoad.push_back(&child); - } else { - _findAndIntersectVisibleTiles(&child, rayInfo, newTilesToLoad); - } - } - } -} -void TilesetHeightFinder::_processTilesLoadingQueue(RayIntersect& rayInfo) { - std::vector newTilesToLoad; - for (auto it = rayInfo.tilesLoading.begin(); - it != rayInfo.tilesLoading.end();) { - Tile* pTile = *it; - if (_loadTileIfNeeded(pTile)) { - ++it; - } else { - it = rayInfo.tilesLoading.erase(it); - _findAndIntersectVisibleTiles(pTile, rayInfo, newTilesToLoad); + // Child is a candidate, traverse it and its children + _findCandidateTiles(&child, rayInfo, candidateTiles); } } - rayInfo.tilesLoading.insert( - rayInfo.tilesLoading.end(), - newTilesToLoad.begin(), - newTilesToLoad.end()); } -void TilesetHeightFinder::_processHeightRequests() { +void TilesetHeightFinder::_processHeightRequests( + std::vector& tilesNeedingLoading) { HeightRequests& requests = _heightRequests.front(); RayIntersect* currentIntersect = &requests.rayIntersects[requests.numRaysDone]; - _processTilesLoadingQueue(*currentIntersect); - - while (currentIntersect->tilesLoading.empty()) { - // Our ray is done loading and should have found a hit or not - // Go to next ray in this request batch - requests.numRaysDone++; - - // If there are more rays to process, set up the next one - if (requests.numRaysDone < requests.rayIntersects.size()) { - currentIntersect = &requests.rayIntersects[requests.numRaysDone]; - Tile* pRoot = _pTilesetContentManager->getRootTile(); - _findAndIntersectVisibleTiles( - pRoot, - *currentIntersect, - currentIntersect->tilesLoading); - continue; - } + Tile* pRoot = _pTilesetContentManager->getRootTile(); + + std::vector candidateTiles; + _findCandidateTiles(pRoot, *currentIntersect, candidateTiles); - // All rays are done, create results - Tileset::HeightResults results; - for (RayIntersect& ray : requests.rayIntersects) { - Tileset::HeightResults::CoordinateResult coordinateResult = { - ray.intersectResult.hit.has_value(), - std::move(ray.inputCoordinate), - std::move(ray.intersectResult.warnings)}; + // If any candidates need loading, return them + for (Tile* pTile : candidateTiles) { + if (pTile->getState() != TileLoadState::Done) + tilesNeedingLoading.push_back(pTile); + } - if (coordinateResult.heightAvailable) - coordinateResult.coordinate.height = - RAY_ORIGIN_HEIGHT - - glm::sqrt(ray.intersectResult.hit->rayToWorldPointDistanceSq); + // Bail if we're waiting on tiles to load + if (!tilesNeedingLoading.empty()) + return; - results.coordinateResults.push_back(coordinateResult); - } + // Do the intersect tests + for (Tile* pTile : candidateTiles) + _intersectVisibleTile(pTile, *currentIntersect); - requests.promise.resolve(std::move(results)); - _heightRequests.erase(_heightRequests.begin()); + // Our ray is done loading and should have found a hit or not + // Go to next ray in this request batch + requests.numRaysDone++; + + // If there are more rays to process, come back the next frame + if (requests.numRaysDone < requests.rayIntersects.size()) return; + + // All rays are done, create results + Tileset::HeightResults results; + for (RayIntersect& ray : requests.rayIntersects) { + Tileset::HeightResults::CoordinateResult coordinateResult = { + ray.intersectResult.hit.has_value(), + std::move(ray.inputCoordinate), + std::move(ray.intersectResult.warnings)}; + + if (coordinateResult.heightAvailable) + coordinateResult.coordinate.height = + RAY_ORIGIN_HEIGHT - + glm::sqrt(ray.intersectResult.hit->rayToWorldPointDistanceSq); + + results.coordinateResults.push_back(coordinateResult); } + + requests.promise.resolve(std::move(results)); + _heightRequests.erase(_heightRequests.begin()); } Future TilesetHeightFinder::_getHeightsAtCoordinates( @@ -231,7 +236,7 @@ Future TilesetHeightFinder::_getHeightsAtCoordinates( std::vector rayIntersects; for (const CesiumGeospatial::Cartographic& coordinate : coordinates) rayIntersects.push_back( - RayIntersect{coordinate, createRay(coordinate), {}, {pRoot}}); + RayIntersect{coordinate, createRay(coordinate), {}}); _heightRequests.emplace_back( HeightRequests{std::move(rayIntersects), 0, promise}); diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index f0cee2488..df07ef642 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -14,7 +14,6 @@ class TilesetHeightFinder { CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; - std::vector tilesLoading; }; struct HeightRequests { @@ -36,14 +35,12 @@ class TilesetHeightFinder { void _intersectVisibleTile(Tile* pTile, RayIntersect& rayInfo); - void _findAndIntersectVisibleTiles( + void _findCandidateTiles( Tile* pTile, RayIntersect& rayInfo, - std::vector& newTilesToLoad); + std::vector& tilesNeedingLoading); - void _processTilesLoadingQueue(RayIntersect& rayInfo); - - void _processHeightRequests(); + void _processHeightRequests(std::vector& tilesNeedingLoading); std::vector _heightRequests; Cesium3DTilesSelection::Tileset* _pTileset; From 0d2a165a4e23a65a35311ba127b7263899e9377c Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:30:54 -0600 Subject: [PATCH 13/65] Don't add to load queue if tile already exists --- Cesium3DTilesSelection/src/Tileset.cpp | 33 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 6e82dc81d..bb448cd9a 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -387,12 +387,37 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { CESIUM_ASSERT(loadState != TileLoadState::Done); // Push to appropriate queue based on state - // TO DO, check for overlap with view selection if (pTile->getState() == TileLoadState::Unloaded) { - this->_workerThreadLoadQueue.push_back( - {pTile, priorityGroup, priority}); + + // Only add if not already in queue + auto foundIt = std::find_if( + this->_workerThreadLoadQueue.begin(), + this->_workerThreadLoadQueue.end(), + [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); + + if (foundIt == this->_workerThreadLoadQueue.end()) + this->_workerThreadLoadQueue.push_back( + {pTile, priorityGroup, priority}); + } else if (pTile->getState() == TileLoadState::ContentLoaded) { - this->_mainThreadLoadQueue.push_back({pTile, priorityGroup, priority}); + + if (pTile->isRenderContent()) { + // If it's render content, let our main thread throttling take it + + // Only add if not already in queue + auto foundIt = std::find_if( + this->_mainThreadLoadQueue.begin(), + this->_mainThreadLoadQueue.end(), + [pTile](const TileLoadTask& task) { + return task.pTile == pTile; + }); + if (foundIt == this->_mainThreadLoadQueue.end()) + this->_mainThreadLoadQueue.push_back( + {pTile, priorityGroup, priority}); + } else { + // If not render content, let's transition to done ourselves + this->_pTilesetContentManager->updateTileContent(*pTile, _options); + } } } } From 5eba1aa8ee94fc2d0a8821c542b600f5fb0cc083 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:50:14 -0600 Subject: [PATCH 14/65] process request tile load all at once, rather than one at a time --- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- .../src/TilesetHeightFinder.cpp | 35 ++++++++----------- .../src/TilesetHeightFinder.h | 5 +-- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index bb448cd9a..d778d1748 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -375,7 +375,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { } if (_pTilesetHeightFinder->_heightRequests.size() != 0) { - std::vector tilesNeedingLoading; + std::set tilesNeedingLoading; _pTilesetHeightFinder->_processHeightRequests(tilesNeedingLoading); // Add a load request for tiles that haven't started diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index b5e37ad3b..d33d5ddf9 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -172,19 +172,20 @@ void TilesetHeightFinder::_findCandidateTiles( } void TilesetHeightFinder::_processHeightRequests( - std::vector& tilesNeedingLoading) { + std::set& tilesNeedingLoading) { HeightRequests& requests = _heightRequests.front(); - RayIntersect* currentIntersect = - &requests.rayIntersects[requests.numRaysDone]; Tile* pRoot = _pTilesetContentManager->getRootTile(); - std::vector candidateTiles; - _findCandidateTiles(pRoot, *currentIntersect, candidateTiles); + for (RayIntersect& intersect : requests.rayIntersects) { + intersect.candidateTiles.clear(); - // If any candidates need loading, return them - for (Tile* pTile : candidateTiles) { - if (pTile->getState() != TileLoadState::Done) - tilesNeedingLoading.push_back(pTile); + _findCandidateTiles(pRoot, intersect, intersect.candidateTiles); + + // If any candidates need loading, add to return set + for (Tile* pTile : intersect.candidateTiles) { + if (pTile->getState() != TileLoadState::Done) + tilesNeedingLoading.insert(pTile); + } } // Bail if we're waiting on tiles to load @@ -192,16 +193,10 @@ void TilesetHeightFinder::_processHeightRequests( return; // Do the intersect tests - for (Tile* pTile : candidateTiles) - _intersectVisibleTile(pTile, *currentIntersect); - - // Our ray is done loading and should have found a hit or not - // Go to next ray in this request batch - requests.numRaysDone++; - - // If there are more rays to process, come back the next frame - if (requests.numRaysDone < requests.rayIntersects.size()) - return; + for (RayIntersect& intersect : requests.rayIntersects) { + for (Tile* pTile : intersect.candidateTiles) + _intersectVisibleTile(pTile, intersect); + } // All rays are done, create results Tileset::HeightResults results; @@ -239,7 +234,7 @@ Future TilesetHeightFinder::_getHeightsAtCoordinates( RayIntersect{coordinate, createRay(coordinate), {}}); _heightRequests.emplace_back( - HeightRequests{std::move(rayIntersects), 0, promise}); + HeightRequests{std::move(rayIntersects), promise}); return promise.getFuture(); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index df07ef642..63774d29f 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace Cesium3DTilesSelection { @@ -14,11 +15,11 @@ class TilesetHeightFinder { CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; + std::vector candidateTiles; }; struct HeightRequests { std::vector rayIntersects; - uint32_t numRaysDone; CesiumAsync::Promise promise; }; @@ -40,7 +41,7 @@ class TilesetHeightFinder { RayIntersect& rayInfo, std::vector& tilesNeedingLoading); - void _processHeightRequests(std::vector& tilesNeedingLoading); + void _processHeightRequests(std::set& tilesNeedingLoading); std::vector _heightRequests; Cesium3DTilesSelection::Tileset* _pTileset; From fe35ba3d927ee6c351132cac98374a6612a7d9bf Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:52:42 -0600 Subject: [PATCH 15/65] Fix release build warning --- Cesium3DTilesSelection/src/Tileset.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index d778d1748..35a8bbbbe 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -387,7 +387,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { CESIUM_ASSERT(loadState != TileLoadState::Done); // Push to appropriate queue based on state - if (pTile->getState() == TileLoadState::Unloaded) { + if (loadState == TileLoadState::Unloaded) { // Only add if not already in queue auto foundIt = std::find_if( @@ -399,7 +399,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { this->_workerThreadLoadQueue.push_back( {pTile, priorityGroup, priority}); - } else if (pTile->getState() == TileLoadState::ContentLoaded) { + } else if (loadState == TileLoadState::ContentLoaded) { if (pTile->isRenderContent()) { // If it's render content, let our main thread throttling take it From 44b5ea36575abdcc175a487037f4629c415b8b09 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Fri, 12 Jul 2024 08:54:20 -0600 Subject: [PATCH 16/65] fix build warning --- Cesium3DTilesSelection/src/TilesetHeightFinder.cpp | 3 +-- Cesium3DTilesSelection/src/TilesetHeightFinder.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp index d33d5ddf9..e63e5b892 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp @@ -230,8 +230,7 @@ Future TilesetHeightFinder::_getHeightsAtCoordinates( std::vector rayIntersects; for (const CesiumGeospatial::Cartographic& coordinate : coordinates) - rayIntersects.push_back( - RayIntersect{coordinate, createRay(coordinate), {}}); + rayIntersects.push_back(RayIntersect{coordinate, createRay(coordinate)}); _heightRequests.emplace_back( HeightRequests{std::move(rayIntersects), promise}); diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h index 63774d29f..4095cb367 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ b/Cesium3DTilesSelection/src/TilesetHeightFinder.h @@ -14,8 +14,8 @@ class TilesetHeightFinder { struct RayIntersect { CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; - CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; - std::vector candidateTiles; + CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; + std::vector candidateTiles = {}; }; struct HeightRequests { From c2b47f0ae4808230643682de7b51ab646c5eab72 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:54:00 -0600 Subject: [PATCH 17/65] Reorg to change height finder to terrain query class --- .../include/Cesium3DTilesSelection/Tileset.h | 17 +- Cesium3DTilesSelection/src/TerrainQuery.cpp | 126 +++++++++ Cesium3DTilesSelection/src/TerrainQuery.h | 25 ++ Cesium3DTilesSelection/src/Tileset.cpp | 189 +++++++++----- .../src/TilesetHeightFinder.cpp | 239 ------------------ .../src/TilesetHeightFinder.h | 51 ---- 6 files changed, 297 insertions(+), 350 deletions(-) create mode 100644 Cesium3DTilesSelection/src/TerrainQuery.cpp create mode 100644 Cesium3DTilesSelection/src/TerrainQuery.h delete mode 100644 Cesium3DTilesSelection/src/TilesetHeightFinder.cpp delete mode 100644 Cesium3DTilesSelection/src/TilesetHeightFinder.h diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 5da7a2779..aa7ce6a0f 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -2,6 +2,7 @@ #include "Library.h" #include "RasterOverlayCollection.h" +#include "TerrainQuery.h" #include "Tile.h" #include "TilesetContentLoader.h" #include "TilesetExternals.h" @@ -17,13 +18,14 @@ #include #include +#include #include #include namespace Cesium3DTilesSelection { class TilesetContentManager; class TilesetMetadata; -class TilesetHeightFinder; +class TerrainQuery; /** * @brief A queries; + CesiumAsync::Promise promise; + }; + void _processWorkerThreadLoadQueue(); void _processMainThreadLoadQueue(); @@ -523,13 +530,19 @@ class CESIUM3DTILESSELECTION_API Tileset final { CesiumUtility::IntrusivePointer _pTilesetContentManager; - std::unique_ptr _pTilesetHeightFinder; + std::vector _heightRequests; void addTileToLoadQueue( Tile& tile, TileLoadPriorityGroup priorityGroup, double priority); + void visitHeightRequests(); + + void tryCompleteHeightRequest( + HeightRequest& request, + std::set& tilesNeedingLoading); + static TraversalDetails createTraversalDetailsForSingleTile( const FrameState& frameState, const Tile& tile, diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp new file mode 100644 index 000000000..ab8be6c1c --- /dev/null +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -0,0 +1,126 @@ +#include "TerrainQuery.h" + +#include "TileUtilities.h" +#include "TilesetContentManager.h" + +#include +#include +#include + +using namespace Cesium3DTilesSelection; +using namespace CesiumGeospatial; +using namespace CesiumGeometry; +using namespace CesiumUtility; +using namespace CesiumAsync; + +namespace { +bool boundingVolumeContainsCoordinate( + const BoundingVolume& boundingVolume, + const Ray& ray, + const Cartographic& coordinate) { + struct Operation { + const Ray& ray; + const Cartographic& coordinate; + + bool operator()(const OrientedBoundingBox& boundingBox) noexcept { + double t; + return IntersectionTests::rayOBBParametric(ray, boundingBox, t); + } + + bool operator()(const BoundingRegion& boundingRegion) noexcept { + return boundingRegion.getRectangle().contains(coordinate); + } + + bool operator()(const BoundingSphere& boundingSphere) noexcept { + double t; + return IntersectionTests::raySphereParametric(ray, boundingSphere, t); + } + + bool operator()( + const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept { + return boundingRegion.getBoundingRegion().getRectangle().contains( + coordinate); + } + + bool operator()(const S2CellBoundingVolume& s2Cell) noexcept { + return s2Cell.computeBoundingRegion().getRectangle().contains(coordinate); + } + }; + + return std::visit(Operation{ray, coordinate}, boundingVolume); +} + +} // namespace + +void TerrainQuery::intersectVisibleTile(Tile* pTile) { + TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); + if (!pRenderContent) + return; + + auto gltfIntersectResult = + CesiumGltfContent::GltfUtilities::intersectRayGltfModel( + this->ray, + pRenderContent->getModel(), + true, + pTile->getTransform()); + + if (!gltfIntersectResult.hit.has_value()) + return; + + // Set ray info to this hit if closer, or the first hit + if (!this->intersectResult.hit.has_value()) { + this->intersectResult = std::move(gltfIntersectResult); + } else { + double prevDistSq = this->intersectResult.hit->rayToWorldPointDistanceSq; + double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; + if (thisDistSq < prevDistSq) + this->intersectResult = std::move(gltfIntersectResult); + } +} + +void TerrainQuery::findCandidateTiles(Tile* pTile) { + + // If tile failed to load, this means we can't complete the intersection + // TODO, need to return warning, or abort the intersect + if (pTile->getState() == TileLoadState::Failed) + return; + + // If tile not done loading, add it to the list + if (pTile->getState() != TileLoadState::Done) { + candidateTiles.push_back(pTile); + return; + } + + if (pTile->getChildren().empty()) { + // This is a leaf node, add it to the list + candidateTiles.push_back(pTile); + } else { + // We have children + + // If additive refinement, add parent to the list with children + if (pTile->getRefine() == TileRefine::Add) + candidateTiles.push_back(pTile); + + // Traverse children + for (Tile& child : pTile->getChildren()) { + auto& contentBoundingVolume = child.getContentBoundingVolume(); + + // If content bounding volume exists and no intersection, we can skip it + if (contentBoundingVolume && !boundingVolumeContainsCoordinate( + *contentBoundingVolume, + this->ray, + this->inputCoordinate)) + continue; + + // if bounding volume doesn't intersect this ray, we can skip it + if (!boundingVolumeContainsCoordinate( + child.getBoundingVolume(), + this->ray, + this->inputCoordinate)) + continue; + + // Child is a candidate, traverse it and its children + findCandidateTiles(&child); + } + } +} diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TerrainQuery.h new file mode 100644 index 000000000..91bf920d6 --- /dev/null +++ b/Cesium3DTilesSelection/src/TerrainQuery.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace Cesium3DTilesSelection { + +class TerrainQuery { +public: + CesiumGeospatial::Cartographic inputCoordinate; + CesiumGeometry::Ray ray; + CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; + std::vector candidateTiles = {}; + + void intersectVisibleTile(Tile* pTile); + + void findCandidateTiles(Tile* pTile); +}; + +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 35a8bbbbe..36ebc6efe 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1,6 +1,5 @@ #include "TileUtilities.h" #include "TilesetContentManager.h" -#include "TilesetHeightFinder.h" #include #include @@ -32,6 +31,11 @@ using namespace CesiumGeospatial; using namespace CesiumRasterOverlays; using namespace CesiumUtility; +// 10,000 meters above ellisoid +// Highest point on ellipsoid is Mount Everest at 8,848 m +// Nothing intersectable should be above this +#define RAY_ORIGIN_HEIGHT 10000.0 + namespace Cesium3DTilesSelection { Tileset::Tileset( @@ -56,9 +60,7 @@ Tileset::Tileset( std::vector{}, std::move(pCustomLoader), std::move(pRootTile)), - }, - _pTilesetHeightFinder{ - new TilesetHeightFinder(this, _pTilesetContentManager)} {} + } {} Tileset::Tileset( const TilesetExternals& externals, @@ -79,9 +81,7 @@ Tileset::Tileset( externals, options.ellipsoid}, url), - }, - _pTilesetHeightFinder{ - new TilesetHeightFinder(this, _pTilesetContentManager)} {} + } {} Tileset::Tileset( const TilesetExternals& externals, @@ -101,9 +101,7 @@ Tileset::Tileset( RasterOverlayCollection{_loadedTiles, externals, options.ellipsoid}, ionAssetID, ionAccessToken, - ionAssetEndpointUrl)}, - _pTilesetHeightFinder{ - new TilesetHeightFinder(this, _pTilesetContentManager)} {} + ionAssetEndpointUrl)} {} Tileset::~Tileset() noexcept { this->_pTilesetContentManager->unloadAll(); @@ -310,6 +308,104 @@ Tileset::updateViewOffline(const std::vector& frustums) { return this->_updateResult; } +void Tileset::tryCompleteHeightRequest( + HeightRequest& request, + std::set& tilesNeedingLoading) { + Tile* pRoot = _pTilesetContentManager->getRootTile(); + + for (TerrainQuery& query : request.queries) { + query.candidateTiles.clear(); + + query.findCandidateTiles(pRoot); + + // If any candidates need loading, add to return set + for (Tile* pTile : query.candidateTiles) { + if (pTile->getState() != TileLoadState::Done) + tilesNeedingLoading.insert(pTile); + } + } + + // Bail if we're waiting on tiles to load + if (!tilesNeedingLoading.empty()) + return; + + // Do the intersect tests + for (TerrainQuery& query : request.queries) { + for (Tile* pTile : query.candidateTiles) + query.intersectVisibleTile(pTile); + } + + // All rays are done, create results + Tileset::HeightResults results; + for (TerrainQuery& query : request.queries) { + Tileset::HeightResults::CoordinateResult coordinateResult = { + query.intersectResult.hit.has_value(), + std::move(query.inputCoordinate), + std::move(query.intersectResult.warnings)}; + + if (coordinateResult.heightAvailable) + coordinateResult.coordinate.height = + RAY_ORIGIN_HEIGHT - + glm::sqrt(query.intersectResult.hit->rayToWorldPointDistanceSq); + + results.coordinateResults.push_back(coordinateResult); + } + + request.promise.resolve(std::move(results)); + _heightRequests.erase(_heightRequests.begin()); +} + +void Tileset::visitHeightRequests() { + if (_heightRequests.size() == 0) + return; + + HeightRequest& request = _heightRequests.front(); + + std::set tilesNeedingLoading; + tryCompleteHeightRequest(request, tilesNeedingLoading); + + // Add a load request for tiles that haven't started + TileLoadPriorityGroup priorityGroup = TileLoadPriorityGroup::Urgent; + double priority = 0.0; + for (Tile* pTile : tilesNeedingLoading) { + TileLoadState loadState = pTile->getState(); + + CESIUM_ASSERT(loadState != TileLoadState::Done); + + // Push to appropriate queue based on state + if (loadState == TileLoadState::Unloaded) { + + // Only add if not already in queue + auto foundIt = std::find_if( + this->_workerThreadLoadQueue.begin(), + this->_workerThreadLoadQueue.end(), + [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); + + if (foundIt == this->_workerThreadLoadQueue.end()) + this->_workerThreadLoadQueue.push_back( + {pTile, priorityGroup, priority}); + + } else if (loadState == TileLoadState::ContentLoaded) { + + if (pTile->isRenderContent()) { + // If it's render content, let our main thread throttling take it + + // Only add if not already in queue + auto foundIt = std::find_if( + this->_mainThreadLoadQueue.begin(), + this->_mainThreadLoadQueue.end(), + [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); + if (foundIt == this->_mainThreadLoadQueue.end()) + this->_mainThreadLoadQueue.push_back( + {pTile, priorityGroup, priority}); + } else { + // If not render content, let's transition to done ourselves + this->_pTilesetContentManager->updateTileContent(*pTile, _options); + } + } + } +} + const ViewUpdateResult& Tileset::updateView(const std::vector& frustums, float deltaTime) { CESIUM_TRACE("Tileset::updateView"); @@ -374,53 +470,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { result = ViewUpdateResult(); } - if (_pTilesetHeightFinder->_heightRequests.size() != 0) { - std::set tilesNeedingLoading; - _pTilesetHeightFinder->_processHeightRequests(tilesNeedingLoading); - - // Add a load request for tiles that haven't started - TileLoadPriorityGroup priorityGroup = TileLoadPriorityGroup::Urgent; - double priority = 0.0; - for (Tile* pTile : tilesNeedingLoading) { - TileLoadState loadState = pTile->getState(); - - CESIUM_ASSERT(loadState != TileLoadState::Done); - - // Push to appropriate queue based on state - if (loadState == TileLoadState::Unloaded) { - - // Only add if not already in queue - auto foundIt = std::find_if( - this->_workerThreadLoadQueue.begin(), - this->_workerThreadLoadQueue.end(), - [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); - - if (foundIt == this->_workerThreadLoadQueue.end()) - this->_workerThreadLoadQueue.push_back( - {pTile, priorityGroup, priority}); - - } else if (loadState == TileLoadState::ContentLoaded) { - - if (pTile->isRenderContent()) { - // If it's render content, let our main thread throttling take it - - // Only add if not already in queue - auto foundIt = std::find_if( - this->_mainThreadLoadQueue.begin(), - this->_mainThreadLoadQueue.end(), - [pTile](const TileLoadTask& task) { - return task.pTile == pTile; - }); - if (foundIt == this->_mainThreadLoadQueue.end()) - this->_mainThreadLoadQueue.push_back( - {pTile, priorityGroup, priority}); - } else { - // If not render content, let's transition to done ourselves - this->_pTilesetContentManager->updateTileContent(*pTile, _options); - } - } - } - } + visitHeightRequests(); result.workerThreadTileLoadQueueLength = static_cast(this->_workerThreadLoadQueue.size()); @@ -591,7 +641,30 @@ CesiumAsync::Future Tileset::loadMetadata() { CesiumAsync::Future Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { - return _pTilesetHeightFinder->_getHeightsAtCoordinates(coordinates); + Tile* pRoot = _pTilesetContentManager->getRootTile(); + if (pRoot == nullptr || coordinates.empty()) { + return this->_asyncSystem.createResolvedFuture({}); + } + + Promise promise = this->_asyncSystem.createPromise(); + + std::vector queries; + for (const CesiumGeospatial::Cartographic& coordinate : coordinates) { + CesiumGeospatial::Cartographic startCoordinate( + coordinate.longitude, + coordinate.latitude, + RAY_ORIGIN_HEIGHT); + + Ray ray( + Ellipsoid::WGS84.cartographicToCartesian(startCoordinate), + -Ellipsoid::WGS84.geodeticSurfaceNormal(startCoordinate)); + + queries.push_back(TerrainQuery{coordinate, std::move(ray)}); + } + + _heightRequests.emplace_back(HeightRequest{std::move(queries), promise}); + + return promise.getFuture(); } static void markTileNonRendered( diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp b/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp deleted file mode 100644 index e63e5b892..000000000 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include "TilesetHeightFinder.h" - -#include "TileUtilities.h" -#include "TilesetContentManager.h" - -#include -#include -#include - -using namespace Cesium3DTilesSelection; -using namespace CesiumGeospatial; -using namespace CesiumGeometry; -using namespace CesiumUtility; -using namespace CesiumAsync; - -// 10,000 meters above ellisoid -// Highest point on ellipsoid is Mount Everest at 8,848 m -// Nothing intersectable should be above this -#define RAY_ORIGIN_HEIGHT 10000.0 - -namespace { -bool boundingVolumeContainsCoordinate( - const BoundingVolume& boundingVolume, - const Ray& ray, - const Cartographic& coordinate) { - struct Operation { - const Ray& ray; - const Cartographic& coordinate; - - bool operator()(const OrientedBoundingBox& boundingBox) noexcept { - double t; - return IntersectionTests::rayOBBParametric(ray, boundingBox, t); - } - - bool operator()(const BoundingRegion& boundingRegion) noexcept { - return boundingRegion.getRectangle().contains(coordinate); - } - - bool operator()(const BoundingSphere& boundingSphere) noexcept { - double t; - return IntersectionTests::raySphereParametric(ray, boundingSphere, t); - } - - bool operator()( - const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept { - return boundingRegion.getBoundingRegion().getRectangle().contains( - coordinate); - } - - bool operator()(const S2CellBoundingVolume& s2Cell) noexcept { - return s2Cell.computeBoundingRegion().getRectangle().contains(coordinate); - } - }; - - return std::visit(Operation{ray, coordinate}, boundingVolume); -} - -Ray createRay(Cartographic cartographic) { - cartographic.height = RAY_ORIGIN_HEIGHT; - return Ray( - Ellipsoid::WGS84.cartographicToCartesian(cartographic), - -Ellipsoid::WGS84.geodeticSurfaceNormal(cartographic)); -} -} // namespace - -bool TilesetHeightFinder::_loadTileIfNeeded(Tile* pTile) { - if (pTile->getChildren().size() != 0 && - pTile->getRefine() != TileRefine::Add) { - return false; - } - const TilesetOptions& options = _pTileset->getOptions(); - switch (pTile->getState()) { - case TileLoadState::Unloaded: - case TileLoadState::FailedTemporarily: - if (_pTilesetContentManager->getNumberOfTilesLoading() < - static_cast(options.maximumSimultaneousTileLoads)) - _pTilesetContentManager->loadTileContent(*pTile, options); - return true; - case TileLoadState::ContentLoading: - case TileLoadState::Unloading: - return true; - case TileLoadState::ContentLoaded: - if (!_pTilesetContentManager->getRasterOverlayCollection() - .getOverlays() - .empty()) { - _pTilesetContentManager->updateTileContent(*pTile, options); - } - return false; - case TileLoadState::Done: - case TileLoadState::Failed: - return false; - } - return false; -} - -void TilesetHeightFinder::_intersectVisibleTile( - Tile* pTile, - RayIntersect& rayInfo) { - TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); - if (!pRenderContent) - return; - - auto intersectResult = - CesiumGltfContent::GltfUtilities::intersectRayGltfModel( - rayInfo.ray, - pRenderContent->getModel(), - true, - pTile->getTransform()); - - if (!intersectResult.hit.has_value()) - return; - - // Set ray info to this hit if closer, or the first hit - if (!rayInfo.intersectResult.hit.has_value()) { - rayInfo.intersectResult = std::move(intersectResult); - } else { - double prevDistSq = rayInfo.intersectResult.hit->rayToWorldPointDistanceSq; - double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; - if (thisDistSq < prevDistSq) - rayInfo.intersectResult = std::move(intersectResult); - } -} - -void TilesetHeightFinder::_findCandidateTiles( - Tile* pTile, - RayIntersect& rayInfo, - std::vector& candidateTiles) { - - // If tile failed to load, this means we can't complete the intersection - // TODO, need to return warning, or abort the intersect - if (pTile->getState() == TileLoadState::Failed) - return; - - // If tile not done loading, add it to the list - if (pTile->getState() != TileLoadState::Done) { - candidateTiles.push_back(pTile); - return; - } - - if (pTile->getChildren().empty()) { - // This is a leaf node, add it to the list - candidateTiles.push_back(pTile); - } else { - // We have children - - // If additive refinement, add parent to the list with children - if (pTile->getRefine() == TileRefine::Add) - candidateTiles.push_back(pTile); - - // Traverse children - for (Tile& child : pTile->getChildren()) { - auto& contentBoundingVolume = child.getContentBoundingVolume(); - - // If content bounding volume exists and no intersection, we can skip it - if (contentBoundingVolume && !boundingVolumeContainsCoordinate( - *contentBoundingVolume, - rayInfo.ray, - rayInfo.inputCoordinate)) - continue; - - // if bounding volume doesn't intersect this ray, we can skip it - if (!boundingVolumeContainsCoordinate( - child.getBoundingVolume(), - rayInfo.ray, - rayInfo.inputCoordinate)) - continue; - - // Child is a candidate, traverse it and its children - _findCandidateTiles(&child, rayInfo, candidateTiles); - } - } -} - -void TilesetHeightFinder::_processHeightRequests( - std::set& tilesNeedingLoading) { - HeightRequests& requests = _heightRequests.front(); - Tile* pRoot = _pTilesetContentManager->getRootTile(); - - for (RayIntersect& intersect : requests.rayIntersects) { - intersect.candidateTiles.clear(); - - _findCandidateTiles(pRoot, intersect, intersect.candidateTiles); - - // If any candidates need loading, add to return set - for (Tile* pTile : intersect.candidateTiles) { - if (pTile->getState() != TileLoadState::Done) - tilesNeedingLoading.insert(pTile); - } - } - - // Bail if we're waiting on tiles to load - if (!tilesNeedingLoading.empty()) - return; - - // Do the intersect tests - for (RayIntersect& intersect : requests.rayIntersects) { - for (Tile* pTile : intersect.candidateTiles) - _intersectVisibleTile(pTile, intersect); - } - - // All rays are done, create results - Tileset::HeightResults results; - for (RayIntersect& ray : requests.rayIntersects) { - Tileset::HeightResults::CoordinateResult coordinateResult = { - ray.intersectResult.hit.has_value(), - std::move(ray.inputCoordinate), - std::move(ray.intersectResult.warnings)}; - - if (coordinateResult.heightAvailable) - coordinateResult.coordinate.height = - RAY_ORIGIN_HEIGHT - - glm::sqrt(ray.intersectResult.hit->rayToWorldPointDistanceSq); - - results.coordinateResults.push_back(coordinateResult); - } - - requests.promise.resolve(std::move(results)); - _heightRequests.erase(_heightRequests.begin()); -} - -Future TilesetHeightFinder::_getHeightsAtCoordinates( - const std::vector& coordinates) { - Tile* pRoot = _pTilesetContentManager->getRootTile(); - if (pRoot == nullptr || coordinates.empty()) { - return _pTileset->getAsyncSystem() - .createResolvedFuture({}); - } - Promise promise = - _pTileset->getAsyncSystem().createPromise(); - - std::vector rayIntersects; - for (const CesiumGeospatial::Cartographic& coordinate : coordinates) - rayIntersects.push_back(RayIntersect{coordinate, createRay(coordinate)}); - - _heightRequests.emplace_back( - HeightRequests{std::move(rayIntersects), promise}); - - return promise.getFuture(); -} diff --git a/Cesium3DTilesSelection/src/TilesetHeightFinder.h b/Cesium3DTilesSelection/src/TilesetHeightFinder.h deleted file mode 100644 index 4095cb367..000000000 --- a/Cesium3DTilesSelection/src/TilesetHeightFinder.h +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -namespace Cesium3DTilesSelection { - -class TilesetHeightFinder { - friend class Tileset; - - struct RayIntersect { - CesiumGeospatial::Cartographic inputCoordinate; - CesiumGeometry::Ray ray; - CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; - std::vector candidateTiles = {}; - }; - - struct HeightRequests { - std::vector rayIntersects; - CesiumAsync::Promise promise; - }; - - TilesetHeightFinder( - Cesium3DTilesSelection::Tileset* pTileset, - CesiumUtility::IntrusivePointer - pTilesetContentManager) - : _pTileset(pTileset), _pTilesetContentManager(pTilesetContentManager){}; - - CesiumAsync::Future _getHeightsAtCoordinates( - const std::vector& coordinates); - - bool _loadTileIfNeeded(Tile* pTile); - - void _intersectVisibleTile(Tile* pTile, RayIntersect& rayInfo); - - void _findCandidateTiles( - Tile* pTile, - RayIntersect& rayInfo, - std::vector& tilesNeedingLoading); - - void _processHeightRequests(std::set& tilesNeedingLoading); - - std::vector _heightRequests; - Cesium3DTilesSelection::Tileset* _pTileset; - CesiumUtility::IntrusivePointer - _pTilesetContentManager; -}; -} // namespace Cesium3DTilesSelection From 48d1b8bfeefeb265bae22733e53121ad848ec652 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:03:46 -0600 Subject: [PATCH 18/65] move include path to fix build error --- Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h | 1 - Cesium3DTilesSelection/src/Tileset.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index aa7ce6a0f..bb3653a93 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -2,7 +2,6 @@ #include "Library.h" #include "RasterOverlayCollection.h" -#include "TerrainQuery.h" #include "Tile.h" #include "TilesetContentLoader.h" #include "TilesetExternals.h" diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 36ebc6efe..255385dda 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1,3 +1,4 @@ +#include "TerrainQuery.h" #include "TileUtilities.h" #include "TilesetContentManager.h" From 73e213b021d87d31e6e941ad245f5e0bfc44ecfe Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:14:32 -0600 Subject: [PATCH 19/65] Fix compile error --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index ab8be6c1c..3896a38ed 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -23,8 +23,9 @@ bool boundingVolumeContainsCoordinate( const Cartographic& coordinate; bool operator()(const OrientedBoundingBox& boundingBox) noexcept { - double t; - return IntersectionTests::rayOBBParametric(ray, boundingBox, t); + std::optional t = + IntersectionTests::rayOBBParametric(ray, boundingBox); + return t && t.value() >= 0; } bool operator()(const BoundingRegion& boundingRegion) noexcept { @@ -32,8 +33,9 @@ bool boundingVolumeContainsCoordinate( } bool operator()(const BoundingSphere& boundingSphere) noexcept { - double t; - return IntersectionTests::raySphereParametric(ray, boundingSphere, t); + std::optional t = + IntersectionTests::raySphereParametric(ray, boundingSphere); + return t && t.value() >= 0; } bool operator()( From 5f6e9e47fb28f34bb650750919ee6bf70b6ea588 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:59:24 -0600 Subject: [PATCH 20/65] process all height requests on every tick, rather than one at a time --- .../include/Cesium3DTilesSelection/Tileset.h | 4 ++-- Cesium3DTilesSelection/src/Tileset.cpp | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index bb3653a93..e7c1e7698 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -529,7 +529,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { CesiumUtility::IntrusivePointer _pTilesetContentManager; - std::vector _heightRequests; + std::list _heightRequests; void addTileToLoadQueue( Tile& tile, @@ -538,7 +538,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { void visitHeightRequests(); - void tryCompleteHeightRequest( + bool tryCompleteHeightRequest( HeightRequest& request, std::set& tilesNeedingLoading); diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 0997b4224..299bfdf5d 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -310,7 +310,7 @@ Tileset::updateViewOffline(const std::vector& frustums) { return this->_updateResult; } -void Tileset::tryCompleteHeightRequest( +bool Tileset::tryCompleteHeightRequest( HeightRequest& request, std::set& tilesNeedingLoading) { Tile* pRoot = _pTilesetContentManager->getRootTile(); @@ -329,7 +329,7 @@ void Tileset::tryCompleteHeightRequest( // Bail if we're waiting on tiles to load if (!tilesNeedingLoading.empty()) - return; + return false; // Do the intersect tests for (TerrainQuery& query : request.queries) { @@ -354,19 +354,28 @@ void Tileset::tryCompleteHeightRequest( } request.promise.resolve(std::move(results)); - _heightRequests.erase(_heightRequests.begin()); + return true; } void Tileset::visitHeightRequests() { if (_heightRequests.size() == 0) return; - HeightRequest& request = _heightRequests.front(); - + // Go through all requests, either complete them, or gather the tiles they + // need for completion std::set tilesNeedingLoading; - tryCompleteHeightRequest(request, tilesNeedingLoading); + for (auto it = _heightRequests.begin(); it != _heightRequests.end();) { + HeightRequest& request = *it; + if (!tryCompleteHeightRequest(request, tilesNeedingLoading)) { + ++it; + } else { + auto deleteIt = it; + ++it; + _heightRequests.erase(deleteIt); + } + } - // Add a load request for tiles that haven't started + // Add a load request for tiles that are needed, and haven't started TileLoadPriorityGroup priorityGroup = TileLoadPriorityGroup::Urgent; double priority = 0.0; for (Tile* pTile : tilesNeedingLoading) { From 57cf86907f392e1fdae2145286dd46ba1bcbbf0e Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:07:42 -0600 Subject: [PATCH 21/65] fix warning --- Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index e7c1e7698..b7b096322 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace Cesium3DTilesSelection { class TilesetContentManager; From 7a1eae8a9f4ad5e38983dc056495b7239fbf6df5 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:13:24 -0600 Subject: [PATCH 22/65] formatting --- Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index b7b096322..3d81e7b42 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -15,12 +15,12 @@ #include +#include #include #include #include #include #include -#include namespace Cesium3DTilesSelection { class TilesetContentManager; From 0af2f35c6fcb144c10907db7be9e7b769a84ff48 Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:55:27 -0600 Subject: [PATCH 23/65] fix bug where height queries could unnecessarily wait unneeded tiles to load --- Cesium3DTilesSelection/src/Tileset.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 299bfdf5d..2fdabdc39 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -315,6 +315,7 @@ bool Tileset::tryCompleteHeightRequest( std::set& tilesNeedingLoading) { Tile* pRoot = _pTilesetContentManager->getRootTile(); + bool tileStillNeedsLoading = false; for (TerrainQuery& query : request.queries) { query.candidateTiles.clear(); @@ -322,13 +323,15 @@ bool Tileset::tryCompleteHeightRequest( // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { - if (pTile->getState() != TileLoadState::Done) + if (pTile->getState() != TileLoadState::Done) { tilesNeedingLoading.insert(pTile); + tileStillNeedsLoading = true; + } } } // Bail if we're waiting on tiles to load - if (!tilesNeedingLoading.empty()) + if (tileStillNeedsLoading) return false; // Do the intersect tests From 5a0e06eefdf8802d7cdb2f8fcf79938d531e627b Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:11:44 -0600 Subject: [PATCH 24/65] add support for warnings that occur during tile traversal --- .../include/Cesium3DTilesSelection/Tileset.h | 2 +- Cesium3DTilesSelection/src/TerrainQuery.cpp | 11 +++++++---- Cesium3DTilesSelection/src/TerrainQuery.h | 2 +- Cesium3DTilesSelection/src/Tileset.cpp | 17 ++++++++++++++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 3d81e7b42..49e9c0a3f 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -277,10 +277,10 @@ class CESIUM3DTILESSELECTION_API Tileset final { struct CoordinateResult { bool heightAvailable = false; CesiumGeospatial::Cartographic coordinate = {-1, -1, -1}; - std::vector warnings = {}; }; std::vector coordinateResults; + std::vector warnings; }; CesiumAsync::Future getHeightsAtCoordinates( diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index 3896a38ed..3f2e5c106 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -80,12 +80,15 @@ void TerrainQuery::intersectVisibleTile(Tile* pTile) { } } -void TerrainQuery::findCandidateTiles(Tile* pTile) { +void TerrainQuery::findCandidateTiles( + Tile* pTile, + std::vector& warnings) { // If tile failed to load, this means we can't complete the intersection - // TODO, need to return warning, or abort the intersect - if (pTile->getState() == TileLoadState::Failed) + if (pTile->getState() == TileLoadState::Failed) { + warnings.push_back("Tile load failed during query. Ignoring."); return; + } // If tile not done loading, add it to the list if (pTile->getState() != TileLoadState::Done) { @@ -122,7 +125,7 @@ void TerrainQuery::findCandidateTiles(Tile* pTile) { continue; // Child is a candidate, traverse it and its children - findCandidateTiles(&child); + findCandidateTiles(&child, warnings); } } } diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TerrainQuery.h index 91bf920d6..d4d932017 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.h +++ b/Cesium3DTilesSelection/src/TerrainQuery.h @@ -19,7 +19,7 @@ class TerrainQuery { void intersectVisibleTile(Tile* pTile); - void findCandidateTiles(Tile* pTile); + void findCandidateTiles(Tile* pTile, std::vector& warnings); }; } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 2fdabdc39..ec398769e 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -316,10 +316,11 @@ bool Tileset::tryCompleteHeightRequest( Tile* pRoot = _pTilesetContentManager->getRootTile(); bool tileStillNeedsLoading = false; + std::vector warnings; for (TerrainQuery& query : request.queries) { query.candidateTiles.clear(); - query.findCandidateTiles(pRoot); + query.findCandidateTiles(pRoot, warnings); // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { @@ -342,11 +343,21 @@ bool Tileset::tryCompleteHeightRequest( // All rays are done, create results Tileset::HeightResults results; + + // Start with any warnings from tile traversal + results.warnings = std::move(warnings); + + // Populate results with completed queries for (TerrainQuery& query : request.queries) { Tileset::HeightResults::CoordinateResult coordinateResult = { query.intersectResult.hit.has_value(), - std::move(query.inputCoordinate), - std::move(query.intersectResult.warnings)}; + std::move(query.inputCoordinate)}; + + // Add query warnings into the height result + std::copy( + query.intersectResult.warnings.begin(), + query.intersectResult.warnings.end(), + std::back_inserter(results.warnings)); if (coordinateResult.heightAvailable) coordinateResult.coordinate.height = From cc8b03f67b8f370f7bd369feaad412a3875a6dda Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:15:47 -0600 Subject: [PATCH 25/65] remove unnecessary check --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index 3f2e5c106..2e2581627 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -90,12 +90,6 @@ void TerrainQuery::findCandidateTiles( return; } - // If tile not done loading, add it to the list - if (pTile->getState() != TileLoadState::Done) { - candidateTiles.push_back(pTile); - return; - } - if (pTile->getChildren().empty()) { // This is a leaf node, add it to the list candidateTiles.push_back(pTile); From 84493a722aee1e5b1193bd114c9afc3021d676fc Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:11:33 -0600 Subject: [PATCH 26/65] Fix content bounding volume test --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 39 ++++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index 2e2581627..17593ef32 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -90,27 +90,40 @@ void TerrainQuery::findCandidateTiles( return; } + auto& contentBoundingVolume = pTile->getContentBoundingVolume(); + if (pTile->getChildren().empty()) { - // This is a leaf node, add it to the list - candidateTiles.push_back(pTile); + // This is a leaf node, it's a candidate + + // If optional content bounding volume exists, test against it + if (contentBoundingVolume) { + if (boundingVolumeContainsCoordinate( + *contentBoundingVolume, + this->ray, + this->inputCoordinate)) + candidateTiles.push_back(pTile); + } else { + candidateTiles.push_back(pTile); + } } else { // We have children // If additive refinement, add parent to the list with children - if (pTile->getRefine() == TileRefine::Add) - candidateTiles.push_back(pTile); + if (pTile->getRefine() == TileRefine::Add) { + // If optional content bounding volume exists, test against it + if (contentBoundingVolume) { + if (boundingVolumeContainsCoordinate( + *contentBoundingVolume, + this->ray, + this->inputCoordinate)) + candidateTiles.push_back(pTile); + } else { + candidateTiles.push_back(pTile); + } + } // Traverse children for (Tile& child : pTile->getChildren()) { - auto& contentBoundingVolume = child.getContentBoundingVolume(); - - // If content bounding volume exists and no intersection, we can skip it - if (contentBoundingVolume && !boundingVolumeContainsCoordinate( - *contentBoundingVolume, - this->ray, - this->inputCoordinate)) - continue; - // if bounding volume doesn't intersect this ray, we can skip it if (!boundingVolumeContainsCoordinate( child.getBoundingVolume(), From 2accb6a790a21c2b59b721ae7077472f9339b6d2 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 27 Aug 2024 21:43:41 +1000 Subject: [PATCH 27/65] Incremental tree search in tryCompleteHeightRequest. --- Cesium3DTilesSelection/src/TerrainQuery.h | 1 + Cesium3DTilesSelection/src/Tileset.cpp | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TerrainQuery.h index d4d932017..ad97a44d6 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.h +++ b/Cesium3DTilesSelection/src/TerrainQuery.h @@ -16,6 +16,7 @@ class TerrainQuery { CesiumGeometry::Ray ray; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; std::vector candidateTiles = {}; + std::vector previousCandidateTiles = {}; void intersectVisibleTile(Tile* pTile); diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index ec398769e..25657ce79 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -318,9 +318,24 @@ bool Tileset::tryCompleteHeightRequest( bool tileStillNeedsLoading = false; std::vector warnings; for (TerrainQuery& query : request.queries) { - query.candidateTiles.clear(); + if (query.candidateTiles.empty()) { + ++findCandidateTilesCalls; + query.findCandidateTiles(pRoot, warnings); + } else { + std::swap(query.candidateTiles, query.previousCandidateTiles); + + query.candidateTiles.clear(); - query.findCandidateTiles(pRoot, warnings); + for (Tile* pCandidate : query.previousCandidateTiles) { + TileLoadState loadState = pCandidate->getState(); + if (loadState == TileLoadState::Done || + loadState == TileLoadState::Failed) { + query.findCandidateTiles(pCandidate, warnings); + } else { + query.candidateTiles.emplace_back(pCandidate); + } + } + } // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { From 360154bd8dc2ea2fedb8ba2b449acdc32cc51152 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 27 Aug 2024 21:47:52 +1000 Subject: [PATCH 28/65] Remove use of variable from debug code. --- Cesium3DTilesSelection/src/Tileset.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 25657ce79..1c2267109 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -319,7 +319,6 @@ bool Tileset::tryCompleteHeightRequest( std::vector warnings; for (TerrainQuery& query : request.queries) { if (query.candidateTiles.empty()) { - ++findCandidateTilesCalls; query.findCandidateTiles(pRoot, warnings); } else { std::swap(query.candidateTiles, query.previousCandidateTiles); From 68341f6bcdaa9ad3cf499ab1fd2c4ec2028cd022 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 28 Aug 2024 17:56:54 +1000 Subject: [PATCH 29/65] Fix an unknown bug in rayOBBParametric by rewriting it. There were tests, and the old implementation passed them. I added new tests, and it passed those, too. But some real-world height queries were returning way too many potential tiles, and I narrowed it down to a problem with this intersection test. When I rewrote it "the long way", the too-many-tiles problem disappeared, and all the tests still pass. So I'm not quite sure what was wrong with the old implementation, but I'm committing this working one before I write more tests and optimize it. --- CesiumGeometry/src/IntersectionTests.cpp | 55 ++++++++++----- CesiumGeometry/test/TestIntersectionTests.cpp | 67 +++++++++++++------ 2 files changed, 85 insertions(+), 37 deletions(-) diff --git a/CesiumGeometry/src/IntersectionTests.cpp b/CesiumGeometry/src/IntersectionTests.cpp index 1d8079a0c..84fbee491 100644 --- a/CesiumGeometry/src/IntersectionTests.cpp +++ b/CesiumGeometry/src/IntersectionTests.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -229,23 +230,43 @@ IntersectionTests::rayOBB(const Ray& ray, const OrientedBoundingBox& obb) { std::optional IntersectionTests::rayOBBParametric( const Ray& ray, const OrientedBoundingBox& obb) { - - const glm::dmat3x3& inverseHalfAxis = obb.getInverseHalfAxes(); - glm::dmat4x4 transformation( - glm::dvec4(glm::normalize(inverseHalfAxis[0]), 0.0), - glm::dvec4(glm::normalize(inverseHalfAxis[1]), 0.0), - glm::dvec4(glm::normalize(inverseHalfAxis[2]), 0.0), - glm::dvec4(0.0, 0.0, 0.0, 1.0)); - - glm::dvec3 center = - glm::dvec3(transformation * glm::dvec4(obb.getCenter(), 1.0)); - glm::dvec3 halfLengths = obb.getLengths() / 2.0; - glm::dvec3 ll = center - halfLengths; - glm::dvec3 ur = center + halfLengths; - - return rayAABBParametric( - ray.transform(transformation), - AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z)); + const glm::dmat3& halfAxes = obb.getHalfAxes(); + glm::dmat4 cubeToWorld = glm::dmat4( + glm::dvec4(halfAxes[0], 0.0), + glm::dvec4(halfAxes[1], 0.0), + glm::dvec4(halfAxes[2], 0.0), + glm::dvec4(obb.getCenter(), 1.0)); + glm::dmat4 worldToCube = glm::affineInverse(cubeToWorld); + + Ray rayForAABB = ray.transform(worldToCube); + std::optional intersection = IntersectionTests::rayAABBParametric( + rayForAABB, + AxisAlignedBox(-1.0, -1.0, -1.0, 1.0, 1.0, 1.0)); + + if (!intersection) + return intersection; + + glm::dvec3 pointRelativeToAABB = rayForAABB.pointFromDistance(*intersection); + return glm::length( + glm::dvec3(cubeToWorld * glm::dvec4(pointRelativeToAABB, 1.0)) - + ray.getOrigin()); + + // const glm::dmat3x3& inverseHalfAxis = obb.getInverseHalfAxes(); + // glm::dmat4x4 transformation( + // glm::dvec4(glm::normalize(inverseHalfAxis[0]), 0.0), + // glm::dvec4(glm::normalize(inverseHalfAxis[1]), 0.0), + // glm::dvec4(glm::normalize(inverseHalfAxis[2]), 0.0), + // glm::dvec4(0.0, 0.0, 0.0, 1.0)); + + // glm::dvec3 center = + // glm::dvec3(transformation * glm::dvec4(obb.getCenter(), 1.0)); + // glm::dvec3 halfLengths = obb.getLengths() / 2.0; + // glm::dvec3 ll = center - halfLengths; + // glm::dvec3 ur = center + halfLengths; + + // return rayAABBParametric( + // ray.transform(transformation), + // AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z)); } std::optional diff --git a/CesiumGeometry/test/TestIntersectionTests.cpp b/CesiumGeometry/test/TestIntersectionTests.cpp index 1524d9e42..0d8c2aa70 100644 --- a/CesiumGeometry/test/TestIntersectionTests.cpp +++ b/CesiumGeometry/test/TestIntersectionTests.cpp @@ -7,7 +7,7 @@ #include "CesiumUtility/Math.h" #include -#include +#include #include @@ -245,9 +245,7 @@ TEST_CASE("IntersectionTests::rayAABB") { TEST_CASE("IntersectionTests::rayOBB") { struct TestCase { Ray ray; - glm::dvec3 xHalf; - glm::dvec3 yHalf; - glm::dvec3 obbOrigin; + OrientedBoundingBox obb; std::optional expectedIntersectionPoint; }; @@ -255,25 +253,54 @@ TEST_CASE("IntersectionTests::rayOBB") { // 2x2x2 obb at origin that is rotated -45 degrees on the x-axis. TestCase{ Ray(glm::dvec3(0.0, 0.0, 10.0), glm::dvec3(0.0, 0.0, -1.0)), - glm::dvec3(-1.0 / glm::sqrt(2), 0.0, 1.0 / glm::sqrt(2)), - glm::dvec3(0.0, 1.0, 0.0), - glm::dvec3(0.0, 0.0, 0.0), - glm::dvec3(0.0, 0.0, 2.0 / glm::sqrt(2))}, + OrientedBoundingBox( + glm::dvec3(0.0, 0.0, 0.0), + glm::dmat3( + glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))), + glm::dvec3(0.0, 0.0, glm::sqrt(2.0))}, // 2x2x2 obb at (10,10,10) that is rotated -45 degrees on the x-axis. TestCase{ Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)), - glm::dvec3(-1.0 / glm::sqrt(2), 0.0, 1.0 / glm::sqrt(2)), - glm::dvec3(0.0, 1.0, 0.0), - glm::dvec3(10.0, 10.0, 10.0), - glm::dvec3(10.0, 10.0, 10.0 + 2.0 / glm::sqrt(2))}); - std::optional intersectionPoint = IntersectionTests::rayOBB( - testCase.ray, - OrientedBoundingBox( - testCase.obbOrigin, - glm::dmat3x3( - testCase.xHalf, - testCase.yHalf, - glm::cross(testCase.xHalf, testCase.yHalf)))); + OrientedBoundingBox( + glm::dvec3(10.0, 10.0, 10.0), + glm::dmat3( + glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))), + glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(2))}, + // 4x4x4 obb at (10,10,10) that is rotated -45 degrees on the x-axis. + TestCase{ + Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 10.0, 10.0), + 2.0 * glm::dmat3(glm::rotate( + glm::radians(-45.0), + glm::dvec3(1.0, 0.0, 0.0)))), + glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(8))}, + // 4x4x2 obb at (10,10,10) that is not rotated. + TestCase{ + Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 10.0, 10.0), + glm::dmat3(glm::scale(glm::dvec3(2.0, 2.0, 1.0)))), + glm::dvec3(10.0, 10.0, 10.0 + 1.0)}, + // 4x2x4 obb at (10,20,30) that is not rotated. + TestCase{ + Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3(glm::scale(glm::dvec3(2.0, 1.0, 2.0)))), + glm::dvec3(10.0, 20.0, 30.0 + 2.0)}, + // 2x4x2 obb at (10,20,30) that is rotated 45 degrees on the Y-axis. + TestCase{ + Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0))) * + glm::dmat3(glm::rotate( + glm::radians(45.0), + glm::dvec3(0.0, 1.0, 0.0)))), + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))}); + std::optional intersectionPoint = + IntersectionTests::rayOBB(testCase.ray, testCase.obb); CHECK(glm::all(glm::lessThan( glm::abs(*intersectionPoint - *testCase.expectedIntersectionPoint), glm::dvec3(CesiumUtility::Math::Epsilon6)))); From ef273ac26679ea2dc5411ca4cdccfa40dda07c3d Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Thu, 29 Aug 2024 09:42:18 +1000 Subject: [PATCH 30/65] More performant rayOBBParametric. --- Cesium3DTilesSelection/src/Tileset.cpp | 5 +- .../include/CesiumGeometry/AxisAlignedBox.h | 4 +- CesiumGeometry/src/IntersectionTests.cpp | 59 ++++++--------- CesiumGeometry/test/TestIntersectionTests.cpp | 71 ++++++++++++++++++- 4 files changed, 96 insertions(+), 43 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 1c2267109..db3822648 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -327,8 +327,9 @@ bool Tileset::tryCompleteHeightRequest( for (Tile* pCandidate : query.previousCandidateTiles) { TileLoadState loadState = pCandidate->getState(); - if (loadState == TileLoadState::Done || - loadState == TileLoadState::Failed) { + if (!pCandidate->getChildren().empty() && + (loadState == TileLoadState::Done || + loadState == TileLoadState::Failed)) { query.findCandidateTiles(pCandidate, warnings); } else { query.candidateTiles.emplace_back(pCandidate); diff --git a/CesiumGeometry/include/CesiumGeometry/AxisAlignedBox.h b/CesiumGeometry/include/CesiumGeometry/AxisAlignedBox.h index d63ee5eb4..f4033028d 100644 --- a/CesiumGeometry/include/CesiumGeometry/AxisAlignedBox.h +++ b/CesiumGeometry/include/CesiumGeometry/AxisAlignedBox.h @@ -8,7 +8,7 @@ namespace CesiumGeometry { struct CESIUMGEOMETRY_API AxisAlignedBox final { - AxisAlignedBox() noexcept + constexpr AxisAlignedBox() noexcept : minimumX(0.0), minimumY(0.0), minimumZ(0.0), @@ -20,7 +20,7 @@ struct CESIUMGEOMETRY_API AxisAlignedBox final { lengthZ(0.0), center(0.0) {} - AxisAlignedBox( + constexpr AxisAlignedBox( double minimumX_, double minimumY_, double minimumZ_, diff --git a/CesiumGeometry/src/IntersectionTests.cpp b/CesiumGeometry/src/IntersectionTests.cpp index 84fbee491..7029a38a5 100644 --- a/CesiumGeometry/src/IntersectionTests.cpp +++ b/CesiumGeometry/src/IntersectionTests.cpp @@ -230,43 +230,30 @@ IntersectionTests::rayOBB(const Ray& ray, const OrientedBoundingBox& obb) { std::optional IntersectionTests::rayOBBParametric( const Ray& ray, const OrientedBoundingBox& obb) { + // Extract the rotation from the OBB's rotatin/scale transformation and + // invert it. This code assumes that there is not a negative scale, that + // there's no skew, that there's no other funny business. Non-uniform scale + // is fine! const glm::dmat3& halfAxes = obb.getHalfAxes(); - glm::dmat4 cubeToWorld = glm::dmat4( - glm::dvec4(halfAxes[0], 0.0), - glm::dvec4(halfAxes[1], 0.0), - glm::dvec4(halfAxes[2], 0.0), - glm::dvec4(obb.getCenter(), 1.0)); - glm::dmat4 worldToCube = glm::affineInverse(cubeToWorld); - - Ray rayForAABB = ray.transform(worldToCube); - std::optional intersection = IntersectionTests::rayAABBParametric( - rayForAABB, - AxisAlignedBox(-1.0, -1.0, -1.0, 1.0, 1.0, 1.0)); - - if (!intersection) - return intersection; - - glm::dvec3 pointRelativeToAABB = rayForAABB.pointFromDistance(*intersection); - return glm::length( - glm::dvec3(cubeToWorld * glm::dvec4(pointRelativeToAABB, 1.0)) - - ray.getOrigin()); - - // const glm::dmat3x3& inverseHalfAxis = obb.getInverseHalfAxes(); - // glm::dmat4x4 transformation( - // glm::dvec4(glm::normalize(inverseHalfAxis[0]), 0.0), - // glm::dvec4(glm::normalize(inverseHalfAxis[1]), 0.0), - // glm::dvec4(glm::normalize(inverseHalfAxis[2]), 0.0), - // glm::dvec4(0.0, 0.0, 0.0, 1.0)); - - // glm::dvec3 center = - // glm::dvec3(transformation * glm::dvec4(obb.getCenter(), 1.0)); - // glm::dvec3 halfLengths = obb.getLengths() / 2.0; - // glm::dvec3 ll = center - halfLengths; - // glm::dvec3 ur = center + halfLengths; - - // return rayAABBParametric( - // ray.transform(transformation), - // AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z)); + glm::dvec3 halfLengths = obb.getLengths() * 0.5; + glm::dmat3 rotationOnly( + halfAxes[0] / halfLengths.x, + halfAxes[1] / halfLengths.y, + halfAxes[2] / halfLengths.z); + glm::dmat3 inverseRotation = glm::transpose(rotationOnly); + + // Find the equivalent ray in the coordinate system where the OBB is not + // rotated or translated. That is, where it's an AABB at the origin. + glm::dvec3 relativeOrigin = ray.getOrigin() - obb.getCenter(); + glm::dvec3 rayOrigin(inverseRotation * relativeOrigin); + glm::dvec3 rayDirection(inverseRotation * ray.getDirection()); + + // Find the distance to the new ray's intersection with the AABB, which is + // equivalent to the distance of the original ray intersection with the OBB. + glm::dvec3 ll = -halfLengths; + glm::dvec3 ur = +halfLengths; + AxisAlignedBox aabb(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z); + return rayAABBParametric(Ray(rayOrigin, rayDirection), aabb); } std::optional diff --git a/CesiumGeometry/test/TestIntersectionTests.cpp b/CesiumGeometry/test/TestIntersectionTests.cpp index 0d8c2aa70..61d3d49d3 100644 --- a/CesiumGeometry/test/TestIntersectionTests.cpp +++ b/CesiumGeometry/test/TestIntersectionTests.cpp @@ -7,6 +7,7 @@ #include "CesiumUtility/Math.h" #include +#include #include #include @@ -265,7 +266,17 @@ TEST_CASE("IntersectionTests::rayOBB") { glm::dvec3(10.0, 10.0, 10.0), glm::dmat3( glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))), - glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(2))}, + glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(2.0))}, + // 2x2x2 obb at (10,20,30) that is rotated -45 degrees on the x-axis and + // hit from an angle. + TestCase{ + Ray(glm::dvec3(10.0, 20.0 + 2.0, 30.0 + 1.0 + glm::sqrt(2)), + glm::normalize(glm::dvec3(0.0, -2.0, -1.0))), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3( + glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))), + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))}, // 4x4x4 obb at (10,10,10) that is rotated -45 degrees on the x-axis. TestCase{ Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)), @@ -274,7 +285,18 @@ TEST_CASE("IntersectionTests::rayOBB") { 2.0 * glm::dmat3(glm::rotate( glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))), - glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(8))}, + glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(8.0))}, + // 4x4x4 obb at (10,20,30) that is rotated -45 degrees on the x-axis and + // hit from an angle + TestCase{ + Ray(glm::dvec3(10.0, 20.0 + 10.0, 30.0 + 20.0 + glm::sqrt(8.0)), + glm::normalize(glm::dvec3(0.0, -1.0, -2.0))), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + 2.0 * glm::dmat3(glm::rotate( + glm::radians(-45.0), + glm::dvec3(1.0, 0.0, 0.0)))), + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(8.0))}, // 4x4x2 obb at (10,10,10) that is not rotated. TestCase{ Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)), @@ -298,7 +320,50 @@ TEST_CASE("IntersectionTests::rayOBB") { glm::dmat3(glm::rotate( glm::radians(45.0), glm::dvec3(0.0, 1.0, 0.0)))), - glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))}); + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))}, + // 2x4x2 obb at (10,20,30) that is rotated 45 degrees on the X-axis. + TestCase{ + Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3( + glm::rotate(glm::radians(45.0), glm::dvec3(1.0, 0.0, 0.0))) * + glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0)))), + glm::dvec3(10.0, 20.0, 30.0 + 1.0 / glm::cos(glm::radians(45.0)))}, + // 2x4x2 obb at (10,20,30) that is rotated 225 degrees on the Y-axis. + TestCase{ + Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0))) * + glm::dmat3(glm::rotate( + glm::radians(225.0), + glm::dvec3(0.0, 1.0, 0.0)))), + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))}, + // 2x2x4 obb at (10,20,30) that is rotated 90 degrees on the X-axis and + // hit from an angle. + TestCase{ + Ray(glm::dvec3(10.0, 20.0 + 2.0, 30.0 + 1.0 + 1.0), + glm::normalize(glm::dvec3(0.0, -2.0, -1.0))), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + glm::dmat3( + glm::rotate(glm::radians(90.0), glm::dvec3(1.0, 0.0, 0.0))) * + glm::dmat3(glm::scale(glm::dvec3(1.0, 1.0, 2.0)))), + glm::dvec3(10.0, 20.0, 30.0 + 1.0)}, + // 2x2x2 obb at (10,20,30) that is rotated 45 degrees on the X- and + // Y-axis. + TestCase{ + Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)), + OrientedBoundingBox( + glm::dvec3(10.0, 20.0, 30.0), + (glm::dmat3(glm::rotate( + glm::atan(1.0 / 2.0, glm::sqrt(2) / 2.0), + glm::dvec3(1.0, 0.0, 0.0)))) * + glm::dmat3(glm::rotate( + glm::radians(45.0), + glm::dvec3(0.0, 1.0, 0.0)))), + glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(3.0))}); std::optional intersectionPoint = IntersectionTests::rayOBB(testCase.ray, testCase.obb); CHECK(glm::all(glm::lessThan( From 3be4524adba0bc4b724924240bffd7cfc6fd437a Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 10 Sep 2024 15:22:49 +1000 Subject: [PATCH 31/65] We only need ContentLoaded for height queries. --- Cesium3DTilesSelection/src/Tileset.cpp | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index db3822648..f851fbcc2 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -328,8 +328,7 @@ bool Tileset::tryCompleteHeightRequest( for (Tile* pCandidate : query.previousCandidateTiles) { TileLoadState loadState = pCandidate->getState(); if (!pCandidate->getChildren().empty() && - (loadState == TileLoadState::Done || - loadState == TileLoadState::Failed)) { + loadState >= TileLoadState::ContentLoaded) { query.findCandidateTiles(pCandidate, warnings); } else { query.candidateTiles.emplace_back(pCandidate); @@ -339,7 +338,7 @@ bool Tileset::tryCompleteHeightRequest( // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { - if (pTile->getState() != TileLoadState::Done) { + if (pTile->getState() < TileLoadState::ContentLoaded) { tilesNeedingLoading.insert(pTile); tileStillNeedsLoading = true; } @@ -414,7 +413,6 @@ void Tileset::visitHeightRequests() { // Push to appropriate queue based on state if (loadState == TileLoadState::Unloaded) { - // Only add if not already in queue auto foundIt = std::find_if( this->_workerThreadLoadQueue.begin(), @@ -424,24 +422,6 @@ void Tileset::visitHeightRequests() { if (foundIt == this->_workerThreadLoadQueue.end()) this->_workerThreadLoadQueue.push_back( {pTile, priorityGroup, priority}); - - } else if (loadState == TileLoadState::ContentLoaded) { - - if (pTile->isRenderContent()) { - // If it's render content, let our main thread throttling take it - - // Only add if not already in queue - auto foundIt = std::find_if( - this->_mainThreadLoadQueue.begin(), - this->_mainThreadLoadQueue.end(), - [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); - if (foundIt == this->_mainThreadLoadQueue.end()) - this->_mainThreadLoadQueue.push_back( - {pTile, priorityGroup, priority}); - } else { - // If not render content, let's transition to done ourselves - this->_pTilesetContentManager->updateTileContent(*pTile, _options); - } } } } From 1f778804deea6ae54555d3420652f17670a01509 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 10 Sep 2024 15:44:55 +1000 Subject: [PATCH 32/65] Use addTileToLoadQueue, no need to search existing first. --- Cesium3DTilesSelection/src/Tileset.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index f851fbcc2..a6c920b4f 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -338,7 +338,7 @@ bool Tileset::tryCompleteHeightRequest( // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { - if (pTile->getState() < TileLoadState::ContentLoaded) { + if (pTile->getState() <= TileLoadState::Unloaded) { tilesNeedingLoading.insert(pTile); tileStillNeedsLoading = true; } @@ -409,19 +409,16 @@ void Tileset::visitHeightRequests() { for (Tile* pTile : tilesNeedingLoading) { TileLoadState loadState = pTile->getState(); - CESIUM_ASSERT(loadState != TileLoadState::Done); - - // Push to appropriate queue based on state - if (loadState == TileLoadState::Unloaded) { - // Only add if not already in queue - auto foundIt = std::find_if( - this->_workerThreadLoadQueue.begin(), - this->_workerThreadLoadQueue.end(), - [pTile](const TileLoadTask& task) { return task.pTile == pTile; }); + CESIUM_ASSERT(loadState <= TileLoadState::Unloaded); + if (loadState > TileLoadState::Unloaded) + continue; - if (foundIt == this->_workerThreadLoadQueue.end()) - this->_workerThreadLoadQueue.push_back( - {pTile, priorityGroup, priority}); + int32_t currentFrameNumber = this->_previousFrameNumber + 1; + if (pTile->getLastSelectionState().getResult(currentFrameNumber) == + TileSelectionState::Result::None) { + // This tile wasn't visited during the visualization traversal, so it + // hasn't been added to the load queue yet. + addTileToLoadQueue(*pTile, priorityGroup, priority); } } } From 852710e3f409d494b4148461a2c7c2fa6783217e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 10 Sep 2024 20:04:40 +1000 Subject: [PATCH 33/65] Round-robin scheduling of vis and height query loads. --- .../include/Cesium3DTilesSelection/Tileset.h | 1 + Cesium3DTilesSelection/src/Tileset.cpp | 78 +++++++++++++------ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 49e9c0a3f..b0108c222 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -516,6 +516,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { std::vector _mainThreadLoadQueue; std::vector _workerThreadLoadQueue; + std::vector _heightQueryLoadQueue; Tile::LoadedLinkedList _loadedTiles; diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index a6c920b4f..13bf861b2 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -338,7 +338,15 @@ bool Tileset::tryCompleteHeightRequest( // If any candidates need loading, add to return set for (Tile* pTile : query.candidateTiles) { - if (pTile->getState() <= TileLoadState::Unloaded) { + TileLoadState state = pTile->getState(); + if (state == TileLoadState::Unloading) { + // This tile is in the process of unloading, which must complete before + // we can load it again. + this->_pTilesetContentManager->unloadTileContent(*pTile); + tileStillNeedsLoading = true; + } else if ( + state == TileLoadState::Unloaded || + state == TileLoadState::FailedTemporarily) { tilesNeedingLoading.insert(pTile); tileStillNeedsLoading = true; } @@ -403,24 +411,9 @@ void Tileset::visitHeightRequests() { } } - // Add a load request for tiles that are needed, and haven't started - TileLoadPriorityGroup priorityGroup = TileLoadPriorityGroup::Urgent; - double priority = 0.0; - for (Tile* pTile : tilesNeedingLoading) { - TileLoadState loadState = pTile->getState(); - - CESIUM_ASSERT(loadState <= TileLoadState::Unloaded); - if (loadState > TileLoadState::Unloaded) - continue; - - int32_t currentFrameNumber = this->_previousFrameNumber + 1; - if (pTile->getLastSelectionState().getResult(currentFrameNumber) == - TileSelectionState::Result::None) { - // This tile wasn't visited during the visualization traversal, so it - // hasn't been added to the load queue yet. - addTileToLoadQueue(*pTile, priorityGroup, priority); - } - } + this->_heightQueryLoadQueue.assign( + tilesNeedingLoading.begin(), + tilesNeedingLoading.end()); } const ViewUpdateResult& @@ -1586,17 +1579,52 @@ void Tileset::_processWorkerThreadLoadQueue() { return; } - std::vector& queue = this->_workerThreadLoadQueue; - std::sort(queue.begin(), queue.end()); + std::sort( + this->_workerThreadLoadQueue.begin(), + this->_workerThreadLoadQueue.end()); + + // Select tiles alternately from the two queues. Each frame, switch which + // queue we pull the first tile from. The goal is to schedule both height + // query and visualization tile loads fairly. + auto visIt = this->_workerThreadLoadQueue.begin(); + auto queryIt = this->_heightQueryLoadQueue.begin(); + + bool nextIsVis = (this->_previousFrameNumber % 2) == 0; + + while (this->_pTilesetContentManager->getNumberOfTilesLoading() < + maximumSimultaneousTileLoads) { + // Tell tiles from the current queue to load until one of them actually + // does. Calling loadTileContent might not actually start the loading + // process + int32_t originalNumberOfTilesLoading = + this->_pTilesetContentManager->getNumberOfTilesLoading(); + if (nextIsVis) { + while (visIt != this->_workerThreadLoadQueue.end() && + originalNumberOfTilesLoading == + this->_pTilesetContentManager->getNumberOfTilesLoading()) { + this->_pTilesetContentManager->loadTileContent(*visIt->pTile, _options); + ++visIt; + } + } else { + while (queryIt != this->_heightQueryLoadQueue.end() && + originalNumberOfTilesLoading == + this->_pTilesetContentManager->getNumberOfTilesLoading()) { + this->_pTilesetContentManager->loadTileContent(**queryIt, _options); + ++queryIt; + } + } - for (TileLoadTask& task : queue) { - this->_pTilesetContentManager->loadTileContent(*task.pTile, _options); - if (this->_pTilesetContentManager->getNumberOfTilesLoading() >= - maximumSimultaneousTileLoads) { + if (visIt == this->_workerThreadLoadQueue.end() && + queryIt == this->_heightQueryLoadQueue.end()) { + // No more work in either queue break; } + + // Get the next tile from the other queue. + nextIsVis = !nextIsVis; } } + void Tileset::_processMainThreadLoadQueue() { CESIUM_TRACE("Tileset::_processMainThreadLoadQueue"); // Process deferred main-thread load tasks with a time budget. From f7450a2aa14bb04839f3812ce62f1996442235ae Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 10 Sep 2024 22:32:43 +1000 Subject: [PATCH 34/65] Incremental traversal for additive-refined tiles, too. --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 4 ++-- Cesium3DTilesSelection/src/TerrainQuery.h | 1 + Cesium3DTilesSelection/src/Tileset.cpp | 24 ++++++++++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index 17593ef32..fedcc4902 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -116,9 +116,9 @@ void TerrainQuery::findCandidateTiles( *contentBoundingVolume, this->ray, this->inputCoordinate)) - candidateTiles.push_back(pTile); + additiveCandidateTiles.push_back(pTile); } else { - candidateTiles.push_back(pTile); + additiveCandidateTiles.push_back(pTile); } } diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TerrainQuery.h index ad97a44d6..5f574712f 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.h +++ b/Cesium3DTilesSelection/src/TerrainQuery.h @@ -15,6 +15,7 @@ class TerrainQuery { CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; + std::vector additiveCandidateTiles = {}; std::vector candidateTiles = {}; std::vector previousCandidateTiles = {}; diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 13bf861b2..bab30509a 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -336,12 +336,14 @@ bool Tileset::tryCompleteHeightRequest( } } - // If any candidates need loading, add to return set - for (Tile* pTile : query.candidateTiles) { + auto checkTile = [this, + &tilesNeedingLoading, + &tileStillNeedsLoading, + &query](Tile* pTile) { TileLoadState state = pTile->getState(); if (state == TileLoadState::Unloading) { - // This tile is in the process of unloading, which must complete before - // we can load it again. + // This tile is in the process of unloading, which must complete + // before we can load it again. this->_pTilesetContentManager->unloadTileContent(*pTile); tileStillNeedsLoading = true; } else if ( @@ -350,6 +352,14 @@ bool Tileset::tryCompleteHeightRequest( tilesNeedingLoading.insert(pTile); tileStillNeedsLoading = true; } + }; + + // If any candidates need loading, add to return set + for (Tile* pTile : query.additiveCandidateTiles) { + checkTile(pTile); + } + for (Tile* pTile : query.candidateTiles) { + checkTile(pTile); } } @@ -359,8 +369,12 @@ bool Tileset::tryCompleteHeightRequest( // Do the intersect tests for (TerrainQuery& query : request.queries) { - for (Tile* pTile : query.candidateTiles) + for (Tile* pTile : query.additiveCandidateTiles) { query.intersectVisibleTile(pTile); + } + for (Tile* pTile : query.candidateTiles) { + query.intersectVisibleTile(pTile); + } } // All rays are done, create results From 5310d2531024c6fca20218fc6ccacff7821aad12 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 11:42:36 +1000 Subject: [PATCH 35/65] Remove unused lambda capture. --- Cesium3DTilesSelection/src/Tileset.cpp | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index bab30509a..b624b6bef 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -336,23 +336,21 @@ bool Tileset::tryCompleteHeightRequest( } } - auto checkTile = [this, - &tilesNeedingLoading, - &tileStillNeedsLoading, - &query](Tile* pTile) { - TileLoadState state = pTile->getState(); - if (state == TileLoadState::Unloading) { - // This tile is in the process of unloading, which must complete - // before we can load it again. - this->_pTilesetContentManager->unloadTileContent(*pTile); - tileStillNeedsLoading = true; - } else if ( - state == TileLoadState::Unloaded || - state == TileLoadState::FailedTemporarily) { - tilesNeedingLoading.insert(pTile); - tileStillNeedsLoading = true; - } - }; + auto checkTile = + [this, &tilesNeedingLoading, &tileStillNeedsLoading](Tile* pTile) { + TileLoadState state = pTile->getState(); + if (state == TileLoadState::Unloading) { + // This tile is in the process of unloading, which must complete + // before we can load it again. + this->_pTilesetContentManager->unloadTileContent(*pTile); + tileStillNeedsLoading = true; + } else if ( + state == TileLoadState::Unloaded || + state == TileLoadState::FailedTemporarily) { + tilesNeedingLoading.insert(pTile); + tileStillNeedsLoading = true; + } + }; // If any candidates need loading, add to return set for (Tile* pTile : query.additiveCandidateTiles) { From 1f01f5a4e95bf2dc065915a6992a6cd63548a1dd Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 13:47:40 +1000 Subject: [PATCH 36/65] Small tweaks and comments. --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 11 ++++++----- Cesium3DTilesSelection/src/TerrainQuery.h | 10 ++++++++++ Cesium3DTilesSelection/src/Tileset.cpp | 5 +++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index fedcc4902..4a0ee6fe0 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -90,7 +90,8 @@ void TerrainQuery::findCandidateTiles( return; } - auto& contentBoundingVolume = pTile->getContentBoundingVolume(); + const std::optional& contentBoundingVolume = + pTile->getContentBoundingVolume(); if (pTile->getChildren().empty()) { // This is a leaf node, it's a candidate @@ -101,9 +102,9 @@ void TerrainQuery::findCandidateTiles( *contentBoundingVolume, this->ray, this->inputCoordinate)) - candidateTiles.push_back(pTile); + this->candidateTiles.push_back(pTile); } else { - candidateTiles.push_back(pTile); + this->candidateTiles.push_back(pTile); } } else { // We have children @@ -116,9 +117,9 @@ void TerrainQuery::findCandidateTiles( *contentBoundingVolume, this->ray, this->inputCoordinate)) - additiveCandidateTiles.push_back(pTile); + this->additiveCandidateTiles.push_back(pTile); } else { - additiveCandidateTiles.push_back(pTile); + this->additiveCandidateTiles.push_back(pTile); } } diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TerrainQuery.h index 5f574712f..2ef94040a 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.h +++ b/Cesium3DTilesSelection/src/TerrainQuery.h @@ -15,8 +15,18 @@ class TerrainQuery { CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; + + // The query ray might intersect these non-leaf tiles that are still relevant + // because of additive refinement. std::vector additiveCandidateTiles = {}; + + // The current set of leaf tiles whose bounding volume the query ray passes + // through. std::vector candidateTiles = {}; + + // The previous set of leaf files. Swapping `candidateTiles` and + // `previousCandidateTiles` each frame allows us to avoid a heap allocation + // for a new vector each frame. std::vector previousCandidateTiles = {}; void intersectVisibleTile(Tile* pTile); diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index b624b6bef..62234a309 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -319,8 +319,13 @@ bool Tileset::tryCompleteHeightRequest( std::vector warnings; for (TerrainQuery& query : request.queries) { if (query.candidateTiles.empty()) { + // Find the initial set of tiles whose bounding volume is intersected by + // the query ray. query.findCandidateTiles(pRoot, warnings); } else { + // Refine the current set of candidate tiles, in case further tiles from + // implicit tiling, external tilesets, etc. having been loaded since last + // frame. std::swap(query.candidateTiles, query.previousCandidateTiles); query.candidateTiles.clear(); From adea29379ed1e26c69bf5a00663378702cfaff19 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 15:51:24 +1000 Subject: [PATCH 37/65] visitHeightRequests -> processHeightRequests --- .../include/Cesium3DTilesSelection/Tileset.h | 2 +- Cesium3DTilesSelection/src/Tileset.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index b0108c222..4bd24c2a7 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -538,7 +538,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { TileLoadPriorityGroup priorityGroup, double priority); - void visitHeightRequests(); + void processHeightRequests(); bool tryCompleteHeightRequest( HeightRequest& request, diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 62234a309..9de4d64cb 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -410,7 +410,7 @@ bool Tileset::tryCompleteHeightRequest( return true; } -void Tileset::visitHeightRequests() { +void Tileset::processHeightRequests() { if (_heightRequests.size() == 0) return; @@ -497,7 +497,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { result = ViewUpdateResult(); } - visitHeightRequests(); + processHeightRequests(); result.workerThreadTileLoadQueueLength = static_cast(this->_workerThreadLoadQueue.size()); From f270e4dc7eecedd0100d8b7751dd3e238de842cb Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 15:51:45 +1000 Subject: [PATCH 38/65] Allow height queries on a tileset that hasn't loaded at all yet. --- Cesium3DTilesSelection/src/Tileset.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 9de4d64cb..8400a8d9a 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -668,8 +668,7 @@ CesiumAsync::Future Tileset::loadMetadata() { CesiumAsync::Future Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { - Tile* pRoot = _pTilesetContentManager->getRootTile(); - if (pRoot == nullptr || coordinates.empty()) { + if (coordinates.empty()) { return this->_asyncSystem.createResolvedFuture({}); } From fbb880bdec93feb5817098d427e24112da7b165e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 15:52:18 +1000 Subject: [PATCH 39/65] Allow a null IPrepareRendererResources. --- .../src/TilesetContentManager.cpp | 131 +++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 4a12a29c2..77b7e7566 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -572,64 +572,69 @@ postProcessContentInWorkerThread( pAssetAccessor, gltfOptions, std::move(gltfResult)) - .thenInWorkerThread( - [result = std::move(result), - projections = std::move(projections), - tileLoadInfo = std::move(tileLoadInfo), - rendererOptions]( - CesiumGltfReader::GltfReaderResult&& gltfResult) mutable { - if (!gltfResult.errors.empty()) { - if (result.pCompletedRequest) { - SPDLOG_LOGGER_ERROR( - tileLoadInfo.pLogger, - "Failed resolving external glTF buffers from {}:\n- {}", - result.pCompletedRequest->url(), - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); - } else { - SPDLOG_LOGGER_ERROR( - tileLoadInfo.pLogger, - "Failed resolving external glTF buffers:\n- {}", - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); - } - } - - if (!gltfResult.warnings.empty()) { - if (result.pCompletedRequest) { - SPDLOG_LOGGER_WARN( - tileLoadInfo.pLogger, - "Warning when resolving external gltf buffers from " - "{}:\n- {}", - result.pCompletedRequest->url(), - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); - } else { - SPDLOG_LOGGER_ERROR( - tileLoadInfo.pLogger, - "Warning resolving external glTF buffers:\n- {}", - CesiumUtility::joinToString(gltfResult.errors, "\n- ")); - } - } - - if (!gltfResult.model) { - return tileLoadInfo.asyncSystem.createResolvedFuture( - TileLoadResultAndRenderResources{ - TileLoadResult::createFailedResult(nullptr), - nullptr}); - } - - result.contentKind = std::move(*gltfResult.model); - - postProcessGltfInWorkerThread( - result, - std::move(projections), - tileLoadInfo); - - // create render resources - return tileLoadInfo.pPrepareRendererResources->prepareInLoadThread( - tileLoadInfo.asyncSystem, - std::move(result), - tileLoadInfo.tileTransform, - rendererOptions); - }); + .thenInWorkerThread([result = std::move(result), + projections = std::move(projections), + tileLoadInfo = std::move(tileLoadInfo), + rendererOptions](CesiumGltfReader::GltfReaderResult&& + gltfResult) mutable { + if (!gltfResult.errors.empty()) { + if (result.pCompletedRequest) { + SPDLOG_LOGGER_ERROR( + tileLoadInfo.pLogger, + "Failed resolving external glTF buffers from {}:\n- {}", + result.pCompletedRequest->url(), + CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + } else { + SPDLOG_LOGGER_ERROR( + tileLoadInfo.pLogger, + "Failed resolving external glTF buffers:\n- {}", + CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + } + } + + if (!gltfResult.warnings.empty()) { + if (result.pCompletedRequest) { + SPDLOG_LOGGER_WARN( + tileLoadInfo.pLogger, + "Warning when resolving external gltf buffers from " + "{}:\n- {}", + result.pCompletedRequest->url(), + CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + } else { + SPDLOG_LOGGER_ERROR( + tileLoadInfo.pLogger, + "Warning resolving external glTF buffers:\n- {}", + CesiumUtility::joinToString(gltfResult.errors, "\n- ")); + } + } + + if (!gltfResult.model) { + return tileLoadInfo.asyncSystem.createResolvedFuture( + TileLoadResultAndRenderResources{ + TileLoadResult::createFailedResult(nullptr), + nullptr}); + } + + result.contentKind = std::move(*gltfResult.model); + + postProcessGltfInWorkerThread( + result, + std::move(projections), + tileLoadInfo); + + // create render resources + if (tileLoadInfo.pPrepareRendererResources) { + return tileLoadInfo.pPrepareRendererResources->prepareInLoadThread( + tileLoadInfo.asyncSystem, + std::move(result), + tileLoadInfo.tileTransform, + rendererOptions); + } else { + return tileLoadInfo.asyncSystem + .createResolvedFuture( + TileLoadResultAndRenderResources{std::move(result), nullptr}); + } + }); } } // namespace @@ -1483,10 +1488,12 @@ void TilesetContentManager::unloadContentLoadedState(Tile& tile) { pRenderContent && "Tile must have render content to be unloaded"); void* pWorkerRenderResources = pRenderContent->getRenderResources(); - this->_externals.pPrepareRendererResources->free( - tile, - pWorkerRenderResources, - nullptr); + if (this->_externals.pPrepareRendererResources) { + this->_externals.pPrepareRendererResources->free( + tile, + pWorkerRenderResources, + nullptr); + } pRenderContent->setRenderResources(nullptr); } From 0a9e7450d4fcf8b60423bed4efb3bd9c292c8c1d Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 15:52:54 +1000 Subject: [PATCH 40/65] Add very basic height query tests. --- .../test/TestTilesetHeightQueries.cpp | 112 ++++++++++++++++++ CesiumNativeTests/src/FileAccessor.cpp | 5 +- 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp new file mode 100644 index 000000000..da08b8a3a --- /dev/null +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace Cesium3DTilesContent; +using namespace Cesium3DTilesSelection; +using namespace CesiumAsync; +using namespace CesiumGeospatial; +using namespace CesiumNativeTests; +using namespace CesiumUtility; + +namespace { + +std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR; + +} + +TEST_CASE("Tileset height queries") { + // The coordinates and expected heights in this file were determined in Cesium + // for Unreal Engine by adding the tileset, putting a cube above the location + // of interest, adding a CesiumGlobeAnchor to it, and pressing the "End" key + // to drop it onto terrain. The coordinates were then copied out of the globe + // anchor, subtracting 0.5 from the height to account for "End" placing the + // bottom of the cube on the surface instead of its center. + + registerAllTileContentTypes(); + + std::shared_ptr pAccessor = + std::make_shared(); + AsyncSystem asyncSystem(std::make_shared()); + + TilesetExternals externals{pAccessor, nullptr, asyncSystem, nullptr}; + + SECTION("Additive-refined tileset") { + std::string url = + "file://" + Uri::nativePathToUriPath( + (testDataPath / "Tileset" / "tileset.json").u8string()); + + Tileset tileset(externals, url); + + Future future = tileset.getHeightsAtCoordinates( + // A point on geometry in "parent.b3dm", which should only be included + // because this tileset is additive-refine. + {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), + + // A point on geometry in a leaf tile. + Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + Tileset::HeightResults results = future.waitInMainThread(); + CHECK(results.warnings.empty()); + REQUIRE(results.coordinateResults.size() == 2); + + CHECK(results.coordinateResults[0].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[0].coordinate.height, + 78.155809, + 0.0, + Math::Epsilon4)); + + CHECK(results.coordinateResults[1].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[1].coordinate.height, + 7.837332, + 0.0, + Math::Epsilon4)); + } + + SECTION("Replace-refined tileset") { + std::string url = + "file://" + + Uri::nativePathToUriPath( + (testDataPath / "ReplaceTileset" / "tileset.json").u8string()); + + Tileset tileset(externals, url); + + Future future = tileset.getHeightsAtCoordinates( + // A point on geometry in "parent.b3dm", which should not be included + // because this tileset is replace-refine. + {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), + + // A point on geometry in a leaf tile. + Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + Tileset::HeightResults results = future.waitInMainThread(); + CHECK(results.warnings.empty()); + REQUIRE(results.coordinateResults.size() == 2); + + CHECK(!results.coordinateResults[0].heightAvailable); + + CHECK(results.coordinateResults[1].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[1].coordinate.height, + 7.837332, + 0.0, + Math::Epsilon4)); + } +} diff --git a/CesiumNativeTests/src/FileAccessor.cpp b/CesiumNativeTests/src/FileAccessor.cpp index cc94e8716..b59a53963 100644 --- a/CesiumNativeTests/src/FileAccessor.cpp +++ b/CesiumNativeTests/src/FileAccessor.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -8,7 +9,6 @@ namespace CesiumNativeTests { namespace { std::unique_ptr readFileUri(const std::string& uri) { - std::vector result; CesiumAsync::HttpHeaders headers; std::string contentType; @@ -23,7 +23,8 @@ std::unique_ptr readFileUri(const std::string& uri) { if (protocolPos != 0) { return response(400); } - std::string path = uri.substr(std::strlen("file://")); + std::string path = + CesiumUtility::Uri::uriPathToNativePath(CesiumUtility::Uri::getPath(uri)); std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) { return response(404); From dcc6c09c0bbe97028acadaf153c88264fb7c09bf Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 16:32:08 +1000 Subject: [PATCH 41/65] Don't do a full search if there are already additive candidates. --- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 8400a8d9a..46838a61a 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -318,7 +318,7 @@ bool Tileset::tryCompleteHeightRequest( bool tileStillNeedsLoading = false; std::vector warnings; for (TerrainQuery& query : request.queries) { - if (query.candidateTiles.empty()) { + if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) { // Find the initial set of tiles whose bounding volume is intersected by // the query ray. query.findCandidateTiles(pRoot, warnings); From 349bc224593974d4b7f28e6a6b9dc2835b1c699c Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 16:32:32 +1000 Subject: [PATCH 42/65] Add external and implicit tileset tests. Implicit tileset test is currently failing. --- .../test/TestTilesetHeightQueries.cpp | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index da08b8a3a..3fc8d8278 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -47,7 +47,7 @@ TEST_CASE("Tileset height queries") { Future future = tileset.getHeightsAtCoordinates( // A point on geometry in "parent.b3dm", which should only be included - // because this tileset is additive-refine. + // because this tileset is additive-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), // A point on geometry in a leaf tile. @@ -86,7 +86,7 @@ TEST_CASE("Tileset height queries") { Future future = tileset.getHeightsAtCoordinates( // A point on geometry in "parent.b3dm", which should not be included - // because this tileset is replace-refine. + // because this tileset is replace-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), // A point on geometry in a leaf tile. @@ -109,4 +109,82 @@ TEST_CASE("Tileset height queries") { 0.0, Math::Epsilon4)); } + + SECTION("External tileset") { + std::string url = + "file://" + + Uri::nativePathToUriPath( + (testDataPath / "AddTileset" / "tileset.json").u8string()); + + Tileset tileset(externals, url); + + Future future = tileset.getHeightsAtCoordinates( + // A point on geometry in "0/0/0.b3dm", which should only be included + // because this tileset is additive-refined. + {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), + + // A point on geometry in a leaf tile. + Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + Tileset::HeightResults results = future.waitInMainThread(); + CHECK(results.warnings.empty()); + REQUIRE(results.coordinateResults.size() == 2); + + CHECK(results.coordinateResults[0].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[0].coordinate.height, + 78.155809, + 0.0, + Math::Epsilon4)); + + CHECK(results.coordinateResults[1].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[1].coordinate.height, + 7.837332, + 0.0, + Math::Epsilon4)); + } + + SECTION("Implicit tileset") { + std::string url = + "file://" + + Uri::nativePathToUriPath( + (testDataPath / "ImplicitTileset" / "tileset_1.1.json").u8string()); + + Tileset tileset(externals, url); + + Future future = tileset.getHeightsAtCoordinates( + // A point on geometry in "0/0/0.b3dm", which should only be included + // because this tileset is additive-refined. + {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), + + // A point on geometry in a leaf tile. + Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + Tileset::HeightResults results = future.waitInMainThread(); + CHECK(results.warnings.empty()); + REQUIRE(results.coordinateResults.size() == 2); + + CHECK(results.coordinateResults[0].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[0].coordinate.height, + 78.155809, + 0.0, + Math::Epsilon4)); + + CHECK(results.coordinateResults[1].heightAvailable); + CHECK(Math::equalsEpsilon( + results.coordinateResults[1].coordinate.height, + 7.837332, + 0.0, + Math::Epsilon4)); + } } From b7a42443dd92d47e44d6b38587e4d116ea9d973e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 11 Sep 2024 21:50:40 +1000 Subject: [PATCH 43/65] Height queries against implicit tilesets. --- .../include/Cesium3DTilesSelection/Tile.h | 22 ++++++++++--- Cesium3DTilesSelection/src/Tile.cpp | 16 +++++----- Cesium3DTilesSelection/src/Tileset.cpp | 4 +++ .../src/TilesetContentManager.cpp | 31 +++++++++---------- .../src/TilesetContentManager.h | 4 +++ .../test/MockTilesetContentManager.cpp | 2 +- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h index df900f4b6..c878bf1c3 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h @@ -501,10 +501,24 @@ class CESIUM3DTILESSELECTION_API Tile final { void setState(TileLoadState state) noexcept; - bool shouldContentContinueUpdating() const noexcept; + /** + * @brief Gets a flag indicating whether this tile might have latent children. + * Latent children don't exist in the `_children` property, but can be created + * by the {@link TilesetContentLoader}. + * + * When true, this tile might have children that can be created by the + * TilesetContentLoader but that aren't reflected in the `_children` property + * yet. For example, in implicit tiling, we save memory by only creating + * explicit Tile instances from implicit availability as those instances are + * needed. When this flag is true, that creation of explicit instances hasn't + * been done yet for this tile. + * + * If this flag is false, the children have already been created, if they + * exist. The tile may still have no children because it is a leaf node. + */ + bool getMightHaveLatentChildren() const noexcept; - void - setContentShouldContinueUpdating(bool shouldContentContinueUpdating) noexcept; + void setMightHaveLatentChildren(bool mightHaveLatentChildren) noexcept; // Position in bounding-volume hierarchy. Tile* _pParent; @@ -528,7 +542,7 @@ class CESIUM3DTILESSELECTION_API Tile final { TileContent _content; TilesetContentLoader* _pLoader; TileLoadState _loadState; - bool _shouldContentContinueUpdating; + bool _mightHaveLatentChildren; // mapped raster overlay std::vector _rasterTiles; diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 932dd5b0d..721ba5b63 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -57,7 +57,7 @@ Tile::Tile( _content{std::forward(args)...}, _pLoader{pLoader}, _loadState{loadState}, - _shouldContentContinueUpdating{true} {} + _mightHaveLatentChildren{true} {} Tile::Tile(Tile&& rhs) noexcept : _pParent(rhs._pParent), @@ -74,7 +74,7 @@ Tile::Tile(Tile&& rhs) noexcept _content(std::move(rhs._content)), _pLoader{rhs._pLoader}, _loadState{rhs._loadState}, - _shouldContentContinueUpdating{rhs._shouldContentContinueUpdating} { + _mightHaveLatentChildren{rhs._mightHaveLatentChildren} { // since children of rhs will have the parent pointed to rhs, // we will reparent them to this tile as rhs will be destroyed after this for (Tile& tile : this->_children) { @@ -105,7 +105,7 @@ Tile& Tile::operator=(Tile&& rhs) noexcept { this->_content = std::move(rhs._content); this->_pLoader = rhs._pLoader; this->_loadState = rhs._loadState; - this->_shouldContentContinueUpdating = rhs._shouldContentContinueUpdating; + this->_mightHaveLatentChildren = rhs._mightHaveLatentChildren; } return *this; @@ -227,12 +227,12 @@ void Tile::setParent(Tile* pParent) noexcept { this->_pParent = pParent; } void Tile::setState(TileLoadState state) noexcept { this->_loadState = state; } -bool Tile::shouldContentContinueUpdating() const noexcept { - return this->_shouldContentContinueUpdating; +bool Tile::getMightHaveLatentChildren() const noexcept { + return this->_mightHaveLatentChildren; } -void Tile::setContentShouldContinueUpdating( - bool shouldContentContinueUpdating) noexcept { - this->_shouldContentContinueUpdating = shouldContentContinueUpdating; +void Tile::setMightHaveLatentChildren(bool mightHaveLatentChildren) noexcept { + this->_mightHaveLatentChildren = mightHaveLatentChildren; } + } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 46838a61a..ce04d5d9d 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -343,6 +343,10 @@ bool Tileset::tryCompleteHeightRequest( auto checkTile = [this, &tilesNeedingLoading, &tileStillNeedsLoading](Tile* pTile) { + this->_pTilesetContentManager->createLatentChildrenIfNecessary( + *pTile, + this->getOptions()); + TileLoadState state = pTile->getState(); if (state == TileLoadState::Unloading) { // This tile is in the process of unloading, which must complete diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 77b7e7566..39bd89a97 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -1070,16 +1070,22 @@ void TilesetContentManager::updateTileContent( updateDoneState(tile, tilesetOptions); } - if (tile.shouldContentContinueUpdating()) { + this->createLatentChildrenIfNecessary(tile, tilesetOptions); +} + +void TilesetContentManager::createLatentChildrenIfNecessary( + Tile& tile, + const TilesetOptions& tilesetOptions) { + if (tile.getMightHaveLatentChildren()) { TileChildrenResult childrenResult = this->_pLoader->createTileChildren(tile, tilesetOptions.ellipsoid); if (childrenResult.state == TileLoadResultState::Success) { tile.createChildTiles(std::move(childrenResult.children)); } - bool shouldTileContinueUpdated = + bool mightStillHaveLatentChildren = childrenResult.state == TileLoadResultState::RetryLater; - tile.setContentShouldContinueUpdating(shouldTileContinueUpdated); + tile.setMightHaveLatentChildren(mightStillHaveLatentChildren); } } @@ -1379,19 +1385,12 @@ void TilesetContentManager::updateContentLoadedState( void TilesetContentManager::updateDoneState( Tile& tile, const TilesetOptions& tilesetOptions) { - // The reason for this method to terminate early when - // Tile::shouldContentContinueUpdating() returns true is that: When a tile has - // Tile::shouldContentContinueUpdating() to be true, it means the tile's - // children need to be created by the - // TilesetContentLoader::createTileChildren() which is invoked in the - // TilesetContentManager::updateTileContent() method. In the - // updateDoneState(), RasterOverlayTiles that are mapped to the tile will - // begin updating. If there are more RasterOverlayTiles with higher LOD and - // the current tile is a leaf, more upsample children will be created for that - // tile. So to accurately determine if a tile is a leaf, it needs the tile to - // have no children and Tile::shouldContentContinueUpdating() to return false - // which means the loader has no more children for this tile. - if (tile.shouldContentContinueUpdating()) { + if (tile.getMightHaveLatentChildren()) { + // This tile might have latent children, so we don't know yet whether it has + // children or not. We need to know that before we can continue this + // function, which will decide whether or not to create upsampled children + // for this tile. It only makes sense to create upsampled children for a + // tile that definitely doesn't have real children. return; } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index d750b2207..553fbd3e7 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -64,6 +64,10 @@ class TilesetContentManager void updateTileContent(Tile& tile, const TilesetOptions& tilesetOptions); + void createLatentChildrenIfNecessary( + Tile& tile, + const TilesetOptions& tilesetOptions); + bool unloadTileContent(Tile& tile); void waitUntilIdle(); diff --git a/Cesium3DTilesSelection/test/MockTilesetContentManager.cpp b/Cesium3DTilesSelection/test/MockTilesetContentManager.cpp index 47719fd9c..5f1afcc53 100644 --- a/Cesium3DTilesSelection/test/MockTilesetContentManager.cpp +++ b/Cesium3DTilesSelection/test/MockTilesetContentManager.cpp @@ -10,6 +10,6 @@ void MockTilesetContentManagerTestFixture::setTileLoadState( void MockTilesetContentManagerTestFixture::setTileShouldContinueUpdating( Cesium3DTilesSelection::Tile& tile, bool shouldContinueUpdating) { - tile.setContentShouldContinueUpdating(shouldContinueUpdating); + tile.setMightHaveLatentChildren(shouldContinueUpdating); } } // namespace Cesium3DTilesSelection From d9ff8acb9b0ddcb09b8727ff55597b9468769442 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 13 Sep 2024 16:16:31 +1000 Subject: [PATCH 44/65] Report a warning for unsupported extensions. --- Cesium3DTilesSelection/src/TerrainQuery.cpp | 12 ++++++--- .../test/TestTilesetHeightQueries.cpp | 25 +++++++++++++++++++ CesiumGltfContent/src/GltfUtilities.cpp | 19 ++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TerrainQuery.cpp index 4a0ee6fe0..34ca38d05 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TerrainQuery.cpp @@ -66,17 +66,21 @@ void TerrainQuery::intersectVisibleTile(Tile* pTile) { true, pTile->getTransform()); - if (!gltfIntersectResult.hit.has_value()) - return; + if (!gltfIntersectResult.warnings.empty()) { + this->intersectResult.warnings.insert( + this->intersectResult.warnings.end(), + gltfIntersectResult.warnings.begin(), + gltfIntersectResult.warnings.end()); + } // Set ray info to this hit if closer, or the first hit if (!this->intersectResult.hit.has_value()) { - this->intersectResult = std::move(gltfIntersectResult); + this->intersectResult.hit = std::move(gltfIntersectResult.hit); } else { double prevDistSq = this->intersectResult.hit->rayToWorldPointDistanceSq; double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; if (thisDistSq < prevDistSq) - this->intersectResult = std::move(gltfIntersectResult); + this->intersectResult.hit = std::move(gltfIntersectResult.hit); } } diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index 3fc8d8278..c13ec654f 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -187,4 +187,29 @@ TEST_CASE("Tileset height queries") { 0.0, Math::Epsilon4)); } + + SECTION("Instanced model is not yet supported") { + std::string url = + "file://" + + Uri::nativePathToUriPath( + (testDataPath / "i3dm" / "InstancedWithBatchTable" / "tileset.json") + .u8string()); + + Tileset tileset(externals, url); + + Future future = tileset.getHeightsAtCoordinates( + {Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + Tileset::HeightResults results = future.waitInMainThread(); + REQUIRE(results.warnings.size() == 1); + REQUIRE(results.coordinateResults.size() == 1); + CHECK(!results.coordinateResults[0].heightAvailable); + CHECK( + results.warnings[0].find("EXT_mesh_gpu_instancing") != + std::string::npos); + } } diff --git a/CesiumGltfContent/src/GltfUtilities.cpp b/CesiumGltfContent/src/GltfUtilities.cpp index 2686151af..9cfabe707 100644 --- a/CesiumGltfContent/src/GltfUtilities.cpp +++ b/CesiumGltfContent/src/GltfUtilities.cpp @@ -1373,6 +1373,12 @@ std::optional intersectRayScenePrimitive( return transformedRay.pointFromDistance(tClosest); } +std::string intersectGltfUnsupportedExtensions[] = { + ExtensionKhrDracoMeshCompression::ExtensionName, + ExtensionBufferViewExtMeshoptCompression::ExtensionName, + ExtensionExtMeshGpuInstancing::ExtensionName, + "KHR_mesh_quantization"}; + } // namespace GltfUtilities::IntersectResult GltfUtilities::intersectRayGltfModel( @@ -1380,6 +1386,19 @@ GltfUtilities::IntersectResult GltfUtilities::intersectRayGltfModel( const CesiumGltf::Model& gltf, bool cullBackFaces, const glm::dmat4x4& gltfTransform) { + // We can't currently intersect a ray with a model if the model has any funny + // business with its vertex positions or if it uses instancing. + for (const std::string& unsupportedExtension : + intersectGltfUnsupportedExtensions) { + if (gltf.isExtensionRequired(unsupportedExtension)) { + return IntersectResult{ + std::nullopt, + {fmt::format( + "Cannot intersect a ray with a glTF model with the {} extension.", + unsupportedExtension)}}; + } + } + glm::dmat4x4 rootTransform = applyRtcCenter(gltf, gltfTransform); rootTransform = applyGltfUpAxisTransform(gltf, rootTransform); From 11d9f8e69c80ddfd5c6fc52388692fd51ec84608 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 13 Sep 2024 17:07:24 +1000 Subject: [PATCH 45/65] Rename TerrainQuery to TilesetHeightQuery. --- .../include/Cesium3DTilesSelection/Tileset.h | 4 ++-- Cesium3DTilesSelection/src/Tileset.cpp | 12 ++++++------ .../src/{TerrainQuery.cpp => TilesetHeightQuery.cpp} | 6 +++--- .../src/{TerrainQuery.h => TilesetHeightQuery.h} | 7 ++++--- 4 files changed, 15 insertions(+), 14 deletions(-) rename Cesium3DTilesSelection/src/{TerrainQuery.cpp => TilesetHeightQuery.cpp} (96%) rename Cesium3DTilesSelection/src/{TerrainQuery.h => TilesetHeightQuery.h} (92%) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 4bd24c2a7..5376619c8 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -25,7 +25,7 @@ namespace Cesium3DTilesSelection { class TilesetContentManager; class TilesetMetadata; -class TerrainQuery; +class TilesetHeightQuery; /** * @brief A queries; + std::vector queries; CesiumAsync::Promise promise; }; diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index ce04d5d9d..891860961 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1,6 +1,6 @@ -#include "TerrainQuery.h" #include "TileUtilities.h" #include "TilesetContentManager.h" +#include "TilesetHeightQuery.h" #include #include @@ -317,7 +317,7 @@ bool Tileset::tryCompleteHeightRequest( bool tileStillNeedsLoading = false; std::vector warnings; - for (TerrainQuery& query : request.queries) { + for (TilesetHeightQuery& query : request.queries) { if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) { // Find the initial set of tiles whose bounding volume is intersected by // the query ray. @@ -375,7 +375,7 @@ bool Tileset::tryCompleteHeightRequest( return false; // Do the intersect tests - for (TerrainQuery& query : request.queries) { + for (TilesetHeightQuery& query : request.queries) { for (Tile* pTile : query.additiveCandidateTiles) { query.intersectVisibleTile(pTile); } @@ -391,7 +391,7 @@ bool Tileset::tryCompleteHeightRequest( results.warnings = std::move(warnings); // Populate results with completed queries - for (TerrainQuery& query : request.queries) { + for (TilesetHeightQuery& query : request.queries) { Tileset::HeightResults::CoordinateResult coordinateResult = { query.intersectResult.hit.has_value(), std::move(query.inputCoordinate)}; @@ -678,7 +678,7 @@ Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { Promise promise = this->_asyncSystem.createPromise(); - std::vector queries; + std::vector queries; for (const CesiumGeospatial::Cartographic& coordinate : coordinates) { CesiumGeospatial::Cartographic startCoordinate( coordinate.longitude, @@ -689,7 +689,7 @@ Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { Ellipsoid::WGS84.cartographicToCartesian(startCoordinate), -Ellipsoid::WGS84.geodeticSurfaceNormal(startCoordinate)); - queries.push_back(TerrainQuery{coordinate, std::move(ray)}); + queries.push_back(TilesetHeightQuery{coordinate, std::move(ray)}); } _heightRequests.emplace_back(HeightRequest{std::move(queries), promise}); diff --git a/Cesium3DTilesSelection/src/TerrainQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp similarity index 96% rename from Cesium3DTilesSelection/src/TerrainQuery.cpp rename to Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 34ca38d05..857d43f58 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -1,4 +1,4 @@ -#include "TerrainQuery.h" +#include "TilesetHeightQuery.h" #include "TileUtilities.h" #include "TilesetContentManager.h" @@ -54,7 +54,7 @@ bool boundingVolumeContainsCoordinate( } // namespace -void TerrainQuery::intersectVisibleTile(Tile* pTile) { +void TilesetHeightQuery::intersectVisibleTile(Tile* pTile) { TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!pRenderContent) return; @@ -84,7 +84,7 @@ void TerrainQuery::intersectVisibleTile(Tile* pTile) { } } -void TerrainQuery::findCandidateTiles( +void TilesetHeightQuery::findCandidateTiles( Tile* pTile, std::vector& warnings) { diff --git a/Cesium3DTilesSelection/src/TerrainQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h similarity index 92% rename from Cesium3DTilesSelection/src/TerrainQuery.h rename to Cesium3DTilesSelection/src/TilesetHeightQuery.h index 2ef94040a..f3962073d 100644 --- a/Cesium3DTilesSelection/src/TerrainQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -1,16 +1,17 @@ #pragma once -#include #include #include #include -#include +#include #include namespace Cesium3DTilesSelection { -class TerrainQuery { +class Tile; + +class TilesetHeightQuery { public: CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; From 7c167ee15a480e78a06975fdccb5508da2277e22 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 13 Sep 2024 19:04:00 +1000 Subject: [PATCH 46/65] Minor reorg and documentation. --- .../SampleHeightResult.h | 42 +++++++++++ .../include/Cesium3DTilesSelection/Tileset.h | 30 ++++---- Cesium3DTilesSelection/src/Tileset.cpp | 42 ++++++----- .../test/TestTilesetHeightQueries.cpp | 74 +++++++++---------- 4 files changed, 119 insertions(+), 69 deletions(-) create mode 100644 Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h new file mode 100644 index 000000000..9b79e4662 --- /dev/null +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +namespace Cesium3DTilesSelection { + +/** + * @brief The result of sampling heights with + * {@link Tileset::sampleHeightMostDetailed}. + */ +struct SampleHeightResult { + /** + * @brief The positions and sampled heights. + * + * The longitudes and latitudes will match the values at the same index in the + * original input positions. Each height will either be the height sampled + * from the tileset at that position, or the original input height if the + * height could not be sampled. To determine which, look at the value of + * {@link SampleHeightResult::heightSampled} at the same index. + */ + std::vector positions; + + /** + * @brief Specifies whether the height for the position at this index was + * sampled successfully. If true, {@link SampleHeightResult::positions} has + * a valid height sampled from the tileset at this index. If false, the height + * could not be sampled for the position at this index, and so the height in + * {@link SampleHeightResult::positions} is unchanged from the original input + * height. + */ + std::vector heightSampled; + + /** + * @brief Any warnings that occurred while sampling heights. + */ + std::vector warnings; +}; + +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 5376619c8..073c3e3dd 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -2,6 +2,7 @@ #include "Library.h" #include "RasterOverlayCollection.h" +#include "SampleHeightResult.h" #include "Tile.h" #include "TilesetContentLoader.h" #include "TilesetExternals.h" @@ -273,18 +274,21 @@ class CESIUM3DTILESSELECTION_API Tileset final { */ CesiumAsync::Future loadMetadata(); - struct HeightResults { - struct CoordinateResult { - bool heightAvailable = false; - CesiumGeospatial::Cartographic coordinate = {-1, -1, -1}; - }; - - std::vector coordinateResults; - std::vector warnings; - }; - - CesiumAsync::Future getHeightsAtCoordinates( - const std::vector& coordinates); + /** + * @brief Initiates an asynchronous query for the height of this tileset at a + * list of positions, expressed as longitude and latitude. The most detailed + * available tiles are used to determine each height. + * + * The height of the input positions is ignored. On output, the height is + * expressed in meters above the ellipsoid (usually WGS84), which should not + * be confused with a height above mean sea level. + * + * @param positions The positions for which to sample heights. + * @return A future that asynchronously resolves to the result of the height + * query. + */ + CesiumAsync::Future sampleHeightMostDetailed( + const std::vector& positions); private: /** @@ -441,7 +445,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { struct HeightRequest { std::vector queries; - CesiumAsync::Promise promise; + CesiumAsync::Promise promise; }; void _processWorkerThreadLoadQueue(); diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 891860961..af9495f01 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -385,29 +385,33 @@ bool Tileset::tryCompleteHeightRequest( } // All rays are done, create results - Tileset::HeightResults results; + SampleHeightResult results; // Start with any warnings from tile traversal results.warnings = std::move(warnings); + results.positions.resize(request.queries.size(), Cartographic(0.0, 0.0, 0.0)); + results.heightSampled.resize(request.queries.size()); + // Populate results with completed queries - for (TilesetHeightQuery& query : request.queries) { - Tileset::HeightResults::CoordinateResult coordinateResult = { - query.intersectResult.hit.has_value(), - std::move(query.inputCoordinate)}; + for (size_t i = 0; i < request.queries.size(); ++i) { + const TilesetHeightQuery& query = request.queries[i]; - // Add query warnings into the height result - std::copy( - query.intersectResult.warnings.begin(), - query.intersectResult.warnings.end(), - std::back_inserter(results.warnings)); + bool heightSampled = query.intersectResult.hit.has_value(); + results.heightSampled[i] = heightSampled; + results.positions[i] = query.inputCoordinate; - if (coordinateResult.heightAvailable) - coordinateResult.coordinate.height = + if (heightSampled) { + results.positions[i].height = RAY_ORIGIN_HEIGHT - glm::sqrt(query.intersectResult.hit->rayToWorldPointDistanceSq); + } - results.coordinateResults.push_back(coordinateResult); + // Add query warnings into the height result + results.warnings.insert( + results.warnings.end(), + query.intersectResult.warnings.begin(), + query.intersectResult.warnings.end()); } request.promise.resolve(std::move(results)); @@ -670,16 +674,16 @@ CesiumAsync::Future Tileset::loadMetadata() { }); } -CesiumAsync::Future -Tileset::getHeightsAtCoordinates(const std::vector& coordinates) { - if (coordinates.empty()) { - return this->_asyncSystem.createResolvedFuture({}); +CesiumAsync::Future +Tileset::sampleHeightMostDetailed(const std::vector& positions) { + if (positions.empty()) { + return this->_asyncSystem.createResolvedFuture({}); } - Promise promise = this->_asyncSystem.createPromise(); + Promise promise = this->_asyncSystem.createPromise(); std::vector queries; - for (const CesiumGeospatial::Cartographic& coordinate : coordinates) { + for (const CesiumGeospatial::Cartographic& coordinate : positions) { CesiumGeospatial::Cartographic startCoordinate( coordinate.longitude, coordinate.latitude, diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index c13ec654f..84cd7c7de 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -45,7 +45,7 @@ TEST_CASE("Tileset height queries") { Tileset tileset(externals, url); - Future future = tileset.getHeightsAtCoordinates( + Future future = tileset.sampleHeightMostDetailed( // A point on geometry in "parent.b3dm", which should only be included // because this tileset is additive-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), @@ -57,20 +57,20 @@ TEST_CASE("Tileset height queries") { tileset.updateView({}); } - Tileset::HeightResults results = future.waitInMainThread(); + SampleHeightResult results = future.waitInMainThread(); CHECK(results.warnings.empty()); - REQUIRE(results.coordinateResults.size() == 2); + REQUIRE(results.positions.size() == 2); - CHECK(results.coordinateResults[0].heightAvailable); + CHECK(results.heightSampled[0]); CHECK(Math::equalsEpsilon( - results.coordinateResults[0].coordinate.height, + results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.coordinateResults[1].heightAvailable); + CHECK(results.heightSampled[1]); CHECK(Math::equalsEpsilon( - results.coordinateResults[1].coordinate.height, + results.positions[1].height, 7.837332, 0.0, Math::Epsilon4)); @@ -84,9 +84,9 @@ TEST_CASE("Tileset height queries") { Tileset tileset(externals, url); - Future future = tileset.getHeightsAtCoordinates( - // A point on geometry in "parent.b3dm", which should not be included - // because this tileset is replace-refined. + Future future = tileset.sampleHeightMostDetailed( + // A point on geometry in "parent.b3dm", which should not be + // included because this tileset is replace-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), // A point on geometry in a leaf tile. @@ -96,15 +96,15 @@ TEST_CASE("Tileset height queries") { tileset.updateView({}); } - Tileset::HeightResults results = future.waitInMainThread(); + SampleHeightResult results = future.waitInMainThread(); CHECK(results.warnings.empty()); - REQUIRE(results.coordinateResults.size() == 2); + REQUIRE(results.positions.size() == 2); - CHECK(!results.coordinateResults[0].heightAvailable); + CHECK(!results.heightSampled[0]); - CHECK(results.coordinateResults[1].heightAvailable); + CHECK(results.heightSampled[1]); CHECK(Math::equalsEpsilon( - results.coordinateResults[1].coordinate.height, + results.positions[1].height, 7.837332, 0.0, Math::Epsilon4)); @@ -118,9 +118,9 @@ TEST_CASE("Tileset height queries") { Tileset tileset(externals, url); - Future future = tileset.getHeightsAtCoordinates( - // A point on geometry in "0/0/0.b3dm", which should only be included - // because this tileset is additive-refined. + Future future = tileset.sampleHeightMostDetailed( + // A point on geometry in "0/0/0.b3dm", which should only be + // included because this tileset is additive-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), // A point on geometry in a leaf tile. @@ -130,20 +130,20 @@ TEST_CASE("Tileset height queries") { tileset.updateView({}); } - Tileset::HeightResults results = future.waitInMainThread(); + SampleHeightResult results = future.waitInMainThread(); CHECK(results.warnings.empty()); - REQUIRE(results.coordinateResults.size() == 2); + REQUIRE(results.positions.size() == 2); - CHECK(results.coordinateResults[0].heightAvailable); + CHECK(results.heightSampled[0]); CHECK(Math::equalsEpsilon( - results.coordinateResults[0].coordinate.height, + results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.coordinateResults[1].heightAvailable); + CHECK(results.heightSampled[1]); CHECK(Math::equalsEpsilon( - results.coordinateResults[1].coordinate.height, + results.positions[1].height, 7.837332, 0.0, Math::Epsilon4)); @@ -157,9 +157,9 @@ TEST_CASE("Tileset height queries") { Tileset tileset(externals, url); - Future future = tileset.getHeightsAtCoordinates( - // A point on geometry in "0/0/0.b3dm", which should only be included - // because this tileset is additive-refined. + Future future = tileset.sampleHeightMostDetailed( + // A point on geometry in "0/0/0.b3dm", which should only be + // included because this tileset is additive-refined. {Cartographic::fromDegrees(-75.612088, 40.042526, 0.0), // A point on geometry in a leaf tile. @@ -169,20 +169,20 @@ TEST_CASE("Tileset height queries") { tileset.updateView({}); } - Tileset::HeightResults results = future.waitInMainThread(); + SampleHeightResult results = future.waitInMainThread(); CHECK(results.warnings.empty()); - REQUIRE(results.coordinateResults.size() == 2); + REQUIRE(results.positions.size() == 2); - CHECK(results.coordinateResults[0].heightAvailable); + CHECK(results.heightSampled[0]); CHECK(Math::equalsEpsilon( - results.coordinateResults[0].coordinate.height, + results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.coordinateResults[1].heightAvailable); + CHECK(results.heightSampled[1]); CHECK(Math::equalsEpsilon( - results.coordinateResults[1].coordinate.height, + results.positions[1].height, 7.837332, 0.0, Math::Epsilon4)); @@ -197,17 +197,17 @@ TEST_CASE("Tileset height queries") { Tileset tileset(externals, url); - Future future = tileset.getHeightsAtCoordinates( + Future future = tileset.sampleHeightMostDetailed( {Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)}); while (!future.isReady()) { tileset.updateView({}); } - Tileset::HeightResults results = future.waitInMainThread(); + SampleHeightResult results = future.waitInMainThread(); REQUIRE(results.warnings.size() == 1); - REQUIRE(results.coordinateResults.size() == 1); - CHECK(!results.coordinateResults[0].heightAvailable); + REQUIRE(results.positions.size() == 1); + CHECK(!results.heightSampled[0]); CHECK( results.warnings[0].find("EXT_mesh_gpu_instancing") != std::string::npos); From 06d9e791a64f194e16833b9ef3d8a05818665274 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 13 Sep 2024 21:00:22 +1000 Subject: [PATCH 47/65] Move height query functionality out of Tileset.cpp. --- .../include/Cesium3DTilesSelection/Tileset.h | 15 +- Cesium3DTilesSelection/src/Tileset.cpp | 153 ++-------------- .../src/TilesetHeightQuery.cpp | 167 ++++++++++++++++++ .../src/TilesetHeightQuery.h | 35 +++- 4 files changed, 210 insertions(+), 160 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 073c3e3dd..472d629ec 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -27,6 +26,7 @@ namespace Cesium3DTilesSelection { class TilesetContentManager; class TilesetMetadata; class TilesetHeightQuery; +class TilesetHeightRequest; /** * @brief A queries; - CesiumAsync::Promise promise; - }; - void _processWorkerThreadLoadQueue(); void _processMainThreadLoadQueue(); @@ -535,19 +530,13 @@ class CESIUM3DTILESSELECTION_API Tileset final { CesiumUtility::IntrusivePointer _pTilesetContentManager; - std::list _heightRequests; + std::list _heightRequests; void addTileToLoadQueue( Tile& tile, TileLoadPriorityGroup priorityGroup, double priority); - void processHeightRequests(); - - bool tryCompleteHeightRequest( - HeightRequest& request, - std::set& tilesNeedingLoading); - static TraversalDetails createTraversalDetailsForSingleTile( const FrameState& frameState, const Tile& tile, diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index af9495f01..b5b1b4777 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -310,137 +310,6 @@ Tileset::updateViewOffline(const std::vector& frustums) { return this->_updateResult; } -bool Tileset::tryCompleteHeightRequest( - HeightRequest& request, - std::set& tilesNeedingLoading) { - Tile* pRoot = _pTilesetContentManager->getRootTile(); - - bool tileStillNeedsLoading = false; - std::vector warnings; - for (TilesetHeightQuery& query : request.queries) { - if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) { - // Find the initial set of tiles whose bounding volume is intersected by - // the query ray. - query.findCandidateTiles(pRoot, warnings); - } else { - // Refine the current set of candidate tiles, in case further tiles from - // implicit tiling, external tilesets, etc. having been loaded since last - // frame. - std::swap(query.candidateTiles, query.previousCandidateTiles); - - query.candidateTiles.clear(); - - for (Tile* pCandidate : query.previousCandidateTiles) { - TileLoadState loadState = pCandidate->getState(); - if (!pCandidate->getChildren().empty() && - loadState >= TileLoadState::ContentLoaded) { - query.findCandidateTiles(pCandidate, warnings); - } else { - query.candidateTiles.emplace_back(pCandidate); - } - } - } - - auto checkTile = - [this, &tilesNeedingLoading, &tileStillNeedsLoading](Tile* pTile) { - this->_pTilesetContentManager->createLatentChildrenIfNecessary( - *pTile, - this->getOptions()); - - TileLoadState state = pTile->getState(); - if (state == TileLoadState::Unloading) { - // This tile is in the process of unloading, which must complete - // before we can load it again. - this->_pTilesetContentManager->unloadTileContent(*pTile); - tileStillNeedsLoading = true; - } else if ( - state == TileLoadState::Unloaded || - state == TileLoadState::FailedTemporarily) { - tilesNeedingLoading.insert(pTile); - tileStillNeedsLoading = true; - } - }; - - // If any candidates need loading, add to return set - for (Tile* pTile : query.additiveCandidateTiles) { - checkTile(pTile); - } - for (Tile* pTile : query.candidateTiles) { - checkTile(pTile); - } - } - - // Bail if we're waiting on tiles to load - if (tileStillNeedsLoading) - return false; - - // Do the intersect tests - for (TilesetHeightQuery& query : request.queries) { - for (Tile* pTile : query.additiveCandidateTiles) { - query.intersectVisibleTile(pTile); - } - for (Tile* pTile : query.candidateTiles) { - query.intersectVisibleTile(pTile); - } - } - - // All rays are done, create results - SampleHeightResult results; - - // Start with any warnings from tile traversal - results.warnings = std::move(warnings); - - results.positions.resize(request.queries.size(), Cartographic(0.0, 0.0, 0.0)); - results.heightSampled.resize(request.queries.size()); - - // Populate results with completed queries - for (size_t i = 0; i < request.queries.size(); ++i) { - const TilesetHeightQuery& query = request.queries[i]; - - bool heightSampled = query.intersectResult.hit.has_value(); - results.heightSampled[i] = heightSampled; - results.positions[i] = query.inputCoordinate; - - if (heightSampled) { - results.positions[i].height = - RAY_ORIGIN_HEIGHT - - glm::sqrt(query.intersectResult.hit->rayToWorldPointDistanceSq); - } - - // Add query warnings into the height result - results.warnings.insert( - results.warnings.end(), - query.intersectResult.warnings.begin(), - query.intersectResult.warnings.end()); - } - - request.promise.resolve(std::move(results)); - return true; -} - -void Tileset::processHeightRequests() { - if (_heightRequests.size() == 0) - return; - - // Go through all requests, either complete them, or gather the tiles they - // need for completion - std::set tilesNeedingLoading; - for (auto it = _heightRequests.begin(); it != _heightRequests.end();) { - HeightRequest& request = *it; - if (!tryCompleteHeightRequest(request, tilesNeedingLoading)) { - ++it; - } else { - auto deleteIt = it; - ++it; - _heightRequests.erase(deleteIt); - } - } - - this->_heightQueryLoadQueue.assign( - tilesNeedingLoading.begin(), - tilesNeedingLoading.end()); -} - const ViewUpdateResult& Tileset::updateView(const std::vector& frustums, float deltaTime) { CESIUM_TRACE("Tileset::updateView"); @@ -505,7 +374,11 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { result = ViewUpdateResult(); } - processHeightRequests(); + TilesetHeightRequest::processHeightRequests( + *this->_pTilesetContentManager, + this->_options, + this->_heightRequests, + this->_heightQueryLoadQueue); result.workerThreadTileLoadQueueLength = static_cast(this->_workerThreadLoadQueue.size()); @@ -683,20 +556,14 @@ Tileset::sampleHeightMostDetailed(const std::vector& positions) { Promise promise = this->_asyncSystem.createPromise(); std::vector queries; - for (const CesiumGeospatial::Cartographic& coordinate : positions) { - CesiumGeospatial::Cartographic startCoordinate( - coordinate.longitude, - coordinate.latitude, - RAY_ORIGIN_HEIGHT); - - Ray ray( - Ellipsoid::WGS84.cartographicToCartesian(startCoordinate), - -Ellipsoid::WGS84.geodeticSurfaceNormal(startCoordinate)); + queries.reserve(positions.size()); - queries.push_back(TilesetHeightQuery{coordinate, std::move(ray)}); + for (const CesiumGeospatial::Cartographic& position : positions) { + queries.emplace_back(position, this->_options.ellipsoid); } - _heightRequests.emplace_back(HeightRequest{std::move(queries), promise}); + this->_heightRequests.emplace_back( + TilesetHeightRequest{std::move(queries), promise}); return promise.getFuture(); } diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 857d43f58..38df95ef4 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -3,6 +3,7 @@ #include "TileUtilities.h" #include "TilesetContentManager.h" +#include #include #include #include @@ -52,8 +53,37 @@ bool boundingVolumeContainsCoordinate( return std::visit(Operation{ray, coordinate}, boundingVolume); } +// The ray for height queries starts at this fraction of the ellipsoid max +// radius above the ellipsoid surface. If a tileset surface is more than this +// distance above the ellipsoid, it may be missed by height queries. +// 0.007 is chosen to accomodate Olympus Mons, the tallest peak on Mars. 0.007 +// is seven-tenths of a percent, or about 44,647 meters for WGS84, well above +// the highest point on Earth. +const double rayOriginHeightFraction = 0.007; + +Ray createRay(const Cartographic& position, const Ellipsoid& ellipsoid) { + Cartographic startPosition( + position.longitude, + position.latitude, + ellipsoid.getMaximumRadius() * rayOriginHeightFraction); + + return Ray( + Ellipsoid::WGS84.cartographicToCartesian(startPosition), + -Ellipsoid::WGS84.geodeticSurfaceNormal(startPosition)); +} + } // namespace +TilesetHeightQuery::TilesetHeightQuery( + const Cartographic& position, + const Ellipsoid& ellipsoid) + : inputCoordinate(position), + ray(createRay(position, ellipsoid)), + intersectResult(), + additiveCandidateTiles(), + candidateTiles(), + previousCandidateTiles() {} + void TilesetHeightQuery::intersectVisibleTile(Tile* pTile) { TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!pRenderContent) @@ -141,3 +171,140 @@ void TilesetHeightQuery::findCandidateTiles( } } } + +/*static*/ void TilesetHeightRequest::processHeightRequests( + TilesetContentManager& contentManager, + const TilesetOptions& options, + std::list& heightRequests, + std::vector& heightQueryLoadQueue) { + if (heightRequests.empty()) + return; + + // Go through all requests, either complete them, or gather the tiles they + // need for completion + std::set tilesNeedingLoading; + for (auto it = heightRequests.begin(); it != heightRequests.end();) { + TilesetHeightRequest& request = *it; + if (!request.tryCompleteHeightRequest( + contentManager, + options, + tilesNeedingLoading)) { + ++it; + } else { + auto deleteIt = it; + ++it; + heightRequests.erase(deleteIt); + } + } + + heightQueryLoadQueue.assign( + tilesNeedingLoading.begin(), + tilesNeedingLoading.end()); +} + +bool TilesetHeightRequest::tryCompleteHeightRequest( + TilesetContentManager& contentManager, + const TilesetOptions& options, + std::set& tilesNeedingLoading) { + bool tileStillNeedsLoading = false; + std::vector warnings; + for (TilesetHeightQuery& query : this->queries) { + if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) { + // Find the initial set of tiles whose bounding volume is intersected by + // the query ray. + query.findCandidateTiles(contentManager.getRootTile(), warnings); + } else { + // Refine the current set of candidate tiles, in case further tiles from + // implicit tiling, external tilesets, etc. having been loaded since last + // frame. + std::swap(query.candidateTiles, query.previousCandidateTiles); + + query.candidateTiles.clear(); + + for (Tile* pCandidate : query.previousCandidateTiles) { + TileLoadState loadState = pCandidate->getState(); + if (!pCandidate->getChildren().empty() && + loadState >= TileLoadState::ContentLoaded) { + query.findCandidateTiles(pCandidate, warnings); + } else { + query.candidateTiles.emplace_back(pCandidate); + } + } + } + + auto checkTile = [&contentManager, + &options, + &tilesNeedingLoading, + &tileStillNeedsLoading](Tile* pTile) { + contentManager.createLatentChildrenIfNecessary(*pTile, options); + + TileLoadState state = pTile->getState(); + if (state == TileLoadState::Unloading) { + // This tile is in the process of unloading, which must complete + // before we can load it again. + contentManager.unloadTileContent(*pTile); + tileStillNeedsLoading = true; + } else if ( + state == TileLoadState::Unloaded || + state == TileLoadState::FailedTemporarily) { + tilesNeedingLoading.insert(pTile); + tileStillNeedsLoading = true; + } + }; + + // If any candidates need loading, add to return set + for (Tile* pTile : query.additiveCandidateTiles) { + checkTile(pTile); + } + for (Tile* pTile : query.candidateTiles) { + checkTile(pTile); + } + } + + // Bail if we're waiting on tiles to load + if (tileStillNeedsLoading) + return false; + + // Do the intersect tests + for (TilesetHeightQuery& query : this->queries) { + for (Tile* pTile : query.additiveCandidateTiles) { + query.intersectVisibleTile(pTile); + } + for (Tile* pTile : query.candidateTiles) { + query.intersectVisibleTile(pTile); + } + } + + // All rays are done, create results + SampleHeightResult results; + + // Start with any warnings from tile traversal + results.warnings = std::move(warnings); + + results.positions.resize(this->queries.size(), Cartographic(0.0, 0.0, 0.0)); + results.heightSampled.resize(this->queries.size()); + + // Populate results with completed queries + for (size_t i = 0; i < this->queries.size(); ++i) { + const TilesetHeightQuery& query = this->queries[i]; + + bool heightSampled = query.intersectResult.hit.has_value(); + results.heightSampled[i] = heightSampled; + results.positions[i] = query.inputCoordinate; + + if (heightSampled) { + results.positions[i].height = + options.ellipsoid.getMaximumRadius() * rayOriginHeightFraction - + glm::sqrt(query.intersectResult.hit->rayToWorldPointDistanceSq); + } + + // Add query warnings into the height result + results.warnings.insert( + results.warnings.end(), + query.intersectResult.warnings.begin(), + query.intersectResult.warnings.end()); + } + + this->promise.resolve(std::move(results)); + return true; +} diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index f3962073d..af4688b64 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -1,38 +1,65 @@ #pragma once +#include +#include #include #include #include +#include +#include #include #include namespace Cesium3DTilesSelection { class Tile; +class TilesetContentManager; +struct TilesetOptions; +struct SampleHeightResult; class TilesetHeightQuery { public: + TilesetHeightQuery( + const CesiumGeospatial::Cartographic& position, + const CesiumGeospatial::Ellipsoid& ellipsoid); + CesiumGeospatial::Cartographic inputCoordinate; CesiumGeometry::Ray ray; - CesiumGltfContent::GltfUtilities::IntersectResult intersectResult = {}; + CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; // The query ray might intersect these non-leaf tiles that are still relevant // because of additive refinement. - std::vector additiveCandidateTiles = {}; + std::vector additiveCandidateTiles; // The current set of leaf tiles whose bounding volume the query ray passes // through. - std::vector candidateTiles = {}; + std::vector candidateTiles; // The previous set of leaf files. Swapping `candidateTiles` and // `previousCandidateTiles` each frame allows us to avoid a heap allocation // for a new vector each frame. - std::vector previousCandidateTiles = {}; + std::vector previousCandidateTiles; void intersectVisibleTile(Tile* pTile); void findCandidateTiles(Tile* pTile, std::vector& warnings); }; +struct TilesetHeightRequest { + static void processHeightRequests( + TilesetContentManager& contentManager, + const TilesetOptions& options, + std::list& heightRequests, + std::vector& heightQueryLoadQueue); + + std::vector queries; + CesiumAsync::Promise promise; + + bool tryCompleteHeightRequest( + TilesetContentManager& contentManager, + const TilesetOptions& options, + std::set& tilesNeedingLoading); +}; + } // namespace Cesium3DTilesSelection From 51a532213e6066efafba7758d493dadb86ff752a Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 16 Sep 2024 09:19:16 +1000 Subject: [PATCH 48/65] Cleanup and doc. --- .../src/TilesetHeightQuery.cpp | 48 ++++---- .../src/TilesetHeightQuery.h | 109 +++++++++++++++--- 2 files changed, 117 insertions(+), 40 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 38df95ef4..cd2744492 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -77,14 +77,16 @@ Ray createRay(const Cartographic& position, const Ellipsoid& ellipsoid) { TilesetHeightQuery::TilesetHeightQuery( const Cartographic& position, const Ellipsoid& ellipsoid) - : inputCoordinate(position), + : inputPosition(position), ray(createRay(position, ellipsoid)), - intersectResult(), + intersection(), additiveCandidateTiles(), candidateTiles(), previousCandidateTiles() {} -void TilesetHeightQuery::intersectVisibleTile(Tile* pTile) { +void TilesetHeightQuery::intersectVisibleTile( + Tile* pTile, + std::vector& outWarnings) { TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!pRenderContent) return; @@ -97,20 +99,20 @@ void TilesetHeightQuery::intersectVisibleTile(Tile* pTile) { pTile->getTransform()); if (!gltfIntersectResult.warnings.empty()) { - this->intersectResult.warnings.insert( - this->intersectResult.warnings.end(), - gltfIntersectResult.warnings.begin(), - gltfIntersectResult.warnings.end()); + outWarnings.insert( + outWarnings.end(), + std::make_move_iterator(gltfIntersectResult.warnings.begin()), + std::make_move_iterator(gltfIntersectResult.warnings.end())); } // Set ray info to this hit if closer, or the first hit - if (!this->intersectResult.hit.has_value()) { - this->intersectResult.hit = std::move(gltfIntersectResult.hit); + if (!this->intersection.has_value()) { + this->intersection = std::move(gltfIntersectResult.hit); } else { - double prevDistSq = this->intersectResult.hit->rayToWorldPointDistanceSq; - double thisDistSq = intersectResult.hit->rayToWorldPointDistanceSq; + double prevDistSq = this->intersection->rayToWorldPointDistanceSq; + double thisDistSq = intersection->rayToWorldPointDistanceSq; if (thisDistSq < prevDistSq) - this->intersectResult.hit = std::move(gltfIntersectResult.hit); + this->intersection = std::move(gltfIntersectResult.hit); } } @@ -135,7 +137,7 @@ void TilesetHeightQuery::findCandidateTiles( if (boundingVolumeContainsCoordinate( *contentBoundingVolume, this->ray, - this->inputCoordinate)) + this->inputPosition)) this->candidateTiles.push_back(pTile); } else { this->candidateTiles.push_back(pTile); @@ -150,7 +152,7 @@ void TilesetHeightQuery::findCandidateTiles( if (boundingVolumeContainsCoordinate( *contentBoundingVolume, this->ray, - this->inputCoordinate)) + this->inputPosition)) this->additiveCandidateTiles.push_back(pTile); } else { this->additiveCandidateTiles.push_back(pTile); @@ -163,7 +165,7 @@ void TilesetHeightQuery::findCandidateTiles( if (!boundingVolumeContainsCoordinate( child.getBoundingVolume(), this->ray, - this->inputCoordinate)) + this->inputPosition)) continue; // Child is a candidate, traverse it and its children @@ -268,10 +270,10 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( // Do the intersect tests for (TilesetHeightQuery& query : this->queries) { for (Tile* pTile : query.additiveCandidateTiles) { - query.intersectVisibleTile(pTile); + query.intersectVisibleTile(pTile, warnings); } for (Tile* pTile : query.candidateTiles) { - query.intersectVisibleTile(pTile); + query.intersectVisibleTile(pTile, warnings); } } @@ -288,21 +290,15 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( for (size_t i = 0; i < this->queries.size(); ++i) { const TilesetHeightQuery& query = this->queries[i]; - bool heightSampled = query.intersectResult.hit.has_value(); + bool heightSampled = query.intersection.has_value(); results.heightSampled[i] = heightSampled; - results.positions[i] = query.inputCoordinate; + results.positions[i] = query.inputPosition; if (heightSampled) { results.positions[i].height = options.ellipsoid.getMaximumRadius() * rayOriginHeightFraction - - glm::sqrt(query.intersectResult.hit->rayToWorldPointDistanceSq); + glm::sqrt(query.intersection->rayToWorldPointDistanceSq); } - - // Add query warnings into the height result - results.warnings.insert( - results.warnings.end(), - query.intersectResult.warnings.begin(), - query.intersectResult.warnings.end()); } this->promise.resolve(std::move(results)); diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index af4688b64..5504daa7d 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -20,42 +20,123 @@ struct SampleHeightResult; class TilesetHeightQuery { public: + /** + * @brief Initializes a new instance. + * + * @param position The position at which to query a height. The existing + * height is ignored. + * @param ellipsoid The ellipsoid on which the position is defined. + */ TilesetHeightQuery( const CesiumGeospatial::Cartographic& position, const CesiumGeospatial::Ellipsoid& ellipsoid); - CesiumGeospatial::Cartographic inputCoordinate; + /** + * @brief The original input position for which the height is to be queried. + */ + CesiumGeospatial::Cartographic inputPosition; + + /** + * @brief A ray created from the {@link TilesetHeightQuery::inputPosition}. + * + */ CesiumGeometry::Ray ray; - CesiumGltfContent::GltfUtilities::IntersectResult intersectResult; - // The query ray might intersect these non-leaf tiles that are still relevant - // because of additive refinement. + /** + * @brief The current intersection of the ray with the tileset. If there are + * multiple intersections, this will be the one closest to the origin of the + * ray. + */ + std::optional intersection; + + /** + * @brief The query ray intersect the bounding volume of these non-leaf tiles + * that are still relevant because of additive refinement. + */ std::vector additiveCandidateTiles; - // The current set of leaf tiles whose bounding volume the query ray passes - // through. + /** + * @brief The current set of leaf tiles whose bounding volume the query ray + * passes through. + */ std::vector candidateTiles; - // The previous set of leaf files. Swapping `candidateTiles` and - // `previousCandidateTiles` each frame allows us to avoid a heap allocation - // for a new vector each frame. + /** + * @brief The previous set of leaf files. Swapping `candidateTiles` and + * `previousCandidateTiles` each frame allows us to avoid a heap allocation + * for a new vector each frame. + */ std::vector previousCandidateTiles; - void intersectVisibleTile(Tile* pTile); + /** + * @brief Find the intersection of the ray with the given tile. If there is + * one, and it is closer to the origin of the ray than the previous + * best-known intersection, then {@link TilesetHeightQuery::intersection} + * will be updated. + * + * @param pTile The tile to test for intersection with the ray. + * @param outWarnings On return, any warnings that occurred while attempting + * to intersect the ray with the given tile. + */ + void intersectVisibleTile(Tile* pTile, std::vector& outWarnings); - void findCandidateTiles(Tile* pTile, std::vector& warnings); + /** + * @brief Find the candidate tiles for query by traversing the tile tree + * starting with a given tile. + * + * Any tile whose bounding volume intersects the ray will be added to the + * {@link TilesetHeightQuery::candidateTiles} vector. Non-leaf tiles that are + * additively-refined will be added to + * {@link TilesetHeightQuery::additiveCandidateTiles}. + * + * @param pTile The tile at which to start traversal. + * @param outWarnings On return, any warnings that occurred during candidate + * search. + */ + void findCandidateTiles(Tile* pTile, std::vector& outWarnings); }; +/** + * @brief A request for a batch of height queries. When all of the queries are + * complete, they will be delivered to the requestor via resolving a promise. + */ struct TilesetHeightRequest { + /** + * @brief The individual height queries in this request. + */ + std::vector queries; + + /** + * @brief The promise to be resolved when all height queries are complete. + */ + CesiumAsync::Promise promise; + + /** + * @brief Process a given list of height requests. This is called by the {@link Tileset} + * every call to {@link Tileset::updateView}. + * + * @param contentManager The content manager. + * @param options Options associated with the tileset. + * @param heightRequests The list of all height requests. Completed requests + * will be removed from this list. + * @param heightQueryLoadQueue Tiles that still need to be loaded before all + * height requests can complete are added to this vector. + */ static void processHeightRequests( TilesetContentManager& contentManager, const TilesetOptions& options, std::list& heightRequests, std::vector& heightQueryLoadQueue); - std::vector queries; - CesiumAsync::Promise promise; - + /** + * @brief Tries to complete this height request. Returns false if further data + * still needs to be loaded and so the request cannot be completed yet. + * + * @param contentManager The content manager. + * @param options Options associated with the tileset. + * @param tilesNeedingLoading Tiles that needs to be loaded before this height + * request can complete. + */ bool tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, From ca62417638d16ef1991e73af307534f6621d86ef Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 16 Sep 2024 11:04:05 +1000 Subject: [PATCH 49/65] Keep query tiles loaded until we're done with them. --- Cesium3DTilesSelection/src/Tileset.cpp | 1 + .../src/TilesetHeightQuery.cpp | 19 ++++++++++++++++--- .../src/TilesetHeightQuery.h | 18 ++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index b5b1b4777..0370c15a0 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -377,6 +377,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { TilesetHeightRequest::processHeightRequests( *this->_pTilesetContentManager, this->_options, + this->_loadedTiles, this->_heightRequests, this->_heightQueryLoadQueue); diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index cd2744492..4ba88226a 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -118,7 +118,10 @@ void TilesetHeightQuery::intersectVisibleTile( void TilesetHeightQuery::findCandidateTiles( Tile* pTile, + Tile::LoadedLinkedList& loadedTiles, std::vector& warnings) { + // Make sure this tile is not unloaded until we're done with it. + loadedTiles.insertAtTail(*pTile); // If tile failed to load, this means we can't complete the intersection if (pTile->getState() == TileLoadState::Failed) { @@ -169,7 +172,7 @@ void TilesetHeightQuery::findCandidateTiles( continue; // Child is a candidate, traverse it and its children - findCandidateTiles(&child, warnings); + findCandidateTiles(&child, loadedTiles, warnings); } } } @@ -177,6 +180,7 @@ void TilesetHeightQuery::findCandidateTiles( /*static*/ void TilesetHeightRequest::processHeightRequests( TilesetContentManager& contentManager, const TilesetOptions& options, + Tile::LoadedLinkedList& loadedTiles, std::list& heightRequests, std::vector& heightQueryLoadQueue) { if (heightRequests.empty()) @@ -190,6 +194,7 @@ void TilesetHeightQuery::findCandidateTiles( if (!request.tryCompleteHeightRequest( contentManager, options, + loadedTiles, tilesNeedingLoading)) { ++it; } else { @@ -207,6 +212,7 @@ void TilesetHeightQuery::findCandidateTiles( bool TilesetHeightRequest::tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, + Tile::LoadedLinkedList& loadedTiles, std::set& tilesNeedingLoading) { bool tileStillNeedsLoading = false; std::vector warnings; @@ -214,7 +220,10 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) { // Find the initial set of tiles whose bounding volume is intersected by // the query ray. - query.findCandidateTiles(contentManager.getRootTile(), warnings); + query.findCandidateTiles( + contentManager.getRootTile(), + loadedTiles, + warnings); } else { // Refine the current set of candidate tiles, in case further tiles from // implicit tiling, external tilesets, etc. having been loaded since last @@ -227,8 +236,12 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( TileLoadState loadState = pCandidate->getState(); if (!pCandidate->getChildren().empty() && loadState >= TileLoadState::ContentLoaded) { - query.findCandidateTiles(pCandidate, warnings); + query.findCandidateTiles(pCandidate, loadedTiles, warnings); } else { + // Make sure this tile stays loaded. + loadedTiles.insertAtTail(*pCandidate); + + // Check again next frame to see if this tile has children. query.candidateTiles.emplace_back(pCandidate); } } diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index 5504daa7d..2dbff1e1d 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -13,7 +14,6 @@ namespace Cesium3DTilesSelection { -class Tile; class TilesetContentManager; struct TilesetOptions; struct SampleHeightResult; @@ -90,10 +90,16 @@ class TilesetHeightQuery { * {@link TilesetHeightQuery::additiveCandidateTiles}. * * @param pTile The tile at which to start traversal. + * @param loadedTiles The linked list of loaded tiles, used to ensure that + * tiles loaded for height queries stay loaded long enough to complete the + * query and no longer. * @param outWarnings On return, any warnings that occurred during candidate * search. */ - void findCandidateTiles(Tile* pTile, std::vector& outWarnings); + void findCandidateTiles( + Tile* pTile, + Tile::LoadedLinkedList& loadedTiles, + std::vector& outWarnings); }; /** @@ -117,6 +123,9 @@ struct TilesetHeightRequest { * * @param contentManager The content manager. * @param options Options associated with the tileset. + * @param loadedTiles The linked list of loaded tiles, used to ensure that + * tiles loaded for height queries stay loaded long enough to complete the + * query and no longer. * @param heightRequests The list of all height requests. Completed requests * will be removed from this list. * @param heightQueryLoadQueue Tiles that still need to be loaded before all @@ -125,6 +134,7 @@ struct TilesetHeightRequest { static void processHeightRequests( TilesetContentManager& contentManager, const TilesetOptions& options, + Tile::LoadedLinkedList& loadedTiles, std::list& heightRequests, std::vector& heightQueryLoadQueue); @@ -134,12 +144,16 @@ struct TilesetHeightRequest { * * @param contentManager The content manager. * @param options Options associated with the tileset. + * @param loadedTiles The linked list of loaded tiles, used to ensure that + * tiles loaded for height queries stay loaded long enough to complete the + * query and no longer. * @param tilesNeedingLoading Tiles that needs to be loaded before this height * request can complete. */ bool tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, + Tile::LoadedLinkedList& loadedTiles, std::set& tilesNeedingLoading); }; From 57536699334ed3d57c11866fdb2f43e982d92e05 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 16 Sep 2024 11:34:58 +1000 Subject: [PATCH 50/65] Better marking of visited tiles. --- .../src/TilesetHeightQuery.cpp | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 4ba88226a..e7ab3475a 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -116,12 +116,27 @@ void TilesetHeightQuery::intersectVisibleTile( } } +namespace { + +void markTileVisited(Tile::LoadedLinkedList& loadedTiles, Tile* pTile) { + // Don't move the root tile to the tail, because this tile is used to mark the + // beginning of the tiles used in the current frame. If we move it, some tiles + // may be deemed to have most recently been used last frame, and so will be + // unloaded. + if (pTile == nullptr || pTile->getParent() == nullptr) + return; + + loadedTiles.insertAtTail(*pTile); +} + +} // namespace + void TilesetHeightQuery::findCandidateTiles( Tile* pTile, Tile::LoadedLinkedList& loadedTiles, std::vector& warnings) { // Make sure this tile is not unloaded until we're done with it. - loadedTiles.insertAtTail(*pTile); + markTileVisited(loadedTiles, pTile); // If tile failed to load, this means we can't complete the intersection if (pTile->getState() == TileLoadState::Failed) { @@ -239,7 +254,7 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( query.findCandidateTiles(pCandidate, loadedTiles, warnings); } else { // Make sure this tile stays loaded. - loadedTiles.insertAtTail(*pCandidate); + markTileVisited(loadedTiles, pCandidate); // Check again next frame to see if this tile has children. query.candidateTiles.emplace_back(pCandidate); @@ -269,6 +284,11 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( // If any candidates need loading, add to return set for (Tile* pTile : query.additiveCandidateTiles) { + // Additive tiles are only enumerated once in findCandidateTiles, so we + // need to continue every frame to make sure they're not unloaded before + // we're done with them. + markTileVisited(loadedTiles, pTile); + checkTile(pTile); } for (Tile* pTile : query.candidateTiles) { From 3a37ba196b918e85782f94fdb4384806851adfc4 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 16 Sep 2024 13:39:59 +1000 Subject: [PATCH 51/65] Fix "done loading" criteria. --- Cesium3DTilesSelection/src/TilesetHeightQuery.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index e7ab3475a..ce3d3e6be 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -274,9 +274,7 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( // before we can load it again. contentManager.unloadTileContent(*pTile); tileStillNeedsLoading = true; - } else if ( - state == TileLoadState::Unloaded || - state == TileLoadState::FailedTemporarily) { + } else if (state <= TileLoadState::ContentLoading) { tilesNeedingLoading.insert(pTile); tileStillNeedsLoading = true; } From bc0770a146e3f2a52e10c17ac19c9a7bff454327 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 16 Sep 2024 20:48:09 +1000 Subject: [PATCH 52/65] Update CHANGES, small cleanup. --- CHANGES.md | 17 +++++++++++++++++ .../include/Cesium3DTilesSelection/Tileset.h | 5 +++++ Cesium3DTilesSelection/src/Tileset.cpp | 5 ----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 023b7e438..faf5f486d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,22 @@ # Change Log +### ? - ? + +##### Breaking Changes :mega: + +- Renamed `shouldContentContinueUpdating` to `getMightHaveLatentChildren` and `setContentShouldContinueUpdating` to `setMightHaveLatentChildren` on the `Tile` class. + +##### Additions :tada: + +- Added `sampleHeightMostDetailed` method to `Tileset`. +- `AxisAlignedBox` now has `constexpr` constructors. + +##### Fixes :wrench: + +- Fixed a bug that prevented use of `Tileset` with a nullptr `IPrepareRendererResources`. +- Fixed a bug in `IntersectionTests::rayOBBParametric` that could cause incorrect results for some oriented bounding boxes. +- `GltfUtilities::intersectRayGltfModel` now reports a warning when given a model it can't compute the intersection with because it uses required extensions that are not supported. + ### v0.39.0 - 2024-09-02 ##### Breaking Changes :mega: diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 472d629ec..9cfe5b9dd 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -283,6 +283,11 @@ class CESIUM3DTILESSELECTION_API Tileset final { * expressed in meters above the ellipsoid (usually WGS84), which should not * be confused with a height above mean sea level. * + * Note that {@link Tileset::updateView} must be called periodically, or else + * the returned `Future` will never resolve. If you are not using this tileset + * for visualization, you can call `updateView` with an empty list of + * frustums. + * * @param positions The positions for which to sample heights. * @return A future that asynchronously resolves to the result of the height * query. diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 0370c15a0..1c1ce18b8 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -33,11 +33,6 @@ using namespace CesiumGeospatial; using namespace CesiumRasterOverlays; using namespace CesiumUtility; -// 10,000 meters above ellisoid -// Highest point on ellipsoid is Mount Everest at 8,848 m -// Nothing intersectable should be above this -#define RAY_ORIGIN_HEIGHT 10000.0 - namespace Cesium3DTilesSelection { Tileset::Tileset( From 4850751a25c3cc95e8b9458a904b043d98bbd284 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 24 Sep 2024 07:46:39 +1000 Subject: [PATCH 53/65] Reject height requests when tileset fails to load. --- Cesium3DTilesSelection/src/Tileset.cpp | 9 +++++++++ Cesium3DTilesSelection/src/TilesetHeightQuery.cpp | 10 ++++++++++ Cesium3DTilesSelection/src/TilesetHeightQuery.h | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 1c1ce18b8..118c61158 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -336,6 +336,15 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { Tile* pRootTile = this->getRootTile(); if (!pRootTile) { + // If the root tile is already supposed to be ready, but it's not, it means + // the tileset couldn't load. Fail any outstanding height requests. + if (!this->_heightRequests.empty() && this->_pTilesetContentManager && + this->_pTilesetContentManager->getRootTileAvailableEvent().isReady()) { + TilesetHeightRequest::failHeightRequests( + this->_heightRequests, + "Height requests could not complete because the tileset failed to " + "load."); + } return result; } diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index ce3d3e6be..3fe489750 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -224,6 +224,16 @@ void TilesetHeightQuery::findCandidateTiles( tilesNeedingLoading.end()); } +void Cesium3DTilesSelection::TilesetHeightRequest::failHeightRequests( + std::list& heightRequests, + const std::string& message) { + for (TilesetHeightRequest& request : heightRequests) { + request.promise.reject(std::runtime_error(message)); + } + + heightRequests.clear(); +} + bool TilesetHeightRequest::tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index 2dbff1e1d..ae61c577e 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -138,6 +138,18 @@ struct TilesetHeightRequest { std::list& heightRequests, std::vector& heightQueryLoadQueue); + /** + * @brief Cancels all outstanding height requests and rejects the associated + * futures. This is useful when it is known that the height requests will + * never complete, such as when the tileset fails to load. + * + * @param heightRequests The height requests to cancel. + * @param message The message explaining what went wrong. + */ + static void failHeightRequests( + std::list& heightRequests, + const std::string& message); + /** * @brief Tries to complete this height request. Returns false if further data * still needs to be loaded and so the request cannot be completed yet. From 7645a91455e68110b2a9a45de063648b7d0a65da Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Wed, 25 Sep 2024 17:07:08 -0400 Subject: [PATCH 54/65] Remove unnecessary linebreak --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 41d2a0ef5..1b8dc924f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,5 @@ # Change Log - ### Not Released Yet ##### Breaking Changes :mega: From 23308ba50251aec0cdaeeb73454f2b91abd6db94 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Wed, 25 Sep 2024 18:24:52 -0400 Subject: [PATCH 55/65] Doc tweaks --- .../include/Cesium3DTilesSelection/Tile.h | 10 ++--- .../include/Cesium3DTilesSelection/Tileset.h | 2 +- .../src/TilesetHeightQuery.h | 44 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h index c878bf1c3..3f22a7a58 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h @@ -507,11 +507,11 @@ class CESIUM3DTILESSELECTION_API Tile final { * by the {@link TilesetContentLoader}. * * When true, this tile might have children that can be created by the - * TilesetContentLoader but that aren't reflected in the `_children` property - * yet. For example, in implicit tiling, we save memory by only creating - * explicit Tile instances from implicit availability as those instances are - * needed. When this flag is true, that creation of explicit instances hasn't - * been done yet for this tile. + * TilesetContentLoader but aren't yet reflected in the `_children` property. + * For example, in implicit tiling, we save memory by only creating explicit + * Tile instances from implicit availability as those instances are needed. + * When this flag is true, the creation of those explicit instances hasn't + * happened yet for this tile. * * If this flag is false, the children have already been created, if they * exist. The tile may still have no children because it is a leaf node. diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 9cfe5b9dd..8cce01626 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -276,7 +276,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { /** * @brief Initiates an asynchronous query for the height of this tileset at a - * list of positions, expressed as longitude and latitude. The most detailed + * list of positions, expressed in longitude and latitude. The most detailed * available tiles are used to determine each height. * * The height of the input positions is ignored. On output, the height is diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index ae61c577e..f140be2e3 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -50,19 +50,19 @@ class TilesetHeightQuery { std::optional intersection; /** - * @brief The query ray intersect the bounding volume of these non-leaf tiles - * that are still relevant because of additive refinement. + * @brief Non-leaf tiles with additive refinement whose bounding volumes are + * intersected by the query ray. */ std::vector additiveCandidateTiles; /** - * @brief The current set of leaf tiles whose bounding volume the query ray - * passes through. + * @brief The current set of leaf tiles whose bounding volumes are intersected + * by the query ray. */ std::vector candidateTiles; /** - * @brief The previous set of leaf files. Swapping `candidateTiles` and + * @brief The previous set of leaf tiles. Swapping `candidateTiles` and * `previousCandidateTiles` each frame allows us to avoid a heap allocation * for a new vector each frame. */ @@ -70,19 +70,19 @@ class TilesetHeightQuery { /** * @brief Find the intersection of the ray with the given tile. If there is - * one, and it is closer to the origin of the ray than the previous - * best-known intersection, then {@link TilesetHeightQuery::intersection} - * will be updated. + * one, and if it's closer to the ray's origin than the previous best-known + * intersection, then {@link TilesetHeightQuery::intersection} will be + * updated. * * @param pTile The tile to test for intersection with the ray. - * @param outWarnings On return, any warnings that occurred while attempting - * to intersect the ray with the given tile. + * @param outWarnings On return, reports any warnings that occurred while + * attempting to intersect the ray with the tile. */ void intersectVisibleTile(Tile* pTile, std::vector& outWarnings); /** - * @brief Find the candidate tiles for query by traversing the tile tree - * starting with a given tile. + * @brief Find candidate tiles for the height query by traversing the tile + * tree, starting with the given tile. * * Any tile whose bounding volume intersects the ray will be added to the * {@link TilesetHeightQuery::candidateTiles} vector. Non-leaf tiles that are @@ -91,10 +91,10 @@ class TilesetHeightQuery { * * @param pTile The tile at which to start traversal. * @param loadedTiles The linked list of loaded tiles, used to ensure that - * tiles loaded for height queries stay loaded long enough to complete the - * query and no longer. - * @param outWarnings On return, any warnings that occurred during candidate - * search. + * tiles loaded for height queries stay loaded just long enough to complete + * the query, and no longer. + * @param outWarnings On return, reports any warnings that occurred during + * candidate search. */ void findCandidateTiles( Tile* pTile, @@ -119,13 +119,13 @@ struct TilesetHeightRequest { /** * @brief Process a given list of height requests. This is called by the {@link Tileset} - * every call to {@link Tileset::updateView}. + * in every call to {@link Tileset::updateView}. * * @param contentManager The content manager. * @param options Options associated with the tileset. * @param loadedTiles The linked list of loaded tiles, used to ensure that - * tiles loaded for height queries stay loaded long enough to complete the - * query and no longer. + * tiles loaded for height queries stay loaded just long enough to complete + * the query, and no longer. * @param heightRequests The list of all height requests. Completed requests * will be removed from this list. * @param heightQueryLoadQueue Tiles that still need to be loaded before all @@ -152,13 +152,13 @@ struct TilesetHeightRequest { /** * @brief Tries to complete this height request. Returns false if further data - * still needs to be loaded and so the request cannot be completed yet. + * still needs to be loaded and thus the request cannot yet complete. * * @param contentManager The content manager. * @param options Options associated with the tileset. * @param loadedTiles The linked list of loaded tiles, used to ensure that - * tiles loaded for height queries stay loaded long enough to complete the - * query and no longer. + * tiles loaded for height queries stay loaded just long enough to complete + * the query, and no longer. * @param tilesNeedingLoading Tiles that needs to be loaded before this height * request can complete. */ From 74acd1a2abd48c32b44ee6247325262b93263793 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Thu, 26 Sep 2024 10:12:28 -0400 Subject: [PATCH 56/65] Additional tweak --- .../include/Cesium3DTilesSelection/Tileset.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 8cce01626..efa32e830 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -276,10 +276,10 @@ class CESIUM3DTILESSELECTION_API Tileset final { /** * @brief Initiates an asynchronous query for the height of this tileset at a - * list of positions, expressed in longitude and latitude. The most detailed + * list of cartographic positions (longitude and latitude). The most detailed * available tiles are used to determine each height. * - * The height of the input positions is ignored. On output, the height is + * The height of the input positions is ignored. The output height is * expressed in meters above the ellipsoid (usually WGS84), which should not * be confused with a height above mean sea level. * From 85226ee4be42e18c5d2e6072319f348caa2f6b63 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:13:22 +1000 Subject: [PATCH 57/65] Update comment. Co-authored-by: Janine Liu <32226860+j9liu@users.noreply.github.com> --- Cesium3DTilesSelection/src/Tileset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 118c61158..258575ee8 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -336,7 +336,7 @@ Tileset::updateView(const std::vector& frustums, float deltaTime) { Tile* pRootTile = this->getRootTile(); if (!pRootTile) { - // If the root tile is already supposed to be ready, but it's not, it means + // If the root tile is marked as ready, but doesn't actually exist, then // the tileset couldn't load. Fail any outstanding height requests. if (!this->_heightRequests.empty() && this->_pTilesetContentManager && this->_pTilesetContentManager->getRootTileAvailableEvent().isReady()) { From e401f5996c7209dc754338ffd932a36660ee18bc Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:14:16 +1000 Subject: [PATCH 58/65] Update comment. Co-authored-by: Janine Liu <32226860+j9liu@users.noreply.github.com> --- Cesium3DTilesSelection/src/TilesetContentManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 39bd89a97..e52b5a163 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -1386,11 +1386,11 @@ void TilesetContentManager::updateDoneState( Tile& tile, const TilesetOptions& tilesetOptions) { if (tile.getMightHaveLatentChildren()) { - // This tile might have latent children, so we don't know yet whether it has - // children or not. We need to know that before we can continue this - // function, which will decide whether or not to create upsampled children - // for this tile. It only makes sense to create upsampled children for a - // tile that definitely doesn't have real children. + // This tile might have latent children, but we don't know yet whether it + // *actually* has children. We need to know that before we can continue + // this function, which will decide whether or not to create upsampled + // children for this tile. It only makes sense to create upsampled children + // for a tile that we know for sure doesn't have real children. return; } From 352e609df9d0b148ca7d8ab5fc2a5a61122f849e Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:50:46 +1000 Subject: [PATCH 59/65] Update doc. Co-authored-by: Janine Liu <32226860+j9liu@users.noreply.github.com> --- .../Cesium3DTilesSelection/SampleHeightResult.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h index 9b79e4662..549158aa1 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h @@ -24,12 +24,14 @@ struct SampleHeightResult { std::vector positions; /** - * @brief Specifies whether the height for the position at this index was - * sampled successfully. If true, {@link SampleHeightResult::positions} has - * a valid height sampled from the tileset at this index. If false, the height - * could not be sampled for the position at this index, and so the height in - * {@link SampleHeightResult::positions} is unchanged from the original input - * height. + * @brief The success of each sample. + * + * Each entry specifies whether the height for the position at the + * corresponding index was successfully sampled. If true, then + * {@link SampleHeightResult::positions} has a valid height sampled from the + * tileset at this index. If false, the height could not be sampled, leaving the + * height in {@link SampleHeightResult::positions} unchanged from the original + * input height. */ std::vector heightSampled; From e0323e9b1a565bda7d26a5a03151dd57f179e15d Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:49:26 +1000 Subject: [PATCH 60/65] Only create latent children if there are not any children yet. And add documentation for createLatentChildrenIfNecessary. --- .../src/TilesetContentManager.cpp | 2 +- .../src/TilesetContentManager.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index e52b5a163..2ee6b657a 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -1076,7 +1076,7 @@ void TilesetContentManager::updateTileContent( void TilesetContentManager::createLatentChildrenIfNecessary( Tile& tile, const TilesetOptions& tilesetOptions) { - if (tile.getMightHaveLatentChildren()) { + if (tile.getChildren().empty() && tile.getMightHaveLatentChildren()) { TileChildrenResult childrenResult = this->_pLoader->createTileChildren(tile, tilesetOptions.ellipsoid); if (childrenResult.state == TileLoadResultState::Success) { diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index 553fbd3e7..43ecb94f7 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -64,6 +64,23 @@ class TilesetContentManager void updateTileContent(Tile& tile, const TilesetOptions& tilesetOptions); + /** + * @brief Creates explicit Tile instances for a tile's latent children, if + * it is necessary and possible to do so. + * + * Latent children are child tiles that can be created by + * {@link TilesetContentLoader::createChildTiles} but that are not yet + * reflected in {@link Tile::getChildren}. For example, in implicit tiling, + * we save memory by only creating explicit Tile instances from implicit + * availability as those instances are needed. Calling this method will create + * the explicit tile instances for the given tile's children. + * + * This method does nothing if the given tile already has children, or if + * {@link Tile::getMightHaveLatentChildren} returns false. + * + * @param tile The tile for which to create latent children. + * @param tilesetOptions The tileset's options. + */ void createLatentChildrenIfNecessary( Tile& tile, const TilesetOptions& tilesetOptions); From 63d6e4dd6dcfa0142929850d16f5a0fbc31d1150 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:54:46 +1000 Subject: [PATCH 61/65] heightSampled -> sampleSuccess. --- .../SampleHeightResult.h | 10 +++++----- .../src/TilesetHeightQuery.cpp | 8 ++++---- .../test/TestTilesetHeightQueries.cpp | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h index 549158aa1..a242772c1 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h @@ -19,7 +19,7 @@ struct SampleHeightResult { * original input positions. Each height will either be the height sampled * from the tileset at that position, or the original input height if the * height could not be sampled. To determine which, look at the value of - * {@link SampleHeightResult::heightSampled} at the same index. + * {@link SampleHeightResult::sampleSuccess} at the same index. */ std::vector positions; @@ -29,11 +29,11 @@ struct SampleHeightResult { * Each entry specifies whether the height for the position at the * corresponding index was successfully sampled. If true, then * {@link SampleHeightResult::positions} has a valid height sampled from the - * tileset at this index. If false, the height could not be sampled, leaving the - * height in {@link SampleHeightResult::positions} unchanged from the original - * input height. + * tileset at this index. If false, the height could not be sampled, leaving + * the height in {@link SampleHeightResult::positions} unchanged from the + * original input height. */ - std::vector heightSampled; + std::vector sampleSuccess; /** * @brief Any warnings that occurred while sampling heights. diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 3fe489750..8c715f041 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -325,17 +325,17 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( results.warnings = std::move(warnings); results.positions.resize(this->queries.size(), Cartographic(0.0, 0.0, 0.0)); - results.heightSampled.resize(this->queries.size()); + results.sampleSuccess.resize(this->queries.size()); // Populate results with completed queries for (size_t i = 0; i < this->queries.size(); ++i) { const TilesetHeightQuery& query = this->queries[i]; - bool heightSampled = query.intersection.has_value(); - results.heightSampled[i] = heightSampled; + bool sampleSuccess = query.intersection.has_value(); + results.sampleSuccess[i] = sampleSuccess; results.positions[i] = query.inputPosition; - if (heightSampled) { + if (sampleSuccess) { results.positions[i].height = options.ellipsoid.getMaximumRadius() * rayOriginHeightFraction - glm::sqrt(query.intersection->rayToWorldPointDistanceSq); diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index 84cd7c7de..bf7ca3e9d 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -61,14 +61,14 @@ TEST_CASE("Tileset height queries") { CHECK(results.warnings.empty()); REQUIRE(results.positions.size() == 2); - CHECK(results.heightSampled[0]); + CHECK(results.sampleSuccess[0]); CHECK(Math::equalsEpsilon( results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.heightSampled[1]); + CHECK(results.sampleSuccess[1]); CHECK(Math::equalsEpsilon( results.positions[1].height, 7.837332, @@ -100,9 +100,9 @@ TEST_CASE("Tileset height queries") { CHECK(results.warnings.empty()); REQUIRE(results.positions.size() == 2); - CHECK(!results.heightSampled[0]); + CHECK(!results.sampleSuccess[0]); - CHECK(results.heightSampled[1]); + CHECK(results.sampleSuccess[1]); CHECK(Math::equalsEpsilon( results.positions[1].height, 7.837332, @@ -134,14 +134,14 @@ TEST_CASE("Tileset height queries") { CHECK(results.warnings.empty()); REQUIRE(results.positions.size() == 2); - CHECK(results.heightSampled[0]); + CHECK(results.sampleSuccess[0]); CHECK(Math::equalsEpsilon( results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.heightSampled[1]); + CHECK(results.sampleSuccess[1]); CHECK(Math::equalsEpsilon( results.positions[1].height, 7.837332, @@ -173,14 +173,14 @@ TEST_CASE("Tileset height queries") { CHECK(results.warnings.empty()); REQUIRE(results.positions.size() == 2); - CHECK(results.heightSampled[0]); + CHECK(results.sampleSuccess[0]); CHECK(Math::equalsEpsilon( results.positions[0].height, 78.155809, 0.0, Math::Epsilon4)); - CHECK(results.heightSampled[1]); + CHECK(results.sampleSuccess[1]); CHECK(Math::equalsEpsilon( results.positions[1].height, 7.837332, @@ -207,7 +207,7 @@ TEST_CASE("Tileset height queries") { SampleHeightResult results = future.waitInMainThread(); REQUIRE(results.warnings.size() == 1); REQUIRE(results.positions.size() == 1); - CHECK(!results.heightSampled[0]); + CHECK(!results.sampleSuccess[0]); CHECK( results.warnings[0].find("EXT_mesh_gpu_instancing") != std::string::npos); From b48893af9919d2dcba1b72797e5668eeba23c8b3 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 09:59:14 +1000 Subject: [PATCH 62/65] tilesNeedingLoading -> tileLoadSet --- Cesium3DTilesSelection/src/TilesetHeightQuery.cpp | 14 ++++++-------- Cesium3DTilesSelection/src/TilesetHeightQuery.h | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 8c715f041..0bed6c942 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -203,14 +203,14 @@ void TilesetHeightQuery::findCandidateTiles( // Go through all requests, either complete them, or gather the tiles they // need for completion - std::set tilesNeedingLoading; + std::set tileLoadSet; for (auto it = heightRequests.begin(); it != heightRequests.end();) { TilesetHeightRequest& request = *it; if (!request.tryCompleteHeightRequest( contentManager, options, loadedTiles, - tilesNeedingLoading)) { + tileLoadSet)) { ++it; } else { auto deleteIt = it; @@ -219,9 +219,7 @@ void TilesetHeightQuery::findCandidateTiles( } } - heightQueryLoadQueue.assign( - tilesNeedingLoading.begin(), - tilesNeedingLoading.end()); + heightQueryLoadQueue.assign(tileLoadSet.begin(), tileLoadSet.end()); } void Cesium3DTilesSelection::TilesetHeightRequest::failHeightRequests( @@ -238,7 +236,7 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, Tile::LoadedLinkedList& loadedTiles, - std::set& tilesNeedingLoading) { + std::set& tileLoadSet) { bool tileStillNeedsLoading = false; std::vector warnings; for (TilesetHeightQuery& query : this->queries) { @@ -274,7 +272,7 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( auto checkTile = [&contentManager, &options, - &tilesNeedingLoading, + &tileLoadSet, &tileStillNeedsLoading](Tile* pTile) { contentManager.createLatentChildrenIfNecessary(*pTile, options); @@ -285,7 +283,7 @@ bool TilesetHeightRequest::tryCompleteHeightRequest( contentManager.unloadTileContent(*pTile); tileStillNeedsLoading = true; } else if (state <= TileLoadState::ContentLoading) { - tilesNeedingLoading.insert(pTile); + tileLoadSet.insert(pTile); tileStillNeedsLoading = true; } }; diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index f140be2e3..3794ae5e2 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -159,14 +159,14 @@ struct TilesetHeightRequest { * @param loadedTiles The linked list of loaded tiles, used to ensure that * tiles loaded for height queries stay loaded just long enough to complete * the query, and no longer. - * @param tilesNeedingLoading Tiles that needs to be loaded before this height - * request can complete. + * @param tileLoadSet Tiles that needs to be loaded before this height request + * can complete. */ bool tryCompleteHeightRequest( TilesetContentManager& contentManager, const TilesetOptions& options, Tile::LoadedLinkedList& loadedTiles, - std::set& tilesNeedingLoading); + std::set& tileLoadSet); }; } // namespace Cesium3DTilesSelection From 87a4e686f18201834de66a0741ab5159261e6d60 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 10:02:59 +1000 Subject: [PATCH 63/65] Update doc. --- .../Cesium3DTilesSelection/SampleHeightResult.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h index a242772c1..ee25eacfb 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/SampleHeightResult.h @@ -13,12 +13,12 @@ namespace Cesium3DTilesSelection { */ struct SampleHeightResult { /** - * @brief The positions and sampled heights. + * @brief The positions and their sampled heights. * - * The longitudes and latitudes will match the values at the same index in the - * original input positions. Each height will either be the height sampled - * from the tileset at that position, or the original input height if the - * height could not be sampled. To determine which, look at the value of + * For each resulting position, its longitude and latitude values will match + * values from its input. Its height will either be the height sampled from + * the tileset at that position, or the original input height if the sample + * was unsuccessful. To determine which, look at the value of * {@link SampleHeightResult::sampleSuccess} at the same index. */ std::vector positions; From ad18abfc03c4ad041e02a643d06663a1903ef9c0 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 10:10:02 +1000 Subject: [PATCH 64/65] Add sampleHeightMostDetailed test against a broken tileset. --- .../test/TestTilesetHeightQueries.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index bf7ca3e9d..a5deaa259 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -212,4 +212,17 @@ TEST_CASE("Tileset height queries") { results.warnings[0].find("EXT_mesh_gpu_instancing") != std::string::npos); } + + SECTION("broken tileset") { + Tileset tileset(externals, "http://localhost/notgonnawork"); + + Future future = tileset.sampleHeightMostDetailed( + {Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + REQUIRE_THROWS(future.waitInMainThread()); + } } From fb38ac9f2cf3a4baec39e0b73616391258181e82 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 27 Sep 2024 20:34:54 +1000 Subject: [PATCH 65/65] Fail height requests when Tileset is destroyed. --- Cesium3DTilesSelection/src/Tileset.cpp | 4 ++++ Cesium3DTilesSelection/src/TilesetHeightQuery.h | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 258575ee8..ee9d6f9ca 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -101,6 +101,10 @@ Tileset::Tileset( ionAssetEndpointUrl)} {} Tileset::~Tileset() noexcept { + TilesetHeightRequest::failHeightRequests( + this->_heightRequests, + "Tileset is being destroyed."); + this->_pTilesetContentManager->unloadAll(); if (this->_externals.pTileOcclusionProxyPool) { this->_externals.pTileOcclusionProxyPool->destroyPool(); diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.h b/Cesium3DTilesSelection/src/TilesetHeightQuery.h index 3794ae5e2..fdb2e0317 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.h +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.h @@ -141,7 +141,8 @@ struct TilesetHeightRequest { /** * @brief Cancels all outstanding height requests and rejects the associated * futures. This is useful when it is known that the height requests will - * never complete, such as when the tileset fails to load. + * never complete, such as when the tileset fails to load or when it is being + * destroyed. * * @param heightRequests The height requests to cancel. * @param message The message explaining what went wrong.