Merge branch 'main' into get-up-axis-transform

This commit is contained in:
Sean Lilley 2024-10-01 10:47:43 -04:00
commit 91125ce4e0
24 changed files with 1424 additions and 201 deletions

View File

@ -3,7 +3,7 @@ on: [push, pull_request]
jobs:
QuickChecks:
name: "Quick Checks"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Check out repository code
uses: actions/checkout@v4
@ -12,7 +12,7 @@ jobs:
npm install
npm run format -- --dry-run -Werror
Documentation:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Install Doxygen
run: |
@ -75,11 +75,11 @@ jobs:
fail-fast: false
matrix:
compiler: [ gcc, clang ]
platform: [ ubuntu-latest, ubuntu-20.04, macos-12 ]
platform: [ ubuntu-22.04, ubuntu-20.04, macos-12 ]
build_type: [Debug, RelWithDebInfo]
exclude:
- compiler: clang
platform: ubuntu-latest
platform: ubuntu-22.04
- compiler: gcc
platform: macos-12
- compiler: gcc

View File

@ -2,18 +2,36 @@
### Not Released Yet
##### Breaking Changes :mega:
- `LoadedRasterOverlayImage` now has a single `errorList` property instead of separate `errors` and `warnings` properties.
##### Additions :tada:
- Added `CesiumGeometry::Transforms::getUpAxisTransform` to get the transform that converts from one up axis to another.
### v0.40.1 - 2024-10-01
##### Fixes :wrench:
- Fixed a regression in v0.40.0 that could cause tilesets with raster overlays to fail to load in some cases.
### v0.40.0 - 2024-10-01
##### Breaking Changes :mega:
- Renamed `shouldContentContinueUpdating` to `getMightHaveLatentChildren` and `setContentShouldContinueUpdating` to `setMightHaveLatentChildren` on the `Tile` class.
- `LoadedRasterOverlayImage` now has a single `errorList` property instead of separate `errors` and `warnings` properties.
##### Additions :tada:
- Added `sampleHeightMostDetailed` method to `Tileset`.
- `AxisAlignedBox` now has `constexpr` constructors.
##### Fixes :wrench:
- Fixed a bug that prevented use of `Tileset` with a nullptr `IPrepareRendererResources`.
- Fixed a bug in `IntersectionTests::rayOBBParametric` that could cause incorrect results for some oriented bounding boxes.
- `GltfUtilities::intersectRayGltfModel` now reports a warning when given a model it can't compute the intersection with because it uses required extensions that are not supported.
- Errors while loading raster overlays are now logged. Previously, they were silently ignored in many cases.
- A raster overlay image failing to load will no longer completely prevent the geometry tile to which it is attached from rendering. Instead, once the raster overlay fails, the geometry tile will be shown without the raster overlay.
- Fixed a bug in the various `catchImmediately` and `catchInMainThread` functions in `CesiumAsync` that prevented use of a mutable lambda.
### v0.39.0 - 2024-09-02

View File

@ -25,6 +25,22 @@ endif()
message(STATUS "VCPKG_TRIPLET ${VCPKG_TRIPLET}")
if (NOT VCPKG_OVERLAY_PORTS)
if (DEFINED ENV{VCPKG_OVERLAY_PORTS})
set(VCPKG_OVERLAY_PORTS "$ENV{VCPKG_OVERLAY_PORTS}")
endif()
endif()
message(STATUS "VCPKG_OVERLAY_PORTS ${VCPKG_OVERLAY_PORTS}")
if (NOT VCPKG_OVERLAY_TRIPLETS)
if (DEFINED ENV{VCPKG_OVERLAY_TRIPLETS})
set(VCPKG_OVERLAY_TRIPLETS "$ENV{VCPKG_OVERLAY_TRIPLETS}")
endif()
endif()
message(STATUS "VCPKG_OVERLAY_TRIPLETS ${VCPKG_OVERLAY_TRIPLETS}")
# These packages are used in the public headers of Cesium libraries, so we need to distribute the headers and binaries
# with the installation
# Note that fmt is a public dependency of the vcpkg version of spdlog

View File

@ -0,0 +1,44 @@
#pragma once
#include <CesiumGeospatial/Cartographic.h>
#include <string>
#include <vector>
namespace Cesium3DTilesSelection {
/**
* @brief The result of sampling heights with
* {@link Tileset::sampleHeightMostDetailed}.
*/
struct SampleHeightResult {
/**
* @brief The positions and their sampled heights.
*
* For each resulting position, its longitude and latitude values will match
* values from its input. Its height will either be the height sampled from
* the tileset at that position, or the original input height if the sample
* was unsuccessful. To determine which, look at the value of
* {@link SampleHeightResult::sampleSuccess} at the same index.
*/
std::vector<CesiumGeospatial::Cartographic> positions;
/**
* @brief The success of each sample.
*
* Each entry specifies whether the height for the position at the
* corresponding index was successfully sampled. If true, then
* {@link SampleHeightResult::positions} has a valid height sampled from the
* tileset at this index. If false, the height could not be sampled, leaving
* the height in {@link SampleHeightResult::positions} unchanged from the
* original input height.
*/
std::vector<bool> sampleSuccess;
/**
* @brief Any warnings that occurred while sampling heights.
*/
std::vector<std::string> warnings;
};
} // namespace Cesium3DTilesSelection

View File

@ -501,10 +501,24 @@ private:
void setState(TileLoadState state) noexcept;
bool shouldContentContinueUpdating() const noexcept;
/**
* @brief Gets a flag indicating whether this tile might have latent children.
* Latent children don't exist in the `_children` property, but can be created
* by the {@link TilesetContentLoader}.
*
* When true, this tile might have children that can be created by the
* TilesetContentLoader but aren't yet reflected in the `_children` property.
* For example, in implicit tiling, we save memory by only creating explicit
* Tile instances from implicit availability as those instances are needed.
* When this flag is true, the creation of those explicit instances hasn't
* happened yet for this tile.
*
* If this flag is false, the children have already been created, if they
* exist. The tile may still have no children because it is a leaf node.
*/
bool getMightHaveLatentChildren() const noexcept;
void
setContentShouldContinueUpdating(bool shouldContentContinueUpdating) noexcept;
void setMightHaveLatentChildren(bool mightHaveLatentChildren) noexcept;
// Position in bounding-volume hierarchy.
Tile* _pParent;
@ -528,7 +542,7 @@ private:
TileContent _content;
TilesetContentLoader* _pLoader;
TileLoadState _loadState;
bool _shouldContentContinueUpdating;
bool _mightHaveLatentChildren;
// mapped raster overlay
std::vector<RasterMappedTo3DTile> _rasterTiles;

View File

