Skip to content

Commit

Permalink
Code for I3dm batch table conversion, not compiling yet
Browse files Browse the repository at this point in the history
  • Loading branch information
timoore committed Dec 26, 2024
1 parent f348c47 commit 151f570
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 118 deletions.
207 changes: 207 additions & 0 deletions Cesium3DTilesContent/src/BatchTableToGltfStructuralMetadata.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include "BatchTableToGltfStructuralMetadata.h"

#include "BatchTableHierarchyPropertyValues.h"
#include "MetadataProperty.h"

#include <CesiumGltf/ExtensionExtInstanceFeatures.h>
#include <CesiumGltf/ExtensionExtMeshFeatures.h>
#include <CesiumGltf/ExtensionExtMeshGpuInstancing.h>
#include <CesiumGltf/ExtensionKhrDracoMeshCompression.h>
#include <CesiumGltf/ExtensionModelExtStructuralMetadata.h>
#include <CesiumGltf/Model.h>
Expand All @@ -14,6 +17,8 @@
#include <glm/glm.hpp>
#include <rapidjson/writer.h>

#include <algorithm>
#include <cstring>
#include <limits>
#include <map>
#include <type_traits>
Expand Down Expand Up @@ -1976,4 +1981,206 @@ ErrorList BatchTableToGltfStructuralMetadata::convertFromPnts(

return result;
}

// Does something like this already exist?

template<typename T>
int32_t componentTypeFromCpp();

template<>
int32_t componentTypeFromCpp<uint8_t>() {
return Accessor::ComponentType::UNSIGNED_BYTE;
}

template<>
int32_t componentTypeFromCpp<uint16_t>() {
return Accessor::ComponentType::UNSIGNED_SHORT;
}

template<>
int32_t componentTypeFromCpp<uint32_t>() {
return Accessor::ComponentType::UNSIGNED_INT;
}

// encapsulation of the binary batch id data in an I3dm
struct BatchIdSemantic {
std::variant<
std::span<const uint8_t>,
std::span<const uint16_t>,
std::span<const uint32_t>> batchSpan;
const std::byte* rawData;
uint32_t numElements;
uint32_t byteSize;

template<typename UintType>
std::span<const UintType> makeSpan(
const std::byte* byteData,
uint32_t offset,
uint32_t numElements) {
return std::span<const UintType>(
reinterpret_cast<const UintType*>(byteData + offset),
numElements);
}

BatchIdSemantic(
const rapidjson::Document& featureTableJson,
uint32_t numInstances,
const std::span<const std::byte>& featureTableJsonData)
: rawData(nullptr), numElements(0), byteSize(0)
{
const auto batchIdIt = featureTableJson.FindMember("BATCH_ID");
if (batchIdIt == featureTableJson.MemberEnd() ||
!batchIdIt->value.IsObject()) {
return;
}
const auto byteOffsetIt = batchIdIt->value.FindMember("byteOffset");
if (byteOffsetIt == batchIdIt->value.MemberEnd() ||
!byteOffsetIt->value.IsUint()) {
// Warning
}
uint32_t byteOffset = byteOffsetIt->value.GetUint();
const auto componentTypeIt = batchIdIt->value.FindMember("componentType");
if (componentTypeIt != featureTableJson.MemberEnd() &&
componentTypeIt->value.IsString()) {
const std::string& componentTypeString = componentTypeIt->value.GetString();
if (MetadataProperty::stringToMetadataComponentType.find(componentTypeString) ==
MetadataProperty::stringToMetadataComponentType.end()) {
// Warning
}
MetadataProperty::ComponentType componentType =
MetadataProperty::stringToMetadataComponentType.at(componentTypeString);
rawData = featureTableJsonData.data();
if (componentType == MetadataProperty::ComponentType::UNSIGNED_BYTE) {
batchSpan = makeSpan<uint8_t>(rawData, byteOffset, numInstances);
numElements = numInstances;
byteSize = numElements * sizeof(uint8_t);
} else if (componentType == MetadataProperty::ComponentType::UNSIGNED_SHORT) {
batchSpan = makeSpan<uint8_t>(rawData, byteOffset, numInstances);
numElements = numInstances;
byteSize = numElements * sizeof(uint16_t);
} else if (componentType == MetadataProperty::ComponentType::UNSIGNED_INT) {
batchSpan = makeSpan<uint32_t>(rawData, byteOffset, numInstances);
numElements = numInstances;
byteSize = numElements * sizeof(uint32_t);
}
}
}

size_t idSize() const {
return std::visit([](auto&& batchIds) {
return sizeof(batchIds[0]);
},
batchSpan);
}

uint32_t maxBatchId()
{
return std::visit(
[](auto&& batchIds) {
auto itr = std::ranges::max_element(batchIds);
return *itr;
},
batchSpan);
}

int32_t componentType() {
return std::visit(
[](auto&& batchIds) {
using span_type = std::remove_reference_t<decltype(batchIds)>;
return componentTypeFromCpp<span_type::value_type>();
},
batchSpan);
}
};

