MetaCore/Source/MetaCoreScene/Private/MetaCorePrefab.cpp
2026-07-27 17:02:26 +08:00

389 lines
19 KiB
C++

#include "MetaCoreScene/MetaCorePrefab.h"
#include "MetaCoreScene/MetaCoreSceneWorld.h"
#include <algorithm>
#include <nlohmann/json.hpp>
#include <unordered_set>
namespace MetaCore {
namespace {
using json = nlohmann::json;
glm::vec3 ReadVec3(std::string_view value, bool& valid) {
try {
const json parsed = json::parse(value);
if (!parsed.is_array() || parsed.size() < 3) return {};
valid = true;
return {parsed[0].get<float>(), parsed[1].get<float>(), parsed[2].get<float>()};
} catch (...) {
return {};
}
}
template <typename T>
bool ReadScalar(std::string_view value, T& output) {
try {
output = json::parse(value).get<T>();
return true;
} catch (...) {
return false;
}
}
MetaCoreGameObjectData* FindNode(MetaCorePrefabDocument& document, const MetaCoreAssetGuid& nodeGuid) {
const auto iterator = std::find_if(document.GameObjects.begin(), document.GameObjects.end(), [&](const auto& object) {
return object.StableObjectGuid == nodeGuid;
});
return iterator == document.GameObjects.end() ? nullptr : &*iterator;
}
const MetaCoreGameObjectData* FindNode(const MetaCorePrefabDocument& document, const MetaCoreAssetGuid& nodeGuid) {
const auto iterator = std::find_if(document.GameObjects.begin(), document.GameObjects.end(), [&](const auto& object) {
return object.StableObjectGuid == nodeGuid;
});
return iterator == document.GameObjects.end() ? nullptr : &*iterator;
}
void NormalizePrefab(MetaCorePrefabDocument& document) {
if (!document.PrefabGuid.IsValid()) document.PrefabGuid = MetaCoreAssetGuid::Generate();
for (auto& object : document.GameObjects) {
if (!object.StableObjectGuid.IsValid())
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.PrefabGuid, object.Id);
}
document.SchemaVersion = GMetaCorePrefabSchemaVersion;
}
void MergeVariantObjects(MetaCorePrefabDocument& target, const MetaCorePrefabDocument& layer) {
for (const auto& object : layer.GameObjects) {
const auto iterator = std::find_if(target.GameObjects.begin(), target.GameObjects.end(), [&](const auto& current) {
return current.StableObjectGuid == object.StableObjectGuid;
});
if (iterator == target.GameObjects.end()) target.GameObjects.push_back(object);
else *iterator = object;
}
}
} // namespace
MetaCorePrefabOverrideRegistry::MetaCorePrefabOverrideRegistry() {
RegisterPropertyHandler("Name", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
return path == "Value" && ReadScalar(value, object.Name);
});
RegisterPropertyHandler("Transform", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
bool valid = false;
const glm::vec3 parsed = ReadVec3(value, valid);
if (!valid) return false;
if (path == "Position") object.Transform.Position = parsed;
else if (path == "RotationEulerDegrees") object.Transform.RotationEulerDegrees = parsed;
else if (path == "Scale") object.Transform.Scale = parsed;
else return false;
return true;
});
RegisterPropertyHandler("Active", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
if (path != "Active") return false;
if (!object.Active) object.Active = MetaCoreActiveComponent{};
return ReadScalar(value, object.Active->Active);
});
RegisterPropertyHandler("Camera", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
if (!object.Camera) object.Camera = MetaCoreCameraComponent{};
if (path == "FieldOfViewDegrees") return ReadScalar(value, object.Camera->FieldOfViewDegrees);
if (path == "NearClip") return ReadScalar(value, object.Camera->NearClip);
if (path == "FarClip") return ReadScalar(value, object.Camera->FarClip);
if (path == "Enabled") return ReadScalar(value, object.Camera->Enabled);
if (path == "IsPrimary") return ReadScalar(value, object.Camera->IsPrimary);
return false;
});
RegisterPropertyHandler("Light", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
if (!object.Light) object.Light = MetaCoreLightComponent{};
if (path == "Intensity") return ReadScalar(value, object.Light->Intensity);
if (path == "Enabled") return ReadScalar(value, object.Light->Enabled);
if (path == "Color") {
bool valid = false;
const auto parsed = ReadVec3(value, valid);
if (valid) object.Light->Color = parsed;
return valid;
}
return false;
});
}
void MetaCorePrefabOverrideRegistry::RegisterPropertyHandler(std::string componentTypeId, PropertyHandler handler) {
PropertyHandlers_[std::move(componentTypeId)] = std::move(handler);
}
bool MetaCorePrefabOverrideRegistry::Apply(
MetaCorePrefabDocument& document,
const MetaCorePrefabOverrideDocument& overrideValue
) const {
MetaCoreGameObjectData* object = FindNode(document, overrideValue.TargetNodeGuid);
if (!object) return false;
switch (overrideValue.Operation) {
case MetaCorePrefabOverrideOperation::SetProperty: {
const auto handler = PropertyHandlers_.find(overrideValue.ComponentTypeId);
return handler != PropertyHandlers_.end() &&
handler->second(*object, overrideValue.PropertyPath, overrideValue.ValueJson);
}
case MetaCorePrefabOverrideOperation::AddComponent:
if (overrideValue.ComponentTypeId == "Active") object->Active = MetaCoreActiveComponent{};
else if (overrideValue.ComponentTypeId == "Camera") object->Camera = MetaCoreCameraComponent{};
else if (overrideValue.ComponentTypeId == "Light") object->Light = MetaCoreLightComponent{};
else if (overrideValue.ComponentTypeId == "MeshRenderer") object->MeshRenderer = MetaCoreMeshRendererComponent{};
else if (overrideValue.ComponentTypeId == "Collider") object->Collider = MetaCoreColliderComponent{};
else if (overrideValue.ComponentTypeId == "RigidBody") object->RigidBody = MetaCoreRigidBodyComponent{};
else if (overrideValue.ComponentTypeId == "Rotator") object->Rotator = MetaCoreRotatorComponent{};
else if (overrideValue.ComponentTypeId == "Animator") object->Animator = MetaCoreAnimatorComponent{};
else if (overrideValue.ComponentTypeId == "TimelineDirector") object->TimelineDirector = MetaCoreTimelineDirectorComponent{};
else if (overrideValue.ComponentTypeId == "Script") object->Script = MetaCoreScriptComponent{};
else if (overrideValue.ComponentTypeId == "UiRenderer") object->UiRenderer = MetaCoreUiRendererComponent{};
else if (overrideValue.ComponentTypeId == "Interactable") object->Interactable = MetaCoreInteractableComponent{};
else if (overrideValue.ComponentTypeId == "PhysicsConstraint") object->PhysicsConstraint = MetaCorePhysicsConstraintComponent{};
else if (overrideValue.ComponentTypeId == "CharacterController") object->CharacterController = MetaCoreCharacterControllerComponent{};
else if (overrideValue.ComponentTypeId == "ReflectionProbe") object->ReflectionProbe = MetaCoreReflectionProbeComponent{};
else if (overrideValue.ComponentTypeId == "LodGroup") object->LodGroup = MetaCoreLodGroupComponent{};
else if (overrideValue.ComponentTypeId == "ParticleEmitter") object->ParticleEmitter = MetaCoreParticleEmitterComponent{};
else if (overrideValue.ComponentTypeId == "LineRenderer") object->LineRenderer = MetaCoreLineRendererComponent{};
else if (overrideValue.ComponentTypeId == "TrailRenderer") object->TrailRenderer = MetaCoreTrailRendererComponent{};
else if (overrideValue.ComponentTypeId == "DecalProjector") object->DecalProjector = MetaCoreDecalProjectorComponent{};
else if (overrideValue.ComponentTypeId == "Terrain") object->Terrain = MetaCoreTerrainComponent{};
else return false;
return true;
case MetaCorePrefabOverrideOperation::RemoveComponent:
if (overrideValue.ComponentTypeId == "Active") object->Active.reset();
else if (overrideValue.ComponentTypeId == "Camera") object->Camera.reset();
else if (overrideValue.ComponentTypeId == "Light") object->Light.reset();
else if (overrideValue.ComponentTypeId == "MeshRenderer") object->MeshRenderer.reset();
else if (overrideValue.ComponentTypeId == "Collider") object->Collider.reset();
else if (overrideValue.ComponentTypeId == "RigidBody") object->RigidBody.reset();
else if (overrideValue.ComponentTypeId == "Rotator") object->Rotator.reset();
else if (overrideValue.ComponentTypeId == "Animator") object->Animator.reset();
else if (overrideValue.ComponentTypeId == "TimelineDirector") object->TimelineDirector.reset();
else if (overrideValue.ComponentTypeId == "Script") object->Script.reset();
else if (overrideValue.ComponentTypeId == "UiRenderer") object->UiRenderer.reset();
else if (overrideValue.ComponentTypeId == "Interactable") object->Interactable.reset();
else if (overrideValue.ComponentTypeId == "PhysicsConstraint") object->PhysicsConstraint.reset();
else if (overrideValue.ComponentTypeId == "CharacterController") object->CharacterController.reset();
else if (overrideValue.ComponentTypeId == "ReflectionProbe") object->ReflectionProbe.reset();
else if (overrideValue.ComponentTypeId == "LodGroup") object->LodGroup.reset();
else if (overrideValue.ComponentTypeId == "ParticleEmitter") object->ParticleEmitter.reset();
else if (overrideValue.ComponentTypeId == "LineRenderer") object->LineRenderer.reset();
else if (overrideValue.ComponentTypeId == "TrailRenderer") object->TrailRenderer.reset();
else if (overrideValue.ComponentTypeId == "DecalProjector") object->DecalProjector.reset();
else if (overrideValue.ComponentTypeId == "Terrain") object->Terrain.reset();
else return false;
return true;
case MetaCorePrefabOverrideOperation::RemoveChild: {
std::unordered_set<MetaCoreId> removed{object->Id};
bool changed = true;
while (changed) {
changed = false;
for (const auto& candidate : document.GameObjects)
if (removed.contains(candidate.ParentId) && removed.insert(candidate.Id).second) changed = true;
}
std::erase_if(document.GameObjects, [&](const auto& candidate) { return removed.contains(candidate.Id); });
return true;
}
case MetaCorePrefabOverrideOperation::Reparent: {
const auto parentGuid = MetaCoreAssetGuid::Parse(overrideValue.ValueJson);
if (!parentGuid) return false;
const auto* parent = FindNode(document, *parentGuid);
if (!parent || parent->Id == object->Id) return false;
object->ParentId = parent->Id;
return true;
}
case MetaCorePrefabOverrideOperation::AddChild: {
try {
const json value = json::parse(overrideValue.ValueJson);
MetaCoreGameObjectData child;
child.Id = 1;
for (const auto& current : document.GameObjects) child.Id = std::max(child.Id, current.Id + 1);
child.ParentId = object->Id;
child.Name = value.value("Name", std::string("GameObject"));
child.StableObjectGuid = MetaCoreAssetGuid::Parse(value.value("StableObjectGuid", std::string{}))
.value_or(MetaCoreBuildDeterministicGuid(document.PrefabGuid, child.Id));
if (value.contains("Position")) {
bool valid = false;
child.Transform.Position = ReadVec3(value["Position"].dump(), valid);
if (!valid) return false;
}
if (value.contains("RotationEulerDegrees")) {
bool valid = false;
child.Transform.RotationEulerDegrees = ReadVec3(value["RotationEulerDegrees"].dump(), valid);
if (!valid) return false;
}
if (value.contains("Scale")) {
bool valid = false;
child.Transform.Scale = ReadVec3(value["Scale"].dump(), valid);
if (!valid) return false;
}
document.GameObjects.push_back(std::move(child));
return true;
} catch (...) {
return false;
}
}
default:
return false;
}
}
MetaCorePrefabResolver::MetaCorePrefabResolver(PrefabLoader loader, MetaCorePrefabOverrideRegistry registry)
: Loader_(std::move(loader)), OverrideRegistry_(std::move(registry)) {}
std::optional<MetaCoreResolvedPrefab> MetaCorePrefabResolver::Resolve(
const MetaCoreAssetGuid& prefabGuid,
std::string* error
) const {
std::vector<MetaCoreAssetGuid> stack;
return ResolveInternal(prefabGuid, stack, error);
}
std::optional<MetaCoreResolvedPrefab> MetaCorePrefabResolver::ResolveInternal(
const MetaCoreAssetGuid& prefabGuid,
std::vector<MetaCoreAssetGuid>& stack,
std::string* error
) const {
if (!prefabGuid.IsValid() || !Loader_) {
if (error) *error = "Prefab GUID is invalid or no loader is configured";
return std::nullopt;
}
if (std::find(stack.begin(), stack.end(), prefabGuid) != stack.end()) {
if (error) *error = "Prefab base/nesting dependency cycle at " + prefabGuid.ToString();
return std::nullopt;
}
auto layer = Loader_(prefabGuid);
if (!layer) {
if (error) *error = "Prefab asset is missing: " + prefabGuid.ToString();
return std::nullopt;
}
if (layer->SchemaVersion > GMetaCorePrefabSchemaVersion) {
if (error) *error = "Prefab schema is newer than this engine: " + prefabGuid.ToString();
return std::nullopt;
}
if (!layer->PrefabGuid.IsValid()) layer->PrefabGuid = prefabGuid;
NormalizePrefab(*layer);
stack.push_back(prefabGuid);
MetaCoreResolvedPrefab result;
if (layer->BasePrefabGuid.IsValid()) {
auto base = ResolveInternal(layer->BasePrefabGuid, stack, error);
if (!base) {
stack.pop_back();
return std::nullopt;
}
result = std::move(*base);
result.Document.PrefabGuid = layer->PrefabGuid;
result.Document.BasePrefabGuid = layer->BasePrefabGuid;
result.Document.Name = layer->Name;
MergeVariantObjects(result.Document, *layer);
} else {
result.Document = *layer;
}
result.DependencyChain.push_back(prefabGuid);
for (const auto& value : layer->Overrides)
if (!OverrideRegistry_.Apply(result.Document, value)) result.OrphanOverrides.push_back(value);
for (const auto& nested : layer->NestedPrefabs) {
auto nestedResult = ResolveInternal(nested.PrefabAssetGuid, stack, error);
if (!nestedResult) {
stack.pop_back();
return std::nullopt;
}
for (const auto& value : nested.Overrides)
if (!OverrideRegistry_.Apply(nestedResult->Document, value)) result.OrphanOverrides.push_back(value);
const auto* mount = FindNode(result.Document, nested.MountNodeGuid);
if (!mount) {
MetaCorePrefabOverrideDocument orphan;
orphan.TargetNodeGuid = nested.MountNodeGuid;
orphan.ComponentTypeId = "NestedPrefab";
orphan.ValueJson = nested.PrefabAssetGuid.ToString();
result.OrphanOverrides.push_back(std::move(orphan));
continue;
}
const MetaCoreAssetGuid instanceGuid = nested.InstanceGuid.IsValid()
? nested.InstanceGuid
: MetaCoreBuildDeterministicGuid(layer->PrefabGuid, nested.PrefabAssetGuid);
std::unordered_map<MetaCoreId, MetaCoreId> remap;
for (const auto& object : nestedResult->Document.GameObjects)
remap[object.Id] = MetaCoreIdGenerator::Generate();
for (auto object : nestedResult->Document.GameObjects) {
const MetaCoreId oldId = object.Id;
object.Id = remap.at(oldId);
if (object.ParentId == 0) object.ParentId = mount->Id;
else if (const auto parent = remap.find(object.ParentId); parent != remap.end()) object.ParentId = parent->second;
else {
if (error) *error = "Nested prefab contains a missing parent object";
stack.pop_back();
return std::nullopt;
}
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(instanceGuid, object.StableObjectGuid);
result.Document.GameObjects.push_back(std::move(object));
}
result.DependencyChain.insert(result.DependencyChain.end(), nestedResult->DependencyChain.begin(), nestedResult->DependencyChain.end());
result.OrphanOverrides.insert(result.OrphanOverrides.end(), nestedResult->OrphanOverrides.begin(), nestedResult->OrphanOverrides.end());
}
stack.pop_back();
return result;
}
std::optional<std::vector<MetaCoreGameObjectData>> MetaCorePrefabResolver::Instantiate(
const MetaCoreAssetGuid& prefabGuid,
const MetaCoreAssetGuid& instanceGuid,
const std::vector<MetaCorePrefabOverrideDocument>& instanceOverrides,
std::string* error
) const {
if (!instanceGuid.IsValid()) {
if (error) *error = "Prefab instance GUID is invalid";
return std::nullopt;
}
auto resolved = Resolve(prefabGuid, error);
if (!resolved) return std::nullopt;
for (const auto& value : instanceOverrides)
if (!OverrideRegistry_.Apply(resolved->Document, value)) resolved->OrphanOverrides.push_back(value);
std::unordered_map<MetaCoreId, MetaCoreId> remap;
for (const auto& object : resolved->Document.GameObjects) remap[object.Id] = MetaCoreIdGenerator::Generate();
for (auto& object : resolved->Document.GameObjects) {
const MetaCoreId oldId = object.Id;
const MetaCoreAssetGuid nodeGuid = object.StableObjectGuid;
object.Id = remap.at(oldId);
if (object.ParentId != 0) {
const auto parent = remap.find(object.ParentId);
if (parent == remap.end()) {
if (error) *error = "Prefab contains a missing parent object";
return std::nullopt;
}
object.ParentId = parent->second;
}
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(instanceGuid, nodeGuid);
MetaCorePrefabInstanceMetadata metadata;
metadata.PrefabAssetGuid = prefabGuid;
metadata.PrefabObjectId = oldId;
metadata.InstanceGuid = instanceGuid;
metadata.PrefabNodeGuid = nodeGuid;
object.PrefabInstance = metadata;
}
const MetaCoreId rootId = [&] {
const auto root = std::find_if(resolved->Document.GameObjects.begin(), resolved->Document.GameObjects.end(),
[](const auto& object) { return object.ParentId == 0; });
return root == resolved->Document.GameObjects.end() ? MetaCoreId{} : root->Id;
}();
for (auto& object : resolved->Document.GameObjects)
if (object.PrefabInstance) object.PrefabInstance->PrefabInstanceRootId = rootId;
return resolved->Document.GameObjects;
}
std::vector<std::string> MetaCorePrefabResolver::ValidateDependencies(const MetaCoreAssetGuid& prefabGuid) const {
std::string error;
const auto result = Resolve(prefabGuid, &error);
std::vector<std::string> issues;
if (!result) issues.push_back(error);
else if (!result->OrphanOverrides.empty())
issues.push_back("Prefab contains " + std::to_string(result->OrphanOverrides.size()) + " orphan override(s)");
return issues;
}
} // namespace MetaCore