diff --git a/CHANGES.md b/CHANGES.md index 4057878f2..300e1463e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Removed `Math::rotation`. Use `glm::rotation` from `` instead. - Removed `Math::perpVector`. Use `glm::perp` from `` instead. - Using Cesium Native in non-cmake projects now requires manually defining `GLM_ENABLE_EXPERIMENTAL`. +- cesium-native no longer uses the `GLM_FORCE_SIZE_T_LENGTH` option with the `glm` library ##### Additions :tada: @@ -14,11 +15,13 @@ - Added `CesiumIonClient::Connection::geocode` method for making geocoding queries against the Cesium ion geocoder API. - Added `UrlTemplateRasterOverlay` for requesting raster tiles from services using a templated URL. - `upsampleGltfForRasterOverlays` is now compatible with meshes using TRIANGLE_STRIP, TRIANGLE_FAN, or non-indexed TRIANGLES primitives. +- Added `requestHeaders` field to `TilesetOptions` to allow per-tileset request headers to be specified. ##### Fixes :wrench: - Fixed a crash in `GltfWriter` that would happen when the `EXT_structural_metadata` `schema` property was null. - Fixed a bug in `SharedAssetDepot` that could cause assertion failures in debug builds, and could rarely cause premature deletion of shared assets even in release builds. +- Fixed a bug that could cause `Tileset::sampleHeightMostDetailed` to return a height that is not the highest one when the sampled tileset contained multiple heights at the given location. ### v0.43.0 - 2025-01-02 diff --git a/Cesium3DTilesContent/src/I3dmToGltfConverter.cpp b/Cesium3DTilesContent/src/I3dmToGltfConverter.cpp index 7dd61228d..ce0d53415 100644 --- a/Cesium3DTilesContent/src/I3dmToGltfConverter.cpp +++ b/Cesium3DTilesContent/src/I3dmToGltfConverter.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -492,7 +493,7 @@ CesiumAsync::Future convertI3dmContent( decodedInstances.positions.begin(), [&parsedContent](auto&& posQuantized) { glm::vec3 position; - for (unsigned j = 0; j < 3; ++j) { + for (glm::length_t j = 0; j < 3; ++j) { position[j] = static_cast( posQuantized[j] / 65535.0 * (*parsedContent.quantizedVolumeScale)[j] + diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetOptions.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetOptions.h index 6e1d6b755..75e061ddb 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetOptions.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetOptions.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -328,6 +329,11 @@ struct CESIUM3DTILESSELECTION_API TilesetOptions { * If no ellipsoid is set, Ellipsoid::WGS84 will be used by default. */ CesiumGeospatial::Ellipsoid ellipsoid = CesiumGeospatial::Ellipsoid::WGS84; + + /** + * @brief HTTP headers to attach to requests made for this tileset. + */ + std::vector requestHeaders; }; } // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index da61c1486..2e0a41f52 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -74,7 +74,6 @@ Tileset::Tileset( _loadedTiles, externals, options.ellipsoid}, - std::vector{}, std::move(pCustomLoader), std::move(pRootTile)), } {} diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 89663c016..6750ef025 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -448,8 +448,8 @@ void calcRasterOverlayDetailsInWorkerThread( const TileContentLoadInfo& tileLoadInfo) { CesiumGltf::Model& model = std::get(result.contentKind); - // we will use the fittest bounding volume to calculate raster overlay details - // below + // we will use the best-fitting bounding volume to calculate raster overlay + // details below const BoundingVolume& contentBoundingVolume = getEffectiveContentBoundingVolume( tileLoadInfo.tileBoundingVolume, @@ -705,11 +705,10 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile) : _externals{externals}, - _requestHeaders{std::move(requestHeaders)}, + _requestHeaders{tilesetOptions.requestHeaders}, _pLoader{std::move(pLoader)}, _pRootTile{std::move(pRootTile)}, _userCredit( @@ -739,7 +738,7 @@ TilesetContentManager::TilesetContentManager( RasterOverlayCollection&& overlayCollection, const std::string& url) : _externals{externals}, - _requestHeaders{}, + _requestHeaders{tilesetOptions.requestHeaders}, _pLoader{}, _pRootTile{}, _userCredit( @@ -891,7 +890,7 @@ TilesetContentManager::TilesetContentManager( const std::string& ionAccessToken, const std::string& ionAssetEndpointUrl) : _externals{externals}, - _requestHeaders{}, + _requestHeaders{tilesetOptions.requestHeaders}, _pLoader{}, _pRootTile{}, _userCredit( diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index 0a4af3831..f8c749482 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -28,7 +28,6 @@ class TilesetContentManager const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile); diff --git a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp index 170eaa6e6..6fa2c60ff 100644 --- a/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp +++ b/Cesium3DTilesSelection/src/TilesetHeightQuery.cpp @@ -133,9 +133,9 @@ void TilesetHeightQuery::intersectVisibleTile( // Set ray info to this hit if closer, or the first hit if (!this->intersection.has_value()) { this->intersection = std::move(gltfIntersectResult.hit); - } else { + } else if (gltfIntersectResult.hit) { double prevDistSq = this->intersection->rayToWorldPointDistanceSq; - double thisDistSq = intersection->rayToWorldPointDistanceSq; + double thisDistSq = gltfIntersectResult.hit->rayToWorldPointDistanceSq; if (thisDistSq < prevDistSq) this->intersection = std::move(gltfIntersectResult.hit); } diff --git a/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp b/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp index e769d2c0f..408a11669 100644 --- a/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetContentManager.cpp @@ -506,7 +506,6 @@ TEST_CASE("Test tile state machine") { externals, options, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -612,7 +611,6 @@ TEST_CASE("Test tile state machine") { externals, options, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -689,7 +687,6 @@ TEST_CASE("Test tile state machine") { externals, options, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -792,7 +789,6 @@ TEST_CASE("Test tile state machine") { externals, options, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -947,7 +943,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1017,7 +1012,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, options, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1083,7 +1077,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1134,7 +1127,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, std::move(rasterOverlayCollection), - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1428,7 +1420,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, std::move(rasterOverlayCollection), - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1651,7 +1642,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, std::move(rasterOverlayCollection), - {}, std::move(pMockedLoader), std::move(pRootTile)}; @@ -1720,7 +1710,6 @@ TEST_CASE("Test the tileset content manager's post processing for gltf") { externals, {}, RasterOverlayCollection{loadedTiles, externals}, - {}, std::move(loaderResult.pLoader), std::move(loaderResult.pRootTile)}; diff --git a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp index 6c5bfdce1..0facaa86f 100644 --- a/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp +++ b/Cesium3DTilesSelection/test/TestTilesetHeightQueries.cpp @@ -275,4 +275,35 @@ TEST_CASE("Tileset height queries") { 0.0, Math::Epsilon4)); } + + SUBCASE("stacked-cubes") { + // This tileset has two cubes on top of each other, each in a different + // tile, so we can test that the height of the top one is returned. + // The bottom cube has a height of 78.0 meters, the upper cube has a height + // of 83.0 meters. + std::string url = + "file://" + + Uri::nativePathToUriPath(StringHelpers::toStringUtf8( + (testDataPath / "stacked-cubes" / "tileset.json").u8string())); + + Tileset tileset(externals, url); + + Future future = tileset.sampleHeightMostDetailed( + {Cartographic::fromDegrees(10.0, 45.0, 0.0)}); + + while (!future.isReady()) { + tileset.updateView({}); + } + + SampleHeightResult results = future.waitInMainThread(); + CHECK(results.warnings.empty()); + REQUIRE(results.positions.size() == 1); + + CHECK(results.sampleSuccess[0]); + CHECK(Math::equalsEpsilon( + results.positions[0].height, + 83.0, + 0.0, + Math::Epsilon1)); + } } diff --git a/Cesium3DTilesSelection/test/data/stacked-cubes/cube.b3dm b/Cesium3DTilesSelection/test/data/stacked-cubes/cube.b3dm new file mode 100644 index 000000000..c86e47cac Binary files /dev/null and b/Cesium3DTilesSelection/test/data/stacked-cubes/cube.b3dm differ diff --git a/Cesium3DTilesSelection/test/data/stacked-cubes/tileset.json b/Cesium3DTilesSelection/test/data/stacked-cubes/tileset.json new file mode 100644 index 000000000..d1da9db6f --- /dev/null +++ b/Cesium3DTilesSelection/test/data/stacked-cubes/tileset.json @@ -0,0 +1,71 @@ +{ + "asset": { + "version": "1.0" + }, + "geometricError": 99999, + "root": { + "geometricError": 99999, + "boundingVolume": { + "region": [ + -3.14, + -1.57, + 3.14, + 1.57, + -100, + 200 + ] + }, + "children": [ + { + "geometricError": 99999, + "boundingVolume": { "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] }, + "children": [ + { + "boundingVolume": { "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] }, + "geometricError": 3.4641016151377544, + "children": [ + { + "boundingVolume": { + "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] + }, + "content": { "uri": "cube.b3dm" }, + "geometricError": 0 + } + ] + } + ], + "transform": [ + -0.17364817766693044, 0.9848077530122081, 5.551115123125782e-17, 0, + -0.6963642682339138, -0.1227878088909457, 0.7071067528420348, 0, + 0.696364212406123, 0.12278779904699996, 0.7071068095310592, 0, + 4449011.446109926, 784480.7554299649, 4487402.148981291, 1 + ] + }, + { + "geometricError": 99999, + "boundingVolume": { "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] }, + "children": [ + { + "boundingVolume": { "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] }, + "geometricError": 3.4641016151377544, + "children": [ + { + "boundingVolume": { + "box": [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1] + }, + "content": { "uri": "cube.b3dm" }, + "geometricError": 0 + } + ] + } + ], + "transform": [ + -0.17364817766693044, 0.9848077530122081, 5.551115123125782e-17, 0, + -0.6963642682339138, -0.1227878088909457, 0.7071067528420348, 0, + 0.696364212406123, 0.12278779904699996, 0.7071068095310592, 0, + 4449014.9279309884, 784481.36936896015, 4487405.6845153384, 1 + ] + } + ] + } +} diff --git a/CesiumGeometry/src/IntersectionTests.cpp b/CesiumGeometry/src/IntersectionTests.cpp index ed6549bef..0acbf6e19 100644 --- a/CesiumGeometry/src/IntersectionTests.cpp +++ b/CesiumGeometry/src/IntersectionTests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include -#include #include #include #include @@ -205,7 +205,7 @@ std::optional IntersectionTests::rayAABBParametric( double tmin = greatestMin; double tmax = smallestMax; - for (uint32_t i = 0; i < 3; ++i) { + for (glm::length_t i = 0; i < 3; ++i) { if (glm::abs(dir[i]) < Math::Epsilon6) { continue; } else { diff --git a/CesiumGeospatial/include/CesiumGeospatial/Ellipsoid.h b/CesiumGeospatial/include/CesiumGeospatial/Ellipsoid.h index de0e7ee82..8e9a26d90 100644 --- a/CesiumGeospatial/include/CesiumGeospatial/Ellipsoid.h +++ b/CesiumGeospatial/include/CesiumGeospatial/Ellipsoid.h @@ -33,6 +33,8 @@ namespace CesiumGeospatial { * 1`. This is primarily used by Cesium to represent the shape of planetary * bodies. Rather than constructing this object directly, one of the provided * constants is normally used. + * + * @see \ref what-is-an-ellipsoid */ class CESIUMGEOSPATIAL_API Ellipsoid final { public: diff --git a/CesiumGeospatial/include/CesiumGeospatial/GlobeAnchor.h b/CesiumGeospatial/include/CesiumGeospatial/GlobeAnchor.h index 2b81ed8dd..750bca6a9 100644 --- a/CesiumGeospatial/include/CesiumGeospatial/GlobeAnchor.h +++ b/CesiumGeospatial/include/CesiumGeospatial/GlobeAnchor.h @@ -14,7 +14,7 @@ class LocalHorizontalCoordinateSystem; /** * @brief Anchors an object to the globe by defining a transformation from the * object's coordinate to the globe-fixed coordinate system (usually - * \ref glossary-ecef). + * \ref what-are-ecef-coordinates). * * This class allows the anchored coordinate system to be realized in any * {@link LocalHorizontalCoordinateSystem}. When the object is moved, either by diff --git a/CesiumGeospatial/include/CesiumGeospatial/LocalHorizontalCoordinateSystem.h b/CesiumGeospatial/include/CesiumGeospatial/LocalHorizontalCoordinateSystem.h index 91c2b311b..705c2c867 100644 --- a/CesiumGeospatial/include/CesiumGeospatial/LocalHorizontalCoordinateSystem.h +++ b/CesiumGeospatial/include/CesiumGeospatial/LocalHorizontalCoordinateSystem.h @@ -55,8 +55,8 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { const Ellipsoid& ellipsoid = CesiumGeospatial::Ellipsoid::WGS84); /** - * @brief Create a new coordinate system centered at a \ref glossary-ecef - * "Earth-Centered, Earth-Fixed" position. + * @brief Create a new coordinate system centered at a \ref + * what-are-ecef-coordinates "Earth-Centered, Earth-Fixed" position. * * @param originEcef The origin of the coordinate system. * @param xAxisDirection The local direction in which the X axis points at the @@ -80,8 +80,8 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Create a new coordinate system with a specified transformation to - * the \ref glossary-ecef "Earth-Centered, Earth-Fixed" frame. This is an - * advanced constructor and should be avoided in most cases. + * the \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed" frame. + * This is an advanced constructor and should be avoided in most cases. * * This constructor can be used to save/restore the state of an instance. It * can also be used to create unusual coordinate systems that can't be created @@ -96,8 +96,8 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Create a new coordinate system with the specified transformations * between the local frame and the - * \ref glossary-ecef "Earth-Centered, Earth-Fixed" frame. This is an advanced - * constructor and should be avoided in most cases. + * \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed" frame. This is + * an advanced constructor and should be avoided in most cases. * * This constructor can be used to save/restore the state of an instance. It * can also be used to create unusual coordinate systems that can't be created @@ -117,7 +117,7 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Gets the transformation matrix from the local horizontal coordinate - * system managed by this instance to the \ref glossary-ecef. + * system managed by this instance to the \ref what-are-ecef-coordinates. * * @return The transformation. */ @@ -126,8 +126,8 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { } /** - * @brief Gets the transformation matrix from \ref glossary-ecef to the - * local horizontal coordinate system managed by this instance. + * @brief Gets the transformation matrix from \ref what-are-ecef-coordinates + * to the local horizontal coordinate system managed by this instance. * * @return The transformation. */ @@ -138,7 +138,7 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Converts a position in the local horizontal coordinate system * managed by this instance to - * \ref glossary-ecef "Earth-Centered, Earth-Fixed (ECEF)". + * \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed (ECEF)". * * @param localPosition The position in the local coordinate system. * @return The equivalent position in the ECEF coordinate system. @@ -148,8 +148,9 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Converts a position in the - * \ref glossary-ecef "Earth-Centered, Earth-Fixed (ECEF)" coordinate system - * to the local horizontal coordinate system managed by this instance. + * \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed (ECEF)" + * coordinate system to the local horizontal coordinate system managed by this + * instance. * * @param ecefPosition The position in the ECEF coordinate system. * @return The equivalent position in the local coordinate system. @@ -159,7 +160,7 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Converts a direction in the local horizontal coordinate system * managed by this instance to - * \ref glossary-ecef "Earth-Centered, Earth-Fixed (ECEF)". + * \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed (ECEF)". * * Because the vector is treated as a direction only, the translation portion * of the transformation is ignored. @@ -172,8 +173,9 @@ class CESIUMGEOSPATIAL_API LocalHorizontalCoordinateSystem { /** * @brief Converts a direction in the - * \ref glossary-ecef "Earth-Centered, Earth-Fixed (ECEF)" coordinate system - * to the local horizontal coordinate system managed by this instance. + * \ref what-are-ecef-coordinates "Earth-Centered, Earth-Fixed (ECEF)" + * coordinate system to the local horizontal coordinate system managed by this + * instance. * * Because the vector is treated as a direction only, the translation portion * of the transformation is ignored. diff --git a/CesiumGltfContent/include/CesiumGltfContent/SkirtMeshMetadata.h b/CesiumGltfContent/include/CesiumGltfContent/SkirtMeshMetadata.h index 594be7ddf..e20fda4e6 100644 --- a/CesiumGltfContent/include/CesiumGltfContent/SkirtMeshMetadata.h +++ b/CesiumGltfContent/include/CesiumGltfContent/SkirtMeshMetadata.h @@ -89,7 +89,8 @@ struct SkirtMeshMetadata { */ uint32_t noSkirtVerticesCount; /** - * @brief The center coordinates of the mesh, in \ref glossary-ecef. + * @brief The center coordinates of the mesh, in \ref + * what-are-ecef-coordinates. */ glm::dvec3 meshCenter; /** diff --git a/CesiumGltfReader/test/TestGltfReader.cpp b/CesiumGltfReader/test/TestGltfReader.cpp index 544b111c5..31c398d43 100644 --- a/CesiumGltfReader/test/TestGltfReader.cpp +++ b/CesiumGltfReader/test/TestGltfReader.cpp @@ -137,7 +137,7 @@ T getRange(const CesiumGltf::AccessorView& accessorView) { T max{std::numeric_limits::lowest()}; for (int32_t i = 0; i < accessorView.size(); ++i) { const T& value = accessorView[i]; - for (uint32_t j = 0; j < static_cast(value.length()); ++j) { + for (glm::length_t j = 0; j < value.length(); ++j) { min[j] = glm::min(min[j], value[j]); max[j] = glm::max(max[j], value[j]); } @@ -181,7 +181,7 @@ VertexAttributeRange getVertexAttributeRange(const Model& model) { template bool epsilonCompare(const T& v1, const T& v2, double epsilon) { - for (uint32_t i = 0; i < static_cast(v1.length()); ++i) { + for (glm::length_t i = 0; i < v1.length(); ++i) { if (!CesiumUtility::Math::equalsEpsilon(v1[i], v2[i], epsilon)) { return false; } diff --git a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h index 202b6b9cc..86b9b1084 100644 --- a/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h +++ b/CesiumRasterOverlays/include/CesiumRasterOverlays/RasterOverlayTileProvider.h @@ -28,8 +28,8 @@ struct CESIUMRASTEROVERLAYS_API LoadedRasterOverlayImage { /** * @brief The loaded image. * - * This will be an empty optional if the loading failed. In this case, - * the `errors` vector will contain the corresponding error messages. + * This will be nullptr if the loading failed. In this case, the `errors` + * vector will contain the corresponding error messages. */ CesiumUtility::IntrusivePointer pImage{nullptr}; diff --git a/CesiumRasterOverlays/src/RasterOverlayUtilities.cpp b/CesiumRasterOverlays/src/RasterOverlayUtilities.cpp index 86b492e62..b741a97f1 100644 --- a/CesiumRasterOverlays/src/RasterOverlayUtilities.cpp +++ b/CesiumRasterOverlays/src/RasterOverlayUtilities.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -1477,9 +1478,11 @@ void addSkirt( position -= center; for (uint32_t c = 0; c < 3; ++c) { - output.push_back(static_cast(position[c])); - attribute.minimums[c] = glm::min(attribute.minimums[c], position[c]); - attribute.maximums[c] = glm::max(attribute.maximums[c], position[c]); + output.push_back(static_cast(position[glm::length_t(c)])); + attribute.minimums[c] = + glm::min(attribute.minimums[c], position[glm::length_t(c)]); + attribute.maximums[c] = + glm::max(attribute.maximums[c], position[glm::length_t(c)]); } } else { for (uint32_t c = 0; diff --git a/CesiumRasterOverlays/test/ExamplesRasterOverlays.cpp b/CesiumRasterOverlays/test/ExamplesRasterOverlays.cpp new file mode 100644 index 000000000..d38525f50 --- /dev/null +++ b/CesiumRasterOverlays/test/ExamplesRasterOverlays.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include + +using namespace CesiumRasterOverlays; + +namespace { + +class MyRasterOverlay : public RasterOverlay { +public: + MyRasterOverlay() : RasterOverlay("name", {}) {} + + virtual CesiumAsync::Future createTileProvider( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const std::shared_ptr& pCreditSystem, + const std::shared_ptr& + pPrepareRendererResources, + const std::shared_ptr& pLogger, + CesiumUtility::IntrusivePointer pOwner) + const override; +}; + +//! [use-url-template] +CesiumAsync::Future +MyRasterOverlay::createTileProvider( + const CesiumAsync::AsyncSystem& asyncSystem, + const std::shared_ptr& pAssetAccessor, + const std::shared_ptr& pCreditSystem, + const std::shared_ptr& + pPrepareRendererResources, + const std::shared_ptr& pLogger, + CesiumUtility::IntrusivePointer pOwner) const { + // Create a new raster overlay with a URL template. + CesiumGeometry::Rectangle coverageRectangle = CesiumGeospatial:: + WebMercatorProjection::computeMaximumProjectedRectangle(); + + UrlTemplateRasterOverlayOptions options{ + .credit = "Copyright (c) Some Amazing Source", + .projection = CesiumGeospatial::WebMercatorProjection(), + .tilingScheme = + CesiumGeometry::QuadtreeTilingScheme(coverageRectangle, 1, 1), + .minimumLevel = 0, + .maximumLevel = 15, + .tileWidth = 256, + .tileHeight = 256, + .coverageRectangle = coverageRectangle, + }; + + CesiumUtility::IntrusivePointer pUrlTemplate = + new UrlTemplateRasterOverlay( + this->getName(), + "https://example.com/level-{z}/column-{x}/row-{y}.png", + {}, + options); + + // Get that raster overlay's tile provider. + return pUrlTemplate->createTileProvider( + asyncSystem, + pAssetAccessor, + pCreditSystem, + pPrepareRendererResources, + pLogger, + pOwner != nullptr ? pOwner : this); +} +//! [use-url-template] + +} // namespace + +TEST_CASE("RasterOverlay examples") { + CesiumUtility::IntrusivePointer pOverlay = + new MyRasterOverlay(); +} \ No newline at end of file diff --git a/CesiumUtility/src/Assert.cpp b/CesiumUtility/src/Assert.cpp index 8e1dbdf45..18034265e 100644 --- a/CesiumUtility/src/Assert.cpp +++ b/CesiumUtility/src/Assert.cpp @@ -1,4 +1,3 @@ - #if defined CESIUM_FORCE_ASSERTIONS && defined NDEBUG #undef NDEBUG diff --git a/cmake/macros/configure_cesium_library.cmake b/cmake/macros/configure_cesium_library.cmake index 159e425f1..b64a7ef3f 100644 --- a/cmake/macros/configure_cesium_library.cmake +++ b/cmake/macros/configure_cesium_library.cmake @@ -22,7 +22,6 @@ function(configure_cesium_library targetName) PUBLIC GLM_FORCE_XYZW_ONLY # Disable .rgba and .stpq to make it easier to view values from debugger GLM_FORCE_EXPLICIT_CTOR # Disallow implicit conversions between dvec3 <-> dvec4, dvec3 <-> fvec3, etc - GLM_FORCE_SIZE_T_LENGTH # Make vec.length() and vec[idx] use size_t instead of int GLM_ENABLE_EXPERIMENTAL # Allow use of experimental extensions ) endif() diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 20cb6bdd0..83c92c4f1 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -115,6 +115,8 @@ if(DOXYGEN_FOUND) set(DOXYGEN_VERBATIM_VARS DOXYGEN_ALIASES DOXYGEN_HTML_EXTRA_FILES) list(APPEND DOXYGEN_EXAMPLE_PATH "${CMAKE_CURRENT_LIST_DIR}/diagrams") + list(APPEND DOXYGEN_TAGFILES "${CMAKE_CURRENT_LIST_DIR}/community.tag=https://github.com/CesiumGS/community/blob/main") + cesium_glob_files( CESIUM_DOC_TOPICS ${CMAKE_CURRENT_LIST_DIR}/topics/*.md diff --git a/doc/community.tag b/doc/community.tag new file mode 100644 index 000000000..b0d46f210 --- /dev/null +++ b/doc/community.tag @@ -0,0 +1,14 @@ + + + + + geospatial-guide + Geospatial Guide + GeospatialGuide/README.md + what-are-ecef-coordinates + what-is-an-ellipsoid + + \ No newline at end of file diff --git a/doc/img/raster-overlay-mapping.svg b/doc/img/raster-overlay-mapping.svg new file mode 100644 index 000000000..67540f38b --- /dev/null +++ b/doc/img/raster-overlay-mapping.svg @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pixels in overlay source + RasterOverlayTile rectangle + Geometry tile + Output image rectangle + + + + + + + diff --git a/doc/raster-overlays.md b/doc/raster-overlays.md deleted file mode 100644 index ccfa18669..000000000 --- a/doc/raster-overlays.md +++ /dev/null @@ -1,17 +0,0 @@ -# RasterOverlayTileProvider - -Loads or creates a `RasterOverlayTile` to cover a given geometry tile. - -The rest of the 3D Tiles engine calls `getTile`, giving it the exact rectangle that the returned tile must cover. The tile provider has a `Projection`, and the provided rectangle is expressed in that projection. - -`getTile` returns a `RasterOverlayTile`, but does not immediately start loading it. The 3D Tiles engine calls `RasterOverlayTileProvider::loadTile` to kick off the loading process. `getTile` may return nullptr if the provider has no data within the given rectangle. - -While the returned `RasterOverlayTile` is loading, the 3D Tiles engine will use the geometry from the parent geometry tile. The `RasterOverlayTileProvider` doesn't need to concern itself with this at all. It is handled by `RasterMappedTo3DTile`. - -`RasterOverlayTileProvider` also, in general, does not need to do any caching. The 3D Tiles engine will only call `getTile` once per geometry tile. - -`getTile` internally calls the polymorphic `loadTileImage`. Derived `RasterOverlayTileProvider` classes implement this method to kick off a request, if necessary, then decode the result and provide the decoded pixels as a `LoadedRasterOverlayImage`. All the lifecycle management is handled automatically by `RasterOverlayTileProvider`, so that derived classes only need to implement this one async method. - -# QuadtreeRasterOverlayTileProvider - -Derives from `RasterOveralyTileProvider` and provides an implementation for `RasterOverlayTileProvider::loadTileImage`. This implementation looks a bit like the old `mapRasterTilesToGeometryTile`. It figures out which quadtree tiles fall inside the rectangle passed to `loadTileImage`. Then, it starts an async load of each of these tiles and waits for them _all_ to either finish loading or fail to load. For the ones that fail to load, we try a parent tile, which may require more waiting. Eventually we have tiles that cover the entire rectangle, and we can blit them to the output texture. `loadTileImage` is an async method start to finish, so this is straightforward. It doesn't require any tricky lifetime management. diff --git a/doc/topics/developer-setup.md b/doc/topics/developer-setup.md index 919bd6099..442622ea9 100644 --- a/doc/topics/developer-setup.md +++ b/doc/topics/developer-setup.md @@ -159,7 +159,7 @@ cmake -B build -S . 3. Next, we run the clang-tidy target of Cesium Native's cmake build, which will run clang-tidy. clang-tidy produces a lot of not-very-useful output, so we send it to a file instead of the console: ``` -cmake --build build-tidy --target clang-tidy > clang-tidy.log +cmake --build build --target clang-tidy > clang-tidy.log ``` 4. Finally, we use `sed` to extract the errors and warnings from the log: @@ -187,7 +187,7 @@ cmake -B build -S . -DCLANG_TIDY_PATH=$(brew --prefix llvm)/bin/clang-tidy -DCLA 3. Next, we run the clang-tidy target of Cesium Native's cmake build, which will run clang-tidy. clang-tidy produces a lot of not-very-useful output, so we send it to a file instead of the console: ``` -cmake --build build-tidy --target clang-tidy > clang-tidy.log +cmake --build build --target clang-tidy > clang-tidy.log ``` 4. Finally, we use `sed` to extract the errors and warnings from the log: diff --git a/doc/topics/developer.md b/doc/topics/developer.md index 5db41f388..cd2f4186e 100644 --- a/doc/topics/developer.md +++ b/doc/topics/developer.md @@ -5,9 +5,11 @@ * \subpage developer-setup * \subpage style-guide * \subpage contributing +* \subpage geospatial-guide ## Architecture of Cesium Native * \subpage multithreading * \subpage selection-algorithm-details -* \subpage rendering-3d-tiles \ No newline at end of file +* \subpage rendering-3d-tiles +* \subpage raster-overlays \ No newline at end of file diff --git a/doc/topics/glossary.md b/doc/topics/glossary.md deleted file mode 100644 index 0154b6e4b..000000000 --- a/doc/topics/glossary.md +++ /dev/null @@ -1,9 +0,0 @@ -# Glossary {#glossary} - -Terminology and jargon used throughout Cesium Native and its documentation is collected here for clarity's sake. - -## Earth-Centered, Earth-Fixed Coordinates (ECEF) {#glossary-ecef} - -Earth-Centered, Earth-Fixed (ECEF) coordinates are, as the name describes, in a 3D Cartesian coordinate system fixed to the Earth with the center at the center of the Earth's ellipsoid. As the Earth spins, the coordinate system spins with it, meaning an ECEF coordinate and its equivalent coordinate in a cartographic coordinate system (like Longitude, Latitude, Height) will remain the same. - -For example, Philadelphia, Pennsylvania is located at -75.1652215° longitude, 39.952839° latitude, at a height of 14.34m. The equivalent ECEF coordinates are (1253556.69, -4732887.41, 4073982.02). \ No newline at end of file diff --git a/doc/topics/how-raster-overlays-work.md b/doc/topics/how-raster-overlays-work.md new file mode 100644 index 000000000..5de144cfd --- /dev/null +++ b/doc/topics/how-raster-overlays-work.md @@ -0,0 +1,42 @@ +# How Raster Overlays Work {#how-raster-overlays-work} + +This topic explains how Cesium Native's raster overlay system is implemented. + +> [!note] +> In the explanation below, we distinguish between a "geometry tile", which is an instance of [Tile](\ref Cesium3DTilesSelection::Tile) from a 3D Tiles or quantized-mesh-1.0 tileset, and a "raster overlay tile" which is an instance of [RasterOverlayTile](\ref CesiumRasterOverlays::RasterOverlayTile) that represents a raster overlay image applied to a geometry tile. While a `Tile` may have textures, too, a `RasterOverlayTile` never has geometry. + +## Determining the raster overlay rectangle + +In most cases, a `RasterOverlayTile` is mapped to a `Tile` when that geometry tile first transitions out of the _Unloaded_ state and starts its loading process. This happens with a call to the [RastedMappedTo3DTile::mapOverlayToTile](\ref Cesium3DTilesSelection::RasterMappedTo3DTile::mapOverlayToTile) static method. The first job of this method is to determine a bounding rectangle for this geometry tile, expressed in the coordinates and projection of the raster overlay. + +If the renderable content for this tile were already loaded, this process would be simple. We would simply compute the projected coordinates of each vertex in the tile's model, and form a bounding rectangle from the minimum and maximum of these. Unfortunately, waiting until the model vertices are available is inefficient. It requires that we completely download, parse, and decode the tile's model before we can even start the requests for the raster overlay images. Ideally, we would be able to request the tile content and the raster overlay images simultaneously, which would significantly reduce latency. + +To that end, we try to determine an accurate bounding rectangle for the geometry tile from the geometry tile's 3D bounding volume whenever we can. It's extremely important that the bounding rectangle be accurate, though. If we estimate a significantly larger rectangle than the geometry tile actually covers, we'll end up unnecessarily loading more raster overlay data than we actually need, eliminating the efficiency advantage we were hoping to gain. So `mapOverlayToTile` will only map actual raster overlay tiles to unloaded geometry tiles when it is sure it can determine an accurate rectangle. In all other cases, it instead adds a "placeholder" raster overlay tile, which will be turned into a real one later, after the geometry tile is loaded. + +The current implementation can only determine an accurate rectangle for tiles with a [region](https://github.com/CesiumGS/3d-tiles/tree/main/specification#core-region) bounding volume. The reasoning is as follows: + +1. Cesium Native currently only supports raster overlays with a [Geographic](\ref CesiumGeospatial::GeographicProjection) or [Web Mercator](\ref CesiumGeospatial::WebMercatorProjection) projection. +2. Both of these projections are aligned to longitude/latitude bounds. A constant X or Y coordinate in either projection corresponds to a constant longitude or latitude. +3. Therefore, as long as the 3D bounding region is accurate, the 2D bounding rectangle will be as well. + +For all other bounding volumes, while we could attempt to estimate a bounding rectangle from the bounding volume, we would not have high confidence in the estimate. So, instead, we accept the latency and load the geometry tile first. + +## Texture Coordinates {#texture-coordinates} + +When rendered, raster overlay images are applied to geometry tiles just like any other texture: they're sampled in a pixel/fragment shader according to texture coordinates associated with every vertex in the geometry. Cesium Native generates one or more sets of overlay texture coordinates for every geometry tile that has raster overlays. + +In fact, it generates a set of texture coordinate for each unique projection used by raster overlays. We need texture coordinates per projection because the raster overlay texture sampling also serves to unproject the overlay images from the raster overlay's projected coordinate system to the 3D world coordinates. + +This unprojection is not perfect. Texture coordinates are defined at vertices and are linearly interpolated over triangles. This linear interpolation may be noticeably different from the result we would get if we actually computed the projected coordinates at a given world position within the triangle. A more correct solution would be to evaluate the projection in the shader or material. This would be less performant, however, and most map projections are difficult to evaluate accurately in the single-precision floating point arithmetic that is commonly available on GPUs. Most importantly, unprojecting with texture coordinates and sampling is good enough in all but pathological cases. Geospatial models tend to have vertices that are close enough together to avoid noticeable artifacts caused by inaccurate unprojection in between them. + +Texture coordinates are computed using [RasterOverlayUtilities::createRasterOverlayTextureCoordinates](\ref CesiumRasterOverlays::RasterOverlayUtilities::createRasterOverlayTextureCoordinates), which is called from a worker thread during the geometry tile loading process. + +## Target Screen Pixels + +The pixel resolution used for the raster overlay texture applied to a given geometry tile is controlled by the overlay tile's "target screen pixels". This is a parameter that Cesium Native passes to the `RasterOverlayTileProvider` when it requests an overlay image. Cesium Native computes this quantity by calling [RasterOverlayUtilities::computeDesiredScreenPixels](\ref CesiumRasterOverlays::RasterOverlayUtilities::computeDesiredScreenPixels). The return value is a 2D vector, with the X and Y components representing the target number of pixels in the projected X and Y directions, respectively. See the reference documentation for that method, as well as the comments in the implementation, for further details. + +We can think of this as the maximum pixel size of the geometry tile on the screen, just before moving a little closer to it would cause a switch to a higher level-of-detail. This is a function of the geometry tile's bounding box, as well as its geometric error and the tileset's maximum screen-space error. For leaf tiles with a geometric error of zero, we compute this quantity as if the tile had a geometric error that is half of its parent's. + +The "Target Screen Pixels" is not usually used directly as the size of the raster overlay texture, however, but it is used to compute it. First, it is divided by the [maximumScreenSpaceError](\ref CesiumRasterOverlays::RasterOverlayOptions::maximumScreenSpaceError) configured on the `RasterOverlay`. Then, it is clamped to the [maximumTextureSize](\ref CesiumRasterOverlays::RasterOverlayOptions::maximumTextureSize). This formulation makes raster overlay detail shown on the screen a function of its own maximum screen-space error property, and largely independent of the maximum screen-space error configured on the `Tileset`. We can cut the geometry detail in half by changing [TilesetOptions::maximumScreenSpaceError](\ref Cesium3DTilesSelection::TilesetOptions::maximumScreenSpaceError) from 16.0 to 32.0, and this will not affect the sharpness of the raster overlays. + +This formulation does, however, mean that changing `TilesetOptions::maximumScreenSpaceError` requires remapping raster overlaps to geometry tiles, which is usually accomplished by reloading the `Tileset` entirely. diff --git a/doc/topics/implementing-a-new-raster-overlay-type.md b/doc/topics/implementing-a-new-raster-overlay-type.md new file mode 100644 index 000000000..995be5750 --- /dev/null +++ b/doc/topics/implementing-a-new-raster-overlay-type.md @@ -0,0 +1,25 @@ +# Implementing a new RasterOverlay Type {#implementing-a-new-raster-overlay-type} + +Raster overlays are implemented by deriving from the [RasterOverlay](\ref CesiumRasterOverlays::RasterOverlay) abstract base class, so new ones can be easily added even from outside of Cesium Native. `RasterOverlay` has just a single pure-virtual method that must be implemented: [createTileProvider](\ref CesiumRasterOverlays::RasterOverlay::createTileProvider). This method [asynchronously](#async-system) produces an instance of a class derived from [RasterOverlayTileProvider](\ref CesiumRasterOverlays::RasterOverlayTileProvider). + +A `RasterOverlayTileProvider` has a particular [Projection](\ref CesiumGeospatial::Projection), which is used to select or generate appropriate texture coordinates for this raster overlay, and a rectangle that the raster overlay covers, expressed in the coordinates of that map projection. + +> [!note] +> In the explanation below, we distinguish between a "geometry tile", which is an instance of [Tile](\ref Cesium3DTilesSelection::Tile) from a 3D Tiles or quantized-mesh-1.0 tileset, and a "raster overlay tile" which is an instance of [RasterOverlayTile](\ref CesiumRasterOverlays::RasterOverlayTile) that represents a raster overlay image applied to a geometry tile. While a `Tile` may have textures, too, a `RasterOverlayTile` never has geometry. + +While it's possible to derive a class from `RasterOverlayTileProvider` directly and implement the [loadTileImage](\ref CesiumRasterOverlays::RasterOverlayTileProvider::loadTileImage) method, there are two shortcuts available that often save a lot of implementation effort. + +In the very common scenario where a raster overlay source is organized into a quadtree of tiles, and each tile can be downloaded from a web URL, we can implement `createTileProvider` to construct an instance of [UrlTemplateRasterOverlay](\ref CesiumRasterOverlays::UrlTemplateRasterOverlay) with the appropriate templatized URL and then call its `createTileProvider`: + +\snippet{trimleft} ExamplesRasterOverlays.cpp use-url-template + +If we need a little more control, or if the raster overlay images are not downloaded from web URLs, then we can derive a new class from [QuadtreeRasterOverlayTileProvider](\ref CesiumRasterOverlays::QuadtreeRasterOverlayTileProvider) and create and return an instance of it from `createTileProvider`. This requires implementing the [loadQuadtreeTileImage](\ref CesiumRasterOverlays::QuadtreeRasterOverlayTileProvider::loadQuadtreeTileImage) method, which is given a quadtree tile ID (level, x, and y) and must asynchronously return the image for that quadtree tile. `QuadtreeRasterOverlayTileProvider` itself will automatically figure out an appropriate quadtree level to use for ther raster overlay tile attached to a given geometry tile, and it will make multiple calls to `loadQuadtreeTileImage` as necessary to get all of the quadtree images that cover it. In the common case that multiple geometry tiles overlap a single raster overlay quadtree tile, a small cache ensures that raster overlay tiles are not requested more than is necessary. + +If our raster overlay source is not arranged in a quadtree, however, we're left with the final option, which is deriving from `RasterOverlayTileProvider` directly. This requires implementing the [loadTileImage](\ref CesiumRasterOverlays::RasterOverlayTileProvider::loadTileImage) method. When Cesium Native calls this method, it passes a [RasterOverlayTile](\ref CesiumRasterOverlays::RasterOverlayTile) which captures the requirements for the raster overlay tile that covers this geometry tile: + +* [Rectangle](\ref CesiumRasterOverlays::RasterOverlayTile::getRectangle): Describes the minimum rectangle that the provided image must cover, expressed in the provider's [Projection](\ref CesiumRasterOverlays::RasterOverlayTileProvider::getProjection). The returned image is allowed to be bigger than this, but the extra pixels will be wasted. +* [TargetScreenPixels](\ref CesiumRasterOverlays::RasterOverlayTile::getTargetScreenPixels): The number of pixels on the screen that the rectangle is expected to map to, just before the geometry tile switches to a higher level-of-detail. This is used to control how detailed the image will be. + +In a typical implementation, the target screen pixels is divided by the raster overlay's configured [maximumScreenSpaceError](\ref CesiumRasterOverlays::RasterOverlayOptions::maximumScreenSpaceError) to determine the target number of pixels in the raster overlay image. Then, pixels are copied into the output image in order to completely fill the rectangle. The output image rectangle is usually larger than the `RasterOverlayTile` rectangle, and the number of pixels is slightly higher than the target number of pixels in the raster overlay image, because both must be rounded up in order to fully include partial pixels. + +\image html raster-overlay-mapping.svg diff --git a/doc/topics/raster-overlays.md b/doc/topics/raster-overlays.md new file mode 100644 index 000000000..02de14252 --- /dev/null +++ b/doc/topics/raster-overlays.md @@ -0,0 +1,20 @@ +# Raster Overlays {#raster-overlays} + +Cesium Native's raster overlays are georeferenced 2D images - perhaps consisting of trillions of pixels or more! - that are draped over the top of a [Tileset](\ref Cesium3DTilesSelection::Tileset). A classic example of a raster overlay is a satellite imagery layer. A `Tileset` can have multiple raster overlays, and they're usually alpha-blended together in a layered fashion. They can also be used for more sophisticated effects, however. For example, a raster overlay could represent a "mask" of where on Earth is land versus water, and that mask used in a custom shader or material to render waves in the water-covered areas. + +The following raster overlay types are currently included in Cesium Native: + +* [BingMapsRasterOverlay](\ref CesiumRasterOverlays::BingMapsRasterOverlay) +* [DebugColorizeTilesRasterOverlay](\ref CesiumRasterOverlays::DebugColorizeTilesRasterOverlay) +* [IonRasterOverlay](\ref CesiumRasterOverlays::IonRasterOverlay) +* [TileMapServiceRasterOverlay](\ref CesiumRasterOverlays::TileMapServiceRasterOverlay) +* [UrlTemplateRasterOverlay](\ref CesiumRasterOverlays::UrlTemplateRasterOverlay) +* [WebMapServiceRasterOverlay](\ref CesiumRasterOverlays::WebMapServiceRasterOverlay) +* [WebMapTileServiceRasterOverlay](\ref CesiumRasterOverlays::WebMapTileServiceRasterOverlay) + +To add a raster overlay to a `Tileset`, construct an instance of the appropriate class and add it to the [RasterOverlayCollection](\ref Cesium3DTilesSelection::RasterOverlayCollection) returned by [Tileset::getOverlays](\ref Cesium3DTilesSelection::Tileset::getOverlays). See the reference documentation for each overlay for details about how to configure that overlay type. + +For more information about `RasterOverlays`, see the following topics: + +* \subpage implementing-a-new-raster-overlay-type - How to implement a new kind of `RasterOverlay`. +* \subpage how-raster-overlays-work - How Cesium Native's Raster Overlay system works under-the-hood. diff --git a/doc/topics/style-guide.md b/doc/topics/style-guide.md index 274461c54..2363fde29 100644 --- a/doc/topics/style-guide.md +++ b/doc/topics/style-guide.md @@ -147,6 +147,68 @@ Not covered by C++ Core Guidelines. * Test files should be prefixed with `Test`, e.g. `TestModel` instead of `ModelTests`. +## ✏️ Documentation + +Not covered by C++ Core Guidelines. + +Our documentation is generated by Doxygen before being published [online](https://cesium.com/learn/cesium-native/ref-doc). Therefore, any public API should be documented with Doxygen-compatible comments that make use of the following tags (in the listed order): + +- `@brief`: Summarize the purpose of the class, function, etc. +- `@remarks`: Use for any side comments, e.g., how invalid inputs or special cases are handled. +- `@warning`: Convey any warnings about the use of the class, function, etc., e.g., invalid values that make it unsafe. +- `@tparam`: Describe template parameters. +- `@param`: Denote and describe function parameters. +- `@returns`: Describe the return value of a non-`void` function. +- `@throws`: Describe any exceptions that a function throws. + +Additionally, make sure to: + +- Put comments **above** the thing being documented (instead of inline). +- Use the `/** ... */` comment style (instead of `///`). +- Use `@ref` when referencing other classes, functions, etc. in the Cesium Native API. +- Use proper capitalization, grammar, and spelling. +- Use back apostrophes (`) to encapsulate references to types and variable names. +- Use triple back apostrophes (```) to encapsulate any snippets of math or code. + +You can optionally use `@snippet` to include examples of how functions are used in other parts of the code (usually test cases). First, surround the target code with comments containing a descriptive name: + +```cpp +// In TestPlane.cpp... + +TEST_CASE("Plane constructor from normal and distance") { + //! [constructor-normal-distance] + // The plane x=0 + Plane plane(glm::dvec3(1.0, 0.0, 0.0), 0.0); + //! [constructor-normal-distance] +} +``` + +Then reference the file and the name of the snippet: + +```cpp +/** + * @brief Constructs a new plane from a normal and a distance from the origin. + * + * Example: + * @snippet TestPlane.cpp constructor-normal-distance + */ +Plane(const glm::dvec3& normal, double distance); +``` + +If you find that comments are duplicated across multiple classes, functions, etc., then: + +- Keep the comment on the most sensible instance, e.g., a base class or a pure virtual function. +- Use `@copydoc` for the others. + +For `@copydoc`, keep the comment to one line instead of adding the usual linebreak after `/**`. + +```cpp +/** @copydoc Foo */ +struct Bar { ... } +``` + +> Although private classes and functions aren't required to have the same level of documentation, it never hurts to add any, especially if they have non-obvious assumptions, scope, or consequences. + ## 🗂️ Other Not covered by C++ Core Guidelines.