// returns an accessor ID for the added feature IDs
int32_t
addFeatureIdsToGltf(CesiumGltf::Model& gltf, const BatchIdSemantic& batchIds) {
int32_t featuresBufferId = createBufferInGltf(gltf);
auto& featuresBuffer =
gltf->buffers[static_cast<uint32_t>(featureBufferId)];
featuresBuffer.cesium.data.resize(batchIds.byteSize);
std::memcpy(
&featuresBuffer.cesium.data[0],
batchIds.rawData,
batchIds.byteSize);
int32_t featureBufferViewId = createBufferViewInGltf(
gltf,
featureBufferId,
0,
static_cast<int64_t>(batchIds.idSize()));
gltf->bufferViews[featureBufferViewId].byteLength = batchIds.byteSize;

int32_t accessorId = createAccessorInGltf(
gltf,
featureBufferViewId,
batchIds.componentType(),
batchIds.numElements,
Accessor::Type::SCALAR);
return accessorId;
}

ErrorList BatchTableToGltfStructuralMetadata::convertFromI3dm(
const rapidjson::Document& featureTableJson,
const rapidjson::Document& batchTableJson,
const std::span<const std::byte>& featureTableJsonData,
const std::span<const std::byte>& batchTableBinaryData,
CesiumGltf::Model& gltf) {
// Check to make sure a char of rapidjson is 1 byte
static_assert(
sizeof(rapidjson::Value::Ch) == 1,
"RapidJson::Value::Ch is not 1 byte");

ErrorList result;

// Parse the batch table and convert it to the EXT_structural_metadata
// extension.

// Batch table length is either the max batch ID + 1 or, if there are no batch
// IDs, the number of instances.
int64_t featureCount = 0;
std::optional<BatchIdSemantic> optBatchIds;
const auto batchIdIt = featureTableJson.FindMember("BATCH_ID");
std::optional<uint32_t> optInstancesLength =
getValue<uint32_t>(featureTableJson, "INSTANCES_LENGTH");
if (batchIdIt == featureTableJson.MemberEnd()) {
featureCount = *optInstancesLength;
} else {
optBatchIds = BatchIdSemantic(
featureTableJson,
*optInstancesLength,
featureTableJsonData);
uint32_t maxBatchId = optBatchIds->maxBatchId();
featureCount = maxBatchId + 1;
}

convertBatchTableToGltfStructuralMetadataExtension(
batchTableJson,
batchTableBinaryData,
gltf,
featureCount,
result);

int32_t featureIdAccessor = -1;
if (optBatchIds.has_value()) {
featureIdAccessor = addFeatureIdsToGltf(gltf, *optBatchIds);
}

// Create an EXT_instance_features extension for node that has an
// EXT_mesh_gpu_instancing extension
for (Node& node : gltf.nodes) {
if (auto* pGpuInstancing = node.getExtension<ExtensionExtMeshGpuInstancing>()) {
auto& instanceFeatureExt = node.addExtension<ExtensionExtInstanceFeatures>();
instanceFeatureExt.featureIds.resize(1);
instanceFeatureExt.featureIds[0].featureCount = featureCount;
instanceFeatureExt.featureIds[0].propertyTable = 0;
if (featureIdAccessor >= 0) {
(*pGpuInstancing)["_FEATURE_ID_0"] = featureIdAccessor;
instanceFeatureExt.featureIds[0].attribute = 0;
}
}

}
return result;
}
} // namespace Cesium3DTilesContent
7 changes: 7 additions & 0 deletions Cesium3DTilesContent/src/BatchTableToGltfStructuralMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,12 @@ struct BatchTableToGltfStructuralMetadata {
const rapidjson::Document& batchTableJson,
const std::span<const std::byte>& batchTableBinaryData,
CesiumGltf::Model& gltf);