@ -2,6 +2,7 @@
#include "Library.h"
#include "RasterOverlayCollection.h"
#include "SampleHeightResult.h"
#include "Tile.h"
#include "TilesetContentLoader.h"
#include "TilesetExternals.h"
@ -15,6 +16,7 @@
#include <rapidjson/fwd.h>
#include <list>
#include <memory>
#include <optional>
#include <string>
@ -23,6 +25,8 @@
namespace Cesium3DTilesSelection {
class TilesetContentManager;
class TilesetMetadata;
class TilesetHeightQuery;
class TilesetHeightRequest;
/**
* @brief A <a
@ -270,6 +274,27 @@ public:
*/
CesiumAsync::Future<const TilesetMetadata*> loadMetadata();
/**
* @brief Initiates an asynchronous query for the height of this tileset at a
* list of cartographic positions (longitude and latitude). The most detailed
* available tiles are used to determine each height.
*
* The height of the input positions is ignored. The output height is
* expressed in meters above the ellipsoid (usually WGS84), which should not
* be confused with a height above mean sea level.
*
* Note that {@link Tileset::updateView} must be called periodically, or else
* the returned `Future` will never resolve. If you are not using this tileset
* for visualization, you can call `updateView` with an empty list of
* frustums.
*
* @param positions The positions for which to sample heights.
* @return A future that asynchronously resolves to the result of the height
* query.
*/
CesiumAsync::Future<SampleHeightResult> sampleHeightMostDetailed(
const std::vector<CesiumGeospatial::Cartographic>& positions);
private:
/**
* @brief The result of traversing one branch of the tile hierarchy.
@ -495,6 +520,7 @@ private:
std::vector<TileLoadTask> _mainThreadLoadQueue;
std::vector<TileLoadTask> _workerThreadLoadQueue;
std::vector<Tile*> _heightQueryLoadQueue;
Tile::LoadedLinkedList _loadedTiles;
@ -509,6 +535,8 @@ private:
CesiumUtility::IntrusivePointer<TilesetContentManager>
_pTilesetContentManager;
std::list<TilesetHeightRequest> _heightRequests;
void addTileToLoadQueue(
Tile& tile,
TileLoadPriorityGroup priorityGroup,

View File

@ -57,7 +57,7 @@ Tile::Tile(
_content{std::forward<TileContentArgs>(args)...},
_pLoader{pLoader},
_loadState{loadState},
_shouldContentContinueUpdating{true} {}
_mightHaveLatentChildren{true} {}
Tile::Tile(Tile&& rhs) noexcept
: _pParent(rhs._pParent),
@ -74,7 +74,7 @@ Tile::Tile(Tile&& rhs) noexcept
_content(std::move(rhs._content)),
_pLoader{rhs._pLoader},
_loadState{rhs._loadState},
_shouldContentContinueUpdating{rhs._shouldContentContinueUpdating} {
_mightHaveLatentChildren{rhs._mightHaveLatentChildren} {
// since children of rhs will have the parent pointed to rhs,
// we will reparent them to this tile as rhs will be destroyed after this
for (Tile& tile : this->_children) {
@ -105,7 +105,7 @@ Tile& Tile::operator=(Tile&& rhs) noexcept {
this->_content = std::move(rhs._content);
this->_pLoader = rhs._pLoader;
this->_loadState = rhs._loadState;
this->_shouldContentContinueUpdating = rhs._shouldContentContinueUpdating;
this->_mightHaveLatentChildren = rhs._mightHaveLatentChildren;
}
return *this;
@ -227,12 +227,12 @@ void Tile::setParent(Tile* pParent) noexcept { this->_pParent = pParent; }
void Tile::setState(TileLoadState state) noexcept { this->_loadState = state; }
bool Tile::shouldContentContinueUpdating() const noexcept {
return this->_shouldContentContinueUpdating;
bool Tile::getMightHaveLatentChildren() const noexcept {
return this->_mightHaveLatentChildren;
}
void Tile::setContentShouldContinueUpdating(
bool shouldContentContinueUpdating) noexcept {
this->_shouldContentContinueUpdating = shouldContentContinueUpdating;
void Tile::setMightHaveLatentChildren(bool mightHaveLatentChildren) noexcept {
this->_mightHaveLatentChildren = mightHaveLatentChildren;
}
} // namespace Cesium3DTilesSelection

View File

@ -1,5 +1,6 @@
#include "TileUtilities.h"
#include "TilesetContentManager.h"
#include "TilesetHeightQuery.h"
#include <Cesium3DTilesSelection/ITileExcluder.h>
#include <Cesium3DTilesSelection/TileID.h>
@ -45,13 +46,18 @@ Tileset::Tileset(
_previousFrameNumber(0),
_distances(),
_childOcclusionProxies(),
_pTilesetContentManager{new TilesetContentManager(
_externals,
_options,
RasterOverlayCollection{_loadedTiles, externals, options.ellipsoid},
std::vector<CesiumAsync::IAssetAccessor::THeader>{},
std::move(pCustomLoader),
std::move(pRootTile))} {}
_pTilesetContentManager{
new TilesetContentManager(
_externals,
_options,
RasterOverlayCollection{
_loadedTiles,
externals,
options.ellipsoid},
std::vector<CesiumAsync::IAssetAccessor::THeader>{},
std::move(pCustomLoader),
std::move(pRootTile)),
} {}
Tileset::Tileset(
const TilesetExternals& externals,
@ -63,11 +69,16 @@ Tileset::Tileset(
_previousFrameNumber(0),
_distances(),
_childOcclusionProxies(),
_pTilesetContentManager{new TilesetContentManager(
_externals,
_options,
RasterOverlayCollection{_loadedTiles, externals, options.ellipsoid},
url)} {}
_pTilesetContentManager{
new TilesetContentManager(
_externals,
_options,
RasterOverlayCollection{
_loadedTiles,
externals,
options.ellipsoid},
url),
} {}
Tileset::Tileset(
const TilesetExternals& externals,
@ -90,6 +101,10 @@ Tileset::Tileset(
ionAssetEndpointUrl)} {}
Tileset::~Tileset() noexcept {
TilesetHeightRequest::failHeightRequests(
this->_heightRequests,
"Tileset is being destroyed.");
this->_pTilesetContentManager->unloadAll();
if (this->_externals.pTileOcclusionProxyPool) {
this->_externals.pTileOcclusionProxyPool->destroyPool();
@ -325,6 +340,15 @@ Tileset::updateView(const std::vector<ViewState>& frustums, float deltaTime) {
Tile* pRootTile = this->getRootTile();
if (!pRootTile) {
// If the root tile is marked as ready, but doesn't actually exist, then
// the tileset couldn't load. Fail any outstanding height requests.
if (!this->_heightRequests.empty() && this->_pTilesetContentManager &&
this->_pTilesetContentManager->getRootTileAvailableEvent().isReady()) {
TilesetHeightRequest::failHeightRequests(
this->_heightRequests,
"Height requests could not complete because the tileset failed to "
"load.");
}
return result;
}
@ -358,6 +382,13 @@ Tileset::updateView(const std::vector<ViewState>& frustums, float deltaTime) {
result = ViewUpdateResult();
}
TilesetHeightRequest::processHeightRequests(
*this->_pTilesetContentManager,
this->_options,
this->_loadedTiles,
this->_heightRequests,
this->_heightQueryLoadQueue);
result.workerThreadTileLoadQueueLength =
static_cast<int32_t>(this->_workerThreadLoadQueue.size());
result.mainThreadTileLoadQueueLength =
@ -525,6 +556,27 @@ CesiumAsync::Future<const TilesetMetadata*> Tileset::loadMetadata() {
});
}
CesiumAsync::Future<SampleHeightResult>
Tileset::sampleHeightMostDetailed(const std::vector<Cartographic>& positions) {
if (positions.empty()) {
return this->_asyncSystem.createResolvedFuture<SampleHeightResult>({});
}
Promise promise = this->_asyncSystem.createPromise<SampleHeightResult>();
std::vector<TilesetHeightQuery> queries;
queries.reserve(positions.size());
for (const CesiumGeospatial::Cartographic& position : positions) {
queries.emplace_back(position, this->_options.ellipsoid);
}
this->_heightRequests.emplace_back(
TilesetHeightRequest{std::move(queries), promise});
return promise.getFuture();
}
static void markTileNonRendered(
TileSelectionState::Result lastResult,
Tile& tile,
@ -1427,17 +1479,52 @@ void Tileset::_processWorkerThreadLoadQueue() {
return;
}
std::vector<TileLoadTask>& queue = this->_workerThreadLoadQueue;
std::sort(queue.begin(), queue.end());
std::sort(
this->_workerThreadLoadQueue.begin(),
this->_workerThreadLoadQueue.end());
for (TileLoadTask& task : queue) {
this->_pTilesetContentManager->loadTileContent(*task.pTile, _options);
if (this->_pTilesetContentManager->getNumberOfTilesLoading() >=
maximumSimultaneousTileLoads) {
// Select tiles alternately from the two queues. Each frame, switch which
// queue we pull the first tile from. The goal is to schedule both height
// query and visualization tile loads fairly.
auto visIt = this->_workerThreadLoadQueue.begin();
auto queryIt = this->_heightQueryLoadQueue.begin();
bool nextIsVis = (this->_previousFrameNumber % 2) == 0;
while (this->_pTilesetContentManager->getNumberOfTilesLoading() <
maximumSimultaneousTileLoads) {
// Tell tiles from the current queue to load until one of them actually
// does. Calling loadTileContent might not actually start the loading
// process
int32_t originalNumberOfTilesLoading =
this->_pTilesetContentManager->getNumberOfTilesLoading();
if (nextIsVis) {
while (visIt != this->_workerThreadLoadQueue.end() &&
originalNumberOfTilesLoading ==
this->_pTilesetContentManager->getNumberOfTilesLoading()) {
this->_pTilesetContentManager->loadTileContent(*visIt->pTile, _options);
++visIt;
}
} else {
while (queryIt != this->_heightQueryLoadQueue.end() &&
originalNumberOfTilesLoading ==
this->_pTilesetContentManager->getNumberOfTilesLoading()) {
this->_pTilesetContentManager->loadTileContent(**queryIt, _options);
++queryIt;
}
}
if (visIt == this->_workerThreadLoadQueue.end() &&
queryIt == this->_heightQueryLoadQueue.end()) {
// No more work in either queue
break;
}
// Get the next tile from the other queue.
nextIsVis = !nextIsVis;
}
}
void Tileset::_processMainThreadLoadQueue() {
CESIUM_TRACE("Tileset::_processMainThreadLoadQueue");
// Process deferred main-thread load tasks with a time budget.

View File

@ -572,64 +572,69 @@ postProcessContentInWorkerThread(
pAssetAccessor,
gltfOptions,
std::move(gltfResult))
.thenInWorkerThread(
[result = std::move(result),
projections = std::move(projections),
tileLoadInfo = std::move(tileLoadInfo),
rendererOptions](
CesiumGltfReader::GltfReaderResult&& gltfResult) mutable {
if (!gltfResult.errors.empty()) {
if (result.pCompletedRequest) {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Failed resolving external glTF buffers from {}:\n- {}",
result.pCompletedRequest->url(),
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
} else {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Failed resolving external glTF buffers:\n- {}",
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
}
}
.thenInWorkerThread([result = std::move(result),
projections = std::move(projections),
tileLoadInfo = std::move(tileLoadInfo),
rendererOptions](CesiumGltfReader::GltfReaderResult&&
gltfResult) mutable {
if (!gltfResult.errors.empty()) {
if (result.pCompletedRequest) {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Failed resolving external glTF buffers from {}:\n- {}",
result.pCompletedRequest->url(),
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
} else {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Failed resolving external glTF buffers:\n- {}",
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
}
}
if (!gltfResult.warnings.empty()) {
if (result.pCompletedRequest) {
SPDLOG_LOGGER_WARN(
tileLoadInfo.pLogger,
"Warning when resolving external gltf buffers from "
"{}:\n- {}",
result.pCompletedRequest->url(),
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
} else {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Warning resolving external glTF buffers:\n- {}",
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
}
}
if (!gltfResult.warnings.empty()) {
if (result.pCompletedRequest) {
SPDLOG_LOGGER_WARN(
tileLoadInfo.pLogger,
"Warning when resolving external gltf buffers from "
"{}:\n- {}",
result.pCompletedRequest->url(),
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
} else {
SPDLOG_LOGGER_ERROR(
tileLoadInfo.pLogger,
"Warning resolving external glTF buffers:\n- {}",
CesiumUtility::joinToString(gltfResult.errors, "\n- "));
}
}
if (!gltfResult.model) {
return tileLoadInfo.asyncSystem.createResolvedFuture(
TileLoadResultAndRenderResources{
TileLoadResult::createFailedResult(nullptr),
nullptr});
}
if (!gltfResult.model) {
return tileLoadInfo.asyncSystem.createResolvedFuture(
TileLoadResultAndRenderResources{
TileLoadResult::createFailedResult(nullptr),
nullptr});
}
result.contentKind = std::move(*gltfResult.model);
result.contentKind = std::move(*gltfResult.model);
postProcessGltfInWorkerThread(
result,
std::move(projections),
tileLoadInfo);
postProcessGltfInWorkerThread(
result,
std::move(projections),
tileLoadInfo);
// create render resources
return tileLoadInfo.pPrepareRendererResources->prepareInLoadThread(
tileLoadInfo.asyncSystem,
std::move(result),
tileLoadInfo.tileTransform,
rendererOptions);
});
// create render resources
if (tileLoadInfo.pPrepareRendererResources) {
return tileLoadInfo.pPrepareRendererResources->prepareInLoadThread(
tileLoadInfo.asyncSystem,
std::move(result),
tileLoadInfo.tileTransform,
rendererOptions);
} else {
return tileLoadInfo.asyncSystem
.createResolvedFuture<TileLoadResultAndRenderResources>(
TileLoadResultAndRenderResources{std::move(result), nullptr});
}
});
}
} // namespace
@ -1065,16 +1070,29 @@ void TilesetContentManager::updateTileContent(
updateDoneState(tile, tilesetOptions);
}
if (tile.shouldContentContinueUpdating()) {
this->createLatentChildrenIfNecessary(tile, tilesetOptions);
}
void TilesetContentManager::createLatentChildrenIfNecessary(
Tile& tile,
const TilesetOptions& tilesetOptions) {
if (!tile.getMightHaveLatentChildren())
return;
// If this tile has no children yet, attempt to create them.
if (tile.getChildren().empty()) {
TileChildrenResult childrenResult =
this->_pLoader->createTileChildren(tile, tilesetOptions.ellipsoid);
if (childrenResult.state == TileLoadResultState::Success) {
tile.createChildTiles(std::move(childrenResult.children));
}
bool shouldTileContinueUpdated =
bool mightStillHaveLatentChildren =
childrenResult.state == TileLoadResultState::RetryLater;
tile.setContentShouldContinueUpdating(shouldTileContinueUpdated);
tile.setMightHaveLatentChildren(mightStillHaveLatentChildren);
} else {
// A tile with real children can't have latent children.
tile.setMightHaveLatentChildren(false);
}
}
@ -1374,19 +1392,12 @@ void TilesetContentManager::updateContentLoadedState(
void TilesetContentManager::updateDoneState(
Tile& tile,
const TilesetOptions& tilesetOptions) {
// The reason for this method to terminate early when
// Tile::shouldContentContinueUpdating() returns true is that: When a tile has
// Tile::shouldContentContinueUpdating() to be true, it means the tile's
// children need to be created by the
// TilesetContentLoader::createTileChildren() which is invoked in the
// TilesetContentManager::updateTileContent() method. In the
// updateDoneState(), RasterOverlayTiles that are mapped to the tile will
// begin updating. If there are more RasterOverlayTiles with higher LOD and
// the current tile is a leaf, more upsample children will be created for that
// tile. So to accurately determine if a tile is a leaf, it needs the tile to
// have no children and Tile::shouldContentContinueUpdating() to return false
// which means the loader has no more children for this tile.
if (tile.shouldContentContinueUpdating()) {
if (tile.getMightHaveLatentChildren()) {
// This tile might have latent children, but we don't know yet whether it
// *actually* has children. We need to know that before we can continue
// this function, which will decide whether or not to create upsampled
// children for this tile. It only makes sense to create upsampled children
// for a tile that we know for sure doesn't have real children.
return;
}
@ -1483,10 +1494,12 @@ void TilesetContentManager::unloadContentLoadedState(Tile& tile) {
pRenderContent && "Tile must have render content to be unloaded");
void* pWorkerRenderResources = pRenderContent->getRenderResources();
this->_externals.pPrepareRendererResources->free(
tile,
pWorkerRenderResources,
nullptr);
if (this->_externals.pPrepareRendererResources) {
this->_externals.pPrepareRendererResources->free(
tile,
pWorkerRenderResources,
nullptr);
}
pRenderContent->setRenderResources(nullptr);
}

View File

@ -64,6 +64,27 @@ public:
void updateTileContent(Tile& tile, const TilesetOptions& tilesetOptions);
/**
* @brief Creates explicit Tile instances for a tile's latent children, if
* it is necessary and possible to do so.
*
* Latent children are child tiles that can be created by
* {@link TilesetContentLoader::createChildTiles} but that are not yet
* reflected in {@link Tile::getChildren}. For example, in implicit tiling,
* we save memory by only creating explicit Tile instances from implicit
* availability as those instances are needed. Calling this method will create
* the explicit tile instances for the given tile's children.
*
* This method does nothing if the given tile already has children, or if
* {@link Tile::getMightHaveLatentChildren} returns false.
*
* @param tile The tile for which to create latent children.
* @param tilesetOptions The tileset's options.
*/
void createLatentChildrenIfNecessary(
Tile& tile,
const TilesetOptions& tilesetOptions);
bool unloadTileContent(Tile& tile);
void waitUntilIdle();

View File

@ -0,0 +1,354 @@
#include "TilesetHeightQuery.h"
#include "TileUtilities.h"
#include "TilesetContentManager.h"
#include <Cesium3DTilesSelection/SampleHeightResult.h>
#include <CesiumGeometry/IntersectionTests.h>
#include <CesiumGeospatial/GlobeRectangle.h>
#include <CesiumGltfContent/GltfUtilities.h>
using namespace Cesium3DTilesSelection;
using namespace CesiumGeospatial;
using namespace CesiumGeometry;
using namespace CesiumUtility;
using namespace CesiumAsync;
namespace {
bool boundingVolumeContainsCoordinate(
const BoundingVolume& boundingVolume,
const Ray& ray,
const Cartographic& coordinate) {
struct Operation {
const Ray& ray;
const Cartographic& coordinate;
bool operator()(const OrientedBoundingBox& boundingBox) noexcept {
std::optional<double> t =
IntersectionTests::rayOBBParametric(ray, boundingBox);
return t && t.value() >= 0;
}
bool operator()(const BoundingRegion& boundingRegion) noexcept {
return boundingRegion.getRectangle().contains(coordinate);
}
bool operator()(const BoundingSphere& boundingSphere) noexcept {
std::optional<double> t =
IntersectionTests::raySphereParametric(ray, boundingSphere);
return t && t.value() >= 0;
}
bool operator()(
const BoundingRegionWithLooseFittingHeights& boundingRegion) noexcept {
return boundingRegion.getBoundingRegion().getRectangle().contains(
coordinate);
}
bool operator()(const S2CellBoundingVolume& s2Cell) noexcept {
return s2Cell.computeBoundingRegion().getRectangle().contains(coordinate);
}
};
return std::visit(Operation{ray, coordinate}, boundingVolume);
}
// The ray for height queries starts at this fraction of the ellipsoid max
// radius above the ellipsoid surface. If a tileset surface is more than this
// distance above the ellipsoid, it may be missed by height queries.
// 0.007 is chosen to accomodate Olympus Mons, the tallest peak on Mars. 0.007
// is seven-tenths of a percent, or about 44,647 meters for WGS84, well above
// the highest point on Earth.
const double rayOriginHeightFraction = 0.007;
Ray createRay(const Cartographic& position, const Ellipsoid& ellipsoid) {
Cartographic startPosition(
position.longitude,
position.latitude,
ellipsoid.getMaximumRadius() * rayOriginHeightFraction);
return Ray(
Ellipsoid::WGS84.cartographicToCartesian(startPosition),
-Ellipsoid::WGS84.geodeticSurfaceNormal(startPosition));
}
} // namespace
TilesetHeightQuery::TilesetHeightQuery(
const Cartographic& position,
const Ellipsoid& ellipsoid)
: inputPosition(position),
ray(createRay(position, ellipsoid)),
intersection(),
additiveCandidateTiles(),
candidateTiles(),
previousCandidateTiles() {}
void TilesetHeightQuery::intersectVisibleTile(
Tile* pTile,
std::vector<std::string>& outWarnings) {
TileRenderContent* pRenderContent = pTile->getContent().getRenderContent();
if (!pRenderContent)
return;
auto gltfIntersectResult =
CesiumGltfContent::GltfUtilities::intersectRayGltfModel(
this->ray,
pRenderContent->getModel(),
true,
pTile->getTransform());
if (!gltfIntersectResult.warnings.empty()) {
outWarnings.insert(
outWarnings.end(),
std::make_move_iterator(gltfIntersectResult.warnings.begin()),
std::make_move_iterator(gltfIntersectResult.warnings.end()));
}
// Set ray info to this hit if closer, or the first hit
if (!this->intersection.has_value()) {
this->intersection = std::move(gltfIntersectResult.hit);
} else {
double prevDistSq = this->intersection->rayToWorldPointDistanceSq;
double thisDistSq = intersection->rayToWorldPointDistanceSq;
if (thisDistSq < prevDistSq)
this->intersection = std::move(gltfIntersectResult.hit);
}
}
namespace {
void markTileVisited(Tile::LoadedLinkedList& loadedTiles, Tile* pTile) {
// Don't move the root tile to the tail, because this tile is used to mark the
// beginning of the tiles used in the current frame. If we move it, some tiles
// may be deemed to have most recently been used last frame, and so will be
// unloaded.
if (pTile == nullptr || pTile->getParent() == nullptr)
return;
loadedTiles.insertAtTail(*pTile);
}
} // namespace
void TilesetHeightQuery::findCandidateTiles(
Tile* pTile,
Tile::LoadedLinkedList& loadedTiles,
std::vector<std::string>& warnings) {
// Make sure this tile is not unloaded until we're done with it.
markTileVisited(loadedTiles, pTile);
// If tile failed to load, this means we can't complete the intersection
if (pTile->getState() == TileLoadState::Failed) {
warnings.push_back("Tile load failed during query. Ignoring.");
return;
}
const std::optional<BoundingVolume>& contentBoundingVolume =
pTile->getContentBoundingVolume();
if (pTile->getChildren().empty()) {
// This is a leaf node, it's a candidate
// If optional content bounding volume exists, test against it
if (contentBoundingVolume) {
if (boundingVolumeContainsCoordinate(
*contentBoundingVolume,
this->ray,
this->inputPosition))
this->candidateTiles.push_back(pTile);
} else {
this->candidateTiles.push_back(pTile);
}
} else {
// We have children
// If additive refinement, add parent to the list with children
if (pTile->getRefine() == TileRefine::Add) {
// If optional content bounding volume exists, test against it
if (contentBoundingVolume) {
if (boundingVolumeContainsCoordinate(
*contentBoundingVolume,
this->ray,
this->inputPosition))
this->additiveCandidateTiles.push_back(pTile);
} else {
this->additiveCandidateTiles.push_back(pTile);
}
}
// Traverse children
for (Tile& child : pTile->getChildren()) {
// if bounding volume doesn't intersect this ray, we can skip it
if (!boundingVolumeContainsCoordinate(
child.getBoundingVolume(),
this->ray,
this->inputPosition))
continue;
// Child is a candidate, traverse it and its children
findCandidateTiles(&child, loadedTiles, warnings);
}
}
}
/*static*/ void TilesetHeightRequest::processHeightRequests(
TilesetContentManager& contentManager,
const TilesetOptions& options,
Tile::LoadedLinkedList& loadedTiles,
std::list<TilesetHeightRequest>& heightRequests,
std::vector<Tile*>& heightQueryLoadQueue) {
if (heightRequests.empty())
return;
// Go through all requests, either complete them, or gather the tiles they
// need for completion
std::set<Tile*> tileLoadSet;
for (auto it = heightRequests.begin(); it != heightRequests.end();) {
TilesetHeightRequest& request = *it;
if (!request.tryCompleteHeightRequest(
contentManager,
options,
loadedTiles,
tileLoadSet)) {
++it;
} else {
auto deleteIt = it;
++it;
heightRequests.erase(deleteIt);
}
}
heightQueryLoadQueue.assign(tileLoadSet.begin(), tileLoadSet.end());
}
void Cesium3DTilesSelection::TilesetHeightRequest::failHeightRequests(
std::list<TilesetHeightRequest>& heightRequests,
const std::string& message) {
for (TilesetHeightRequest& request : heightRequests) {
SampleHeightResult result;
result.warnings.emplace_back(message);
result.sampleSuccess.resize(request.queries.size(), false);
result.positions.reserve(request.queries.size());
for (const TilesetHeightQuery& query : request.queries) {
result.positions.emplace_back(query.inputPosition);
}
request.promise.resolve(std::move(result));
}
heightRequests.clear();
}
bool TilesetHeightRequest::tryCompleteHeightRequest(
TilesetContentManager& contentManager,
const TilesetOptions& options,
Tile::LoadedLinkedList& loadedTiles,
std::set<Tile*>& tileLoadSet) {
bool tileStillNeedsLoading = false;
std::vector<std::string> warnings;
for (TilesetHeightQuery& query : this->queries) {
if (query.candidateTiles.empty() && query.additiveCandidateTiles.empty()) {
// Find the initial set of tiles whose bounding volume is intersected by
// the query ray.
query.findCandidateTiles(
contentManager.getRootTile(),
loadedTiles,
warnings);
} else {
// Refine the current set of candidate tiles, in case further tiles from
// implicit tiling, external tilesets, etc. having been loaded since last
// frame.
std::swap(query.candidateTiles, query.previousCandidateTiles);
query.candidateTiles.clear();
for (Tile* pCandidate : query.previousCandidateTiles) {
TileLoadState loadState = pCandidate->getState();
if (!pCandidate->getChildren().empty() &&
loadState >= TileLoadState::ContentLoaded) {
query.findCandidateTiles(pCandidate, loadedTiles, warnings);
} else {
// Make sure this tile stays loaded.
markTileVisited(loadedTiles, pCandidate);
// Check again next frame to see if this tile has children.
query.candidateTiles.emplace_back(pCandidate);
}
}
}
auto checkTile = [&contentManager,
&options,
&tileLoadSet,
&tileStillNeedsLoading](Tile* pTile) {
contentManager.createLatentChildrenIfNecessary(*pTile, options);
TileLoadState state = pTile->getState();
if (state == TileLoadState::Unloading) {
// This tile is in the process of unloading, which must complete
// before we can load it again.
contentManager.unloadTileContent(*pTile);
tileStillNeedsLoading = true;
} else if (state <= TileLoadState::ContentLoading) {
tileLoadSet.insert(pTile);
tileStillNeedsLoading = true;
}
};
// If any candidates need loading, add to return set
for (Tile* pTile : query.additiveCandidateTiles) {
// Additive tiles are only enumerated once in findCandidateTiles, so we
// need to continue every frame to make sure they're not unloaded before
// we're done with them.
markTileVisited(loadedTiles, pTile);
checkTile(pTile);
}
for (Tile* pTile : query.candidateTiles) {
checkTile(pTile);
}
}
// Bail if we're waiting on tiles to load
if (tileStillNeedsLoading)
return false;
// Do the intersect tests
for (TilesetHeightQuery& query : this->queries) {
for (Tile* pTile : query.additiveCandidateTiles) {
query.intersectVisibleTile(pTile, warnings);
}
for (Tile* pTile : query.candidateTiles) {
query.intersectVisibleTile(pTile, warnings);
}
}
// All rays are done, create results
SampleHeightResult results;
// Start with any warnings from tile traversal
results.warnings = std::move(warnings);
results.positions.resize(this->queries.size(), Cartographic(0.0, 0.0, 0.0));
results.sampleSuccess.resize(this->queries.size());
// Populate results with completed queries
for (size_t i = 0; i < this->queries.size(); ++i) {
const TilesetHeightQuery& query = this->queries[i];
bool sampleSuccess = query.intersection.has_value();
results.sampleSuccess[i] = sampleSuccess;
results.positions[i] = query.inputPosition;
if (sampleSuccess) {
results.positions[i].height =
options.ellipsoid.getMaximumRadius() * rayOriginHeightFraction -
glm::sqrt(query.intersection->rayToWorldPointDistanceSq);
}
}
this->promise.resolve(std::move(results));
return true;
}

View File

@ -0,0 +1,173 @@
#pragma once
#include <Cesium3DTilesSelection/Tile.h>
#include <CesiumAsync/Future.h>
#include <CesiumAsync/Promise.h>
#include <CesiumGeometry/Ray.h>
#include <CesiumGeospatial/Cartographic.h>
#include <CesiumGltfContent/GltfUtilities.h>
#include <list>
#include <set>
#include <string>
#include <vector>
namespace Cesium3DTilesSelection {
class TilesetContentManager;
struct TilesetOptions;
struct SampleHeightResult;
class TilesetHeightQuery {
public:
/**
* @brief Initializes a new instance.
*
* @param position The position at which to query a height. The existing
* height is ignored.
* @param ellipsoid The ellipsoid on which the position is defined.
*/
TilesetHeightQuery(
const CesiumGeospatial::Cartographic& position,
const CesiumGeospatial::Ellipsoid& ellipsoid);
/**
* @brief The original input position for which the height is to be queried.
*/
CesiumGeospatial::Cartographic inputPosition;
/**
* @brief A ray created from the {@link TilesetHeightQuery::inputPosition}.
*
*/
CesiumGeometry::Ray ray;
/**
* @brief The current intersection of the ray with the tileset. If there are
* multiple intersections, this will be the one closest to the origin of the
* ray.
*/
std::optional<CesiumGltfContent::GltfUtilities::RayGltfHit> intersection;
/**
* @brief Non-leaf tiles with additive refinement whose bounding volumes are
* intersected by the query ray.
*/
std::vector<Tile*> additiveCandidateTiles;
/**
* @brief The current set of leaf tiles whose bounding volumes are intersected
* by the query ray.
*/
std::vector<Tile*> candidateTiles;
/**
* @brief The previous set of leaf tiles. Swapping `candidateTiles` and
* `previousCandidateTiles` each frame allows us to avoid a heap allocation
* for a new vector each frame.
*/
std::vector<Tile*> previousCandidateTiles;
/**
* @brief Find the intersection of the ray with the given tile. If there is
* one, and if it's closer to the ray's origin than the previous best-known
* intersection, then {@link TilesetHeightQuery::intersection} will be
* updated.
*
* @param pTile The tile to test for intersection with the ray.
* @param outWarnings On return, reports any warnings that occurred while
* attempting to intersect the ray with the tile.
*/
void intersectVisibleTile(Tile* pTile, std::vector<std::string>& outWarnings);
/**
* @brief Find candidate tiles for the height query by traversing the tile
* tree, starting with the given tile.
*
* Any tile whose bounding volume intersects the ray will be added to the
* {@link TilesetHeightQuery::candidateTiles} vector. Non-leaf tiles that are
* additively-refined will be added to
* {@link TilesetHeightQuery::additiveCandidateTiles}.
*
* @param pTile The tile at which to start traversal.
* @param loadedTiles The linked list of loaded tiles, used to ensure that
* tiles loaded for height queries stay loaded just long enough to complete
* the query, and no longer.
* @param outWarnings On return, reports any warnings that occurred during
* candidate search.
*/
void findCandidateTiles(
Tile* pTile,
Tile::LoadedLinkedList& loadedTiles,
std::vector<std::string>& outWarnings);
};
/**
* @brief A request for a batch of height queries. When all of the queries are
* complete, they will be delivered to the requestor via resolving a promise.
*/
struct TilesetHeightRequest {
/**
* @brief The individual height queries in this request.
*/
std::vector<TilesetHeightQuery> queries;
/**
* @brief The promise to be resolved when all height queries are complete.
*/
CesiumAsync::Promise<SampleHeightResult> promise;
/**
* @brief Process a given list of height requests. This is called by the {@link Tileset}
* in every call to {@link Tileset::updateView}.
*
* @param contentManager The content manager.
* @param options Options associated with the tileset.
* @param loadedTiles The linked list of loaded tiles, used to ensure that
* tiles loaded for height queries stay loaded just long enough to complete
* the query, and no longer.
* @param heightRequests The list of all height requests. Completed requests
* will be removed from this list.
* @param heightQueryLoadQueue Tiles that still need to be loaded before all
* height requests can complete are added to this vector.
*/
static void processHeightRequests(
TilesetContentManager& contentManager,
const TilesetOptions& options,
Tile::LoadedLinkedList& loadedTiles,
std::list<TilesetHeightRequest>& heightRequests,
std::vector<Tile*>& heightQueryLoadQueue);
/**
* @brief Cancels all outstanding height requests and rejects the associated
* futures. This is useful when it is known that the height requests will
* never complete, such as when the tileset fails to load or when it is being
* destroyed.
*
* @param heightRequests The height requests to cancel.
* @param message The message explaining what went wrong.
*/
static void failHeightRequests(
std::list<TilesetHeightRequest>& heightRequests,
const std::string& message);
/**
* @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 contentManager The content manager.
* @param options Options associated with the tileset.
* @param loadedTiles The linked list of loaded tiles, used to ensure that
* tiles loaded for height queries stay loaded just long enough to complete
* the query, and no longer.
* @param tileLoadSet Tiles that needs to be loaded before this height request
* can complete.
*/
bool tryCompleteHeightRequest(
TilesetContentManager& contentManager,
const TilesetOptions& options,
Tile::LoadedLinkedList& loadedTiles,
std::set<Tile*>& tileLoadSet);
};
} // namespace Cesium3DTilesSelection

View File

@ -10,6 +10,6 @@ void MockTilesetContentManagerTestFixture::setTileLoadState(
void MockTilesetContentManagerTestFixture::setTileShouldContinueUpdating(
Cesium3DTilesSelection::Tile& tile,
bool shouldContinueUpdating) {
tile.setContentShouldContinueUpdating(shouldContinueUpdating);
tile.setMightHaveLatentChildren(shouldContinueUpdating);
}
} // namespace Cesium3DTilesSelection

View File

@ -0,0 +1,233 @@
#include <Cesium3DTilesContent/registerAllTileContentTypes.h>
#include <Cesium3DTilesSelection/Tileset.h>
#include <CesiumGeospatial/Cartographic.h>
#include <CesiumNativeTests/FileAccessor.h>
#include <CesiumNativeTests/SimpleTaskProcessor.h>
#include <CesiumUtility/Uri.h>
#include <catch2/catch.hpp>
#include <filesystem>
using namespace Cesium3DTilesContent;
using namespace Cesium3DTilesSelection;
using namespace CesiumAsync;
using namespace CesiumGeospatial;
using namespace CesiumNativeTests;
using namespace CesiumUtility;
namespace {
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
}
TEST_CASE("Tileset height queries") {
// The coordinates and expected heights in this file were determined in Cesium
// for Unreal Engine by adding the tileset, putting a cube above the location
// of interest, adding a CesiumGlobeAnchor to it, and pressing the "End" key
// to drop it onto terrain. The coordinates were then copied out of the globe
// anchor, subtracting 0.5 from the height to account for "End" placing the
// bottom of the cube on the surface instead of its center.
registerAllTileContentTypes();
std::shared_ptr<IAssetAccessor> pAccessor =
std::make_shared<CesiumNativeTests::FileAccessor>();
AsyncSystem asyncSystem(std::make_shared<SimpleTaskProcessor>());
TilesetExternals externals{pAccessor, nullptr, asyncSystem, nullptr};
SECTION("Additive-refined tileset") {
std::string url =
"file://" + Uri::nativePathToUriPath(
(testDataPath / "Tileset" / "tileset.json").u8string());
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "parent.b3dm", which should only be included
// because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SECTION("Replace-refined tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(
(testDataPath / "ReplaceTileset" / "tileset.json").u8string());
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "parent.b3dm", which should not be
// included because this tileset is replace-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(!results.sampleSuccess[0]);
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SECTION("External tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(
(testDataPath / "AddTileset" / "tileset.json").u8string());
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "0/0/0.b3dm", which should only be
// included because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SECTION("Implicit tileset") {
std::string url =
"file://" +
Uri::nativePathToUriPath(
(testDataPath / "ImplicitTileset" / "tileset_1.1.json").u8string());
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
// A point on geometry in "0/0/0.b3dm", which should only be
// included because this tileset is additive-refined.
{Cartographic::fromDegrees(-75.612088, 40.042526, 0.0),
// A point on geometry in a leaf tile.
Cartographic::fromDegrees(-75.612025, 40.041684, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
CHECK(results.warnings.empty());
REQUIRE(results.positions.size() == 2);
CHECK(results.sampleSuccess[0]);
CHECK(Math::equalsEpsilon(
results.positions[0].height,
78.155809,
0.0,
Math::Epsilon4));
CHECK(results.sampleSuccess[1]);
CHECK(Math::equalsEpsilon(
results.positions[1].height,
7.837332,
0.0,
Math::Epsilon4));
}
SECTION("Instanced model is not yet supported") {
std::string url =
"file://" +
Uri::nativePathToUriPath(
(testDataPath / "i3dm" / "InstancedWithBatchTable" / "tileset.json")
.u8string());
Tileset tileset(externals, url);
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
REQUIRE(results.warnings.size() == 1);
REQUIRE(results.positions.size() == 1);
CHECK(!results.sampleSuccess[0]);
CHECK(
results.warnings[0].find("EXT_mesh_gpu_instancing") !=
std::string::npos);
}
SECTION("broken tileset") {
Tileset tileset(externals, "http://localhost/notgonnawork");
Future<SampleHeightResult> future = tileset.sampleHeightMostDetailed(
{Cartographic::fromDegrees(-75.612559, 40.042183, 0.0)});
while (!future.isReady()) {
tileset.updateView({});
}
SampleHeightResult results = future.waitInMainThread();
REQUIRE(results.warnings.size() == 1);
REQUIRE(results.positions.size() == 1);
REQUIRE(results.sampleSuccess.size() == 1);
CHECK(!results.sampleSuccess[0]);
CHECK(results.warnings[0].find("failed to load") != std::string::npos);
}
}

View File

@ -12,6 +12,7 @@
#include <CesiumUtility/Tracing.h>
#include <memory>
#include <type_traits>
namespace CesiumAsync {
class ITaskProcessor;
@ -181,10 +182,27 @@ public:
std::forward<Func>(f))));
}
/**
* @brief The value type of the Future returned by {@link all}.
*
* This will be either `std::vector<T>`, if the input Futures passed to the
* `all` function return values, or `void` if they do not.
*
* @tparam T The value type of the input Futures passed to the function.
*/
template <typename T>
using AllValueType =
std::conditional_t<std::is_void_v<T>, void, std::vector<T>>;
/**
* @brief Creates a Future that resolves when every Future in a vector
* resolves, and rejects when any Future in the vector rejects.
*
* If the input Futures resolve to non-void values, the returned Future
* resolves to a vector of the values, in the same order as the input Futures.
* If the input Futures resolve to void, the returned Future resolves to void
* as well.
*
* If any of the Futures rejects, the returned Future rejects as well. The
* exception included in the rejection will be from the first Future in the
* vector that rejects.
@ -199,7 +217,7 @@ public:
* rejects when any Future in the vector rejects.
*/
template <typename T>
Future<std::vector<T>> all(std::vector<Future<T>>&& futures) const {
Future<AllValueType<T>> all(std::vector<Future<T>>&& futures) const {
return this->all<T, Future<T>>(
std::forward<std::vector<Future<T>>>(futures));
}
@ -208,21 +226,26 @@ public:
* @brief Creates a Future that resolves when every Future in a vector
* resolves, and rejects when any Future in the vector rejects.
*
* If any of the Futures rejects, the returned Future rejects as well. The
* exception included in the rejection will be from the first Future in the
* vector that rejects.
* If the input SharedFutures resolve to non-void values, the returned Future
* resolves to a vector of the values, in the same order as the input
* SharedFutures. If the input SharedFutures resolve to void, the returned
* Future resolves to void as well.
*
* To get detailed rejection information from each of the Futures,
* If any of the SharedFutures rejects, the returned Future rejects as well.
* The exception included in the rejection will be from the first SharedFuture
* in the vector that rejects.
*
* To get detailed rejection information from each of the SharedFutures,
* attach a `catchInMainThread` continuation prior to passing the
* list into `all`.
*
* @tparam T The type that each Future resolves to.
* @param futures The list of futures.
* @return A Future that resolves when all the given Futures resolve, and
* rejects when any Future in the vector rejects.
* @tparam T The type that each SharedFuture resolves to.
* @param futures The list of shared futures.
* @return A Future that resolves when all the given SharedFutures resolve,
* and rejects when any SharedFuture in the vector rejects.
*/
template <typename T>
Future<std::vector<T>> all(std::vector<SharedFuture<T>>&& futures) const {
Future<AllValueType<T>> all(std::vector<SharedFuture<T>>&& futures) const {
return this->all<T, SharedFuture<T>>(
std::forward<std::vector<SharedFuture<T>>>(futures));
}
@ -293,7 +316,7 @@ public:
private:
// Common implementation of 'all' for both Future and SharedFuture.
template <typename T, typename TFutureType>
Future<std::vector<T>> all(std::vector<TFutureType>&& futures) const {
Future<AllValueType<T>> all(std::vector<TFutureType>&& futures) const {
using TTaskType = decltype(TFutureType::_task);
std::vector<TTaskType> tasks;
tasks.reserve(futures.size());
@ -304,22 +327,30 @@ private:
futures.clear();
async::task<std::vector<T>> task =
async::task<AllValueType<T>> task =
async::when_all(tasks.begin(), tasks.end())
.then(
async::inline_scheduler(),
[](std::vector<TTaskType>&& tasks) {
// Get all the results. If any tasks rejected, we'll bail with
// an exception.
std::vector<T> results;
results.reserve(tasks.size());
if constexpr (std::is_void_v<T>) {
// Tasks return void. "Get" each task so that error
// information is propagated.
for (auto it = tasks.begin(); it != tasks.end(); ++it) {
it->get();
}
} else {
// Get all the results. If any tasks rejected, we'll bail
// with an exception.
std::vector<T> results;
results.reserve(tasks.size());
for (auto it = tasks.begin(); it != tasks.end(); ++it) {
results.emplace_back(it->get());
for (auto it = tasks.begin(); it != tasks.end(); ++it) {
results.emplace_back(it->get());
}
return results;
}
return results;
});
return Future<std::vector<T>>(this->_pSchedulers, std::move(task));
return Future<AllValueType<T>>(this->_pSchedulers, std::move(task));
}
std::shared_ptr<CesiumImpl::AsyncSystemSchedulers> _pSchedulers;

View File

@ -22,7 +22,7 @@ struct CatchFunction {
} catch (...) {
// Make an exception_ptr task, then scheduler to a wrapper around f that
// throws it, catches it, and calls f with a reference to it.
auto ptrToException = [f = std::move(f)](std::exception_ptr&& e) {
auto ptrToException = [f = std::move(f)](std::exception_ptr&& e) mutable {
try {
std::rethrow_exception(e);
} catch (std::exception& e) {
@ -52,7 +52,7 @@ struct CatchFunction<Func, void, Scheduler, TaskParameter> {
} catch (...) {
// Make an exception_ptr task, then scheduler to a wrapper around f that
// throws it, catches it, and calls f with a reference to it.
auto ptrToException = [f = std::move(f)](std::exception_ptr&& e) {
auto ptrToException = [f = std::move(f)](std::exception_ptr&& e) mutable {
try {
std::rethrow_exception(e);
} catch (std::exception& e) {

View File

@ -290,6 +290,30 @@ TEST_CASE("AsyncSystem") {
CHECK(resolved);
}
SECTION("Can use `all` with void-returning Futures") {
auto one = asyncSystem.createPromise<void>();
auto two = asyncSystem.createPromise<void>();
auto three = asyncSystem.createPromise<void>();
std::vector<Future<void>> futures;
futures.emplace_back(one.getFuture());
futures.emplace_back(two.getFuture());
futures.emplace_back(three.getFuture());
Future<void> all = asyncSystem.all(std::move(futures));
bool resolved = false;
Future<void> last =
std::move(all).thenImmediately([&resolved]() { resolved = true; });
three.resolve();
one.resolve();
two.resolve();
last.wait();
CHECK(resolved);
}
SECTION("Future returned by 'all' rejects when any Future rejects") {
auto one = asyncSystem.createPromise<int>();
auto two = asyncSystem.createPromise<int>();
@ -493,6 +517,38 @@ TEST_CASE("AsyncSystem") {
CHECK(result[1] == 11);
}
SECTION("can join two shared futures returning void") {
auto promise = asyncSystem.createPromise<void>();
auto sharedFuture = promise.getFuture().share();
bool executed1 = false;
Future<void> one =
sharedFuture.thenInWorkerThread([&executed1]() { CHECK(!executed1); })
.thenInWorkerThread([&executed1]() {
CHECK(!executed1);
executed1 = true;
});
bool executed2 = false;
Future<void> two =
sharedFuture.thenInWorkerThread([&executed2]() { CHECK(!executed2); })
.thenInWorkerThread([&executed2]() {
CHECK(!executed2);
executed2 = true;
});
std::vector<SharedFuture<void>> futures;
futures.emplace_back(std::move(one).share());
futures.emplace_back(std::move(two).share());
Future<void> joined = asyncSystem.all(std::move(futures));
promise.resolve();
joined.wait();
CHECK(executed1);
CHECK(executed2);
}
SECTION("can catch from shared future") {
auto promise = asyncSystem.createPromise<int>();
auto sharedFuture = promise.getFuture().share();
@ -701,5 +757,23 @@ TEST_CASE("AsyncSystem") {
CHECK_THROWS(future.waitInMainThread());
CHECK(!called);
}
SECTION(
"catchImmediately can return a value from a mutable lambda capture") {
auto promise = asyncSystem.createPromise<std::string>();
promise.reject(std::runtime_error("Some exception"));
std::string myValue = "value from catch";
Future<std::string> future =
promise.getFuture()
.catchImmediately([myValue = std::move(myValue)](
std::exception&& exception) mutable {
CHECK(std::string(exception.what()) == "Some exception");
return myValue;
})
.thenImmediately(
[](std::string&& result) { return std::move(result); });
std::string result = future.waitInMainThread();
CHECK(result == "value from catch");
}
}
}

View File

@ -8,7 +8,7 @@ namespace CesiumGeometry {
struct CESIUMGEOMETRY_API AxisAlignedBox final {
AxisAlignedBox() noexcept
constexpr AxisAlignedBox() noexcept
: minimumX(0.0),
minimumY(0.0),
minimumZ(0.0),
@ -20,7 +20,7 @@ struct CESIUMGEOMETRY_API AxisAlignedBox final {
lengthZ(0.0),
center(0.0) {}
AxisAlignedBox(
constexpr AxisAlignedBox(
double minimumX_,
double minimumY_,
double minimumZ_,

View File

@ -10,6 +10,7 @@
#include <glm/ext/matrix_transform.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtx/norm.hpp>
#include <limits>
@ -229,23 +230,30 @@ IntersectionTests::rayOBB(const Ray& ray, const OrientedBoundingBox& obb) {
std::optional<double> IntersectionTests::rayOBBParametric(
const Ray& ray,
const OrientedBoundingBox& obb) {
// Extract the rotation from the OBB's rotatin/scale transformation and
// invert it. This code assumes that there is not a negative scale, that
// there's no skew, that there's no other funny business. Non-uniform scale
// is fine!
const glm::dmat3& halfAxes = obb.getHalfAxes();
glm::dvec3 halfLengths = obb.getLengths() * 0.5;
glm::dmat3 rotationOnly(
halfAxes[0] / halfLengths.x,
halfAxes[1] / halfLengths.y,
halfAxes[2] / halfLengths.z);
glm::dmat3 inverseRotation = glm::transpose(rotationOnly);
const glm::dmat3x3& inverseHalfAxis = obb.getInverseHalfAxes();
glm::dmat4x4 transformation(
glm::dvec4(glm::normalize(inverseHalfAxis[0]), 0.0),
glm::dvec4(glm::normalize(inverseHalfAxis[1]), 0.0),
glm::dvec4(glm::normalize(inverseHalfAxis[2]), 0.0),
glm::dvec4(0.0, 0.0, 0.0, 1.0));
// Find the equivalent ray in the coordinate system where the OBB is not
// rotated or translated. That is, where it's an AABB at the origin.
glm::dvec3 relativeOrigin = ray.getOrigin() - obb.getCenter();
glm::dvec3 rayOrigin(inverseRotation * relativeOrigin);
glm::dvec3 rayDirection(inverseRotation * ray.getDirection());
glm::dvec3 center =
glm::dvec3(transformation * glm::dvec4(obb.getCenter(), 1.0));
glm::dvec3 halfLengths = obb.getLengths() / 2.0;
glm::dvec3 ll = center - halfLengths;
glm::dvec3 ur = center + halfLengths;
return rayAABBParametric(
ray.transform(transformation),
AxisAlignedBox(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z));
// Find the distance to the new ray's intersection with the AABB, which is
// equivalent to the distance of the original ray intersection with the OBB.
glm::dvec3 ll = -halfLengths;
glm::dvec3 ur = +halfLengths;
AxisAlignedBox aabb(ll.x, ll.y, ll.z, ur.x, ur.y, ur.z);
return rayAABBParametric(Ray(rayOrigin, rayDirection), aabb);
}
std::optional<glm::dvec3>

View File

@ -7,7 +7,8 @@
#include "CesiumUtility/Math.h"
#include <catch2/catch.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtx/transform.hpp>
#include <array>
@ -245,9 +246,7 @@ TEST_CASE("IntersectionTests::rayAABB") {
TEST_CASE("IntersectionTests::rayOBB") {
struct TestCase {
Ray ray;
glm::dvec3 xHalf;
glm::dvec3 yHalf;
glm::dvec3 obbOrigin;
OrientedBoundingBox obb;
std::optional<glm::dvec3> expectedIntersectionPoint;
};
@ -255,25 +254,118 @@ TEST_CASE("IntersectionTests::rayOBB") {
// 2x2x2 obb at origin that is rotated -45 degrees on the x-axis.
TestCase{
Ray(glm::dvec3(0.0, 0.0, 10.0), glm::dvec3(0.0, 0.0, -1.0)),
glm::dvec3(-1.0 / glm::sqrt(2), 0.0, 1.0 / glm::sqrt(2)),
glm::dvec3(0.0, 1.0, 0.0),
glm::dvec3(0.0, 0.0, 0.0),
glm::dvec3(0.0, 0.0, 2.0 / glm::sqrt(2))},
OrientedBoundingBox(
glm::dvec3(0.0, 0.0, 0.0),
glm::dmat3(
glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))),
glm::dvec3(0.0, 0.0, glm::sqrt(2.0))},
// 2x2x2 obb at (10,10,10) that is rotated -45 degrees on the x-axis.
TestCase{
Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)),
glm::dvec3(-1.0 / glm::sqrt(2), 0.0, 1.0 / glm::sqrt(2)),
glm::dvec3(0.0, 1.0, 0.0),
glm::dvec3(10.0, 10.0, 10.0),
glm::dvec3(10.0, 10.0, 10.0 + 2.0 / glm::sqrt(2))});
std::optional<glm::dvec3> intersectionPoint = IntersectionTests::rayOBB(
testCase.ray,
OrientedBoundingBox(
testCase.obbOrigin,
glm::dmat3x3(
testCase.xHalf,
testCase.yHalf,
glm::cross(testCase.xHalf, testCase.yHalf))));
OrientedBoundingBox(
glm::dvec3(10.0, 10.0, 10.0),
glm::dmat3(
glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))),
glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(2.0))},
// 2x2x2 obb at (10,20,30) that is rotated -45 degrees on the x-axis and
// hit from an angle.
TestCase{
Ray(glm::dvec3(10.0, 20.0 + 2.0, 30.0 + 1.0 + glm::sqrt(2)),
glm::normalize(glm::dvec3(0.0, -2.0, -1.0))),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(
glm::rotate(glm::radians(-45.0), glm::dvec3(1.0, 0.0, 0.0)))),
glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))},
// 4x4x4 obb at (10,10,10) that is rotated -45 degrees on the x-axis.
TestCase{
Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 10.0, 10.0),
2.0 * glm::dmat3(glm::rotate(
glm::radians(-45.0),
glm::dvec3(1.0, 0.0, 0.0)))),
glm::dvec3(10.0, 10.0, 10.0 + glm::sqrt(8.0))},
// 4x4x4 obb at (10,20,30) that is rotated -45 degrees on the x-axis and
// hit from an angle
TestCase{
Ray(glm::dvec3(10.0, 20.0 + 10.0, 30.0 + 20.0 + glm::sqrt(8.0)),
glm::normalize(glm::dvec3(0.0, -1.0, -2.0))),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
2.0 * glm::dmat3(glm::rotate(
glm::radians(-45.0),
glm::dvec3(1.0, 0.0, 0.0)))),
glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(8.0))},
// 4x4x2 obb at (10,10,10) that is not rotated.
TestCase{
Ray(glm::dvec3(10.0, 10.0, 20.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 10.0, 10.0),
glm::dmat3(glm::scale(glm::dvec3(2.0, 2.0, 1.0)))),
glm::dvec3(10.0, 10.0, 10.0 + 1.0)},
// 4x2x4 obb at (10,20,30) that is not rotated.
TestCase{
Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(glm::scale(glm::dvec3(2.0, 1.0, 2.0)))),
glm::dvec3(10.0, 20.0, 30.0 + 2.0)},
// 2x4x2 obb at (10,20,30) that is rotated 45 degrees on the Y-axis.
TestCase{
Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0))) *
glm::dmat3(glm::rotate(
glm::radians(45.0),
glm::dvec3(0.0, 1.0, 0.0)))),
glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))},
// 2x4x2 obb at (10,20,30) that is rotated 45 degrees on the X-axis.
TestCase{
Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(
glm::rotate(glm::radians(45.0), glm::dvec3(1.0, 0.0, 0.0))) *
glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0)))),
glm::dvec3(10.0, 20.0, 30.0 + 1.0 / glm::cos(glm::radians(45.0)))},
// 2x4x2 obb at (10,20,30) that is rotated 225 degrees on the Y-axis.
TestCase{
Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(glm::scale(glm::dvec3(1.0, 2.0, 1.0))) *
glm::dmat3(glm::rotate(
glm::radians(225.0),
glm::dvec3(0.0, 1.0, 0.0)))),
glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(2.0))},
// 2x2x4 obb at (10,20,30) that is rotated 90 degrees on the X-axis and
// hit from an angle.
TestCase{
Ray(glm::dvec3(10.0, 20.0 + 2.0, 30.0 + 1.0 + 1.0),
glm::normalize(glm::dvec3(0.0, -2.0, -1.0))),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
glm::dmat3(
glm::rotate(glm::radians(90.0), glm::dvec3(1.0, 0.0, 0.0))) *
glm::dmat3(glm::scale(glm::dvec3(1.0, 1.0, 2.0)))),
glm::dvec3(10.0, 20.0, 30.0 + 1.0)},
// 2x2x2 obb at (10,20,30) that is rotated 45 degrees on the X- and
// Y-axis.
TestCase{
Ray(glm::dvec3(10.0, 20.0, 40.0), glm::dvec3(0.0, 0.0, -1.0)),
OrientedBoundingBox(
glm::dvec3(10.0, 20.0, 30.0),
(glm::dmat3(glm::rotate(
glm::atan(1.0 / 2.0, glm::sqrt(2) / 2.0),
glm::dvec3(1.0, 0.0, 0.0)))) *
glm::dmat3(glm::rotate(
glm::radians(45.0),
glm::dvec3(0.0, 1.0, 0.0)))),
glm::dvec3(10.0, 20.0, 30.0 + glm::sqrt(3.0))});
std::optional<glm::dvec3> intersectionPoint =
IntersectionTests::rayOBB(testCase.ray, testCase.obb);
CHECK(glm::all(glm::lessThan(
glm::abs(*intersectionPoint - *testCase.expectedIntersectionPoint),
glm::dvec3(CesiumUtility::Math::Epsilon6))));

