Skip to content

Commit

Permalink
Add support for merging models with metadata.
Browse files Browse the repository at this point in the history
  • Loading branch information
kring committed Apr 9, 2024
1 parent 277359d commit 3cda6d5
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 4 deletions.
7 changes: 4 additions & 3 deletions CesiumGltf/include/CesiumGltf/Model.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#pragma once

#include "CesiumGltf/Library.h"
#include "CesiumGltf/ModelSpec.h"
#include <CesiumGltf/Library.h>
#include <CesiumGltf/ModelSpec.h>
#include <CesiumUtility/ErrorList.h>

#include <glm/mat4x4.hpp>

Expand All @@ -22,7 +23,7 @@ struct CESIUMGLTF_API Model : public ModelSpec {
*
* @param rhs The model to merge into this one.
*/
void merge(Model&& rhs);
CesiumUtility::ErrorList merge(Model&& rhs);

/**
* @brief A callback function for {@link forEachPrimitiveInScene}.
Expand Down
211 changes: 210 additions & 1 deletion CesiumGltf/src/Model.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
#include "CesiumGltf/Model.h"

#include "CesiumGltf/AccessorView.h"
#include "CesiumGltf/ExtensionExtMeshFeatures.h"
#include "CesiumGltf/ExtensionKhrDracoMeshCompression.h"
#include "CesiumGltf/ExtensionMeshPrimitiveExtStructuralMetadata.h"
#include "CesiumGltf/ExtensionModelExtStructuralMetadata.h"

#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/norm.hpp>
#include <glm/vec3.hpp>
#include <gsl/span>

#include <algorithm>
#include <charconv>

using namespace CesiumUtility;

namespace CesiumGltf {

namespace {

template <typename T>
size_t copyElements(std::vector<T>& to, std::vector<T>& from) {
const size_t out = to.size();
Expand All @@ -29,9 +37,24 @@ void updateIndex(int32_t& index, size_t offset) noexcept {
}
index += int32_t(offset);
}

void updateIndex(int64_t& index, size_t offset) noexcept {
if (index == -1) {
return;
}
index += int64_t(offset);
}

void mergeSchemas(
Schema& lhs,
Schema& rhs,
std::map<std::string, std::string>& classNameMap);

} // namespace

void Model::merge(Model&& rhs) {
ErrorList Model::merge(Model&& rhs) {
ErrorList result;

// TODO: we could generate this pretty easily if the glTF JSON schema made
// it clear which index properties refer to which types of objects.

Expand Down Expand Up @@ -65,6 +88,83 @@ void Model::merge(Model&& rhs) {
const size_t firstSkin = copyElements(this->skins, rhs.skins);
const size_t firstTexture = copyElements(this->textures, rhs.textures);

size_t firstPropertyTable = 0;
size_t firstPropertyTexture = 0;
size_t firstPropertyAttribute = 0;

ExtensionModelExtStructuralMetadata* pRhsMetadata =
rhs.getExtension<ExtensionModelExtStructuralMetadata>();
if (pRhsMetadata) {
ExtensionModelExtStructuralMetadata& metadata =
this->addExtension<ExtensionModelExtStructuralMetadata>();

if (metadata.schemaUri && pRhsMetadata->schemaUri &&
*metadata.schemaUri != *pRhsMetadata->schemaUri) {
// We can't merge schema URIs. So the thing to do here is download both
// schemas and merge them. But for now we're just reporting an error.
result.emplaceError("Cannot merge EXT_structural_metadata extensions "
"with different schemaUris.");
} else if (pRhsMetadata->schemaUri) {
metadata.schemaUri = pRhsMetadata->schemaUri;
}

std::map<std::string, std::string> classNameMap;

if (metadata.schema && pRhsMetadata->schema) {
mergeSchemas(*metadata.schema, *pRhsMetadata->schema, classNameMap);
} else if (pRhsMetadata->schema) {
metadata.schema = std::move(pRhsMetadata->schema);
}

firstPropertyTable =
copyElements(metadata.propertyTables, pRhsMetadata->propertyTables);
firstPropertyTexture =
copyElements(metadata.propertyTextures, pRhsMetadata->propertyTextures);
firstPropertyAttribute = copyElements(
metadata.propertyAttributes,
pRhsMetadata->propertyAttributes);

for (size_t i = firstPropertyTable; i < metadata.propertyTables.size();
++i) {
PropertyTable& propertyTable = metadata.propertyTables[i];
auto it = classNameMap.find(propertyTable.classProperty);
if (it != classNameMap.end()) {
propertyTable.classProperty = it->second;
}

for (std::pair<const std::string, PropertyTableProperty>& pair :
propertyTable.properties) {
updateIndex(pair.second.values, firstBufferView);
updateIndex(pair.second.arrayOffsets, firstBufferView);
updateIndex(pair.second.stringOffsets, firstBufferView);
}
}

for (size_t i = firstPropertyTexture; i < metadata.propertyTextures.size();
++i) {
PropertyTexture& propertyTexture = metadata.propertyTextures[i];
auto it = classNameMap.find(propertyTexture.classProperty);
if (it != classNameMap.end()) {
propertyTexture.classProperty = it->second;
}

for (std::pair<const std::string, PropertyTextureProperty>& pair :
propertyTexture.properties) {
updateIndex(pair.second.index, firstTexture);
}
}

for (size_t i = firstPropertyAttribute;
i < metadata.propertyAttributes.size();
++i) {
PropertyAttribute& propertyAttribute = metadata.propertyAttributes[i];
auto it = classNameMap.find(propertyAttribute.classProperty);
if (it != classNameMap.end()) {
propertyAttribute.classProperty = it->second;
}
}
}

// Update the copied indices
for (size_t i = firstAccessor; i < this->accessors.size(); ++i) {
Accessor& accessor = this->accessors[i];
Expand Down Expand Up @@ -122,6 +222,32 @@ void Model::merge(Model&& rhs) {
if (pDraco) {
updateIndex(pDraco->bufferView, firstBufferView);
}

ExtensionMeshPrimitiveExtStructuralMetadata* pMetadata =
primitive.getExtension<ExtensionMeshPrimitiveExtStructuralMetadata>();
if (pMetadata) {
for (int64_t& propertyTextureID : pMetadata->propertyTextures) {
updateIndex(propertyTextureID, firstPropertyTexture);
}

for (int64_t& propertyAttributeID : pMetadata->propertyAttributes) {
updateIndex(propertyAttributeID, firstPropertyAttribute);
}
}

ExtensionExtMeshFeatures* pMeshFeatures =
primitive.getExtension<ExtensionExtMeshFeatures>();
if (pMeshFeatures) {
for (FeatureId& featureId : pMeshFeatures->featureIds) {
if (featureId.propertyTable) {
updateIndex(*featureId.propertyTable, firstPropertyTable);
}

if (featureId.texture) {
updateIndex(featureId.texture->index, firstTexture);
}
}
}
}
}

Expand Down Expand Up @@ -215,6 +341,8 @@ void Model::merge(Model&& rhs) {

this->scene = int32_t(this->scenes.size() - 1);
}

return result;
}

namespace {
Expand Down Expand Up @@ -694,4 +822,85 @@ bool Model::isExtensionRequired(
extensionName) != this->extensionsRequired.end();
}

namespace {

template <typename T>
std::string findAvailableName(
const std::unordered_map<std::string, T>& map,
const std::string& name) {
auto it = map.find(name);
if (it == map.end())
return name;

// Name already exists in the map, so find a numbered name that doesn't.

uint64_t number = 1;
while (number < std::numeric_limits<uint64_t>::max()) {
std::string newName = fmt::format("{}_{}", name, number);
it = map.find(newName);
if (it == map.end()) {
return newName;
}

++number;
}

// Realistically, this can't happen. It would mean we checked all 2^64
// possible names and none of them are available.
assert(false);
return name;
}

void mergeSchemas(
Schema& lhs,
Schema& rhs,
std::map<std::string, std::string>& classNameMap) {
if (!lhs.name)
lhs.name = rhs.name;
else if (rhs.name && *lhs.name != *rhs.name)
lhs.name.emplace("Merged");

if (!lhs.description)
lhs.description = rhs.description;
else if (rhs.description && *lhs.description != *rhs.description)
lhs.description.emplace("This is a merged schema created by combining "
"together the schemas from multiple glTFs.");

if (!lhs.version)
lhs.version = rhs.version;
else if (rhs.version && *lhs.version != *rhs.version)
lhs.version.reset();

std::map<std::string, std::string> enumNameMap;

for (std::pair<const std::string, Enum>& pair : rhs.enums) {
std::string availableName = findAvailableName(lhs.enums, pair.first);
lhs.enums[availableName] = std::move(pair.second);
if (availableName != pair.first)
enumNameMap.emplace(pair.first, availableName);
}

for (std::pair<const std::string, Class>& pair : rhs.classes) {
std::string availableName = findAvailableName(lhs.classes, pair.first);
Class& klass = lhs.classes[availableName];
klass = std::move(pair.second);

if (availableName != pair.first)
classNameMap.emplace(pair.first, availableName);

// Remap enum names in class properties.
for (std::pair<const std::string, ClassProperty>& propertyPair :
klass.properties) {
if (propertyPair.second.enumType) {
auto it = enumNameMap.find(*propertyPair.second.enumType);
if (it != enumNameMap.end()) {
propertyPair.second.enumType = it->second;
}
}
}
}
}

} // namespace

} // namespace CesiumGltf

0 comments on commit 3cda6d5

Please sign in to comment.