static CesiumUtility::ErrorList convertFromI3dm(
const rapidjson::Document& featureTableJson,
const rapidjson::Document& batchTableJson,
const std::span<const std::byte>& featureTableJsonData,
const std::span<const std::byte>& batchTableBinaryData,
CesiumGltf::Model& gltf);
};
} // namespace Cesium3DTilesContent
100 changes: 84 additions & 16 deletions Cesium3DTilesContent/src/I3dmToGltfConverter.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Heavily inspired by PntsToGltfConverter.cpp
#include "BatchTableToGltfStructuralMetadata.h"

#include <Cesium3DTilesContent/BinaryToGltfConverter.h>
#include <Cesium3DTilesContent/GltfConverterUtility.h>
Expand Down Expand Up @@ -171,6 +172,12 @@ glm::quat rotationFromUpRight(const glm::vec3& up, const glm::vec3& right) {
struct ConvertedI3dm {
GltfConverterResult gltfResult;
DecodedInstances decodedInstances;
// If there's a batch table, then its data -- and the feature table too --
// need to be saved until the glTF model has been processed.
rapidjson::Document featureTableJson;
rapidjson::Document batchTableJson;
std::vector<std::byte> featureTableBinaryData;
std::vector<std::byte> batchTableBinaryData;
};

/* The approach:
Expand All @@ -185,24 +192,71 @@ struct ConvertedI3dm {
table, hashed by mesh transform.
+ Add the instance transforms to the glTF buffers, buffer views, and
accessors.
+ Future work: Metadata / feature id?
*/

std::optional<I3dmContent> parseI3dmJson(
const std::span<const std::byte> featureTableJsonData,
void parseJsonAndBinaryData(
const std::span<const std::byte>& instancesBinary,
const I3dmHeader& header,
uint32_t headerLength,
ConvertedI3dm& convertedI3dm,
CesiumUtility::ErrorList& errors) {
rapidjson::Document featureTableJson;
featureTableJson.Parse(
// Offset to the beginning of each section as it is parsed in turn.
size_t dataSectionOffset = headerLength;
auto featureTableJsonData =
instancesBinary.subspan(dataSectionOffset, header.featureTableJsonByteLength);
convertedI3dm.featureTableJson.Parse(
reinterpret_cast<const char*>(featureTableJsonData.data()),
featureTableJsonData.size());
if (featureTableJson.HasParseError()) {
errors.emplaceError(fmt::format(
"Error when parsing feature table JSON, error code {} at byte offset "
"{}",
static_cast<uint64_t>(featureTableJson.GetParseError()),
featureTableJson.GetErrorOffset()));
return {};
if (convertedI3dm.featureTableJson.HasParseError()) {
errors.emplaceError(
fmt::format(
"Error when parsing feature table JSON, error code {} at byte offset "
"{}",
static_cast<uint64_t>(convertedI3dm.featureTableJson.GetParseError()),
convertedI3dm.featureTableJson.GetErrorOffset()));
return;
}
dataSectionOffset += header.featureTableJsonByteLength;
auto featureTableBinaryData = instancesBinary.subspan(
dataSectionOffset,
header.featureTableBinaryByteLength);
convertedI3dm.featureTableBinaryData.insert(
convertedI3dm.featureTableBinaryData.begin(),
featureTableBinaryData.begin(),
featureTableBinaryData.end());
if (header.batchTableJsonByteLength > 0) {
dataSectionOffset += header.featureTableBinaryByteLength;
auto batchTableJsonData = instancesBinary.subspan(
dataSectionOffset,
header.batchTableJsonByteLength);
convertedI3dm.batchTableJson.Parse(
reinterpret_cast<const char*>(batchTableJsonData.data()),
batchTableJsonData.size());
if (convertedI3dm.batchTableJson.HasParseError()) {
errors.emplaceError(
fmt::format(
"Error when parsing batch table JSON, error code {} at byte offset "
"{}",
static_cast<uint64_t>(convertedI3dm.featureTableJson.GetParseError()),
convertedI3dm.featureTableJson.GetErrorOffset()));
return;
}
}
if (header.batchTableBinaryByteLength > 0) {
dataSectionOffset += header.batchTableJsonByteLength;
auto batchTableBinaryData = instancesBinary.subspan(
dataSectionOffset,
header.batchTableBinaryByteLength);
convertedI3dm.batchTableBinaryData.insert(
convertedI3dm.batchTableBinaryData.begin(),
batchTableBinaryData.begin(),
batchTableBinaryData.end());
}
}

std::optional<I3dmContent> parseI3dmJson(
rapidjson::Document& featureTableJson,
CesiumUtility::ErrorList& errors) {
I3dmContent parsedContent;
// Global semantics
if (std::optional<uint32_t> optInstancesLength =
Expand Down Expand Up @@ -322,14 +376,19 @@ CesiumAsync::Future<ConvertedI3dm> convertI3dmContent(
header.batchTableBinaryByteLength;
const uint32_t gltfEnd = header.byteLength;
auto gltfData = instancesBinary.subspan(gltfStart, gltfEnd - gltfStart);
std::optional<CesiumAsync::Future<AssetFetcherResult>> assetFuture;
auto featureTableJsonData =
instancesBinary.subspan(headerLength, header.featureTableJsonByteLength);
parseJsonAndBinaryData(
instancesBinary,
header,
headerLength,
convertedI3dm,
convertedI3dm.gltfResult.errors);
// XXX check for errors
std::optional<I3dmContent> parsedJsonResult =
parseI3dmJson(featureTableJsonData, convertedI3dm.gltfResult.errors);
parseI3dmJson(convertedI3dm.featureTableJson, convertedI3dm.gltfResult.errors);
if (!parsedJsonResult) {
return finishEarly();
}
std::optional<CesiumAsync::Future<AssetFetcherResult>> assetFuture;
const I3dmContent& parsedContent = *parsedJsonResult;
decodedInstances.rtcCenter = parsedContent.rtcCenter;
decodedInstances.rotationENU = parsedContent.eastNorthUp;
Expand Down Expand Up @@ -907,7 +966,16 @@ CesiumAsync::Future<GltfConverterResult> I3dmToGltfConverter::convert(
instantiateGltfInstances(
convertedI3dm.gltfResult,
convertedI3dm.decodedInstances);

}
ErrorList batchTableErrors =
BatchTableToGltfStructuralMetadata::convertFromI3dm(
convertedI3dm.featureTableJson,
convertedI3dm.batchTableJson,
convertedI3dm.featureTableJsonData,
convertedI3dm.batchTableJsonData,
convertedI3dm.gltfResult.model);
convertedI3dm.gltfResult.errors.merge(batchTableErrors);
return convertedI3dm.gltfResult;
});
}
Expand Down
Loading

0 comments on commit 151f570

Please sign in to comment.