Merge remote-tracking branch 'origin/i3dm-2024' into i3dm-2024

This commit is contained in:
Tim Moore 2024-05-17 19:32:19 +02:00
commit c6b5321d0a
24 changed files with 444 additions and 344 deletions

View File

@ -10,6 +10,8 @@
- `EXT_mesh_gpu_instancing`
- `CESIUM_primitive_outline`
- `CESIUM_tile_edges`
- Fixed a bug in `GltfUtilities::compactBuffer` where it would not preserve the alignment of the bufferViews.
- The `collapseToSingleBuffer` and `moveBufferContent` functions in `GltfUtilities` now align to an 8-byte boundary rather than a 4-byte boundary, because bufferViews associated with some glTF extensions require this larger alignment.
### v0.35.0 - 2024-05-01

View File

@ -11,12 +11,12 @@
#include <optional>
namespace Cesium3DTilesContent {
struct ConverterSubprocessor;
struct AssetFetcher;
struct B3dmToGltfConverter {
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& b3dmBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& assetFetcher);
};
} // namespace Cesium3DTilesContent

View File

@ -9,20 +9,19 @@
#include <cstddef>
namespace Cesium3DTilesContent {
struct ConverterSubprocessor;
struct AssetFetcher;
struct BinaryToGltfConverter {
public:
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& gltfBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subProcessor);
const AssetFetcher& assetFetcher);
private:
static GltfConverterResult convertImmediate(
const gsl::span<const std::byte>& gltfBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subProcessor);
const CesiumGltfReader::GltfReaderOptions& options);
static CesiumGltfReader::GltfReader _gltfReader;
};
} // namespace Cesium3DTilesContent

View File

@ -9,12 +9,12 @@
#include <cstddef>
namespace Cesium3DTilesContent {
struct ConverterSubprocessor;
struct AssetFetcher;
struct CmptToGltfConverter {
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& cmptBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor&);
const AssetFetcher& assetFetcher);
};
} // namespace Cesium3DTilesContent

View File

