Add helper functions to Tileset

This commit is contained in:
Sean Lilley 2025-01-22 18:23:58 -05:00
parent 1c99abb249
commit ab26363bd7
8 changed files with 628 additions and 3 deletions

2
.gitignore vendored
View File

@ -10,3 +10,5 @@ CMakeSettings.json
test.db
build-wsl
.idea
build-debug
clang-tidy.log

View File

@ -8,6 +8,7 @@
##### Additions :tada:
- Added `forEachTile`, `forEachContent`, `addExtensionUsed`, `addExtensionRequired`, `removeExtensionUsed`, `removeExtensionRequired`, `isExtensionUsed`, and `isExtensionRequired` to `Cesium3DTiles::Tileset`.
- Added conversion of I3dm batch table metadata to `EXT_structural_metadata` and `EXT_instance_features` extensions.
- 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.

View File

@ -21,8 +21,7 @@ namespace Cesium3DTiles {
/**
* @brief A 3D Tiles tileset.
*/
struct CESIUM3DTILES_API Tileset final
: public CesiumUtility::ExtensibleObject {
struct CESIUM3DTILES_API TilesetSpec : public CesiumUtility::ExtensibleObject {
/**
* @brief The original name of this type.
*/
@ -98,7 +97,7 @@ struct CESIUM3DTILES_API Tileset final
*/
int64_t getSizeBytes() const {
int64_t accum = 0;
accum += int64_t(sizeof(Tileset));
accum += int64_t(sizeof(TilesetSpec));
accum += CesiumUtility::ExtensibleObject::getSizeBytes() -
int64_t(sizeof(CesiumUtility::ExtensibleObject));
accum += this->asset.getSizeBytes() - int64_t(sizeof(Cesium3DTiles::Asset));
@ -135,5 +134,12 @@ struct CESIUM3DTILES_API Tileset final
accum += int64_t(sizeof(std::string) * this->extensionsRequired.capacity());
return accum;
}
protected:
/**
* @brief This class is not meant to be instantiated directly. Use {@link Tileset} instead.
*/
TilesetSpec() = default;
friend struct Tileset;
};
} // namespace Cesium3DTiles

View File

@ -0,0 +1,129 @@
#pragma once
#include <Cesium3DTiles/Library.h>
#include <Cesium3DTiles/TilesetSpec.h>
#include <glm/mat4x4.hpp>
#include <functional>
namespace Cesium3DTiles {
/** @copydoc TilesetSpec */
struct CESIUM3DTILES_API Tileset : public TilesetSpec {
Tileset() = default;
/**
* @brief A callback function for {@link forEachTile}.
*/
typedef void ForEachTileCallback(
Tileset& tileset,
Tile& tile,
const glm::dmat4& transform);
/**
* @brief Apply the given callback to all tiles.
*
* @param callback The callback to apply
*/
void forEachTile(std::function<ForEachTileCallback>&& callback);
/**
* @brief A callback function for {@link forEachTile}.
*/
typedef void ForEachTileConstCallback(
const Tileset& tileset,
const Tile& tile,
const glm::dmat4& transform);
/** @copydoc Tileset::forEachTile */
void forEachTile(std::function<ForEachTileConstCallback>&& callback) const;
/**
* @brief A callback function for {@link forEachContent}.
*/
typedef void ForEachContentCallback(
Tileset& tileset,
Tile& tile,
Content& content,
const glm::dmat4& transform);
/**
* @brief Apply the given callback to all contents.
*
* @param callback The callback to apply
*/
void forEachContent(std::function<ForEachContentCallback>&& callback);
/**
* @brief A callback function for {@link forEachContent}.
*/
typedef void ForEachContentConstCallback(
const Tileset& tileset,
const Tile& tile,
const Content& content,
const glm::dmat4& transform);
/** @copydoc Tileset::forEachContent */
void
forEachContent(std::function<ForEachContentConstCallback>&& callback) const;
/**
* @brief Adds an extension to the {@link TilesetSpec::extensionsUsed}
* property, if it is not already present.
*
* @param extensionName The name of the used extension.
*/
void addExtensionUsed(const std::string& extensionName);
/**
* @brief Adds an extension to the {@link TilesetSpec::extensionsRequired}
* property, if it is not already present.
*
* Calling this function also adds the extension to `extensionsUsed`, if it's
* not already present.
*
* @param extensionName The name of the required extension.
*/
void addExtensionRequired(const std::string& extensionName);
/**
* @brief Removes an extension from the {@link TilesetSpec::extensionsUsed}
* property.
*
* @param extensionName The name of the used extension.
*/
void removeExtensionUsed(const std::string& extensionName);
/**
* @brief Removes an extension from the {@link TilesetSpec::extensionsRequired}
* property.
*
* Calling this function also removes the extension from `extensionsUsed`.
*
* @param extensionName The name of the required extension.
*/
void removeExtensionRequired(const std::string& extensionName);
/**
* @brief Determines whether a given extension name is listed in the tileset's
* {@link TilesetSpec::extensionsUsed} property.
*
* @param extensionName The extension name to check.
* @returns True if the extension is found in `extensionsUsed`; otherwise,
* false.
*/
bool isExtensionUsed(const std::string& extensionName) const noexcept;
/**
* @brief Determines whether a given extension name is listed in the tileset's
* {@link TilesetSpec::extensionsRequired} property.
*
* @param extensionName The extension name to check.
* @returns True if the extension is found in `extensionsRequired`; otherwise,
* false.
*/
bool isExtensionRequired(const std::string& extensionName) const noexcept;
};
} // namespace Cesium3DTiles

View File

@ -0,0 +1,163 @@
#include <Cesium3DTiles/Content.h>
#include <Cesium3DTiles/Tile.h>
#include <Cesium3DTiles/Tileset.h>
#include <glm/ext/matrix_double4x4.hpp>
#include <algorithm>
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace Cesium3DTiles {
namespace {
std::optional<glm::dmat4> getTileTransform(const Cesium3DTiles::Tile& tile) {
const std::vector<double>& transform = tile.transform;
if (transform.empty()) {
return glm::dmat4(1.0);
}
if (transform.size() < 16) {
return std::nullopt;
}
return glm::dmat4(
glm::dvec4(transform[0], transform[1], transform[2], transform[3]),
glm::dvec4(transform[4], transform[5], transform[6], transform[7]),
glm::dvec4(transform[8], transform[9], transform[10], transform[11]),
glm::dvec4(transform[12], transform[13], transform[14], transform[15]));
}
template <typename TCallback>
void forEachContentRecursive(
const glm::dmat4& transform,
const Tileset& tileset,
const Tile& tile,
TCallback& callback) {
glm::dmat4 tileTransform =
transform * getTileTransform(tile).value_or(glm::dmat4(1.0));
if (tile.content) {
callback(tileset, tile, *tile.content, tileTransform);
}
// 3D Tiles 1.1 multiple contents
for (const Cesium3DTiles::Content& content : tile.contents) {
callback(tileset, tile, content, tileTransform);
}
for (const Cesium3DTiles::Tile& childTile : tile.children) {
forEachContentRecursive(tileTransform, tileset, childTile, callback);
}
}
template <typename TCallback>
void forEachTileRecursive(
const glm::dmat4& transform,
const Tileset& tileset,
const Tile& tile,
TCallback& callback) {
glm::dmat4 tileTransform =
transform * getTileTransform(tile).value_or(glm::dmat4(1.0));
callback(tileset, tile, tileTransform);
for (const Cesium3DTiles::Tile& childTile : tile.children) {
forEachTileRecursive(tileTransform, tileset, childTile, callback);
}
}
} // namespace
void Tileset::forEachTile(std::function<ForEachTileCallback>&& callback) {
return const_cast<const Tileset*>(this)->forEachTile(
[&callback](
const Tileset& tileset,
const Tile& tile,
const glm::dmat4& transform) {
callback(
const_cast<Tileset&>(tileset),
const_cast<Tile&>(tile),
transform);
});
}
void Tileset::forEachTile(
std::function<ForEachTileConstCallback>&& callback) const {
forEachTileRecursive(glm::dmat4(1.0), *this, this->root, callback);
}
void Tileset::forEachContent(std::function<ForEachContentCallback>&& callback) {
return const_cast<const Tileset*>(this)->forEachContent(
[&callback](
const Tileset& tileset,
const Tile& tile,
const Content& content,
const glm::dmat4& transform) {
callback(
const_cast<Tileset&>(tileset),
const_cast<Tile&>(tile),
const_cast<Content&>(content),
transform);
});
}
void Tileset::forEachContent(
std::function<ForEachContentConstCallback>&& callback) const {
forEachContentRecursive(glm::dmat4(1.0), *this, this->root, callback);
}
void Tileset::addExtensionUsed(const std::string& extensionName) {
if (!this->isExtensionUsed(extensionName)) {
this->extensionsUsed.emplace_back(extensionName);
}
}
void Tileset::addExtensionRequired(const std::string& extensionName) {
this->addExtensionUsed(extensionName);
if (!this->isExtensionRequired(extensionName)) {
this->extensionsRequired.emplace_back(extensionName);
}
}
void Tileset::removeExtensionUsed(const std::string& extensionName) {
this->extensionsUsed.erase(
std::remove(
this->extensionsUsed.begin(),
this->extensionsUsed.end(),
extensionName),
this->extensionsUsed.end());
}
void Tileset::removeExtensionRequired(const std::string& extensionName) {
this->removeExtensionUsed(extensionName);
this->extensionsRequired.erase(
std::remove(
this->extensionsRequired.begin(),
this->extensionsRequired.end(),
extensionName),
this->extensionsRequired.end());
}
bool Tileset::isExtensionUsed(const std::string& extensionName) const noexcept {
return std::find(
this->extensionsUsed.begin(),
this->extensionsUsed.end(),
extensionName) != this->extensionsUsed.end();
}
bool Tileset::isExtensionRequired(
const std::string& extensionName) const noexcept {
return std::find(
this->extensionsRequired.begin(),
this->extensionsRequired.end(),
extensionName) != this->extensionsRequired.end();
}
} // namespace Cesium3DTiles

View File

@ -0,0 +1,303 @@
#include <Cesium3DTiles/Tileset.h>
#include <doctest/doctest.h>
#include <cstring>
using namespace Cesium3DTiles;
TEST_CASE("forEachTile") {
Tileset tileset;
Tile& root = tileset.root;
root.children.emplace_back();
root.children.emplace_back();
Tile& child0 = root.children[0];
Tile& child1 = root.children[1];
Tile& grandchild = child0.children.emplace_back();
glm::dmat4 rootTransform = glm::dmat4(2.0);
glm::dmat4 child0Transform = glm::dmat4(3.0);
glm::dmat4 child1Transform = glm::dmat4(4.0);
glm::dmat4 grandchildTransform = glm::dmat4(5.0);
std::memcpy(root.transform.data(), &rootTransform, sizeof(glm::dmat4));
std::memcpy(child0.transform.data(), &child0Transform, sizeof(glm::dmat4));
std::memcpy(child1.transform.data(), &child1Transform, sizeof(glm::dmat4));
std::memcpy(
grandchild.transform.data(),
&grandchildTransform,
sizeof(glm::dmat4));
glm::dmat4 expectedRootTransform = rootTransform;
glm::dmat4 expectedChild0Transform = rootTransform * child0Transform;
glm::dmat4 expectedChild1Transform = rootTransform * child1Transform;
glm::dmat4 expectedGrandchildTransform =
rootTransform * child0Transform * grandchildTransform;
std::vector<glm::dmat4> transforms;
tileset.forEachTile(
[&transforms](
Tileset& /* tileset */,
Tile& /* tile */,
const glm::dmat4& transform) { transforms.push_back(transform); });
REQUIRE(transforms.size() == 4);
CHECK(transforms[0] == expectedRootTransform);
CHECK(transforms[1] == expectedChild0Transform);
CHECK(transforms[2] == expectedGrandchildTransform);
CHECK(transforms[3] == expectedChild1Transform);
}
TEST_CASE("forEachContent") {
Tileset tileset;
Tile& root = tileset.root;
root.children.emplace_back();
root.children.emplace_back();
Tile& child0 = root.children[0];
Tile& child1 = root.children[1];
Tile& grandchild = child0.children.emplace_back();
const Content& rootContent = root.content.emplace();
const Content& child1Content = child1.content.emplace();
grandchild.contents.emplace_back();
grandchild.contents.emplace_back();
const Content& grandchildContent0 = grandchild.contents[0];
const Content& grandchildContent1 = grandchild.contents[1];
glm::dmat4 rootTransform = glm::dmat4(2.0);
glm::dmat4 child0Transform = glm::dmat4(3.0);
glm::dmat4 child1Transform = glm::dmat4(4.0);
glm::dmat4 grandchildTransform = glm::dmat4(5.0);
std::memcpy(root.transform.data(), &rootTransform, sizeof(glm::dmat4));
std::memcpy(child0.transform.data(), &child0Transform, sizeof(glm::dmat4));
std::memcpy(child1.transform.data(), &child1Transform, sizeof(glm::dmat4));
std::memcpy(
grandchild.transform.data(),
&grandchildTransform,
sizeof(glm::dmat4));
glm::dmat4 expectedRootTransform = rootTransform;
glm::dmat4 expectedChild1Transform = rootTransform * child1Transform;
glm::dmat4 expectedGrandchildTransform =
rootTransform * child0Transform * grandchildTransform;
std::vector<glm::dmat4> transforms;
std::vector<Cesium3DTiles::Content*> contents;
tileset.forEachContent([&transforms, &contents](
Tileset& /* tileset */,
Tile& /* tile */,
Content& content,
const glm::dmat4& transform) {
transforms.push_back(transform);
contents.push_back(&content);
});
REQUIRE(transforms.size() == 4);
CHECK(transforms[0] == expectedRootTransform);
CHECK(transforms[1] == expectedGrandchildTransform);
CHECK(transforms[2] == expectedGrandchildTransform);
CHECK(transforms[3] == expectedChild1Transform);
REQUIRE(contents.size() == 4);
CHECK(contents[0] == &rootContent);
CHECK(contents[1] == &grandchildContent0);
CHECK(contents[2] == &grandchildContent1);
CHECK(contents[3] == &child1Content);
}
TEST_CASE("addExtensionUsed") {
SUBCASE("adds a new extension") {
Tileset tileset;
tileset.addExtensionUsed("Foo");
tileset.addExtensionUsed("Bar");
CHECK(tileset.extensionsUsed.size() == 2);
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Foo") != tileset.extensionsUsed.end());
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Bar") != tileset.extensionsUsed.end());
}
SUBCASE("does not add a duplicate extension") {
Tileset tileset;
tileset.addExtensionUsed("Foo");
tileset.addExtensionUsed("Bar");
tileset.addExtensionUsed("Foo");
CHECK(tileset.extensionsUsed.size() == 2);
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Foo") != tileset.extensionsUsed.end());
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Bar") != tileset.extensionsUsed.end());
}
SUBCASE("does not also add the extension to extensionsRequired") {
Tileset tileset;
tileset.addExtensionUsed("Foo");
CHECK(tileset.extensionsRequired.empty());
}
}
TEST_CASE("addExtensionRequired") {
SUBCASE("adds a new extension") {
Tileset tileset;
tileset.addExtensionRequired("Foo");
tileset.addExtensionRequired("Bar");
CHECK(tileset.extensionsRequired.size() == 2);
CHECK(
std::find(
tileset.extensionsRequired.begin(),
tileset.extensionsRequired.end(),
"Foo") != tileset.extensionsRequired.end());
CHECK(
std::find(
tileset.extensionsRequired.begin(),
tileset.extensionsRequired.end(),
"Bar") != tileset.extensionsRequired.end());
}
SUBCASE("does not add a duplicate extension") {
Tileset tileset;
tileset.addExtensionRequired("Foo");
tileset.addExtensionRequired("Bar");
tileset.addExtensionRequired("Foo");
CHECK(tileset.extensionsRequired.size() == 2);
CHECK(
std::find(
tileset.extensionsRequired.begin(),
tileset.extensionsRequired.end(),
"Foo") != tileset.extensionsRequired.end());
CHECK(
std::find(
tileset.extensionsRequired.begin(),
tileset.extensionsRequired.end(),
"Bar") != tileset.extensionsRequired.end());
}
SUBCASE("also adds the extension to extensionsUsed if not already present") {
Tileset tileset;
tileset.addExtensionUsed("Bar");
tileset.addExtensionRequired("Foo");
tileset.addExtensionRequired("Bar");
CHECK(tileset.extensionsUsed.size() == 2);
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Foo") != tileset.extensionsUsed.end());
CHECK(
std::find(
tileset.extensionsUsed.begin(),
tileset.extensionsUsed.end(),
"Bar") != tileset.extensionsUsed.end());
}
}
TEST_CASE("removeExtensionUsed") {
SUBCASE("removes an extension") {
Tileset tileset;
tileset.extensionsUsed = {"Foo", "Bar"};
tileset.removeExtensionUsed("Foo");
CHECK(tileset.extensionsUsed == std::vector<std::string>{"Bar"});
tileset.removeExtensionUsed("Bar");
CHECK(tileset.extensionsUsed.empty());
tileset.removeExtensionUsed("Other");
CHECK(tileset.extensionsUsed.empty());
}
SUBCASE("does not also remove the extension from extensionsRequired") {
Tileset tileset;
tileset.extensionsUsed = {"Foo"};
tileset.extensionsRequired = {"Foo"};
tileset.removeExtensionUsed("Foo");
CHECK(tileset.extensionsUsed.empty());
CHECK(!tileset.extensionsRequired.empty());
}
}
TEST_CASE("removeExtensionRequired") {
SUBCASE("removes an extension") {
Tileset tileset;
tileset.extensionsRequired = {"Foo", "Bar"};
tileset.removeExtensionRequired("Foo");
CHECK(tileset.extensionsRequired == std::vector<std::string>{"Bar"});
tileset.removeExtensionRequired("Bar");
CHECK(tileset.extensionsRequired.empty());
tileset.removeExtensionRequired("Other");
CHECK(tileset.extensionsRequired.empty());
}
SUBCASE("also removes the extension from extensionsUsed if present") {
Tileset tileset;
tileset.extensionsUsed = {"Foo"};
tileset.extensionsRequired = {"Foo"};
tileset.removeExtensionRequired("Foo");
CHECK(tileset.extensionsUsed.empty());
CHECK(tileset.extensionsRequired.empty());
}
}
TEST_CASE("isExtensionUsed") {
Tileset tileset;
tileset.extensionsUsed = {"Foo", "Bar"};
CHECK(tileset.isExtensionUsed("Foo"));
CHECK(tileset.isExtensionUsed("Bar"));
CHECK_FALSE(tileset.isExtensionUsed("Baz"));
}
TEST_CASE("isExtensionRequired") {
Tileset tileset;
tileset.extensionsRequired = {"Foo", "Bar"};
CHECK(tileset.isExtensionRequired("Foo"));
CHECK(tileset.isExtensionRequired("Bar"));
CHECK_FALSE(tileset.isExtensionRequired("Baz"));
}

View File

@ -658,6 +658,24 @@ TEST_CASE("Model::removeExtensionRequired") {
}
}
TEST_CASE("Model::isExtensionUsed") {
Model m;
m.extensionsUsed = {"Foo", "Bar"};
CHECK(m.isExtensionUsed("Foo"));
CHECK(m.isExtensionUsed("Bar"));
CHECK_FALSE(m.isExtensionUsed("Baz"));
}
TEST_CASE("Model::isExtensionRequired") {
Model m;
m.extensionsRequired = {"Foo", "Bar"};
CHECK(m.isExtensionRequired("Foo"));
CHECK(m.isExtensionRequired("Bar"));
CHECK_FALSE(m.isExtensionRequired("Baz"));
}
TEST_CASE("Model::merge") {
SUBCASE("performs a simple merge") {
Model m1;

View File

@ -1,5 +1,8 @@
{
"classes": {
"Tileset": {
"toBeInherited": true
},
"Root Property": {
"overrideName": "CesiumUtility::ExtensibleObject",
"manuallyDefined": true