diff --git a/CHANGES.md b/CHANGES.md index f8c921989..f554ca327 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ##### Additions :tada: - Added support for `EXT_accessor_additional_types` in `AccessorView`. +- Added `EllipsoidTilesetLoader` that will generate a tileset by tesselating the surface of an ellipsoid, producing a simple globe tileset without any terrain features. ### v0.41.0 - 2024-11-01 diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/EllipsoidTilesetLoader.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/EllipsoidTilesetLoader.h new file mode 100644 index 000000000..9f7e2231e --- /dev/null +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/EllipsoidTilesetLoader.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +namespace Cesium3DTilesSelection { +/** + * @brief A loader that will generate a tileset by tesselating the surface of an + * ellipsoid, producing a simple globe tileset without any terrain features. + */ +class EllipsoidTilesetLoader : public TilesetContentLoader { +public: + /** + * @brief Constructs a new instance. + * + * @param ellipsoid The {@link Ellipsoid}. + */ + EllipsoidTilesetLoader( + const CesiumGeospatial::Ellipsoid& ellipsoid CESIUM_DEFAULT_ELLIPSOID); + + /** + * @brief Creates a new tileset with this loader. + * + * @param externals The external interfaces to use. + * @param options Additional options for the tileset. + */ + static std::unique_ptr createTileset( + const TilesetExternals& externals, + const TilesetOptions& options = TilesetOptions{}); + + CesiumAsync::Future + loadTileContent(const TileLoadInput& input) override; + TileChildrenResult createTileChildren( + const Tile& tile, + const CesiumGeospatial::Ellipsoid& ellipsoid + CESIUM_DEFAULT_ELLIPSOID) override; + +private: + struct Geometry { + std::vector indices; + std::vector vertices; + std::vector normals; + }; + + void createChildTile( + const Tile& parent, + std::vector& children, + const CesiumGeometry::QuadtreeTileID& childID) const; + + CesiumGeospatial::BoundingRegion + createBoundingRegion(const CesiumGeometry::QuadtreeTileID& quadtreeID) const; + Geometry createGeometry(const Tile& tile) const; + CesiumGltf::Model createModel(const Geometry& geometry) const; + + CesiumGeospatial::GeographicProjection _projection; + CesiumGeometry::QuadtreeTilingScheme _tilingScheme; +}; +} // namespace Cesium3DTilesSelection diff --git a/Cesium3DTilesSelection/src/EllipsoidTilesetLoader.cpp b/Cesium3DTilesSelection/src/EllipsoidTilesetLoader.cpp new file mode 100644 index 000000000..f19a996cd --- /dev/null +++ b/Cesium3DTilesSelection/src/EllipsoidTilesetLoader.cpp @@ -0,0 +1,245 @@ +#include +#include +#include + +#include + +using namespace CesiumGltf; +using namespace CesiumAsync; +using namespace CesiumUtility; +using namespace CesiumGeometry; +using namespace CesiumGeospatial; +using namespace Cesium3DTilesContent; + +namespace Cesium3DTilesSelection { +EllipsoidTilesetLoader::EllipsoidTilesetLoader(const Ellipsoid& ellipsoid) + : _projection(ellipsoid), + _tilingScheme( + _projection.project(_projection.MAXIMUM_GLOBE_RECTANGLE), + 2, + 1) {} + +/*static*/ std::unique_ptr EllipsoidTilesetLoader::createTileset( + const TilesetExternals& externals, + const TilesetOptions& options) { + std::unique_ptr pCustomLoader = + std::make_unique(options.ellipsoid); + std::unique_ptr pRootTile = + std::make_unique(pCustomLoader.get(), TileEmptyContent{}); + + pRootTile->setRefine(TileRefine::Replace); + pRootTile->setUnconditionallyRefine(); + + std::vector children; + uint32_t rootTilesX = pCustomLoader->_tilingScheme.getRootTilesX(); + children.reserve(rootTilesX); + + for (uint32_t x = 0; x < rootTilesX; x++) { + pCustomLoader->createChildTile( + *pRootTile, + children, + QuadtreeTileID{0, x, 0}); + } + + pRootTile->createChildTiles(std::move(children)); + + return std::make_unique( + externals, + std::move(pCustomLoader), + std::move(pRootTile), + options); +} + +Future +EllipsoidTilesetLoader::loadTileContent(const TileLoadInput& input) { + return input.asyncSystem.createResolvedFuture(TileLoadResult{ + createModel(createGeometry(input.tile)), + Axis::Z, + std::nullopt, + std::nullopt, + std::nullopt, + nullptr, + {}, + TileLoadResultState::Success}); +} + +TileChildrenResult EllipsoidTilesetLoader::createTileChildren( + const Tile& tile, + const CesiumGeospatial::Ellipsoid& /*ellipsoid*/) { + const QuadtreeTileID* pParentID = + std::get_if(&tile.getTileID()); + + if (pParentID) { + std::vector children; + QuadtreeChildren childIDs = + ImplicitTilingUtilities::getChildren(*pParentID); + children.reserve(childIDs.size()); + + for (const QuadtreeTileID& childID : childIDs) { + createChildTile(tile, children, childID); + } + + return TileChildrenResult{ + std::move(children), + TileLoadResultState::Success}; + } + + return TileChildrenResult{{}, TileLoadResultState::Failed}; +} + +void EllipsoidTilesetLoader::createChildTile( + const Tile& parent, + std::vector& children, + const QuadtreeTileID& childID) const { + BoundingRegion boundingRegion = createBoundingRegion(childID); + const GlobeRectangle& globeRectangle = boundingRegion.getRectangle(); + + Tile& child = children.emplace_back(parent.getLoader()); + child.setTileID(childID); + child.setRefine(parent.getRefine()); + child.setTransform(glm::translate( + glm::dmat4x4(1.0), + _projection.getEllipsoid().cartographicToCartesian( + globeRectangle.getNorthwest()))); + child.setBoundingVolume(boundingRegion); + child.setGeometricError( + 8.0 * calcQuadtreeMaxGeometricError(_projection.getEllipsoid()) * + globeRectangle.computeWidth()); +} + +BoundingRegion EllipsoidTilesetLoader::createBoundingRegion( + const QuadtreeTileID& quadtreeID) const { + return BoundingRegion( + _projection.unproject(_tilingScheme.tileToRectangle(quadtreeID)), + 0.0, + 0.0, + _projection.getEllipsoid()); +} + +EllipsoidTilesetLoader::Geometry +EllipsoidTilesetLoader::createGeometry(const Tile& tile) const { + static constexpr uint16_t resolution = 24; + + std::vector indices(6 * (resolution - 1) * (resolution - 1)); + std::vector vertices(resolution * resolution); + std::vector normals(vertices.size()); + + const Ellipsoid& ellipsoid = _projection.getEllipsoid(); + const GlobeRectangle& rectangle = + std::get(tile.getBoundingVolume()).getRectangle(); + + double west = rectangle.getWest(); + double east = rectangle.getEast(); + double north = rectangle.getNorth(); + double south = rectangle.getSouth(); + + double lonStep = (east - west) / (resolution - 1); + double latStep = (south - north) / (resolution - 1); + + glm::dmat4 inverseTransform = glm::inverse(tile.getTransform()); + + for (uint16_t x = 0; x < resolution; x++) { + double longitude = (lonStep * x) + west; + for (uint16_t y = 0; y < resolution; y++) { + double latitude = (latStep * y) + north; + Cartographic cartographic(longitude, latitude); + + uint16_t index = static_cast((resolution * x) + y); + vertices[index] = glm::dvec3( + inverseTransform * + glm::dvec4(ellipsoid.cartographicToCartesian(cartographic), 1.0)); + normals[index] = ellipsoid.geodeticSurfaceNormal(cartographic); + + if (x < resolution - 1 && y < resolution - 1) { + uint16_t a = index + 1; + uint16_t b = index + resolution; + uint16_t c = b + 1; + indices.insert(indices.end(), {b, index, a, b, a, c}); + } + } + } + + return Geometry{std::move(indices), std::move(vertices), std::move(normals)}; +} + +Model EllipsoidTilesetLoader::createModel(const Geometry& geometry) const { + const std::vector& indices = geometry.indices; + const std::vector& vertices = geometry.vertices; + const std::vector& normals = geometry.normals; + + size_t indicesSize = indices.size() * sizeof(uint16_t); + size_t verticesSize = vertices.size() * sizeof(glm::vec3); + size_t normalsSize = verticesSize; + + Model model; + + model.asset.version = "2.0"; + model.extras["gltfUpAxis"] = JsonValue(std::underlying_type_t(Axis::Z)); + + model.buffers.resize(1); + model.bufferViews.resize(3); + model.accessors.resize(3); + model.materials.resize(1); + model.meshes.resize(1); + model.scenes.resize(1); + model.nodes.resize(1); + + model.meshes[0].primitives.resize(1); + model.scenes[0].nodes.emplace_back(0); + model.nodes[0].mesh = 0; + + std::vector& buffer = model.buffers[0].cesium.data; + buffer.resize(indicesSize + verticesSize + normalsSize); + std::memcpy(buffer.data(), indices.data(), indicesSize); + std::memcpy(buffer.data() + indicesSize, vertices.data(), verticesSize); + std::memcpy( + buffer.data() + indicesSize + verticesSize, + normals.data(), + normalsSize); + + BufferView& bufferViewIndices = model.bufferViews[0]; + bufferViewIndices.buffer = 0; + bufferViewIndices.byteOffset = 0; + bufferViewIndices.byteLength = static_cast(indicesSize); + bufferViewIndices.target = BufferView::Target::ELEMENT_ARRAY_BUFFER; + + BufferView& bufferViewVertices = model.bufferViews[1]; + bufferViewVertices.buffer = 0; + bufferViewVertices.byteOffset = static_cast(indicesSize); + bufferViewVertices.byteLength = static_cast(verticesSize); + bufferViewVertices.target = BufferView::Target::ARRAY_BUFFER; + + BufferView& bufferViewNormals = model.bufferViews[2]; + bufferViewNormals.buffer = 0; + bufferViewNormals.byteOffset = + static_cast(indicesSize + verticesSize); + bufferViewNormals.byteLength = static_cast(normalsSize); + bufferViewNormals.target = BufferView::Target::ARRAY_BUFFER; + + Accessor& accessorIndices = model.accessors[0]; + accessorIndices.bufferView = 0; + accessorIndices.count = static_cast(indices.size()); + accessorIndices.componentType = Accessor::ComponentType::UNSIGNED_SHORT; + accessorIndices.type = Accessor::Type::SCALAR; + + Accessor& accessorVertices = model.accessors[1]; + accessorVertices.bufferView = 1; + accessorVertices.count = static_cast(vertices.size()); + accessorVertices.componentType = Accessor::ComponentType::FLOAT; + accessorVertices.type = Accessor::Type::VEC3; + + Accessor& accessorNormals = model.accessors[2]; + accessorNormals.bufferView = 2; + accessorNormals.count = static_cast(normals.size()); + accessorNormals.componentType = Accessor::ComponentType::FLOAT; + accessorNormals.type = Accessor::Type::VEC3; + + MeshPrimitive& primitive = model.meshes[0].primitives[0]; + primitive.attributes["POSITION"] = 1; + primitive.attributes["NORMAL"] = 2; + primitive.indices = 0; + primitive.material = 0; + + return model; +} +} // namespace Cesium3DTilesSelection