Merge from main
This commit is contained in:
commit
3832758a15
|
|
@ -12,10 +12,15 @@
|
|||
- 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.
|
||||
- The `schemaUri` property in the `EXT_structural_metadata` glTF extension is now supported, allowing structural metadata schemas to be loaded from URIs rather than being embedded in the glTF itself.
|
||||
- Added `getHeightSampler` method to `TilesetContentLoader`, allowing loaders to optionally provide a custom, more efficient means of querying heights using the `ITilesetHeightSampler` interface.
|
||||
|
||||
##### Fixes :wrench:
|
||||
|
||||
- Updated the CMake install process to install the vcpkg-built Debug binaries in Debug builds. Previously the Release binaries were installed instead.
|
||||
- Fixed a crash that would occur for raster overlays attempting to dereference a null `CreditSystem`.
|
||||
- Fixed a bug where an empty `extensions` object would get written if an `ExtensibleObject` only had unregistered extensions.
|
||||
- Tightened the tolerance of `IntersectionTests::rayTriangleParametric`, allowing it to find intersections with smaller triangles.
|
||||
- Fixed a bug that could cause `GltfUtilities::intersectRayGltfModel` to crash when the model contains a primitive whose position accessor does not have min/max values.
|
||||
|
||||
### v0.41.0 - 2024-11-01
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "ITilesetHeightSampler.h"
|
||||
|
||||
#include <Cesium3DTilesSelection/Tileset.h>
|
||||
#include <CesiumGeometry/QuadtreeTilingScheme.h>
|
||||
|
||||
|
|
@ -8,7 +10,8 @@ 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 {
|
||||
class EllipsoidTilesetLoader : public TilesetContentLoader,
|
||||
public ITilesetHeightSampler {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new instance.
|
||||
|
|
@ -35,6 +38,12 @@ public:
|
|||
const CesiumGeospatial::Ellipsoid& ellipsoid
|
||||
CESIUM_DEFAULT_ELLIPSOID) override;
|
||||
|
||||
ITilesetHeightSampler* getHeightSampler() override;
|
||||
|
||||
CesiumAsync::Future<SampleHeightResult> sampleHeights(
|
||||
const CesiumAsync::AsyncSystem& asyncSystem,
|
||||
std::vector<CesiumGeospatial::Cartographic>&& positions) override;
|
||||
|
||||
private:
|
||||
struct Geometry {
|
||||
std::vector<uint16_t> indices;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "Library.h"
|
||||
#include "SampleHeightResult.h"
|
||||
|
||||
#include <CesiumAsync/Future.h>
|
||||
#include <CesiumGeospatial/Cartographic.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace CesiumAsync {
|
||||
class AsyncSystem;
|
||||
}
|
||||
|
||||
namespace Cesium3DTilesSelection {
|
||||
|
||||
/**
|
||||
* @brief An interface to query heights from a tileset that can do so
|
||||
* efficiently without necessarily downloading individual tiles.
|
||||
*/
|
||||
class CESIUM3DTILESSELECTION_API ITilesetHeightSampler {
|
||||
public:
|
||||
/**
|
||||
* @brief Queries the heights at a list of locations.
|
||||
*
|
||||
* @param asyncSystem The async system used to do work in threads.
|
||||
* @param positions The positions at which to query heights. The height field
|
||||
* of each {@link Cartographic} is ignored.
|
||||
* @return A future that will be resolved when the heights have been queried.
|
||||
*/
|
||||
virtual CesiumAsync::Future<SampleHeightResult> sampleHeights(
|
||||
const CesiumAsync::AsyncSystem& asyncSystem,
|
||||
std::vector<CesiumGeospatial::Cartographic>&& positions) = 0;
|
||||
};
|
||||
|
||||
} // namespace Cesium3DTilesSelection
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "TileLoadResult.h"
|
||||
#include "TilesetOptions.h"
|
||||
|
||||
#include <Cesium3DTilesSelection/SampleHeightResult.h>
|
||||
#include <CesiumAsync/AsyncSystem.h>
|
||||
#include <CesiumAsync/Future.h>
|
||||
#include <CesiumAsync/IAssetAccessor.h>
|
||||
|
|
@ -23,6 +24,7 @@
|
|||
|
||||
namespace Cesium3DTilesSelection {
|
||||
class Tile;
|
||||
class ITilesetHeightSampler;
|
||||
|
||||
/**
|
||||
* @brief Store the parameters that are needed to load a tile
|
||||
|
|
@ -145,5 +147,24 @@ public:
|
|||
const Tile& tile,
|
||||
const CesiumGeospatial::Ellipsoid& ellipsoid
|
||||
CESIUM_DEFAULT_ELLIPSOID) = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets an interface that can be used to efficiently query heights from
|
||||
* this tileset.
|
||||
*
|
||||
* Some loaders may be able to query heights very efficiently by using a web
|
||||
* service or by using an analytical model, e.g., when the "terrain" is a
|
||||
* simple ellipsoid.
|
||||
*
|
||||
* For loaders that have no particular way to query heights, this method will
|
||||
* return `nullptr`, signaling that heights should be computed by downloading
|
||||
* and sampling individual tiles.
|
||||
*
|
||||
* @return The interface that can be used to efficiently query heights from
|
||||
* this loader, or `nullptr` if this loader has no particular way to do that.
|
||||
* The returned instance must have a lifetime that is at least as long as the
|
||||
* loader itself.
|
||||
*/
|
||||
virtual ITilesetHeightSampler* getHeightSampler() { return nullptr; }
|
||||
};
|
||||
} // namespace Cesium3DTilesSelection
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ TileChildrenResult EllipsoidTilesetLoader::createTileChildren(
|
|||
const QuadtreeTileID* pParentID =
|
||||
std::get_if<QuadtreeTileID>(&tile.getTileID());
|
||||
|
||||
if (pParentID) {
|
||||
// Due to the use of uint32_t for QuadtreeTileID X and Y, we can only support
|
||||
// through level 30.
|
||||
if (pParentID && pParentID->level < 30) {
|
||||
std::vector<Tile> children;
|
||||
QuadtreeChildren childIDs =
|
||||
ImplicitTilingUtilities::getChildren(*pParentID);
|
||||
|
|
@ -87,6 +89,25 @@ TileChildrenResult EllipsoidTilesetLoader::createTileChildren(
|
|||
return TileChildrenResult{{}, TileLoadResultState::Failed};
|
||||
}
|
||||
|
||||
ITilesetHeightSampler* EllipsoidTilesetLoader::getHeightSampler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
CesiumAsync::Future<SampleHeightResult> EllipsoidTilesetLoader::sampleHeights(
|
||||
const CesiumAsync::AsyncSystem& asyncSystem,
|
||||
std::vector<CesiumGeospatial::Cartographic>&& positions) {
|
||||
SampleHeightResult result;
|
||||
|
||||
result.positions = std::move(positions);
|
||||
result.sampleSuccess.resize(result.positions.size(), true);
|
||||
|
||||
for (Cartographic& position : result.positions) {
|
||||
position.height = 0.0;
|
||||
}
|
||||
|
||||
return asyncSystem.createResolvedFuture(std::move(result));
|
||||
}
|
||||
|
||||
void EllipsoidTilesetLoader::createChildTile(
|
||||
const Tile& parent,
|
||||
std::vector<Tile>& children,
|
||||
|
|
@ -120,7 +141,9 @@ EllipsoidTilesetLoader::Geometry
|
|||
EllipsoidTilesetLoader::createGeometry(const Tile& tile) const {
|
||||
static constexpr uint16_t resolution = 24;
|
||||
|
||||
std::vector<uint16_t> indices(6 * (resolution - 1) * (resolution - 1));
|
||||
std::vector<uint16_t> indices;
|
||||
indices.reserve(6 * (resolution - 1) * (resolution - 1));
|
||||
|
||||
std::vector<glm::vec3> vertices(resolution * resolution);
|
||||
std::vector<glm::vec3> normals(vertices.size());
|
||||
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ Tileset::updateView(const std::vector<ViewState>& frustums, float deltaTime) {
|
|||
}
|
||||
|
||||
TilesetHeightRequest::processHeightRequests(
|
||||
this->getAsyncSystem(),
|
||||
*this->_pTilesetContentManager,
|
||||
this->_options,
|
||||
this->_loadedTiles,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "TileUtilities.h"
|
||||
#include "TilesetContentManager.h"
|
||||
|
||||
#include <Cesium3DTilesSelection/ITilesetHeightSampler.h>
|
||||
#include <Cesium3DTilesSelection/SampleHeightResult.h>
|
||||
#include <CesiumGeometry/IntersectionTests.h>
|
||||
#include <CesiumGeospatial/GlobeRectangle.h>
|
||||
|
|
@ -200,6 +201,7 @@ void TilesetHeightQuery::findCandidateTiles(
|
|||
}
|
||||
|
||||
/*static*/ void TilesetHeightRequest::processHeightRequests(
|
||||
const AsyncSystem& asyncSystem,
|
||||
TilesetContentManager& contentManager,
|
||||
const TilesetOptions& options,
|
||||
Tile::LoadedLinkedList& loadedTiles,
|
||||
|
|
@ -214,6 +216,7 @@ void TilesetHeightQuery::findCandidateTiles(
|
|||
for (auto it = heightRequests.begin(); it != heightRequests.end();) {
|
||||
TilesetHeightRequest& request = *it;
|
||||
if (!request.tryCompleteHeightRequest(
|
||||
asyncSystem,
|
||||
contentManager,
|
||||
options,
|
||||
loadedTiles,
|
||||
|
|
@ -249,10 +252,35 @@ void Cesium3DTilesSelection::TilesetHeightRequest::failHeightRequests(
|
|||
}
|
||||
|
||||
bool TilesetHeightRequest::tryCompleteHeightRequest(
|
||||
const AsyncSystem& asyncSystem,
|
||||
TilesetContentManager& contentManager,
|
||||
const TilesetOptions& options,
|
||||
Tile::LoadedLinkedList& loadedTiles,
|
||||
std::set<Tile*>& tileLoadSet) {
|
||||
// If this TilesetContentLoader supports direct height queries, use that
|
||||
// instead of downloading tiles.
|
||||
if (contentManager.getRootTile() &&
|
||||
contentManager.getRootTile()->getLoader()) {
|
||||
ITilesetHeightSampler* pSampler =
|
||||
contentManager.getRootTile()->getLoader()->getHeightSampler();
|
||||
if (pSampler) {
|
||||
std::vector<Cartographic> positions;
|
||||
positions.reserve(this->queries.size());
|
||||
for (TilesetHeightQuery& query : this->queries) {
|
||||
positions.emplace_back(query.inputPosition);
|
||||
}
|
||||
|
||||
pSampler->sampleHeights(asyncSystem, std::move(positions))
|
||||
.thenImmediately(
|
||||
[promise = this->promise](SampleHeightResult&& result) {
|
||||
promise.resolve(std::move(result));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No direct height query possible, so download and sample tiles.
|
||||
bool tileStillNeedsLoading = false;
|
||||
std::vector<std::string> warnings;
|
||||
for (TilesetHeightQuery& query : this->queries) {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ struct TilesetHeightRequest {
|
|||
* @brief Process a given list of height requests. This is called by the {@link Tileset}
|
||||
* in every call to {@link Tileset::updateView}.
|
||||
*
|
||||
* @param asyncSystem The async system used to do work in threads.
|
||||
* @param contentManager The content manager.
|
||||
* @param options Options associated with the tileset.
|
||||
* @param loadedTiles The linked list of loaded tiles, used to ensure that
|
||||
|
|
@ -137,6 +138,7 @@ struct TilesetHeightRequest {
|
|||
* height requests can complete are added to this vector.
|
||||
*/
|
||||
static void processHeightRequests(
|
||||
const CesiumAsync::AsyncSystem& asyncSystem,
|
||||
TilesetContentManager& contentManager,
|
||||
const TilesetOptions& options,
|
||||
Tile::LoadedLinkedList& loadedTiles,
|
||||
|
|
@ -160,6 +162,7 @@ struct TilesetHeightRequest {
|
|||
* @brief Tries to complete this height request. Returns false if further data
|
||||
* still needs to be loaded and thus the request cannot yet complete.
|
||||
*
|
||||
* @param asyncSystem The async system used to do work in threads.
|
||||
* @param contentManager The content manager.
|
||||
* @param options Options associated with the tileset.
|
||||
* @param loadedTiles The linked list of loaded tiles, used to ensure that
|
||||
|
|
@ -169,6 +172,7 @@ struct TilesetHeightRequest {
|
|||
* can complete.
|
||||
*/
|
||||
bool tryCompleteHeightRequest(
|
||||
const CesiumAsync::AsyncSystem& asyncSystem,
|
||||
TilesetContentManager& contentManager,
|
||||
const TilesetOptions& options,
|
||||
Tile::LoadedLinkedList& loadedTiles,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
|
||||
#include <Cesium3DTilesSelection/EllipsoidTilesetLoader.h>
|
||||
#include <Cesium3DTilesSelection/Tileset.h>
|
||||
#include <CesiumGeospatial/Cartographic.h>
|
||||
#include <CesiumNativeTests/FileAccessor.h>
|
||||
|
|
@ -232,4 +233,38 @@ TEST_CASE("Tileset height queries") {
|
|||
CHECK(!results.sampleSuccess[0]);
|
||||
CHECK(results.warnings[0].find("failed to load") != std::string::npos);
|
||||
}
|
||||
|
||||
SECTION("ellipsoid tileset") {
|
||||
std::unique_ptr<Tileset> pTileset =
|
||||
EllipsoidTilesetLoader::createTileset(externals);
|
||||
|
||||
Future<SampleHeightResult> future = pTileset->sampleHeightMostDetailed(
|
||||
{Cartographic::fromDegrees(-75.612559, 40.042183, 1.0)});
|
||||
|
||||
while (!future.isReady()) {
|
||||
pTileset->updateView({});
|
||||
}
|
||||
|
||||
SampleHeightResult results = future.waitInMainThread();
|
||||
|
||||
REQUIRE(results.warnings.size() == 0);
|
||||
REQUIRE(results.positions.size() == 1);
|
||||
REQUIRE(results.sampleSuccess.size() == 1);
|
||||
CHECK(results.sampleSuccess[0]);
|
||||
CHECK(Math::equalsEpsilon(
|
||||
results.positions[0].longitude,
|
||||
Math::degreesToRadians(-75.612559),
|
||||
0.0,
|
||||
Math::Epsilon4));
|
||||
CHECK(Math::equalsEpsilon(
|
||||
results.positions[0].latitude,
|
||||
Math::degreesToRadians(40.042183),
|
||||
0.0,
|
||||
Math::Epsilon4));
|
||||
CHECK(Math::equalsEpsilon(
|
||||
results.positions[0].height,
|
||||
0.0,
|
||||
0.0,
|
||||
Math::Epsilon4));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ void writeExtensibleObject(
|
|||
CesiumJsonWriter::JsonWriter& jsonWriter,
|
||||
const CesiumJsonWriter::ExtensionWriterContext& context) {
|
||||
|
||||
if (!obj.extensions.empty()) {
|
||||
if (hasRegisteredExtensions(obj, jsonWriter, context)) {
|
||||
jsonWriter.Key("extensions");
|
||||
writeJsonExtensions(obj, jsonWriter, context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ std::optional<double> IntersectionTests::rayTriangleParametric(
|
|||
glm::dvec3 p = glm::cross(direction, edge1);
|
||||
double det = glm::dot(edge0, p);
|
||||
if (cullBackFaces) {
|
||||
if (det < Math::Epsilon6)
|
||||
if (det < Math::Epsilon8)
|
||||
return std::nullopt;
|
||||
|
||||
glm::dvec3 tvec = origin - p0;
|
||||
|
|
@ -158,7 +158,7 @@ std::optional<double> IntersectionTests::rayTriangleParametric(
|
|||
|
||||
} else {
|
||||
|
||||
if (glm::abs(det) < Math::Epsilon6)
|
||||
if (glm::abs(det) < Math::Epsilon8)
|
||||
return std::nullopt;
|
||||
|
||||
double invDet = 1.0 / det;
|
||||
|
|
|
|||
|
|
@ -1265,22 +1265,25 @@ std::optional<glm::dvec3> intersectRayScenePrimitive(
|
|||
glm::dmat4x4 worldToPrimitive = glm::inverse(primitiveToWorld);
|
||||
CesiumGeometry::Ray transformedRay = ray.transform(worldToPrimitive);
|
||||
|
||||
// Ignore primitive if ray doesn't intersect bounding box
|
||||
// Ignore primitive if we have an AABB from the accessor min/max and the ray
|
||||
// doesn't intersect it.
|
||||
const std::vector<double>& min = positionAccessor.min;
|
||||
const std::vector<double>& max = positionAccessor.max;
|
||||
|
||||
std::optional<double> boxT =
|
||||
CesiumGeometry::IntersectionTests::rayAABBParametric(
|
||||
transformedRay,
|
||||
CesiumGeometry::AxisAlignedBox(
|
||||
min[0],
|
||||
min[1],
|
||||
min[2],
|
||||
max[0],
|
||||
max[1],
|
||||
max[2]));
|
||||
if (!boxT)
|
||||
return std::optional<glm::dvec3>();
|
||||
if (min.size() >= 3 && max.size() >= 3) {
|
||||
std::optional<double> boxT =
|
||||
CesiumGeometry::IntersectionTests::rayAABBParametric(
|
||||
transformedRay,
|
||||
CesiumGeometry::AxisAlignedBox(
|
||||
min[0],
|
||||
min[1],
|
||||
min[2],
|
||||
max[0],
|
||||
max[1],
|
||||
max[2]));
|
||||
if (!boxT)
|
||||
return std::optional<glm::dvec3>();
|
||||
}
|
||||
|
||||
double tClosest = -1.0;
|
||||
|
||||
|
|
|
|||
|
|
@ -526,7 +526,7 @@ void writeExtensibleObject(
|
|||
CesiumJsonWriter::JsonWriter& jsonWriter,
|
||||
const CesiumJsonWriter::ExtensionWriterContext& context) {
|
||||
|
||||
if (!obj.extensions.empty()) {
|
||||
if (hasRegisteredExtensions(obj, jsonWriter, context)) {
|
||||
jsonWriter.Key("extensions");
|
||||
writeJsonExtensions(obj, jsonWriter, context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,19 +20,38 @@ void writeJsonExtensions(
|
|||
item.first,
|
||||
item.second,
|
||||
TExtended::TypeName);
|
||||
if (!handler) {
|
||||
if (context.getExtensionState(item.first) != ExtensionState::Disabled) {
|
||||
jsonWriter.emplaceWarning(fmt::format(
|
||||
"Encountered unregistered extension {}. This extension will be "
|
||||
"ignored. To silence this warning, disable the extension with "
|
||||
"ExtensionWriterContext::setExtensionState.",
|
||||
item.first));
|
||||
}
|
||||
continue;
|
||||
if (handler) {
|
||||
jsonWriter.Key(item.first);
|
||||
handler(item.second, jsonWriter, context);
|
||||
}
|
||||
jsonWriter.Key(item.first);
|
||||
handler(item.second, jsonWriter, context);
|
||||
}
|
||||
jsonWriter.EndObject();
|
||||
}
|
||||
|
||||
template <typename TExtended>
|
||||
bool hasRegisteredExtensions(
|
||||
const TExtended& obj,
|
||||
JsonWriter& jsonWriter,
|
||||
const ExtensionWriterContext& context) {
|
||||
bool hasRegisteredExtensions = false;
|
||||
|
||||
for (const auto& item : obj.extensions) {
|
||||
auto handler = context.createExtensionHandler(
|
||||
item.first,
|
||||
item.second,
|
||||
TExtended::TypeName);
|
||||
if (handler) {
|
||||
hasRegisteredExtensions = true;
|
||||
} else if (
|
||||
context.getExtensionState(item.first) != ExtensionState::Disabled) {
|
||||
jsonWriter.emplaceWarning(fmt::format(
|
||||
"Encountered unregistered extension {}. This extension will be "
|
||||
"ignored. To silence this warning, disable the extension with "
|
||||
"ExtensionWriterContext::setExtensionState.",
|
||||
item.first));
|
||||
}
|
||||
}
|
||||
|
||||
return hasRegisteredExtensions;
|
||||
}
|
||||
} // namespace CesiumJsonWriter
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ struct CESIUMQUANTIZEDMESHTERRAIN_API LayerSpec
|
|||
return accum;
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
/**
|
||||
* @brief This class is not meant to be instantiated directly. Use {@link Layer} instead.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ void writeExtensibleObject(
|
|||
CesiumJsonWriter::JsonWriter& jsonWriter,
|
||||
const CesiumJsonWriter::ExtensionWriterContext& context) {
|
||||
|
||||
if (!obj.extensions.empty()) {
|
||||
if (hasRegisteredExtensions(obj, jsonWriter, context)) {
|
||||
jsonWriter.Key("extensions");
|
||||
writeJsonExtensions(obj, jsonWriter, context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@
|
|||
|
||||
#include <CesiumAsync/AsyncSystem.h>
|
||||
#include <CesiumAsync/IAssetAccessor.h>
|
||||
#include <CesiumAsync/SharedAssetDepot.h>
|
||||
#include <CesiumGeometry/QuadtreeTileID.h>
|
||||
#include <CesiumGeometry/QuadtreeTilingScheme.h>
|
||||
#include <CesiumUtility/CreditSystem.h>
|
||||
#include <CesiumUtility/Result.h>
|
||||
#include <CesiumUtility/SharedAsset.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
|
@ -111,12 +114,26 @@ private:
|
|||
virtual CesiumAsync::Future<LoadedRasterOverlayImage>
|
||||
loadTileImage(RasterOverlayTile& overlayTile) override final;
|
||||
|
||||
struct LoadedQuadtreeImage {
|
||||
struct LoadedQuadtreeImage
|
||||
: public CesiumUtility::SharedAsset<LoadedQuadtreeImage> {
|
||||
LoadedQuadtreeImage(
|
||||
const std::shared_ptr<LoadedRasterOverlayImage>& pLoaded_,
|
||||
const std::optional<CesiumGeometry::Rectangle>& subset_)
|
||||
: pLoaded(pLoaded_), subset(subset_) {}
|
||||
std::shared_ptr<LoadedRasterOverlayImage> pLoaded = nullptr;
|
||||
std::optional<CesiumGeometry::Rectangle> subset = std::nullopt;
|
||||
|
||||
int64_t getSizeBytes() const {
|
||||
int64_t accum = 0;
|
||||
accum += sizeof(LoadedQuadtreeImage);
|
||||
if (pLoaded) {
|
||||
accum += pLoaded->getSizeBytes();
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
};
|
||||
|
||||
CesiumAsync::SharedFuture<LoadedQuadtreeImage>
|
||||
CesiumAsync::SharedFuture<CesiumUtility::ResultPointer<LoadedQuadtreeImage>>
|
||||
getQuadtreeTile(const CesiumGeometry::QuadtreeTileID& tileID);
|
||||
|
||||
/**
|
||||
|
|
@ -129,13 +146,12 @@ private:
|
|||
* data that is required to cover the rectangle with the given geometric
|
||||
* error.
|
||||
*/
|
||||
std::vector<CesiumAsync::SharedFuture<LoadedQuadtreeImage>>
|
||||
std::vector<CesiumAsync::SharedFuture<
|
||||
CesiumUtility::ResultPointer<LoadedQuadtreeImage>>>
|
||||
mapRasterTilesToGeometryTile(
|
||||
const CesiumGeometry::Rectangle& geometryRectangle,
|
||||
const glm::dvec2 targetScreenPixels);
|
||||
|
||||
void unloadCachedTiles();
|
||||
|
||||
struct CombinedImageMeasurements {
|
||||
CesiumGeometry::Rectangle rectangle;
|
||||
int32_t widthPixels;
|
||||
|
|
@ -146,12 +162,13 @@ private:
|
|||
|
||||
static CombinedImageMeasurements measureCombinedImage(
|
||||
const CesiumGeometry::Rectangle& targetRectangle,
|
||||
const std::vector<LoadedQuadtreeImage>& images);
|
||||
const std::vector<CesiumUtility::ResultPointer<LoadedQuadtreeImage>>&
|
||||
images);
|
||||
|
||||
static LoadedRasterOverlayImage combineImages(
|
||||
const CesiumGeometry::Rectangle& targetRectangle,
|
||||
const CesiumGeospatial::Projection& projection,
|
||||
std::vector<LoadedQuadtreeImage>&& images);
|
||||
std::vector<CesiumUtility::ResultPointer<LoadedQuadtreeImage>>&& images);
|
||||
|
||||
uint32_t _minimumLevel;
|
||||
uint32_t _maximumLevel;
|
||||
|
|
@ -159,22 +176,9 @@ private:
|
|||
uint32_t _imageHeight;
|
||||
CesiumGeometry::QuadtreeTilingScheme _tilingScheme;
|
||||
|
||||
struct CacheEntry {
|
||||
CesiumGeometry::QuadtreeTileID tileID;
|
||||
CesiumAsync::SharedFuture<LoadedQuadtreeImage> future;
|
||||
};
|
||||
|
||||
// Tiles at the beginning of this list are the least recently used (oldest),
|
||||
// while the tiles at the end are most recently used (newest).
|
||||
using TileLeastRecentlyUsedList = std::list<CacheEntry>;
|
||||
TileLeastRecentlyUsedList _tilesOldToRecent;
|
||||
|
||||
// Allows a Future to be looked up by quadtree tile ID.
|
||||
std::unordered_map<
|
||||
CesiumGeometry::QuadtreeTileID,
|
||||
TileLeastRecentlyUsedList::iterator>
|
||||
_tileLookup;
|
||||
|
||||
std::atomic<int64_t> _cachedBytes;
|
||||
CesiumUtility::IntrusivePointer<CesiumAsync::SharedAssetDepot<
|
||||
LoadedQuadtreeImage,
|
||||
CesiumGeometry::QuadtreeTileID>>
|
||||
_pTileDepot;
|
||||
};
|
||||
} // namespace CesiumRasterOverlays
|
||||
|
|
|
|||
|
|
@ -61,6 +61,19 @@ struct CESIUMRASTEROVERLAYS_API LoadedRasterOverlayImage {
|
|||
* the bounds of this image.
|
||||
*/
|
||||
bool moreDetailAvailable = false;
|
||||
|
||||
/**
|
||||
* @brief Returns the size of this `LoadedRasterOverlayImage` in bytes.
|
||||
*/
|
||||
size_t getSizeBytes() const {
|
||||
int64_t accum = 0;
|
||||
accum += sizeof(LoadedRasterOverlayImage);
|
||||
accum += this->credits.capacity() * sizeof(CesiumUtility::Credit);
|
||||
if (this->pImage) {
|
||||
accum += this->pImage->getSizeBytes();
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -266,13 +266,18 @@ namespace {
|
|||
* @param pResource The JSON value for the resource
|
||||
* @param pCreditSystem The `CreditSystem` that will create one credit for
|
||||
* each attribution
|
||||
* @return The `CreditAndCoverageAreas` objects that have been parsed
|
||||
* @return The `CreditAndCoverageAreas` objects that have been parsed, or an
|
||||
* empty vector if pCreditSystem is nullptr.
|
||||
*/
|
||||
std::vector<CreditAndCoverageAreas> collectCredits(
|
||||
const rapidjson::Value* pResource,
|
||||
const std::shared_ptr<CreditSystem>& pCreditSystem,
|
||||
bool showCreditsOnScreen) {
|
||||
std::vector<CreditAndCoverageAreas> credits;
|
||||
if (!pCreditSystem) {
|
||||
return credits;
|
||||
}
|
||||
|
||||
const auto attributionsIt = pResource->FindMember("imageryProviders");
|
||||
if (attributionsIt != pResource->MemberEnd() &&
|
||||
attributionsIt->value.IsArray()) {
|
||||
|
|
|
|||
|
|
@ -52,10 +52,89 @@ QuadtreeRasterOverlayTileProvider::QuadtreeRasterOverlayTileProvider(
|
|||
_maximumLevel(maximumLevel),
|
||||
_imageWidth(imageWidth),
|
||||
_imageHeight(imageHeight),
|
||||
_tilingScheme(tilingScheme),
|
||||
_tilesOldToRecent(),
|
||||
_tileLookup(),
|
||||
_cachedBytes(0) {}
|
||||
_tilingScheme(tilingScheme) {
|
||||
auto loadParentTile = [this](const QuadtreeTileID& key)
|
||||
-> Future<ResultPointer<LoadedQuadtreeImage>> {
|
||||
const Rectangle rectangle = this->getTilingScheme().tileToRectangle(key);
|
||||
const QuadtreeTileID parentID(key.level - 1, key.x >> 1, key.y >> 1);
|
||||
return this->getQuadtreeTile(parentID).thenImmediately(
|
||||
[rectangle](const ResultPointer<LoadedQuadtreeImage>& loaded) {
|
||||
ResultPointer<LoadedQuadtreeImage> result(loaded.errors);
|
||||
if (loaded.pValue) {
|
||||
result.pValue.emplace(*loaded.pValue);
|
||||
result.pValue->subset = rectangle;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
this->_pTileDepot.emplace(std::function(
|
||||
[pThis = this, loadParentTile](
|
||||
const AsyncSystem& asyncSystem,
|
||||
[[maybe_unused]] const std::shared_ptr<IAssetAccessor>&
|
||||
pAssetAccessor,
|
||||
const QuadtreeTileID& key)
|
||||
-> Future<ResultPointer<LoadedQuadtreeImage>> {
|
||||
return pThis->loadQuadtreeTileImage(key)
|
||||
.catchImmediately([](std::exception&& e) {
|
||||
// Turn an exception into an error.
|
||||
LoadedRasterOverlayImage result;
|
||||
result.errorList.emplaceError(e.what());
|
||||
return result;
|
||||
})
|
||||
.thenImmediately(
|
||||
[loadParentTile,
|
||||
key,
|
||||
currentLevel = key.level,
|
||||
minimumLevel = pThis->getMinimumLevel(),
|
||||
asyncSystem](LoadedRasterOverlayImage&& loaded)
|
||||
-> Future<ResultPointer<LoadedQuadtreeImage>> {
|
||||
if (loaded.pImage && !loaded.errorList.hasErrors() &&
|
||||
loaded.pImage->width > 0 && loaded.pImage->height > 0) {
|
||||
#if SHOW_TILE_BOUNDARIES
|
||||
// Highlight the edges in red to show tile boundaries.
|
||||
std::span<uint32_t> pixels =
|
||||
reintepretCastSpan<uint32_t, std::byte>(
|
||||
loaded.image->pixelData);
|
||||
for (int32_t j = 0; j < loaded.pImage->height; ++j) {
|
||||
for (int32_t i = 0; i < loaded.pImage->width; ++i) {
|
||||
if (i == 0 || j == 0 || i == loaded.pImage->width - 1 ||
|
||||
j == loaded.pImage->height - 1) {
|
||||
pixels[j * loaded.pImage->width + i] = 0xFF0000FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
IntrusivePointer<LoadedQuadtreeImage> pLoadedImage;
|
||||
pLoadedImage.emplace(
|
||||
std::make_shared<LoadedRasterOverlayImage>(
|
||||
std::move(loaded)),
|
||||
std::nullopt);
|
||||
return asyncSystem.createResolvedFuture(
|
||||
ResultPointer<LoadedQuadtreeImage>(pLoadedImage));
|
||||
}
|
||||
|
||||
// Tile failed to load, try loading the parent tile instead.
|
||||
// We can only initiate a new tile request from the main
|
||||
// thread, though.
|
||||
if (currentLevel > minimumLevel) {
|
||||
return asyncSystem.runInMainThread([key, loadParentTile]() {
|
||||
return loadParentTile(key);
|
||||
});
|
||||
} else {
|
||||
// No parent available, so return the original failed
|
||||
// result.
|
||||
IntrusivePointer<LoadedQuadtreeImage> pLoadedImage;
|
||||
pLoadedImage.emplace(
|
||||
std::make_shared<LoadedRasterOverlayImage>(
|
||||
std::move(loaded)),
|
||||
std::nullopt);
|
||||
return asyncSystem.createResolvedFuture(
|
||||
ResultPointer<LoadedQuadtreeImage>(pLoadedImage));
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
uint32_t QuadtreeRasterOverlayTileProvider::computeLevelFromTargetScreenPixels(
|
||||
const CesiumGeometry::Rectangle& rectangle,
|
||||
|
|
@ -97,11 +176,12 @@ uint32_t QuadtreeRasterOverlayTileProvider::computeLevelFromTargetScreenPixels(
|
|||
}
|
||||
|
||||
std::vector<CesiumAsync::SharedFuture<
|
||||
QuadtreeRasterOverlayTileProvider::LoadedQuadtreeImage>>
|
||||
ResultPointer<QuadtreeRasterOverlayTileProvider::LoadedQuadtreeImage>>>
|
||||
QuadtreeRasterOverlayTileProvider::mapRasterTilesToGeometryTile(
|
||||
const CesiumGeometry::Rectangle& geometryRectangle,
|
||||
const glm::dvec2 targetScreenPixels) {
|
||||
std::vector<CesiumAsync::SharedFuture<LoadedQuadtreeImage>> result;
|
||||
std::vector<CesiumAsync::SharedFuture<ResultPointer<LoadedQuadtreeImage>>>
|
||||
result;
|
||||
|
||||
const QuadtreeTilingScheme& imageryTilingScheme = this->getTilingScheme();
|
||||
|
||||
|
|
@ -271,7 +351,7 @@ QuadtreeRasterOverlayTileProvider::mapRasterTilesToGeometryTile(
|
|||
continue;
|
||||
}
|
||||
|
||||
CesiumAsync::SharedFuture<LoadedQuadtreeImage> pTile =
|
||||
CesiumAsync::SharedFuture<ResultPointer<LoadedQuadtreeImage>> pTile =
|
||||
this->getQuadtreeTile(QuadtreeTileID(level, i, j));
|
||||
result.emplace_back(std::move(pTile));
|
||||
}
|
||||
|
|
@ -281,101 +361,13 @@ QuadtreeRasterOverlayTileProvider::mapRasterTilesToGeometryTile(
|
|||
}
|
||||
|
||||
CesiumAsync::SharedFuture<
|
||||
QuadtreeRasterOverlayTileProvider::LoadedQuadtreeImage>
|
||||
ResultPointer<QuadtreeRasterOverlayTileProvider::LoadedQuadtreeImage>>
|
||||
QuadtreeRasterOverlayTileProvider::getQuadtreeTile(
|
||||
const CesiumGeometry::QuadtreeTileID& tileID) {
|
||||
auto lookupIt = this->_tileLookup.find(tileID);
|
||||
if (lookupIt != this->_tileLookup.end()) {
|
||||
auto& cacheIt = lookupIt->second;
|
||||
|
||||
// Move this entry to the end, indicating it's most recently used.
|
||||
this->_tilesOldToRecent.splice(
|
||||
this->_tilesOldToRecent.end(),
|
||||
this->_tilesOldToRecent,
|
||||
cacheIt);
|
||||
|
||||
return cacheIt->future;
|
||||
}
|
||||
|
||||
// We create this lambda here instead of where it's used below so that we
|
||||
// don't need to pass `this` through a thenImmediately lambda, which would
|
||||
// create the possibility of accidentally using this pointer to a
|
||||
// non-thread-safe object from another thread and creating a (potentially very
|
||||
// subtle) race condition.
|
||||
auto loadParentTile = [tileID, this]() {
|
||||
const Rectangle rectangle = this->getTilingScheme().tileToRectangle(tileID);
|
||||
const QuadtreeTileID parentID(
|
||||
tileID.level - 1,
|
||||
tileID.x >> 1,
|
||||
tileID.y >> 1);
|
||||
return this->getQuadtreeTile(parentID).thenImmediately(
|
||||
[rectangle](const LoadedQuadtreeImage& loaded) {
|
||||
return LoadedQuadtreeImage{loaded.pLoaded, rectangle};
|
||||
});
|
||||
};
|
||||
|
||||
Future<LoadedQuadtreeImage> future =
|
||||
this->loadQuadtreeTileImage(tileID)
|
||||
.catchImmediately([](std::exception&& e) {
|
||||
// Turn an exception into an error.
|
||||
LoadedRasterOverlayImage result;
|
||||
result.errorList.emplaceError(e.what());
|
||||
return result;
|
||||
})
|
||||
.thenImmediately([&cachedBytes = this->_cachedBytes,
|
||||
currentLevel = tileID.level,
|
||||
minimumLevel = this->getMinimumLevel(),
|
||||
asyncSystem = this->getAsyncSystem(),
|
||||
loadParentTile = std::move(loadParentTile)](
|
||||
LoadedRasterOverlayImage&& loaded) {
|
||||
if (loaded.pImage && !loaded.errorList.hasErrors() &&
|
||||
loaded.pImage->width > 0 && loaded.pImage->height > 0) {
|
||||
// Successfully loaded, continue.
|
||||
cachedBytes += int64_t(loaded.pImage->pixelData.size());
|
||||
|
||||
#if SHOW_TILE_BOUNDARIES
|
||||
// Highlight the edges in red to show tile boundaries.
|
||||
std::span<uint32_t> pixels =
|
||||
reintepretCastSpan<uint32_t, std::byte>(
|
||||
loaded.image->pixelData);
|
||||
for (int32_t j = 0; j < loaded.pImage->height; ++j) {
|
||||
for (int32_t i = 0; i < loaded.pImage->width; ++i) {
|
||||
if (i == 0 || j == 0 || i == loaded.pImage->width - 1 ||
|
||||
j == loaded.pImage->height - 1) {
|
||||
pixels[j * loaded.pImage->width + i] = 0xFF0000FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return asyncSystem.createResolvedFuture(LoadedQuadtreeImage{
|
||||
std::make_shared<LoadedRasterOverlayImage>(std::move(loaded)),
|
||||
std::nullopt});
|
||||
}
|
||||
|
||||
// Tile failed to load, try loading the parent tile instead.
|
||||
// We can only initiate a new tile request from the main thread,
|
||||
// though.
|
||||
if (currentLevel > minimumLevel) {
|
||||
return asyncSystem.runInMainThread(loadParentTile);
|
||||
} else {
|
||||
// No parent available, so return the original failed result.
|
||||
return asyncSystem.createResolvedFuture(LoadedQuadtreeImage{
|
||||
std::make_shared<LoadedRasterOverlayImage>(std::move(loaded)),
|
||||
std::nullopt});
|
||||
}
|
||||
});
|
||||
|
||||
auto newIt = this->_tilesOldToRecent.emplace(
|
||||
this->_tilesOldToRecent.end(),
|
||||
CacheEntry{tileID, std::move(future).share()});
|
||||
this->_tileLookup[tileID] = newIt;
|
||||
|
||||
SharedFuture<LoadedQuadtreeImage> result = newIt->future;
|
||||
|
||||
this->unloadCachedTiles();
|
||||
|
||||
return result;
|
||||
return this->_pTileDepot->getOrCreate(
|
||||
this->getAsyncSystem(),
|
||||
this->getAssetAccessor(),
|
||||
tileID);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
@ -445,95 +437,60 @@ QuadtreeRasterOverlayTileProvider::loadTileImage(
|
|||
RasterOverlayTile& overlayTile) {
|
||||
// Figure out which quadtree level we need, and which tiles from that level.
|
||||
// Load each needed tile (or pull it from cache).
|
||||
std::vector<CesiumAsync::SharedFuture<LoadedQuadtreeImage>> tiles =
|
||||
this->mapRasterTilesToGeometryTile(
|
||||
std::vector<CesiumAsync::SharedFuture<ResultPointer<LoadedQuadtreeImage>>>
|
||||
tiles = this->mapRasterTilesToGeometryTile(
|
||||
overlayTile.getRectangle(),
|
||||
overlayTile.getTargetScreenPixels());
|
||||
|
||||
return this->getAsyncSystem()
|
||||
.all(std::move(tiles))
|
||||
.thenInWorkerThread([projection = this->getProjection(),
|
||||
rectangle = overlayTile.getRectangle()](
|
||||
std::vector<LoadedQuadtreeImage>&& images) {
|
||||
// This set of images is only "useful" if at least one actually has
|
||||
// image data, and that image data is _not_ from an ancestor. We can
|
||||
// identify ancestor images because they have a `subset`.
|
||||
const bool haveAnyUsefulImageData = std::any_of(
|
||||
images.begin(),
|
||||
images.end(),
|
||||
[](const LoadedQuadtreeImage& image) {
|
||||
return image.pLoaded->pImage && !image.subset.has_value();
|
||||
});
|
||||
.thenInWorkerThread(
|
||||
[projection = this->getProjection(),
|
||||
rectangle = overlayTile.getRectangle()](
|
||||
std::vector<ResultPointer<LoadedQuadtreeImage>>&& images) {
|
||||
// This set of images is only "useful" if at least one actually has
|
||||
// image data, and that image data is _not_ from an ancestor. We can
|
||||
// identify ancestor images because they have a `subset`.
|
||||
const bool haveAnyUsefulImageData = std::any_of(
|
||||
images.begin(),
|
||||
images.end(),
|
||||
[](const ResultPointer<LoadedQuadtreeImage>& image) {
|
||||
return image.pValue && image.pValue->pLoaded->pImage &&
|
||||
!image.pValue->subset.has_value();
|
||||
});
|
||||
|
||||
if (!haveAnyUsefulImageData) {
|
||||
// For non-useful sets of images, just return an empty image,
|
||||
// signalling that the parent tile should be used instead.
|
||||
// See https://github.com/CesiumGS/cesium-native/issues/316 for an
|
||||
// edge case that is not yet handled. Be sure to pass through any
|
||||
// errors and warnings.
|
||||
ErrorList errors;
|
||||
for (LoadedQuadtreeImage& image : images) {
|
||||
if (image.pLoaded) {
|
||||
errors.merge(image.pLoaded->errorList);
|
||||
if (!haveAnyUsefulImageData) {
|
||||
// For non-useful sets of images, just return an empty image,
|
||||
// signalling that the parent tile should be used instead.
|
||||
// See https://github.com/CesiumGS/cesium-native/issues/316 for an
|
||||
// edge case that is not yet handled. Be sure to pass through any
|
||||
// errors and warnings.
|
||||
ErrorList errors;
|
||||
for (ResultPointer<LoadedQuadtreeImage>& image : images) {
|
||||
if (image.pValue->pLoaded) {
|
||||
errors.merge(image.pValue->pLoaded->errorList);
|
||||
}
|
||||
errors.merge(image.errors);
|
||||
}
|
||||
return LoadedRasterOverlayImage{
|
||||
new ImageAsset(),
|
||||
Rectangle(),
|
||||
{},
|
||||
std::move(errors),
|
||||
false};
|
||||
}
|
||||
}
|
||||
return LoadedRasterOverlayImage{
|
||||
new ImageAsset(),
|
||||
Rectangle(),
|
||||
{},
|
||||
std::move(errors),
|
||||
false};
|
||||
}
|
||||
|
||||
return QuadtreeRasterOverlayTileProvider::combineImages(
|
||||
rectangle,
|
||||
projection,
|
||||
std::move(images));
|
||||
});
|
||||
}
|
||||
|
||||
void QuadtreeRasterOverlayTileProvider::unloadCachedTiles() {
|
||||
CESIUM_TRACE("QuadtreeRasterOverlayTileProvider::unloadCachedTiles");
|
||||
|
||||
const int64_t maxCacheBytes = this->getOwner().getOptions().subTileCacheBytes;
|
||||
if (this->_cachedBytes <= maxCacheBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = this->_tilesOldToRecent.begin();
|
||||
|
||||
while (it != this->_tilesOldToRecent.end() &&
|
||||
this->_cachedBytes > maxCacheBytes) {
|
||||
const SharedFuture<LoadedQuadtreeImage>& future = it->future;
|
||||
if (!future.isReady()) {
|
||||
// Don't unload tiles that are still loading.
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guaranteed not to block because isReady returned true.
|
||||
const LoadedQuadtreeImage& image = future.wait();
|
||||
|
||||
std::shared_ptr<LoadedRasterOverlayImage> pImage = image.pLoaded;
|
||||
|
||||
this->_tileLookup.erase(it->tileID);
|
||||
it = this->_tilesOldToRecent.erase(it);
|
||||
|
||||
// If this is the last use of this data, it will be freed when the shared
|
||||
// pointer goes out of scope, so reduce the cachedBytes accordingly.
|
||||
if (pImage.use_count() == 1) {
|
||||
if (pImage->pImage) {
|
||||
this->_cachedBytes -= int64_t(pImage->pImage->pixelData.size());
|
||||
CESIUM_ASSERT(this->_cachedBytes >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QuadtreeRasterOverlayTileProvider::combineImages(
|
||||
rectangle,
|
||||
projection,
|
||||
std::move(images));
|
||||
});
|
||||
}
|
||||
|
||||
/*static*/ QuadtreeRasterOverlayTileProvider::CombinedImageMeasurements
|
||||
QuadtreeRasterOverlayTileProvider::measureCombinedImage(
|
||||
const Rectangle& targetRectangle,
|
||||
const std::vector<LoadedQuadtreeImage>& images) {
|
||||
const std::vector<ResultPointer<LoadedQuadtreeImage>>& images) {
|
||||
// Find the image with the densest pixels, and use that to select the
|
||||
// resolution of the target image.
|
||||
|
||||
|
|
@ -547,8 +504,11 @@ QuadtreeRasterOverlayTileProvider::measureCombinedImage(
|
|||
double projectedHeightPerPixel = std::numeric_limits<double>::max();
|
||||
int32_t channels = -1;
|
||||
int32_t bytesPerChannel = -1;
|
||||
for (const LoadedQuadtreeImage& image : images) {
|
||||
const LoadedRasterOverlayImage& loaded = *image.pLoaded;
|
||||
for (const ResultPointer<LoadedQuadtreeImage>& image : images) {
|
||||
if (!image.pValue) {
|
||||
continue;
|
||||
}
|
||||
const LoadedRasterOverlayImage& loaded = *image.pValue->pLoaded;
|
||||
if (!loaded.pImage || loaded.pImage->width <= 0 ||
|
||||
loaded.pImage->height <= 0) {
|
||||
continue;
|
||||
|
|
@ -567,15 +527,19 @@ QuadtreeRasterOverlayTileProvider::measureCombinedImage(
|
|||
|
||||
std::optional<Rectangle> combinedRectangle;
|
||||
|
||||
for (const LoadedQuadtreeImage& image : images) {
|
||||
const LoadedRasterOverlayImage& loaded = *image.pLoaded;
|
||||
for (const ResultPointer<LoadedQuadtreeImage>& image : images) {
|
||||
if (!image.pValue) {
|
||||
continue;
|
||||
}
|
||||
const LoadedRasterOverlayImage& loaded = *image.pValue->pLoaded;
|
||||
if (!loaded.pImage || loaded.pImage->width <= 0 ||
|
||||
loaded.pImage->height <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The portion of the source that we actually need to copy.
|
||||
const Rectangle sourceSubset = image.subset.value_or(loaded.rectangle);
|
||||
const Rectangle sourceSubset =
|
||||
image.pValue->subset.value_or(loaded.rectangle);
|
||||
|
||||
// Find the bounds of the combined image.
|
||||
// Intersect the loaded image's rectangle with the target rectangle.
|
||||
|
|
@ -649,12 +613,13 @@ QuadtreeRasterOverlayTileProvider::measureCombinedImage(
|
|||
QuadtreeRasterOverlayTileProvider::combineImages(
|
||||
const Rectangle& targetRectangle,
|
||||
const Projection& /* projection */,
|
||||
std::vector<LoadedQuadtreeImage>&& images) {
|
||||
std::vector<ResultPointer<LoadedQuadtreeImage>>&& images) {
|
||||
ErrorList errors;
|
||||
for (LoadedQuadtreeImage& image : images) {
|
||||
if (image.pLoaded) {
|
||||
errors.merge(std::move(image.pLoaded->errorList));
|
||||
for (ResultPointer<LoadedQuadtreeImage>& image : images) {
|
||||
if (image.pValue && image.pValue->pLoaded) {
|
||||
errors.merge(std::move(image.pValue->pLoaded->errorList));
|
||||
}
|
||||
errors.merge(image.errors);
|
||||
}
|
||||
|
||||
const CombinedImageMeasurements measurements =
|
||||
|
|
@ -690,7 +655,10 @@ QuadtreeRasterOverlayTileProvider::combineImages(
|
|||
target.width * target.height * target.channels * target.bytesPerChannel));
|
||||
|
||||
for (auto it = images.begin(); it != images.end(); ++it) {
|
||||
const LoadedRasterOverlayImage& loaded = *it->pLoaded;
|
||||
if (!it->pValue) {
|
||||
continue;
|
||||
}
|
||||
const LoadedRasterOverlayImage& loaded = *it->pValue->pLoaded;
|
||||
if (!loaded.pImage) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -702,12 +670,15 @@ QuadtreeRasterOverlayTileProvider::combineImages(
|
|||
result.rectangle,
|
||||
*loaded.pImage,
|
||||
loaded.rectangle,
|
||||
it->subset);
|
||||
it->pValue->subset);
|
||||
}
|
||||
|
||||
size_t combinedCreditsCount = 0;
|
||||
for (auto it = images.begin(); it != images.end(); ++it) {
|
||||
const LoadedRasterOverlayImage& loaded = *it->pLoaded;
|
||||
if (!it->pValue) {
|
||||
continue;
|
||||
}
|
||||
const LoadedRasterOverlayImage& loaded = *it->pValue->pLoaded;
|
||||
if (!loaded.pImage) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -717,7 +688,10 @@ QuadtreeRasterOverlayTileProvider::combineImages(
|
|||
|
||||
result.credits.reserve(combinedCreditsCount);
|
||||
for (auto it = images.begin(); it != images.end(); ++it) {
|
||||
const LoadedRasterOverlayImage& loaded = *it->pLoaded;
|
||||
if (!it->pValue) {
|
||||
continue;
|
||||
}
|
||||
const LoadedRasterOverlayImage& loaded = *it->pValue->pLoaded;
|
||||
if (!loaded.pImage) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,11 +280,12 @@ TileMapServiceRasterOverlay::createTileProvider(
|
|||
|
||||
pOwner = pOwner ? pOwner : this;
|
||||
|
||||
const std::optional<Credit> credit =
|
||||
this->_options.credit ? std::make_optional(pCreditSystem->createCredit(
|
||||
this->_options.credit.value(),
|
||||
pOwner->getOptions().showCreditsOnScreen))
|
||||
: std::nullopt;
|
||||
std::optional<Credit> credit = std::nullopt;
|
||||
if (pCreditSystem && this->_options.credit) {
|
||||
credit = pCreditSystem->createCredit(
|
||||
*this->_options.credit,
|
||||
pOwner->getOptions().showCreditsOnScreen);
|
||||
}
|
||||
|
||||
return getXmlDocument(asyncSystem, pAssetAccessor, xmlUrl, this->_headers)
|
||||
.thenInMainThread(
|
||||
|
|
|
|||
|
|
@ -270,10 +270,12 @@ WebMapServiceRasterOverlay::createTileProvider(
|
|||
|
||||
pOwner = pOwner ? pOwner : this;
|
||||
|
||||
const std::optional<Credit> credit =
|
||||
this->_options.credit ? std::make_optional(pCreditSystem->createCredit(
|
||||
this->_options.credit.value()))
|
||||
: std::nullopt;
|
||||
std::optional<Credit> credit = std::nullopt;
|
||||
if (pCreditSystem && this->_options.credit) {
|
||||
credit = pCreditSystem->createCredit(
|
||||
*this->_options.credit,
|
||||
pOwner->getOptions().showCreditsOnScreen);
|
||||
}
|
||||
|
||||
return pAssetAccessor->get(asyncSystem, xmlUrlGetcapabilities, this->_headers)
|
||||
.thenInMainThread(
|
||||
|
|
|
|||
|
|
@ -193,11 +193,12 @@ WebMapTileServiceRasterOverlay::createTileProvider(
|
|||
|
||||
pOwner = pOwner ? pOwner : this;
|
||||
|
||||
const std::optional<Credit> credit =
|
||||
this->_options.credit ? std::make_optional(pCreditSystem->createCredit(
|
||||
this->_options.credit.value(),
|
||||
pOwner->getOptions().showCreditsOnScreen))
|
||||
: std::nullopt;
|
||||
std::optional<Credit> credit = std::nullopt;
|
||||
if (pCreditSystem && this->_options.credit) {
|
||||
credit = pCreditSystem->createCredit(
|
||||
*this->_options.credit,
|
||||
pOwner->getOptions().showCreditsOnScreen);
|
||||
}
|
||||
|
||||
bool hasError = false;
|
||||
std::string errorMessage;
|
||||
|
|
|
|||
|
|
@ -205,4 +205,56 @@ TEST_CASE("TileMapServiceRasterOverlay") {
|
|||
|
||||
REQUIRE(result);
|
||||
}
|
||||
|
||||
SECTION("loads with credit") {
|
||||
TileMapServiceRasterOverlayOptions options;
|
||||
options.credit = "test credit";
|
||||
IntrusivePointer<TileMapServiceRasterOverlay> pRasterOverlayWithCredit =
|
||||
new TileMapServiceRasterOverlay("test", tmr, {}, options);
|
||||
|
||||
std::shared_ptr<CreditSystem> pCreditSystem =
|
||||
std::make_shared<CreditSystem>();
|
||||
|
||||
RasterOverlay::CreateTileProviderResult result = waitForFuture(
|
||||
asyncSystem,
|
||||
pRasterOverlayWithCredit->createTileProvider(
|
||||
asyncSystem,
|
||||
pMockAssetAccessor,
|
||||
pCreditSystem,
|
||||
nullptr,
|
||||
spdlog::default_logger(),
|
||||
nullptr));
|
||||
|
||||
REQUIRE(result);
|
||||
|
||||
CesiumUtility::IntrusivePointer<RasterOverlayTileProvider> pTileProvider =
|
||||
*result;
|
||||
std::optional<Credit> maybeCredit = pTileProvider->getCredit();
|
||||
|
||||
REQUIRE(maybeCredit);
|
||||
CHECK(pCreditSystem->getHtml(*maybeCredit) == "test credit");
|
||||
}
|
||||
|
||||
SECTION("loads with credit and null credit system") {
|
||||
TileMapServiceRasterOverlayOptions options;
|
||||
options.credit = "test credit";
|
||||
IntrusivePointer<TileMapServiceRasterOverlay> pRasterOverlayWithCredit =
|
||||
new TileMapServiceRasterOverlay("test", tmr, {}, options);
|
||||
|
||||
RasterOverlay::CreateTileProviderResult result = waitForFuture(
|
||||
asyncSystem,
|
||||
pRasterOverlayWithCredit->createTileProvider(
|
||||
asyncSystem,
|
||||
pMockAssetAccessor,
|
||||
nullptr,
|
||||
nullptr,
|
||||
spdlog::default_logger(),
|
||||
nullptr));
|
||||
|
||||
REQUIRE(result);
|
||||
|
||||
CesiumUtility::IntrusivePointer<RasterOverlayTileProvider> pTileProvider =
|
||||
*result;
|
||||
CHECK(!pTileProvider->getCredit());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,7 +293,8 @@ function generate(options, schema, writers) {
|
|||
CesiumJsonReader::JsonReaderOptions _options;
|
||||
};
|
||||
|
||||
} // namespace ${readerNamespace}`;
|
||||
} // namespace ${readerNamespace}
|
||||
`;
|
||||
|
||||
const readerHeaderOutputDir = path.join(
|
||||
readerOutputDir,
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@ function generateCombinedWriter(options) {
|
|||
#include <CesiumJsonWriter/JsonWriter.h>
|
||||
|
||||
${writers
|
||||
.map((writer) => {
|
||||
return writer.writeInclude;
|
||||
})
|
||||
.join("\n")}
|
||||
.map((writer) => {
|
||||
return writer.writeInclude;
|
||||
})
|
||||
.join("\n")}
|
||||
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
#include <CesiumJsonWriter/writeJsonExtensions.h>
|
||||
|
|
@ -78,10 +78,10 @@ function generateCombinedWriter(options) {
|
|||
namespace {
|
||||
|
||||
${writers
|
||||
.map((writer) => {
|
||||
return writer.writeJsonDeclaration;
|
||||
})
|
||||
.join("\n")}
|
||||
.map((writer) => {
|
||||
return writer.writeJsonDeclaration;
|
||||
})
|
||||
.join("\n")}
|
||||
|
||||
// Forward declaration to avoid circular dependency since some properties
|
||||
// are vector of unordered_map and others are unordered_map of vector
|
||||
|
|
@ -96,7 +96,7 @@ function generateCombinedWriter(options) {
|
|||
const CesiumUtility::IntrusivePointer<T>& ptr,
|
||||
CesiumJsonWriter::JsonWriter& jsonWriter,
|
||||
const CesiumJsonWriter::ExtensionWriterContext& context) {
|
||||
writeJson(*ptr, jsonWriter, context);
|
||||
writeJson(*ptr, jsonWriter, context);
|
||||
}
|
||||
|
||||
[[maybe_unused]] void writeJson(
|
||||
|
|
@ -196,7 +196,7 @@ function generateCombinedWriter(options) {
|
|||
CesiumJsonWriter::JsonWriter& jsonWriter,
|
||||
const CesiumJsonWriter::ExtensionWriterContext& context) {
|
||||
|
||||
if (!obj.extensions.empty()) {
|
||||
if (hasRegisteredExtensions(obj, jsonWriter, context)) {
|
||||
jsonWriter.Key("extensions");
|
||||
writeJsonExtensions(obj, jsonWriter, context);
|
||||
}
|
||||
|
|
@ -246,10 +246,10 @@ function generateCombinedWriter(options) {
|
|||
} // namespace
|
||||
|
||||
${writers
|
||||
.map((writer) => {
|
||||
return writer.writeDefinition;
|
||||
})
|
||||
.join("\n")}
|
||||
.map((writer) => {
|
||||
return writer.writeDefinition;
|
||||
})
|
||||
.join("\n")}
|
||||
|
||||
} // namespace ${writerNamespace}
|
||||
`;
|
||||
|
|
|
|||
Loading…
Reference in New Issue