View File

@ -1373,6 +1373,12 @@ std::optional<glm::dvec3> intersectRayScenePrimitive(
return transformedRay.pointFromDistance(tClosest);
}
std::string intersectGltfUnsupportedExtensions[] = {
ExtensionKhrDracoMeshCompression::ExtensionName,
ExtensionBufferViewExtMeshoptCompression::ExtensionName,
ExtensionExtMeshGpuInstancing::ExtensionName,
"KHR_mesh_quantization"};
} // namespace
GltfUtilities::IntersectResult GltfUtilities::intersectRayGltfModel(
@ -1380,6 +1386,19 @@ GltfUtilities::IntersectResult GltfUtilities::intersectRayGltfModel(
const CesiumGltf::Model& gltf,
bool cullBackFaces,
const glm::dmat4x4& gltfTransform) {
// We can't currently intersect a ray with a model if the model has any funny
// business with its vertex positions or if it uses instancing.
for (const std::string& unsupportedExtension :
intersectGltfUnsupportedExtensions) {
if (gltf.isExtensionRequired(unsupportedExtension)) {
return IntersectResult{
std::nullopt,
{fmt::format(
"Cannot intersect a ray with a glTF model with the {} extension.",
unsupportedExtension)}};
}
}
glm::dmat4x4 rootTransform = applyRtcCenter(gltf, gltfTransform);
rootTransform = applyGltfUpAxisTransform(gltf, rootTransform);

View File

@ -1,5 +1,6 @@
#include <CesiumNativeTests/FileAccessor.h>
#include <CesiumNativeTests/SimpleAssetRequest.h>
#include <CesiumUtility/Uri.h>
#include <cstring>
#include <fstream>
@ -8,7 +9,6 @@ namespace CesiumNativeTests {
namespace {
std::unique_ptr<SimpleAssetResponse> readFileUri(const std::string& uri) {
std::vector<std::byte> result;
CesiumAsync::HttpHeaders headers;
std::string contentType;
@ -23,7 +23,8 @@ std::unique_ptr<SimpleAssetResponse> readFileUri(const std::string& uri) {
if (protocolPos != 0) {
return response(400);
}
std::string path = uri.substr(std::strlen("file://"));
std::string path =
CesiumUtility::Uri::uriPathToNativePath(CesiumUtility::Uri::getPath(uri));
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return response(404);

View File

@ -70,8 +70,8 @@ macro(EZVCPKG_CALCULATE_PATHS)
if (NOT VCPKG_TRIPLET)
message(FATAL_ERROR "VCPKG_TRIPLET must be set")
endif()
file(TO_CMAKE_PATH ${EZVCPKG_DIR}/installed/${VCPKG_TRIPLET} EZVCPKG_INSTALLED_DIR)
file(TO_CMAKE_PATH ${EZVCPKG_DIR}/scripts/buildsystems/vcpkg.cmake EZVCPKG_CMAKE_TOOLCHAIN)
file(TO_CMAKE_PATH "${EZVCPKG_DIR}/installed/${VCPKG_TRIPLET}" EZVCPKG_INSTALLED_DIR)
file(TO_CMAKE_PATH "${EZVCPKG_DIR}/scripts/buildsystems/vcpkg.cmake" EZVCPKG_CMAKE_TOOLCHAIN)
endmacro()
macro(EZVCPKG_CHECK_RESULTS)
@ -104,23 +104,19 @@ macro(EZVCPKG_CHECK_RESULTS)
endif()
endmacro()
macro(EZVCPKG_BOOTSTRAP)
if (NOT EXISTS ${EZVCPKG_README})
if (NOT EXISTS "${EZVCPKG_README}")
message(STATUS "EZVCPKG Bootstrapping")
find_package(Git)
if (NOT Git_FOUND)
message(FATAL_ERROR "EZVCPKG Git not found, can't bootstrap vcpkg")
endif()
message(STATUS "EZVCPKG Cloning repository")
execute_process(
COMMAND "${GIT_EXECUTABLE}" "clone" ${EZVCPKG_URL} ${EZVCPKG_DIR}
COMMAND "${GIT_EXECUTABLE}" "clone" ${EZVCPKG_URL} "${EZVCPKG_DIR}"
RESULTS_VARIABLE EZVCPKG_RESULT
OUTPUT_VARIABLE EZVCPKG_OUTPUT
ERROR_VARIABLE EZVCPKG_OUTPUT
# OUTPUT_QUIET
# ERROR_QUIET
)
EZVCPKG_CHECK_RESULTS()
@ -130,35 +126,31 @@ macro(EZVCPKG_BOOTSTRAP)
message(STATUS "EZVCPKG Checking out commit ${EZVCPKG_COMMIT}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" "-c" "advice.detachedHead=false" "checkout" ${EZVCPKG_COMMIT}
WORKING_DIRECTORY ${EZVCPKG_DIR}
WORKING_DIRECTORY "${EZVCPKG_DIR}"
RESULTS_VARIABLE EZVCPKG_RESULT
OUTPUT_VARIABLE EZVCPKG_OUTPUT
ERROR_VARIABLE EZVCPKG_OUTPUT
# OUTPUT_QUIET
# ERROR_QUIET
)
EZVCPKG_CHECK_RESULTS()
endif()
if (EZVCPKG_HOST_VCPKG)
set(EZVCPKG_EXE ${EZVCPKG_HOST_VCPKG})
elseif(NOT EXISTS ${EZVCPKG_EXE})
set(EZVCPKG_EXE "${EZVCPKG_HOST_VCPKG}")
elseif(NOT EXISTS "${EZVCPKG_EXE}")
message(STATUS "EZVCPKG Bootstrapping vcpkg binary")
message(STATUS "EZVCPKG vcpkg bootstrap command: ${EZVCPKG_BOOTSTRAP}")
message(STATUS "EZVCPKG vcpkg working dir: ${EZVCPKG_DIR}")
execute_process(
COMMAND ${EZVCPKG_BOOTSTRAP}
WORKING_DIRECTORY ${EZVCPKG_DIR}
WORKING_DIRECTORY "${EZVCPKG_DIR}"
RESULTS_VARIABLE EZVCPKG_RESULT
OUTPUT_VARIABLE EZVCPKG_OUTPUT
ERROR_VARIABLE EZVCPKG_OUTPUT
# OUTPUT_QUIET
# ERROR_QUIET
)
EZVCPKG_CHECK_RESULTS()
endif()
if (NOT EXISTS ${EZVCPKG_EXE})
if (NOT EXISTS "${EZVCPKG_EXE}")
if (EZVCPKG_IGNORE_ERRORS)
message(WARNING "EZVCPKG vcpkg binary not failed")
return()
@ -168,39 +160,44 @@ macro(EZVCPKG_BOOTSTRAP)
endif()
endmacro()
macro(EZVCPKG_BUILD)
set(INSTALL_COMMAND "${EZVCPKG_EXE}" --vcpkg-root "${EZVCPKG_DIR}" install --triplet ${VCPKG_TRIPLET})
if (DEFINED VCPKG_OVERLAY_PORTS)
set(INSTALL_COMMAND ${INSTALL_COMMAND} --overlay-ports "${VCPKG_OVERLAY_PORTS}")
endif()
if (DEFINED VCPKG_OVERLAY_TRIPLETS)
set(INSTALL_COMMAND ${INSTALL_COMMAND} --overlay-triplets "${VCPKG_OVERLAY_TRIPLETS}")
endif()
if (EZVCPKG_SERIALIZE)
foreach(_PACKAGE ${EZVCPKG_PACKAGES})
message(STATUS "EZVCPKG Building/Verifying package ${_PACKAGE} using triplet ${VCPKG_TRIPLET}")
execute_process(
COMMAND ${EZVCPKG_EXE} --vcpkg-root ${EZVCPKG_DIR} install --triplet ${VCPKG_TRIPLET} ${_PACKAGE}
WORKING_DIRECTORY ${EZVCPKG_DIR}
COMMAND ${INSTALL_COMMAND} ${_PACKAGE}
WORKING_DIRECTORY "${EZVCPKG_DIR}"
RESULTS_VARIABLE EZVCPKG_RESULT
OUTPUT_VARIABLE EZVCPKG_OUTPUT
ERROR_VARIABLE EZVCPKG_OUTPUT
# OUTPUT_QUIET
# ERROR_QUIET
)
EZVCPKG_CHECK_RESULTS()
endforeach()
else()
message(STATUS "EZVCPKG Building/Verifying packages ${EZVCPKG_PACKAGES} using triplet ${VCPKG_TRIPLET}")
execute_process(
COMMAND ${EZVCPKG_EXE} --vcpkg-root ${EZVCPKG_DIR} install --triplet ${VCPKG_TRIPLET} ${EZVCPKG_PACKAGES}
WORKING_DIRECTORY ${EZVCPKG_DIR}
COMMAND ${INSTALL_COMMAND} ${EZVCPKG_PACKAGES}
WORKING_DIRECTORY "${EZVCPKG_DIR}"
RESULTS_VARIABLE EZVCPKG_RESULT
OUTPUT_VARIABLE EZVCPKG_OUTPUT
ERROR_VARIABLE EZVCPKG_OUTPUT
# OUTPUT_QUIET
# ERROR_QUIET
)
EZVCPKG_CHECK_RESULTS()
endif()
# if we didn't blow up, wipe the build trees to avoid huge cache usage on CI servers
if (EZVCPKG_CLEAN_BUILDTREES)
file(TO_CMAKE_PATH ${EZVCPKG_DIR}/buildtrees EZVCPKG_BUILDTREES)
file(TO_CMAKE_PATH "${EZVCPKG_DIR}/buildtrees" EZVCPKG_BUILDTREES)
if (EXISTS ${EZVCPKG_BUILDTREES})
file(REMOVE_RECURSE "${EZVCPKG_DIR}/buildtrees")
endif()

View File

@ -1,6 +1,6 @@
{
"name": "cesium-native",
"version": "0.39.0",
"version": "0.40.1",
"description": "Cesium 3D Geospatial for C++",
"main": "index.js",
"directories": {