@ -15,12 +15,17 @@
namespace Cesium3DTilesContent {
struct ByteResult {
std::vector<std::byte> bytes;
CesiumUtility::ErrorList errorList;
};
/**
* Data required to make a recursive request to fetch an asset, mostly for the
* benefit of I3dm files.
* Object that makes a recursive request to fetch an asset, mostly for the
* benefit of i3dm files.
*/
struct CESIUM3DTILESCONTENT_API ConverterSubprocessor {
ConverterSubprocessor(
struct CESIUM3DTILESCONTENT_API AssetFetcher {
AssetFetcher(
const CesiumAsync::AsyncSystem& asyncSystem_,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor_,
const std::string& baseUrl_,
@ -31,10 +36,13 @@ struct CESIUM3DTILESCONTENT_API ConverterSubprocessor {
baseUrl(baseUrl_),
tileTransform(tileTransform_),
requestHeaders(requestHeaders_) {}
CesiumAsync::Future<ByteResult> get(const std::string& relativeUrl) const;
const CesiumAsync::AsyncSystem& asyncSystem;
const std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor;
const std::string baseUrl;
glm::dmat4 tileTransform;
glm::dmat4 tileTransform; // For ENU transforms in i3dm
const std::vector<CesiumAsync::IAssetAccessor::THeader>& requestHeaders;
};
@ -61,7 +69,7 @@ public:
using ConverterFunction = CesiumAsync::Future<GltfConverterResult> (*)(
const gsl::span<const std::byte>& content,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& subprocessor);
/**
* @brief Register the given function for the given magic header.
@ -144,14 +152,16 @@ public:
* the converter.
* @param content The tile binary content that may contains the magic header
* to look up the converter and is used to convert to gltf model.
* @param options The {@link CesiumGltfReader::GltfReaderOptions} for how to read a glTF.
* @param options The {@link CesiumGltfReader::GltfReaderOptions} for how to
* read a glTF.
* @param assetFetcher An object that can perform recursive asset requests.
* @return The {@link GltfConverterResult} that stores the gltf model converted from the binary data.
*/
static CesiumAsync::Future<GltfConverterResult> convert(
const std::string& filePath,
const gsl::span<const std::byte>& content,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& assetFetcher);
/**
* @brief Creates the {@link GltfConverterResult} from the given
@ -170,13 +180,15 @@ public:
*
* @param content The tile binary content that may contains the magic header
* to look up the converter and is used to convert to gltf model.
* @param options The {@link CesiumGltfReader::GltfReaderOptions} for how to read a glTF.
* @param options The {@link CesiumGltfReader::GltfReaderOptions} for how to
* read a glTF.
* @param assetFetcher An object that can perform recursive asset requests.
* @return The {@link GltfConverterResult} that stores the gltf model converted from the binary data.
*/
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& content,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& assetFetcher);
private:
static std::string toLowerCase(const std::string_view& str);

View File

@ -11,12 +11,12 @@
#include <optional>
namespace Cesium3DTilesContent {
struct ConverterSubprocessor;
struct AssetFetcher;
struct I3dmToGltfConverter {
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& instancesBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& assetFetcher);
};
} // namespace Cesium3DTilesContent

View File

@ -21,16 +21,8 @@ class Buffer;
namespace Cesium3DTilesContent {
struct ByteResult {
std::vector<std::byte> bytes;
CesiumUtility::ErrorList errorList;
};
CesiumAsync::Future<ByteResult>
get(const ConverterSubprocessor& subprocessor, const std::string& relativeUrl);
namespace LegacyUtilities {
std::optional<uint32_t> parseOffset(
std::optional<uint32_t> parseOffsetForSemantic(
const rapidjson::Document& document,
const char* semantic,
CesiumUtility::ErrorList& errorList);
@ -84,9 +76,7 @@ parseArrayValueDVec3(const rapidjson::Value& arrayValue);
std::optional<glm::dvec3>
parseArrayValueDVec3(const rapidjson::Document& document, const char* name);
int32_t createBufferInGltf(CesiumGltf::Model& gltf);
int32_t
createBufferInGltf(CesiumGltf::Model& gltf, std::vector<std::byte>&& buffer);
int32_t createBufferInGltf(CesiumGltf::Model& gltf, std::vector<std::byte> buffer = {});
int32_t createBufferViewInGltf(
CesiumGltf::Model& gltf,
@ -101,27 +91,27 @@ int32_t createAccessorInGltf(
const int64_t count,
const std::string type);
void applyRTC(CesiumGltf::Model& gltf, const glm::dvec3& rtc);
void applyRtcToNodes(CesiumGltf::Model& gltf, const glm::dvec3& rtc);
template <typename GLMType, typename GLTFType>
GLMType toGlm(const GLTFType& gltfVal);
template <typename GlmType, typename GLTFType>
GlmType toGlm(const GLTFType& gltfVal);
template <typename GLMType, typename COMPONENTType>
GLMType toGlm(const CesiumGltf::AccessorTypes::VEC3<COMPONENTType>& gltfVal) {
return GLMType(gltfVal.value[0], gltfVal.value[1], gltfVal.value[2]);
template <typename GlmType, typename ComponentType>
GlmType toGlm(const CesiumGltf::AccessorTypes::VEC3<ComponentType>& gltfVal) {
return GlmType(gltfVal.value[0], gltfVal.value[1], gltfVal.value[2]);
}
template <typename GLMType, typename COMPONENTType>
GLMType
toGlmQuat(const CesiumGltf::AccessorTypes::VEC4<COMPONENTType>& gltfVal) {
if constexpr (std::is_same<COMPONENTType, float>()) {
return GLMType(
template <typename GlmType, typename ComponentType>
GlmType
toGlmQuat(const CesiumGltf::AccessorTypes::VEC4<ComponentType>& gltfVal) {
if constexpr (std::is_same<ComponentType, float>()) {
return GlmType(
gltfVal.value[3],
gltfVal.value[0],
gltfVal.value[1],
gltfVal.value[2]);
} else {
return GLMType(
return GlmType(
CesiumGltf::normalize(gltfVal.value[3]),
CesiumGltf::normalize(gltfVal.value[0]),
CesiumGltf::normalize(gltfVal.value[1]),

View File

@ -11,12 +11,12 @@
#include <optional>
namespace Cesium3DTilesContent {
struct ConverterSubprocessor;
struct AssetFetcher;
struct PntsToGltfConverter {
static CesiumAsync::Future<GltfConverterResult> convert(
const gsl::span<const std::byte>& pntsBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor);
const AssetFetcher& assetFetcher);
};
} // namespace Cesium3DTilesContent

View File

@ -119,7 +119,7 @@ CesiumAsync::Future<GltfConverterResult> convertB3dmContentToGltf(
const B3dmHeader& header,
uint32_t headerLength,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor) {
const AssetFetcher& assetFetcher) {
const uint32_t glbStart = headerLength + header.featureTableJsonByteLength +
header.featureTableBinaryByteLength +
header.batchTableJsonByteLength +
@ -131,13 +131,13 @@ CesiumAsync::Future<GltfConverterResult> convertB3dmContentToGltf(
result.errors.emplaceError(
"The B3DM is invalid because the start of the "
"glTF model is after the end of the entire B3DM.");
return subprocessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
const gsl::span<const std::byte> glbData =
b3dmBinary.subspan(glbStart, glbEnd - glbStart);
return BinaryToGltfConverter::convert(glbData, options, subprocessor);
return BinaryToGltfConverter::convert(glbData, options, assetFetcher);
}
rapidjson::Document parseFeatureTableJsonData(
@ -232,13 +232,13 @@ void convertB3dmMetadataToGltfStructuralMetadata(
CesiumAsync::Future<GltfConverterResult> B3dmToGltfConverter::convert(
const gsl::span<const std::byte>& b3dmBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor) {
const AssetFetcher& assetFetcher) {
GltfConverterResult result;
B3dmHeader header;
uint32_t headerLength = 0;
parseB3dmHeader(b3dmBinary, header, headerLength, result);
if (result.errors) {
return subprocessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
return convertB3dmContentToGltf(
@ -246,7 +246,7 @@ CesiumAsync::Future<GltfConverterResult> B3dmToGltfConverter::convert(
header,
headerLength,
options,
subprocessor)
assetFetcher)
.thenImmediately(
[b3dmBinary, header, headerLength](GltfConverterResult&& glbResult) {
if (!glbResult.errors) {

View File

@ -6,8 +6,7 @@ CesiumGltfReader::GltfReader BinaryToGltfConverter::_gltfReader;
GltfConverterResult BinaryToGltfConverter::convertImmediate(
const gsl::span<const std::byte>& gltfBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor&) {
const CesiumGltfReader::GltfReaderOptions& options) {
CesiumGltfReader::GltfReaderResult loadedGltf =
_gltfReader.readGltf(gltfBinary, options);
@ -21,8 +20,8 @@ GltfConverterResult BinaryToGltfConverter::convertImmediate(
CesiumAsync::Future<GltfConverterResult> BinaryToGltfConverter::convert(
const gsl::span<const std::byte>& gltfBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor) {
return subprocessor.asyncSystem.createResolvedFuture(
convertImmediate(gltfBinary, options, subprocessor));
const AssetFetcher& assetFetcher) {
return assetFetcher.asyncSystem.createResolvedFuture(
convertImmediate(gltfBinary, options));
}
} // namespace Cesium3DTilesContent

View File

@ -25,11 +25,11 @@ static_assert(sizeof(InnerHeader) == 12);
CesiumAsync::Future<GltfConverterResult> CmptToGltfConverter::convert(
const gsl::span<const std::byte>& cmptBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subProcessor) {
const AssetFetcher& assetFetcher) {
GltfConverterResult result;
if (cmptBinary.size() < sizeof(CmptHeader)) {
result.errors.emplaceWarning("Composite tile must be at least 16 bytes.");
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
const CmptHeader* pHeader =
@ -37,14 +37,14 @@ CesiumAsync::Future<GltfConverterResult> CmptToGltfConverter::convert(
if (std::string(pHeader->magic, 4) != "cmpt") {
result.errors.emplaceWarning(
"Composite tile does not have the expected magic vaue 'cmpt'.");
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
if (pHeader->version != 1) {
result.errors.emplaceWarning(fmt::format(
"Unsupported composite tile version {}.",
pHeader->version));
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
if (pHeader->byteLength > cmptBinary.size()) {
@ -52,7 +52,7 @@ CesiumAsync::Future<GltfConverterResult> CmptToGltfConverter::convert(
"Composite tile byteLength is {} but only {} bytes are available.",
pHeader->byteLength,
cmptBinary.size()));
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
std::vector<CesiumAsync::Future<GltfConverterResult>> innerTiles;
@ -80,7 +80,7 @@ CesiumAsync::Future<GltfConverterResult> CmptToGltfConverter::convert(
pos += pInner->byteLength;
innerTiles.emplace_back(
GltfConverters::convert(innerData, options, subProcessor));
GltfConverters::convert(innerData, options, assetFetcher));
}
uint32_t tilesLength = pHeader->tilesLength;
@ -90,10 +90,10 @@ CesiumAsync::Future<GltfConverterResult> CmptToGltfConverter::convert(
"Composite tile does not contain any loadable inner "
"tiles.");
}
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
return subProcessor.asyncSystem.all(std::move(innerTiles))
return assetFetcher.asyncSystem.all(std::move(innerTiles))
.thenImmediately([](std::vector<GltfConverterResult>&& innerResults) {
if (innerResults.size() == 1) {
return innerResults[0];

View File

@ -1,4 +1,6 @@
#include <CesiumAsync/IAssetResponse.h>
#include <Cesium3DTilesContent/GltfConverters.h>
#include <CesiumUtility/Uri.h>
#include <spdlog/spdlog.h>
@ -41,17 +43,17 @@ CesiumAsync::Future<GltfConverterResult> GltfConverters::convert(
const std::string& filePath,
const gsl::span<const std::byte>& content,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor) {
const AssetFetcher& assetFetcher) {
std::string magic;
auto converterFun = getConverterByMagic(content, magic);
if (converterFun) {
return converterFun(content, options, subprocessor);
return converterFun(content, options, assetFetcher);
}
std::string fileExtension;
converterFun = getConverterByFileExtension(filePath, fileExtension);
if (converterFun) {
return converterFun(content, options, subprocessor);
return converterFun(content, options, assetFetcher);
}
ErrorList errors;
@ -61,18 +63,18 @@ CesiumAsync::Future<GltfConverterResult> GltfConverters::convert(
fileExtension,
magic));
return subprocessor.asyncSystem.createResolvedFuture(
return assetFetcher.asyncSystem.createResolvedFuture(
GltfConverterResult{std::nullopt, std::move(errors)});
}
CesiumAsync::Future<GltfConverterResult> GltfConverters::convert(
const gsl::span<const std::byte>& content,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor) {
const AssetFetcher& assetFetcher) {
std::string magic;
auto converter = getConverterByMagic(content, magic);
if (converter) {
return converter(content, options, subprocessor);
return converter(content, options, assetFetcher);
}
ErrorList errors;
@ -80,7 +82,7 @@ CesiumAsync::Future<GltfConverterResult> GltfConverters::convert(
"No loader registered for tile with magic value '{}'",
magic));
return subprocessor.asyncSystem.createResolvedFuture(
return assetFetcher.asyncSystem.createResolvedFuture(
GltfConverterResult{std::nullopt, std::move(errors)});
}
@ -132,4 +134,38 @@ GltfConverters::ConverterFunction GltfConverters::getConverterByMagic(
return nullptr;
}
CesiumAsync::Future<ByteResult>
AssetFetcher::get(const std::string& relativeUrl) const {
auto resolvedUrl = Uri::resolve(baseUrl, relativeUrl);
return pAssetAccessor->get(asyncSystem, resolvedUrl, requestHeaders)
.thenImmediately(
[asyncSystem = asyncSystem](
std::shared_ptr<CesiumAsync::IAssetRequest>&& pCompletedRequest) {
const CesiumAsync::IAssetResponse* pResponse =
pCompletedRequest->response();
ByteResult byteResult;
const auto& url = pCompletedRequest->url();
if (!pResponse) {
byteResult.errorList.emplaceError(fmt::format(
"Did not receive a valid response for asset {}",
url));
return asyncSystem.createResolvedFuture(std::move(byteResult));
}
uint16_t statusCode = pResponse->statusCode();
if (statusCode != 0 && (statusCode < 200 || statusCode >= 300)) {
byteResult.errorList.emplaceError(fmt::format(
"Received status code {} for asset {}",
statusCode,
url));
return asyncSystem.createResolvedFuture(std::move(byteResult));
}
gsl::span<const std::byte> asset = pResponse->data();
std::copy(
asset.begin(),
asset.end(),
std::back_inserter(byteResult.bytes));
return asyncSystem.createResolvedFuture(std::move(byteResult));
});
}
} // namespace Cesium3DTilesContent

View File

@ -28,7 +28,7 @@ namespace Cesium3DTilesContent {
using namespace LegacyUtilities;
namespace {
struct InstancesHeader {
struct I3dmHeader {
unsigned char magic[4] = {0, 0, 0, 0};
uint32_t version = 0;
uint32_t byteLength = 0;
@ -50,10 +50,13 @@ struct DecodedInstances {
// Instance positions may arrive in ECEF coordinates or with other large
// displacements that will cause problems during rendering. Determine the mean
// position of the instances and render them relative to that, creating a new
// RTC center.
// position of the instances and reposition them relative to it, thus creating
// a new RTC center.
//
// If an RTC center value is already present, then the newly-computed center is
// added to it.
void rebaseInstances(DecodedInstances& decodedInstances) {
void repositionInstances(DecodedInstances& decodedInstances) {
if (decodedInstances.positions.empty()) {
return;
}
@ -75,22 +78,22 @@ void rebaseInstances(DecodedInstances& decodedInstances) {
decodedInstances.rtcCenter = newCenter;
}
void parseInstancesHeader(
void parseI3dmHeader(
const gsl::span<const std::byte>& instancesBinary,
InstancesHeader& header,
I3dmHeader& header,
uint32_t& headerLength,
GltfConverterResult& result) {
if (instancesBinary.size() < sizeof(InstancesHeader)) {
if (instancesBinary.size() < sizeof(I3dmHeader)) {
result.errors.emplaceError("The I3DM is invalid because it is too small to "
"include a I3DM header.");
return;
}
const InstancesHeader* pHeader =
reinterpret_cast<const InstancesHeader*>(instancesBinary.data());
const I3dmHeader* pHeader =
reinterpret_cast<const I3dmHeader*>(instancesBinary.data());
header = *pHeader;
headerLength = sizeof(InstancesHeader);
headerLength = sizeof(I3dmHeader);
if (pHeader->version != 1) {
result.errors.emplaceError(fmt::format(
@ -107,7 +110,7 @@ void parseInstancesHeader(
}
}
struct InstanceContent {
struct I3dmContent {
uint32_t instancesLength = 0;
std::optional<glm::dvec3> rtcCenter;
std::optional<glm::dvec3> quantizedVolumeOffset;
@ -124,7 +127,6 @@ struct InstanceContent {
std::optional<uint32_t> scale;
std::optional<uint32_t> scaleNonUniform;
std::optional<uint32_t> batchId;
// batch table format?
CesiumUtility::ErrorList errors;
};
@ -184,7 +186,7 @@ glm::quat rotationFromUpRight(const glm::vec3& up, const glm::vec3& right) {
return upRot * rightRot;
}
struct ConvertResult {
struct ConvertedI3dm {
GltfConverterResult gltfResult;
DecodedInstances decodedInstances;
};
@ -201,176 +203,230 @@ struct ConvertResult {
table, hashed by mesh transform.
+ Add the instance transforms to the glTF buffers, buffer views, and
accessors.
+ Metadata / feature id?
+ Future work: Metadata / feature id?
*/
CesiumAsync::Future<ConvertResult> convertInstancesContent(
const gsl::span<const std::byte>& instancesBinary,
const InstancesHeader& header,
uint32_t headerLength,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subprocessor,
GltfConverterResult& result) {
ConvertResult subResult;
DecodedInstances& decodedInstances = subResult.decodedInstances;
subResult.gltfResult = result;
auto finishEarly = [&]() {
return subprocessor.asyncSystem.createResolvedFuture(std::move(subResult));
};
if (header.featureTableJsonByteLength == 0 ||
header.featureTableBinaryByteLength == 0) {
return finishEarly();
}
const uint32_t glTFStart = headerLength + header.featureTableJsonByteLength +
header.featureTableBinaryByteLength +
header.batchTableJsonByteLength +
header.batchTableBinaryByteLength;
const uint32_t glTFEnd = header.byteLength;
auto gltfData = instancesBinary.subspan(glTFStart, glTFEnd - glTFStart);
std::optional<CesiumAsync::Future<ByteResult>> assetFuture;
auto featureTableJsonData =
instancesBinary.subspan(headerLength, header.featureTableJsonByteLength);
std::optional<I3dmContent> parseI3dmJson(
const gsl::span<const std::byte> featureTableJsonData,
CesiumUtility::ErrorList& errors) {
rapidjson::Document featureTableJson;
featureTableJson.Parse(
reinterpret_cast<const char*>(featureTableJsonData.data()),
featureTableJsonData.size());
if (featureTableJson.HasParseError()) {
subResult.gltfResult.errors.emplaceError(fmt::format(
"Error when parsing feature table JSON, error code {} at byte offset "
"{}",
featureTableJson.GetParseError(),
featureTableJson.GetErrorOffset()));
return finishEarly();
errors.emplaceError(fmt::format(
"Error when parsing feature table JSON, error code {} at byte offset "
"{}",
featureTableJson.GetParseError(),
featureTableJson.GetErrorOffset()));
return {};
}
InstanceContent parsedContent;
I3dmContent parsedContent;
// Global semantics
if (auto optinstancesLength =
if (auto optInstancesLength =
getValue<uint32_t>(featureTableJson, "INSTANCES_LENGTH")) {
parsedContent.instancesLength = *optinstancesLength;
parsedContent.instancesLength = *optInstancesLength;
} else {
subResult.gltfResult.errors.emplaceError(
"Error parsing I3DM feature table, no valid "
"INSTANCES_LENGTH was found.");
return finishEarly();
errors.emplaceError(
"Error parsing I3DM feature table, no valid INSTANCES_LENGTH was found.");
return {};
}
parsedContent.rtcCenter =
parseArrayValueDVec3(featureTableJson, "RTC_CENTER");
decodedInstances.rtcCenter = parsedContent.rtcCenter;
parsedContent.position =
parseOffset(featureTableJson, "POSITION", subResult.gltfResult.errors);
if (!parsedContent.position) {
if (subResult.gltfResult.errors.hasErrors()) {
return finishEarly();
}
parsedContent.positionQuantized = parseOffset(
featureTableJson,
"POSITION_QUANTIZED",
subResult.gltfResult.errors);
if (subResult.gltfResult.errors.hasErrors()) {
return finishEarly();
}
parseOffsetForSemantic(
featureTableJson, "POSITION", errors);
parsedContent.positionQuantized = parseOffsetForSemantic(
featureTableJson,
"POSITION_QUANTIZED",
errors);
if (errors.hasErrors()) {
return {};
}
if (parsedContent.positionQuantized) {
// I would have liked to just test !parsedContent.position, but the perfectly
// reasonable value of 0 causes the test to be false!
if (!(parsedContent.position.has_value() || parsedContent.positionQuantized.has_value())) {
errors.emplaceError(
"I3dm file contains neither POSITION nor POSITION_QUANTIZED semantics.");
return {};
}
if (parsedContent.positionQuantized.has_value()) {
parsedContent.quantizedVolumeOffset =
parseArrayValueDVec3(featureTableJson, "QUANTIZED_VOLUME_OFFSET");
if (!parsedContent.quantizedVolumeOffset) {
subResult.gltfResult.errors.emplaceError(
"Error parsing I3DM feature table, No valid "
"QUANTIZED_VOLUME_OFFSET property");
return finishEarly();
if (!parsedContent.quantizedVolumeOffset.has_value()) {
errors.emplaceError(
"Error parsing I3DM feature table, the I3dm uses quatized positions "
"but has no valid QUANTIZED_VOLUME_OFFSET property");
return {};
}
parsedContent.quantizedVolumeScale =
parseArrayValueDVec3(featureTableJson, "QUANTIZED_VOLUME_SCALE");
if (!parsedContent.quantizedVolumeScale) {
subResult.gltfResult.errors.emplaceError(
"Error parsing I3DM feature table, No valid "
"QUANTIZED_VOLUME_SCALE property");
return finishEarly();
if (!parsedContent.quantizedVolumeScale.has_value()) {
errors.emplaceError(
"Error parsing I3DM feature table, the I3dm uses quatized positions "
"but has no valid QUANTIZED_VOLUME_SCALE property");
return {};
}
}
decodedInstances.rotationENU = false;
if (auto optENU = getValue<bool>(featureTableJson, "EAST_NORTH_UP")) {
parsedContent.eastNorthUp = *optENU;
decodedInstances.rotationENU = *optENU;
}
parsedContent.normalUp =
parseOffset(featureTableJson, "NORMAL_UP", subResult.gltfResult.errors);
parsedContent.normalRight = parseOffset(
parsedContent.normalUp = parseOffsetForSemantic(
featureTableJson,
"NORMAL_UP",
errors);
parsedContent.normalRight = parseOffsetForSemantic(
featureTableJson,
"NORMAL_RIGHT",
subResult.gltfResult.errors);
parsedContent.normalUpOct32p = parseOffset(
errors);
if (errors.hasErrors()) {
return {};
}
if (parsedContent.normalUp.has_value() && !parsedContent.normalRight.has_value()) {
errors.emplaceError("I3dm has NORMAL_UP semantic without NORMAL_RIGHT.");
return {};
}
if (!parsedContent.normalUp.has_value() && parsedContent.normalRight.has_value()) {
errors.emplaceError("I3dm has NORMAL_RIGHT semantic without NORMAL_UP.");
return {};
}
parsedContent.normalUpOct32p = parseOffsetForSemantic(
featureTableJson,
"NORMAL_UP_OCT32P",
subResult.gltfResult.errors);
parsedContent.normalRightOct32p = parseOffset(
errors);
parsedContent.normalRightOct32p = parseOffsetForSemantic(
featureTableJson,
"NORMAL_RIGHT_OCT32P",
subResult.gltfResult.errors);
parsedContent.scale =
parseOffset(featureTableJson, "SCALE", subResult.gltfResult.errors);
parsedContent.scaleNonUniform = parseOffset(
errors);
if (errors.hasErrors()) {
return {};
}
if (parsedContent.normalUpOct32p.has_value() && !parsedContent.normalRightOct32p.has_value()) {
errors.emplaceError("I3dm has NORMAL_UP_OCT32P semantic without NORMAL_RIGHT_OCT32P.");
return {};
}
if (!parsedContent.normalUpOct32p.has_value() && parsedContent.normalRightOct32p.has_value()) {
errors.emplaceError("I3dm has NORMAL_RIGHT_OCT32P semantic without NORMAL_UP_OCT32P.");
return {};
}
parsedContent.scale = parseOffsetForSemantic(
featureTableJson,
"SCALE",
errors);
parsedContent.scaleNonUniform = parseOffsetForSemantic(
featureTableJson,
"SCALE_NON_UNIFORM",
subResult.gltfResult.errors);
parsedContent.batchId =
parseOffset(featureTableJson, "BATCH_ID", subResult.gltfResult.errors);
if (subResult.gltfResult.errors.hasErrors()) {
errors);
parsedContent.batchId = parseOffsetForSemantic(
featureTableJson,
"BATCH_ID",
errors);
if (errors.hasErrors()) {
return {};
}
return parsedContent;
}
CesiumAsync::Future<ConvertedI3dm> convertI3dmContent(
const gsl::span<const std::byte>& instancesBinary,
const I3dmHeader& header,
uint32_t headerLength,
const CesiumGltfReader::GltfReaderOptions& options,
const AssetFetcher& assetFetcher,
GltfConverterResult& result) {
ConvertedI3dm convertedI3dm;
DecodedInstances& decodedInstances = convertedI3dm.decodedInstances;
convertedI3dm.gltfResult = result;
auto finishEarly = [&]() {
return assetFetcher.asyncSystem.createResolvedFuture(std::move(convertedI3dm));
};
if (header.featureTableJsonByteLength == 0 ||
header.featureTableBinaryByteLength == 0) {
return finishEarly();
}
const uint32_t gltfStart = headerLength + header.featureTableJsonByteLength +
header.featureTableBinaryByteLength +
header.batchTableJsonByteLength +
header.batchTableBinaryByteLength;
const uint32_t gltfEnd = header.byteLength;
auto gltfData = instancesBinary.subspan(gltfStart, gltfEnd - gltfStart);
std::optional<CesiumAsync::Future<ByteResult>> assetFuture;
auto featureTableJsonData =
instancesBinary.subspan(headerLength, header.featureTableJsonByteLength);
std::optional<I3dmContent> parsedJsonResult =
parseI3dmJson(featureTableJsonData, convertedI3dm.gltfResult.errors);
if (!parsedJsonResult) {
finishEarly();
}
const I3dmContent& parsedContent = *parsedJsonResult;
decodedInstances.rtcCenter = parsedContent.rtcCenter;
decodedInstances.rotationENU = parsedContent.eastNorthUp;
auto featureTableBinaryData = instancesBinary.subspan(
headerLength + header.featureTableJsonByteLength,
header.featureTableBinaryByteLength);
decodedInstances.positions.resize(
parsedContent.instancesLength,
glm::vec3(0.0f, 0.0f, 0.0f));
if (parsedContent.position) {
const auto* rawPosition = reinterpret_cast<const glm::vec3*>(
featureTableBinaryData.data() + *parsedContent.position);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
decodedInstances.positions[i] += rawPosition[i];
}
auto binaryData = featureTableBinaryData.data();
const uint32_t numInstances = parsedContent.instancesLength;
decodedInstances.positions.resize(numInstances, glm::vec3(0.0f, 0.0f, 0.0f));
if (parsedContent.position.has_value()) {
gsl::span<const glm::vec3> rawPositions(reinterpret_cast<const glm::vec3*>(
binaryData + *parsedContent.position),
numInstances);
decodedInstances.positions.assign(rawPositions.begin(), rawPositions.end());
} else {
const auto* rawQPosition = reinterpret_cast<const uint16_t(*)[3]>(
featureTableBinaryData.data() + *parsedContent.positionQuantized);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
const auto* posQuantized = &rawQPosition[i];
float position[3];
for (unsigned j = 0; j < 3; ++j) {
position[j] = static_cast<float>(
(*posQuantized)[j] / 65535.0 *
gsl::span<const uint16_t[3]> rawQPositions(reinterpret_cast<const uint16_t(*)[3]>(
binaryData + *parsedContent.positionQuantized),
numInstances);
std::transform(
rawQPositions.begin(),
rawQPositions.end(),
decodedInstances.positions.begin(),
[&parsedContent](const auto&& posQuantized) {
glm::vec3 position;
for (unsigned j = 0; j < 3; ++j) {
position[j] = static_cast<float>(
posQuantized[j] / 65535.0 *
(*parsedContent.quantizedVolumeScale)[j] +
(*parsedContent.quantizedVolumeOffset)[j]);
}
decodedInstances.positions[i] +=
glm::vec3(position[0], position[1], position[2]);
}
(*parsedContent.quantizedVolumeOffset)[j]);
}
return position;
});
}
decodedInstances.rotations.resize(
parsedContent.instancesLength,
numInstances,
glm::quat(1.0f, 0.0f, 0.0f, 0.0f));
if (parsedContent.normalUp && parsedContent.normalRight) {
const auto* rawUp = reinterpret_cast<const glm::vec3*>(
featureTableBinaryData.data() + *parsedContent.normalUp);
const auto* rawRight = reinterpret_cast<const glm::vec3*>(
featureTableBinaryData.data() + *parsedContent.normalRight);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
decodedInstances.rotations[i] =
rotationFromUpRight(rawUp[i], rawRight[i]);
}
} else if (parsedContent.normalUpOct32p && parsedContent.normalRightOct32p) {
const auto* rawUpOct = reinterpret_cast<const uint16_t(*)[2]>(
featureTableBinaryData.data() + *parsedContent.normalUpOct32p);
const auto* rawRightOct = reinterpret_cast<const uint16_t(*)[2]>(
featureTableBinaryData.data() + *parsedContent.normalRightOct32p);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
glm::vec3 dUp = decodeOct32P(rawUpOct[i]);
glm::vec3 dRight = decodeOct32P(rawRightOct[i]);
decodedInstances.rotations[i] = rotationFromUpRight(dUp, dRight);
}
if (parsedContent.normalUp.has_value() && parsedContent.normalRight.has_value()) {
gsl::span<const glm::vec3> rawUp(reinterpret_cast<const glm::vec3*>(
binaryData + *parsedContent.normalUp),
numInstances);
gsl::span<const glm::vec3> rawRight(reinterpret_cast<const glm::vec3*>(
binaryData + *parsedContent.normalRight),
numInstances);
std::transform(rawUp.begin(),
rawUp.end(),
rawRight.begin(),
decodedInstances.rotations.begin(),
rotationFromUpRight);
} else if (parsedContent.normalUpOct32p.has_value() && parsedContent.normalRightOct32p.has_value()) {
gsl::span<const uint16_t[2]> rawUpOct(reinterpret_cast<const uint16_t(*)[2]>(
binaryData + *parsedContent.normalUpOct32p),
numInstances);
gsl::span<const uint16_t[2]> rawRightOct(reinterpret_cast<const uint16_t(*)[2]>(
binaryData + *parsedContent.normalRightOct32p),
numInstances);
std::transform(rawUpOct.begin(),
rawUpOct.end(),
rawRightOct.begin(),
decodedInstances.rotations.begin(),
[](const auto&& upOct, const auto&& rightOct) {
return rotationFromUpRight(
decodeOct32P(upOct),
decodeOct32P(rightOct));
});
} else if (decodedInstances.rotationENU) {
glm::dmat4 worldTransform = subprocessor.tileTransform;
glm::dmat4 worldTransform = assetFetcher.tileTransform;
if (decodedInstances.rtcCenter) {
worldTransform = translate(worldTransform, *decodedInstances.rtcCenter);
}
@ -388,63 +444,72 @@ CesiumAsync::Future<ConvertResult> convertInstancesContent(
decodedInstances.rotations[i] = tileFrameRot;
}
}
decodedInstances.scales.resize(
parsedContent.instancesLength,
glm::vec3(1.0, 1.0, 1.0));
if (parsedContent.scale) {
const auto* rawScale = reinterpret_cast<const float*>(
featureTableBinaryData.data() + *parsedContent.scale);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
decodedInstances.scales[i] =
glm::vec3(rawScale[i], rawScale[i], rawScale[i]);
}
} else if (parsedContent.scaleNonUniform) {
const auto* rawScaleNonUniform = reinterpret_cast<const glm::vec3*>(
featureTableBinaryData.data() + *parsedContent.scaleNonUniform);
for (unsigned i = 0; i < parsedContent.instancesLength; ++i) {
decodedInstances.scales[i] = rawScaleNonUniform[i];
}
decodedInstances.scales.resize(numInstances, glm::vec3(1.0, 1.0, 1.0));
if (parsedContent.scale.has_value()) {
gsl::span<const float> rawScales(reinterpret_cast<const float*>(
binaryData + *parsedContent.scale),
numInstances);
std::transform(rawScales.begin(),
rawScales.end(),
decodedInstances.scales.begin(),
[](float scaleVal) {
return glm::vec3(scaleVal);
});
}
rebaseInstances(decodedInstances);
if (parsedContent.scaleNonUniform.has_value()) {
gsl::span<const glm::vec3> rawScalesNonUniform(
reinterpret_cast<const glm::vec3*>(
binaryData + *parsedContent.scaleNonUniform),
numInstances);
std::transform(
decodedInstances.scales.begin(),
decodedInstances.scales.end(),
rawScalesNonUniform.begin(),
decodedInstances.scales.begin(),
[](auto&& scaleUniform, auto&& scaleNonUniform) {
return scaleUniform * scaleNonUniform;
});
}
repositionInstances(decodedInstances);
ByteResult byteResult;
if (header.gltfFormat == 0) {
// Need to recursively read the glTF content.
// Recursively fetch and read the glTF content.
auto gltfUri = std::string(
reinterpret_cast<const char*>(gltfData.data()),
gltfData.size());
return get(subprocessor, gltfUri)
return assetFetcher.get(gltfUri)
.thenImmediately(
[options, subprocessor](ByteResult&& byteResult)
[options, assetFetcher](ByteResult&& byteResult)
-> CesiumAsync::Future<GltfConverterResult> {
if (byteResult.errorList.hasErrors()) {
GltfConverterResult errorResult;
errorResult.errors.merge(byteResult.errorList);
return subprocessor.asyncSystem.createResolvedFuture(
return assetFetcher.asyncSystem.createResolvedFuture(
std::move(errorResult));
}
return BinaryToGltfConverter::convert(
byteResult.bytes,
options,
subprocessor);
assetFetcher);
})
.thenImmediately([subResult = std::move(subResult)](
.thenImmediately([convertedI3dm = std::move(convertedI3dm)](
GltfConverterResult&& converterResult) mutable {
if (converterResult.errors.hasErrors()) {
subResult.gltfResult.errors.merge(converterResult.errors);
convertedI3dm.gltfResult.errors.merge(converterResult.errors);
} else {
subResult.gltfResult = converterResult;
convertedI3dm.gltfResult = converterResult;
}
return subResult;
return convertedI3dm;
});
} else {
return BinaryToGltfConverter::convert(gltfData, options, subprocessor)
.thenImmediately([subResult = std::move(subResult)](
return BinaryToGltfConverter::convert(gltfData, options, assetFetcher)
.thenImmediately([convertedI3dm = std::move(convertedI3dm)](
GltfConverterResult&& converterResult) mutable {
if (converterResult.errors.hasErrors()) {
return subResult;
return convertedI3dm;
}
subResult.gltfResult = converterResult;
return subResult;
convertedI3dm.gltfResult = converterResult;
return convertedI3dm;
});
}
}
@ -464,10 +529,10 @@ composeInstanceTransform(size_t i, const DecodedInstances& decodedInstances) {
return result;
}
std::vector<glm::dmat4> meshGpuInstances(
GltfConverterResult& result,
const ExtensionExtMeshGpuInstancing& gpuExt) {
const Model& model = *result.model;
std::vector<glm::dmat4> getMeshGpuInstancingTransforms(
const Model& model,
const ExtensionExtMeshGpuInstancing& gpuExt,
CesiumUtility::ErrorList& errors) {
std::vector<glm::dmat4> instances;
if (gpuExt.attributes.empty()) {
return instances;
@ -480,7 +545,7 @@ std::vector<glm::dmat4> meshGpuInstances(
return nullptr;
};
auto errorOut = [&](const std::string& errorMsg) {
result.errors.emplaceError(errorMsg);
errors.emplaceError(errorMsg);
return instances;
};
@ -582,7 +647,7 @@ void copyToBuffer(
copyToBuffer(position, rotation, scale, &bufferData[i * totalStride]);
}
void instantiateInstances(
void instantiateGltfInstances(
GltfConverterResult& result,
const DecodedInstances& decodedInstances) {
std::set<CesiumGltf::Node*> meshNodes;
@ -617,7 +682,10 @@ void instantiateInstances(
if (!gpuExt.attributes.empty()) {
// The model already has instances! We will need to create the outer
// product of these instances and those coming from i3dm.
existingInstances = meshGpuInstances(result, gpuExt);
existingInstances = getMeshGpuInstancingTransforms(
*result.model,
gpuExt,
result.errors);
if (numInstances * existingInstances.size() >
std::numeric_limits<uint32_t>::max()) {
result.errors.emplaceError(fmt::format(
@ -627,6 +695,9 @@ void instantiateInstances(
return;
}
}
if (result.errors.hasErrors()) {
return;
}
const uint32_t numNewInstances =
static_cast<uint32_t>(numInstances * existingInstances.size());
const size_t instanceDataSize = totalStride * numNewInstances;
@ -687,7 +758,7 @@ void instantiateInstances(
gpuExt.attributes["SCALE"] = scaleAccessorId;
});
if (decodedInstances.rtcCenter) {
applyRTC(*result.model, *decodedInstances.rtcCenter);
applyRtcToNodes(*result.model, *decodedInstances.rtcCenter);
}
instanceBuffer.byteLength =
static_cast<int64_t>(instanceBuffer.cesium.data.size());
@ -698,30 +769,30 @@ void instantiateInstances(
CesiumAsync::Future<GltfConverterResult> I3dmToGltfConverter::convert(
const gsl::span<const std::byte>& instancesBinary,
const CesiumGltfReader::GltfReaderOptions& options,
const ConverterSubprocessor& subProcessor) {
const AssetFetcher& assetFetcher) {
GltfConverterResult result;
InstancesHeader header;
I3dmHeader header;
uint32_t headerLength = 0;
parseInstancesHeader(instancesBinary, header, headerLength, result);
parseI3dmHeader(instancesBinary, header, headerLength, result);
if (result.errors) {
return subProcessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
DecodedInstances decodedInstances;
return convertInstancesContent(
return convertI3dmContent(
instancesBinary,
header,
headerLength,
options,
subProcessor,
assetFetcher,
result)
.thenImmediately([](ConvertResult&& convertResult) {
if (convertResult.gltfResult.errors.hasErrors()) {
return convertResult.gltfResult;
.thenImmediately([](ConvertedI3dm&& convertedI3dm) {
if (convertedI3dm.gltfResult.errors.hasErrors()) {
return convertedI3dm.gltfResult;
}
instantiateInstances(
convertResult.gltfResult,
convertResult.decodedInstances);
return convertResult.gltfResult;
instantiateGltfInstances(
convertedI3dm.gltfResult,
convertedI3dm.decodedInstances);
return convertedI3dm.gltfResult;
});
}
} // namespace Cesium3DTilesContent

View File

@ -1,5 +1,4 @@
#include <Cesium3DTilesContent/LegacyUtilities.h>
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumGltf/Accessor.h>
#include <CesiumGltf/Buffer.h>
#include <CesiumGltf/BufferView.h>
@ -18,7 +17,7 @@ namespace Cesium3DTilesContent {
namespace LegacyUtilities {
using namespace CesiumGltf;
std::optional<uint32_t> parseOffset(
std::optional<uint32_t> parseOffsetForSemantic(
const rapidjson::Document& document,
const char* semantic,
CesiumUtility::ErrorList& errorList) {
@ -78,19 +77,12 @@ parseArrayValueDVec3(const rapidjson::Document& document, const char* name) {
return {};
}
int32_t createBufferInGltf(Model& gltf) {
int32_t createBufferInGltf(Model& gltf, std::vector<std::byte> buffer) {
size_t bufferId = gltf.buffers.size();
Buffer& gltfBuffer = gltf.buffers.emplace_back();
gltfBuffer.byteLength = 0;
return static_cast<int32_t>(bufferId);
}
int32_t createBufferInGltf(Model& gltf, std::vector<std::byte>&& buffer) {
int32_t bufferId = createBufferInGltf(gltf);
Buffer& gltfBuffer = gltf.buffers[static_cast<uint32_t>(bufferId)];
gltfBuffer.byteLength = static_cast<int32_t>(buffer.size());
gltfBuffer.cesium.data = std::move(buffer);
return bufferId;
return static_cast<int32_t>(bufferId);
}
int32_t createBufferViewInGltf(
@ -126,7 +118,7 @@ int32_t createAccessorInGltf(
return static_cast<int32_t>(accessorId);
}
void applyRTC(Model& gltf, const glm::dvec3& rtc) {
void applyRtcToNodes(Model& gltf, const glm::dvec3& rtc) {
using namespace CesiumGltfContent;
auto upToZ = GltfUtilities::applyGltfUpAxisTransform(gltf, glm::dmat4x4(1.0));
auto rtcTransform = inverse(upToZ);
@ -142,40 +134,4 @@ void applyRTC(Model& gltf, const glm::dvec3& rtc) {
}
} // namespace LegacyUtilities
CesiumAsync::Future<ByteResult>
get(const ConverterSubprocessor& subprocessor, const std::string& relativeUrl) {
auto resolvedUrl =
CesiumUtility::Uri::resolve(subprocessor.baseUrl, relativeUrl);
return subprocessor.pAssetAccessor
->get(subprocessor.asyncSystem, resolvedUrl, subprocessor.requestHeaders)
.thenImmediately(
[asyncSystem = subprocessor.asyncSystem](
std::shared_ptr<CesiumAsync::IAssetRequest>&& pCompletedRequest) {
const CesiumAsync::IAssetResponse* pResponse =
pCompletedRequest->response();
ByteResult byteResult;
const auto& url = pCompletedRequest->url();
if (!pResponse) {
byteResult.errorList.emplaceError(fmt::format(
"Did not receive a valid response for asset {}",
url));
return asyncSystem.createResolvedFuture(std::move(byteResult));
}
uint16_t statusCode = pResponse->statusCode();
if (statusCode != 0 && (statusCode < 200 || statusCode >= 300)) {
byteResult.errorList.emplaceError(fmt::format(
"Received status code {} for asset {}",
statusCode,
url));
return asyncSystem.createResolvedFuture(std::move(byteResult));
}
gsl::span<const std::byte> asset = pResponse->data();
std::copy(
asset.begin(),
asset.end(),
std::back_inserter(byteResult.bytes));
return asyncSystem.createResolvedFuture(std::move(byteResult));
});
}
} // namespace Cesium3DTilesContent

View File

@ -1633,7 +1633,7 @@ void convertPntsContentToGltf(
CesiumAsync::Future<GltfConverterResult> PntsToGltfConverter::convert(
const gsl::span<const std::byte>& pntsBinary,
const CesiumGltfReader::GltfReaderOptions& /*options*/,
const ConverterSubprocessor& subprocessor) {
const AssetFetcher& assetFetcher) {
GltfConverterResult result;
PntsHeader header;
uint32_t headerLength = 0;
@ -1642,6 +1642,6 @@ CesiumAsync::Future<GltfConverterResult> PntsToGltfConverter::convert(
convertPntsContentToGltf(pntsBinary, header, headerLength, result);
}
return subprocessor.asyncSystem.createResolvedFuture(std::move(result));
return assetFetcher.asyncSystem.createResolvedFuture(std::move(result));
}
} // namespace Cesium3DTilesContent

View File

@ -9,11 +9,11 @@ namespace Cesium3DTilesContent {
CesiumAsync::AsyncSystem ConvertTileToGltf::asyncSystem(
std::make_shared<CesiumNativeTests::SimpleTaskProcessor>());
ConverterSubprocessor
ConvertTileToGltf::makeSubprocessor(const std::string& baseUrl) {
AssetFetcher
ConvertTileToGltf::makeAssetFetcher(const std::string& baseUrl) {
auto fileAccessor = std::make_shared<CesiumNativeTests::FileAccessor>();
std::vector<CesiumAsync::IAssetAccessor::THeader> requestHeaders;
return ConverterSubprocessor(
return AssetFetcher(
asyncSystem,
fileAccessor,
baseUrl,
@ -24,18 +24,18 @@ ConvertTileToGltf::makeSubprocessor(const std::string& baseUrl) {
GltfConverterResult ConvertTileToGltf::fromB3dm(
const std::filesystem::path& filePath,
const CesiumGltfReader::GltfReaderOptions& options) {
ConverterSubprocessor subprocessor = makeSubprocessor("");
AssetFetcher assetFetcher = makeAssetFetcher("");
auto bytes = readFile(filePath);
auto future = B3dmToGltfConverter::convert(bytes, options, subprocessor);
auto future = B3dmToGltfConverter::convert(bytes, options, assetFetcher);
return future.wait();
}
GltfConverterResult ConvertTileToGltf::fromPnts(
const std::filesystem::path& filePath,
const CesiumGltfReader::GltfReaderOptions& options) {
ConverterSubprocessor subprocessor = makeSubprocessor("");
AssetFetcher assetFetcher = makeAssetFetcher("");
auto bytes = readFile(filePath);
auto future = PntsToGltfConverter::convert(bytes, options, subprocessor);
auto future = PntsToGltfConverter::convert(bytes, options, assetFetcher);
return future.wait();
}
} // namespace Cesium3DTilesContent

View File

@ -152,13 +152,13 @@ CesiumAsync::Future<TileLoadResult> requestTileContent(
CesiumGltfReader::GltfReaderOptions gltfOptions;
gltfOptions.ktx2TranscodeTargets = ktx2TranscodeTargets;
gltfOptions.applyTextureTransform = applyTextureTransform;
ConverterSubprocessor subprocessor{
AssetFetcher assetFetcher{
asyncSystem,
pAssetAccessor,
tileUrl,
tileTransform,
requestHeaders};
return converter(responseData, gltfOptions, subprocessor)
return converter(responseData, gltfOptions, assetFetcher)
.thenImmediately([pLogger, tileUrl, pCompletedRequest](
GltfConverterResult&& result) {
// Report any errors if there are any

View File

@ -164,13 +164,13 @@ CesiumAsync::Future<TileLoadResult> requestTileContent(
CesiumGltfReader::GltfReaderOptions gltfOptions;
gltfOptions.ktx2TranscodeTargets = ktx2TranscodeTargets;
gltfOptions.applyTextureTransform = applyTextureTransform;
ConverterSubprocessor subprocessor{
AssetFetcher assetFetcher{
asyncSystem,
pAssetAccessor,
tileUrl,
tileTransform,
requestHeaders};
return converter(responseData, gltfOptions, subprocessor)
return converter(responseData, gltfOptions, assetFetcher)
.thenImmediately([pLogger, tileUrl, pCompletedRequest](
GltfConverterResult&& result) {
// Report any errors if there are any

View File

@ -903,7 +903,7 @@ TilesetJsonLoader::loadTileContent(const TileLoadInput& loadInput) {
if (converter) {
// Convert to gltf
ConverterSubprocessor subprocessor{
AssetFetcher assetFetcher{
asyncSystem,
pAssetAccessor,
tileUrl,
@ -914,7 +914,7 @@ TilesetJsonLoader::loadTileContent(const TileLoadInput& loadInput) {
contentOptions.ktx2TranscodeTargets;
gltfOptions.applyTextureTransform =
contentOptions.applyTextureTransform;
return converter(responseData, gltfOptions, subprocessor)
return converter(responseData, gltfOptions, assetFetcher)
.thenImmediately([pLogger, upAxis, tileUrl, pCompletedRequest](
GltfConverterResult&& result) {
logTileLoadResult(pLogger, tileUrl, result.errors);

View File

@ -309,8 +309,8 @@ struct TexCoordFromAccessor {
};
/**
* Type definition for quaternion accessors, as defined for ExtMeshGpuInstancing
* and animation samplers.
* Type definition for quaternion accessors, as used in ExtMeshGpuInstancing
* rotations and animation samplers.
*/
typedef std::variant<
AccessorView<AccessorTypes::VEC4<uint8_t>>,

View File

@ -19,8 +19,7 @@ struct CESIUMGLTF_API Model : public ModelSpec {
* elements that were originally in it _plus_ all of the elements
* that were in `rhs`. Element indices are updated accordingly.
* However, element indices in {@link ExtensibleObject::extras}, if any,
* are _not_ updated. Most extensions aren't supported either, except for
* KHR_draco_mesh_compression and EXT_mesh_gpu_instancing.
* are _not_ updated.
*
* @param rhs The model to merge into this one.
*/

View File

@ -299,12 +299,12 @@ GltfUtilities::parseGltfCopyright(const CesiumGltf::Model& gltf) {
}
// Copy the data to the destination and keep track of where we put it.
// Align each bufferView to a 4-byte boundary.
// Align each bufferView to an 8-byte boundary.
size_t start = destination.cesium.data.size();
size_t alignmentRemainder = start % 4;
size_t alignmentRemainder = start % 8;
if (alignmentRemainder != 0) {
start += 4 - alignmentRemainder;
start += 8 - alignmentRemainder;
}
destination.cesium.data.resize(start + source.cesium.data.size());
@ -628,6 +628,18 @@ void deleteBufferRange(
int64_t bytesToRemove = end - start;
// In order to ensure that we can't disrupt glTF's alignment requirements,
// only remove multiples of 8 bytes from within the buffer (removing any
// number of bytes from the end is fine).
if (end < pBuffer->byteLength) {
// Round down to the nearest multiple of 8 by clearing the low three bits.
bytesToRemove = bytesToRemove & ~0b111;
if (bytesToRemove == 0)
return;
end = start + bytesToRemove;
}
// Adjust bufferView offets for the removed bytes.
for (BufferView& bufferView : gltf.bufferViews) {
if (bufferView.buffer != bufferIndex)

View File

@ -383,12 +383,12 @@ TEST_CASE("GltfUtilities::compactBuffers") {
GltfUtilities::compactBuffers(m);
CHECK(buffer.byteLength == 113);
REQUIRE(buffer.cesium.data.size() == 113);
CHECK(bv.byteOffset == 0);
CHECK(buffer.byteLength == 123 - 8);
REQUIRE(buffer.cesium.data.size() == 123 - 8);
CHECK(bv.byteOffset == 2);
for (size_t i = 0; i < buffer.cesium.data.size(); ++i) {
CHECK(buffer.cesium.data[i] == std::byte(i + 10));
for (size_t i = size_t(bv.byteOffset); i < buffer.cesium.data.size(); ++i) {
CHECK(buffer.cesium.data[i] == std::byte(i + 8));
}
}
@ -400,8 +400,9 @@ TEST_CASE("GltfUtilities::compactBuffers") {
GltfUtilities::compactBuffers(m);
CHECK(buffer.byteLength == 113);
REQUIRE(buffer.cesium.data.size() == 113);
// Any number of bytes can be removed from the end (no alignment impact)
CHECK(buffer.byteLength == 123 - 10);
REQUIRE(buffer.cesium.data.size() == 123 - 10);
for (size_t i = 0; i < buffer.cesium.data.size(); ++i) {
CHECK(buffer.cesium.data[i] == std::byte(i));
@ -416,12 +417,35 @@ TEST_CASE("GltfUtilities::compactBuffers") {
GltfUtilities::compactBuffers(m);
CHECK(buffer.byteLength == 103);
REQUIRE(buffer.cesium.data.size() == 103);
CHECK(bv.byteOffset == 0);
CHECK(buffer.byteLength == 123 - 8 - 10);
REQUIRE(buffer.cesium.data.size() == 123 - 8 - 10);
CHECK(bv.byteOffset == 2);
for (size_t i = 2; i < buffer.cesium.data.size(); ++i) {
CHECK(buffer.cesium.data[i] == std::byte(i + 8));
}
}
SECTION("does not remove gaps less than 8 bytes") {
BufferView& bv1 = m.bufferViews.emplace_back();
bv1.buffer = 0;
bv1.byteOffset = 1;
bv1.byteLength = 99;
BufferView& bv2 = m.bufferViews.emplace_back();
bv2.buffer = 0;
bv2.byteOffset = 105;
bv2.byteLength = 10;
GltfUtilities::compactBuffers(m);
CHECK(buffer.byteLength == 115);
REQUIRE(buffer.cesium.data.size() == 115);
CHECK(m.bufferViews[0].byteOffset == 1);
CHECK(m.bufferViews[1].byteOffset == 105);
for (size_t i = 0; i < buffer.cesium.data.size(); ++i) {
CHECK(buffer.cesium.data[i] == std::byte(i + 10));
CHECK(buffer.cesium.data[i] == std::byte(i));
}
}
}

View File

@ -15,7 +15,7 @@ public:
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>> request(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& /* verb */,
const std::string& verb,
const std::string& url,
const std::vector<THeader>& headers,
const gsl::span<const std::byte>&) override;