478 lines
16 KiB
C++
478 lines
16 KiB
C++
#include "MetaCoreScene/MetaCoreScene.h"
|
|
|
|
#include "MetaCoreFoundation/MetaCoreId.h"
|
|
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
|
|
|
#define GLM_ENABLE_EXPERIMENTAL
|
|
#include <glm/ext/matrix_transform.hpp>
|
|
#include <glm/gtx/euler_angles.hpp>
|
|
#include <glm/gtx/matrix_decompose.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
|
|
|
namespace MetaCore {
|
|
|
|
namespace {
|
|
|
|
std::string MetaCoreBuildCopyName(const std::string& name) {
|
|
return name + " Copy";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MetaCoreGameObject MetaCoreScene::CreateGameObject(const std::string& name, MetaCoreId parentId) {
|
|
return CreateGameObjectWithId(MetaCoreIdGenerator::Generate(), name, parentId);
|
|
}
|
|
|
|
MetaCoreGameObject MetaCoreScene::CreateGameObjectWithId(MetaCoreId id, const std::string& name, MetaCoreId parentId) {
|
|
entt::entity entity = Registry_.create();
|
|
IdToEntity_[id] = entity;
|
|
|
|
MetaCoreGameObject obj(entity, this);
|
|
obj.AddComponent<MetaCoreIdComponent>().Id = id;
|
|
obj.AddComponent<MetaCoreNameComponent>().Name = name;
|
|
obj.AddComponent<MetaCoreHierarchyComponent>().ParentId = parentId;
|
|
obj.AddComponent<MetaCoreTransformComponent>();
|
|
|
|
IncrementRevision();
|
|
|
|
return obj;
|
|
}
|
|
|
|
MetaCoreGameObject MetaCoreScene::FindGameObject(MetaCoreId objectId) {
|
|
auto it = IdToEntity_.find(objectId);
|
|
if (it != IdToEntity_.end()) {
|
|
return MetaCoreGameObject(it->second, this);
|
|
}
|
|
return MetaCoreGameObject();
|
|
}
|
|
|
|
MetaCoreGameObject MetaCoreScene::FindGameObject(MetaCoreId objectId) const {
|
|
auto it = IdToEntity_.find(objectId);
|
|
if (it != IdToEntity_.end()) {
|
|
return MetaCoreGameObject(it->second, const_cast<MetaCoreScene*>(this));
|
|
}
|
|
return MetaCoreGameObject();
|
|
}
|
|
|
|
std::vector<MetaCoreGameObject> MetaCoreScene::GetGameObjects() {
|
|
std::vector<MetaCoreGameObject> objects;
|
|
objects.reserve(IdToEntity_.size());
|
|
for (auto [id, entity] : IdToEntity_) {
|
|
objects.emplace_back(entity, this);
|
|
}
|
|
return objects;
|
|
}
|
|
|
|
std::vector<MetaCoreGameObject> MetaCoreScene::GetGameObjects() const {
|
|
std::vector<MetaCoreGameObject> objects;
|
|
objects.reserve(IdToEntity_.size());
|
|
for (auto [id, entity] : IdToEntity_) {
|
|
objects.emplace_back(entity, const_cast<MetaCoreScene*>(this));
|
|
}
|
|
return objects;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::GetRootObjectIds() const {
|
|
std::vector<MetaCoreId> rootIds;
|
|
auto view = Registry_.view<MetaCoreIdComponent, MetaCoreHierarchyComponent>();
|
|
for (auto entity : view) {
|
|
if (view.get<MetaCoreHierarchyComponent>(entity).ParentId == 0) {
|
|
rootIds.push_back(view.get<MetaCoreIdComponent>(entity).Id);
|
|
}
|
|
}
|
|
return rootIds;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::GetChildrenOf(MetaCoreId parentId) const {
|
|
std::vector<MetaCoreId> childIds;
|
|
auto view = Registry_.view<MetaCoreIdComponent, MetaCoreHierarchyComponent>();
|
|
for (auto entity : view) {
|
|
if (view.get<MetaCoreHierarchyComponent>(entity).ParentId == parentId) {
|
|
childIds.push_back(view.get<MetaCoreIdComponent>(entity).Id);
|
|
}
|
|
}
|
|
return childIds;
|
|
}
|
|
|
|
void MetaCoreScene::CollectPreorder(MetaCoreId objectId, std::vector<MetaCoreId>& output) const {
|
|
if (!FindGameObject(objectId)) {
|
|
return;
|
|
}
|
|
|
|
output.push_back(objectId);
|
|
for (MetaCoreId childId : GetChildrenOf(objectId)) {
|
|
CollectPreorder(childId, output);
|
|
}
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::BuildHierarchyPreorder() const {
|
|
std::vector<MetaCoreId> orderedIds;
|
|
for (MetaCoreId rootId : GetRootObjectIds()) {
|
|
CollectPreorder(rootId, orderedIds);
|
|
}
|
|
return orderedIds;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::GetSubtreeObjectIds(MetaCoreId rootId) const {
|
|
std::vector<MetaCoreId> subtreeIds;
|
|
CollectPreorder(rootId, subtreeIds);
|
|
return subtreeIds;
|
|
}
|
|
|
|
bool MetaCoreScene::IsDescendantOf(MetaCoreId objectId, MetaCoreId ancestorId) const {
|
|
if (objectId == 0 || ancestorId == 0 || objectId == ancestorId) {
|
|
return false;
|
|
}
|
|
|
|
MetaCoreGameObject currentObject = FindGameObject(objectId);
|
|
while (currentObject && currentObject.GetParentId() != 0) {
|
|
if (currentObject.GetParentId() == ancestorId) {
|
|
return true;
|
|
}
|
|
currentObject = FindGameObject(currentObject.GetParentId());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MetaCoreScene::RenameGameObject(MetaCoreId objectId, const std::string& name) {
|
|
MetaCoreGameObject object = FindGameObject(objectId);
|
|
if (!object || name.empty()) {
|
|
return false;
|
|
}
|
|
|
|
object.GetName() = name;
|
|
IncrementRevision();
|
|
return true;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::DeleteGameObjects(const std::vector<MetaCoreId>& objectIds) {
|
|
std::unordered_set<MetaCoreId> selectedIds;
|
|
for (MetaCoreId objectId : objectIds) {
|
|
if (FindGameObject(objectId)) {
|
|
selectedIds.insert(objectId);
|
|
}
|
|
}
|
|
|
|
if (selectedIds.empty()) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<MetaCoreId> rootIds;
|
|
rootIds.reserve(selectedIds.size());
|
|
for (MetaCoreId objectId : selectedIds) {
|
|
MetaCoreGameObject object = FindGameObject(objectId);
|
|
bool hasSelectedAncestor = false;
|
|
while (object && object.GetParentId() != 0) {
|
|
if (selectedIds.contains(object.GetParentId())) {
|
|
hasSelectedAncestor = true;
|
|
break;
|
|
}
|
|
object = FindGameObject(object.GetParentId());
|
|
}
|
|
if (!hasSelectedAncestor) {
|
|
rootIds.push_back(objectId);
|
|
}
|
|
}
|
|
|
|
std::unordered_set<MetaCoreId> idsToDelete;
|
|
for (MetaCoreId rootId : rootIds) {
|
|
for (MetaCoreId subtreeId : GetSubtreeObjectIds(rootId)) {
|
|
idsToDelete.insert(subtreeId);
|
|
}
|
|
}
|
|
|
|
std::vector<MetaCoreId> deletedIds;
|
|
deletedIds.reserve(idsToDelete.size());
|
|
|
|
for (MetaCoreId id : idsToDelete) {
|
|
auto it = IdToEntity_.find(id);
|
|
if (it != IdToEntity_.end()) {
|
|
Registry_.destroy(it->second);
|
|
IdToEntity_.erase(it);
|
|
deletedIds.push_back(id);
|
|
}
|
|
}
|
|
|
|
if (!deletedIds.empty()) {
|
|
IncrementRevision();
|
|
}
|
|
|
|
return deletedIds;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<MetaCoreId>& objectIds) {
|
|
std::unordered_set<MetaCoreId> selectedIds;
|
|
for (MetaCoreId objectId : objectIds) {
|
|
if (FindGameObject(objectId)) {
|
|
selectedIds.insert(objectId);
|
|
}
|
|
}
|
|
|
|
if (selectedIds.empty()) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<MetaCoreId> orderedIds = BuildHierarchyPreorder();
|
|
std::vector<MetaCoreId> rootIds;
|
|
for (MetaCoreId objectId : orderedIds) {
|
|
if (!selectedIds.contains(objectId)) {
|
|
continue;
|
|
}
|
|
|
|
MetaCoreGameObject object = FindGameObject(objectId);
|
|
bool hasSelectedAncestor = false;
|
|
while (object && object.GetParentId() != 0) {
|
|
if (selectedIds.contains(object.GetParentId())) {
|
|
hasSelectedAncestor = true;
|
|
break;
|
|
}
|
|
object = FindGameObject(object.GetParentId());
|
|
}
|
|
|
|
if (!hasSelectedAncestor) {
|
|
rootIds.push_back(objectId);
|
|
}
|
|
}
|
|
|
|
std::vector<MetaCoreId> duplicatedRootIds;
|
|
|
|
const auto cloneSubtree = [&](const auto& self, MetaCoreId sourceId, MetaCoreId duplicatedParentId, bool isRoot) -> void {
|
|
MetaCoreGameObject sourceObject = FindGameObject(sourceId);
|
|
if (!sourceObject) {
|
|
return;
|
|
}
|
|
|
|
MetaCoreId newId = MetaCoreIdGenerator::Generate();
|
|
std::string newName = isRoot ? MetaCoreBuildCopyName(sourceObject.GetName()) : sourceObject.GetName();
|
|
MetaCoreGameObject clonedObject = CreateGameObjectWithId(newId, newName, duplicatedParentId);
|
|
|
|
clonedObject.GetComponent<MetaCoreTransformComponent>() = sourceObject.GetComponent<MetaCoreTransformComponent>();
|
|
|
|
if (sourceObject.HasComponent<MetaCoreCameraComponent>())
|
|
clonedObject.AddComponent<MetaCoreCameraComponent>(sourceObject.GetComponent<MetaCoreCameraComponent>());
|
|
|
|
if (sourceObject.HasComponent<MetaCoreMeshRendererComponent>())
|
|
clonedObject.AddComponent<MetaCoreMeshRendererComponent>(sourceObject.GetComponent<MetaCoreMeshRendererComponent>());
|
|
|
|
if (sourceObject.HasComponent<MetaCoreLightComponent>())
|
|
clonedObject.AddComponent<MetaCoreLightComponent>(sourceObject.GetComponent<MetaCoreLightComponent>());
|
|
|
|
if (sourceObject.HasComponent<MetaCorePrefabInstanceMetadata>())
|
|
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(sourceObject.GetComponent<MetaCorePrefabInstanceMetadata>());
|
|
|
|
if (isRoot) {
|
|
duplicatedRootIds.push_back(newId);
|
|
}
|
|
|
|
for (MetaCoreId childId : GetChildrenOf(sourceId)) {
|
|
self(self, childId, newId, false);
|
|
}
|
|
};
|
|
|
|
for (MetaCoreId rootId : rootIds) {
|
|
MetaCoreGameObject rootObject = FindGameObject(rootId);
|
|
if (!rootObject) {
|
|
continue;
|
|
}
|
|
|
|
cloneSubtree(cloneSubtree, rootId, rootObject.GetParentId(), true);
|
|
}
|
|
|
|
return duplicatedRootIds;
|
|
}
|
|
|
|
bool MetaCoreScene::ReparentGameObjects(const std::vector<MetaCoreId>& objectIds, MetaCoreId newParentId, bool keepWorldTransform) {
|
|
if (newParentId != 0 && !FindGameObject(newParentId)) {
|
|
return false;
|
|
}
|
|
|
|
std::unordered_set<MetaCoreId> selectedIds;
|
|
for (MetaCoreId objectId : objectIds) {
|
|
if (FindGameObject(objectId)) {
|
|
selectedIds.insert(objectId);
|
|
}
|
|
}
|
|
|
|
if (selectedIds.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<MetaCoreId> orderedIds = BuildHierarchyPreorder();
|
|
std::vector<MetaCoreId> rootIds;
|
|
for (MetaCoreId objectId : orderedIds) {
|
|
if (!selectedIds.contains(objectId)) {
|
|
continue;
|
|
}
|
|
|
|
MetaCoreGameObject object = FindGameObject(objectId);
|
|
bool hasSelectedAncestor = false;
|
|
while (object && object.GetParentId() != 0) {
|
|
if (selectedIds.contains(object.GetParentId())) {
|
|
hasSelectedAncestor = true;
|
|
break;
|
|
}
|
|
object = FindGameObject(object.GetParentId());
|
|
}
|
|
|
|
if (!hasSelectedAncestor) {
|
|
rootIds.push_back(objectId);
|
|
}
|
|
}
|
|
|
|
if (rootIds.empty()) {
|
|
return false;
|
|
}
|
|
|
|
for (MetaCoreId objectId : rootIds) {
|
|
if (objectId == newParentId || IsDescendantOf(newParentId, objectId)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::unordered_map<MetaCoreId, glm::mat4> worldMatrices;
|
|
if (keepWorldTransform) {
|
|
const auto buildWorldMatrix = [&](const auto& self, MetaCoreId objectId) -> glm::mat4 {
|
|
MetaCoreGameObject object = FindGameObject(objectId);
|
|
if (!object) {
|
|
return glm::mat4(1.0F);
|
|
}
|
|
|
|
const glm::mat4 localMatrix = MetaCoreBuildTransformMatrix(object.GetComponent<MetaCoreTransformComponent>());
|
|
if (object.GetParentId() == 0) {
|
|
return localMatrix;
|
|
}
|
|
|
|
return self(self, object.GetParentId()) * localMatrix;
|
|
};
|
|
|
|
for (MetaCoreId rootId : rootIds) {
|
|
worldMatrices.emplace(rootId, buildWorldMatrix(buildWorldMatrix, rootId));
|
|
}
|
|
|
|
if (newParentId != 0) {
|
|
worldMatrices.emplace(newParentId, buildWorldMatrix(buildWorldMatrix, newParentId));
|
|
}
|
|
}
|
|
|
|
for (MetaCoreId rootId : rootIds) {
|
|
MetaCoreGameObject object = FindGameObject(rootId);
|
|
if (object) {
|
|
object.SetParentId(newParentId);
|
|
}
|
|
}
|
|
|
|
if (keepWorldTransform) {
|
|
const glm::mat4 newParentWorldMatrix =
|
|
newParentId == 0 ? glm::mat4(1.0F) : worldMatrices[newParentId];
|
|
const glm::mat4 inverseParentWorldMatrix = glm::inverse(newParentWorldMatrix);
|
|
|
|
for (MetaCoreId rootId : rootIds) {
|
|
MetaCoreGameObject object = FindGameObject(rootId);
|
|
if (!object) {
|
|
continue;
|
|
}
|
|
|
|
const auto matrixIterator = worldMatrices.find(rootId);
|
|
if (matrixIterator == worldMatrices.end()) {
|
|
continue;
|
|
}
|
|
|
|
const glm::mat4 newLocalMatrix = inverseParentWorldMatrix * matrixIterator->second;
|
|
MetaCoreApplyMatrixToTransform(newLocalMatrix, object.GetComponent<MetaCoreTransformComponent>());
|
|
}
|
|
}
|
|
|
|
IncrementRevision();
|
|
|
|
return true;
|
|
}
|
|
|
|
MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
|
MetaCoreSceneSnapshot snapshot;
|
|
auto objects = GetGameObjects();
|
|
snapshot.GameObjects.reserve(objects.size());
|
|
for (auto obj : objects) {
|
|
MetaCoreGameObjectData data;
|
|
data.Id = obj.GetId();
|
|
data.ParentId = obj.GetParentId();
|
|
data.Name = obj.GetName();
|
|
data.Transform = obj.GetComponent<MetaCoreTransformComponent>();
|
|
|
|
if (obj.HasComponent<MetaCoreCameraComponent>())
|
|
data.Camera = obj.GetComponent<MetaCoreCameraComponent>();
|
|
if (obj.HasComponent<MetaCoreMeshRendererComponent>())
|
|
data.MeshRenderer = obj.GetComponent<MetaCoreMeshRendererComponent>();
|
|
if (obj.HasComponent<MetaCoreLightComponent>())
|
|
data.Light = obj.GetComponent<MetaCoreLightComponent>();
|
|
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
|
|
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
|
|
if (obj.HasComponent<MetaCoreModelRootTag>())
|
|
data.ModelRootTag = obj.GetComponent<MetaCoreModelRootTag>();
|
|
|
|
snapshot.GameObjects.push_back(std::move(data));
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
|
Registry_.clear();
|
|
IdToEntity_.clear();
|
|
|
|
MetaCoreId maxId = 0;
|
|
for (const MetaCoreGameObjectData& data : snapshot.GameObjects) {
|
|
maxId = std::max(maxId, data.Id);
|
|
MetaCoreGameObject obj = CreateGameObjectWithId(data.Id, data.Name, data.ParentId);
|
|
obj.GetComponent<MetaCoreTransformComponent>() = data.Transform;
|
|
|
|
if (data.Camera.has_value())
|
|
obj.AddComponent<MetaCoreCameraComponent>(data.Camera.value());
|
|
if (data.MeshRenderer.has_value())
|
|
obj.AddComponent<MetaCoreMeshRendererComponent>(data.MeshRenderer.value());
|
|
if (data.Light.has_value())
|
|
obj.AddComponent<MetaCoreLightComponent>(data.Light.value());
|
|
if (data.PrefabInstance.has_value())
|
|
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
|
|
if (data.ModelRootTag.has_value())
|
|
obj.AddComponent<MetaCoreModelRootTag>(data.ModelRootTag.value());
|
|
}
|
|
MetaCoreIdGenerator::EnsureAbove(maxId);
|
|
IncrementRevision();
|
|
}
|
|
|
|
MetaCoreScene MetaCoreCreateDefaultScene() {
|
|
MetaCoreScene scene;
|
|
|
|
MetaCoreGameObject mainCamera = scene.CreateGameObject("Main Camera");
|
|
mainCamera.AddComponent<MetaCoreCameraComponent>().IsPrimary = true;
|
|
mainCamera.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 2.2F, 6.5F);
|
|
mainCamera.GetComponent<MetaCoreTransformComponent>().RotationEulerDegrees = glm::vec3(-15.0F, 0.0F, 0.0F);
|
|
|
|
MetaCoreGameObject directionalLight = scene.CreateGameObject("Directional Light");
|
|
directionalLight.AddComponent<MetaCoreLightComponent>();
|
|
directionalLight.GetComponent<MetaCoreTransformComponent>().RotationEulerDegrees = glm::vec3(-45.0F, 30.0F, 0.0F);
|
|
|
|
MetaCoreGameObject cube = scene.CreateGameObject("box1.glb");
|
|
auto& meshRenderer = cube.AddComponent<MetaCoreMeshRendererComponent>();
|
|
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
|
|
meshRenderer.SourceModelPath = "Assets/Models/box1.glb";
|
|
meshRenderer.SourceModelAssetGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb");
|
|
cube.AddComponent<MetaCoreModelRootTag>(MetaCoreModelRootTag{ "Assets/Models/box1.glb" });
|
|
cube.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 0.5F, 0.0F);
|
|
|
|
MetaCoreGameObject alarmBeacon = scene.CreateGameObject("Alarm Beacon");
|
|
auto& beaconLight = alarmBeacon.AddComponent<MetaCoreLightComponent>();
|
|
beaconLight.Intensity = 0.0F;
|
|
beaconLight.Color = glm::vec3(1.0F, 0.15F, 0.1F);
|
|
alarmBeacon.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 2.5F, 0.0F);
|
|
|
|
return scene;
|
|
}
|
|
|
|
} // namespace MetaCore
|