MetaCore/Source/MetaCoreScene/Private/MetaCoreScene.cpp

431 lines
14 KiB
C++

#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreFoundation/MetaCoreId.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>
namespace MetaCore {
namespace {
glm::mat4 MetaCoreBuildTransformMatrix(const MetaCoreTransformComponent& transform) {
const glm::mat4 translation = glm::translate(glm::mat4(1.0F), transform.Position);
const glm::mat4 rotation = glm::yawPitchRoll(
glm::radians(transform.RotationEulerDegrees.y),
glm::radians(transform.RotationEulerDegrees.x),
glm::radians(transform.RotationEulerDegrees.z)
);
const glm::mat4 scale = glm::scale(glm::mat4(1.0F), transform.Scale);
return translation * rotation * scale;
}
void MetaCoreApplyMatrixToTransform(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
glm::vec3 skew{};
glm::vec4 perspective{};
glm::quat rotation{};
glm::decompose(matrix, transform.Scale, rotation, transform.Position, skew, perspective);
transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation));
}
std::string MetaCoreBuildCopyName(const std::string& name) {
return name + " Copy";
}
} // namespace
MetaCoreGameObject& MetaCoreScene::CreateGameObject(const std::string& name, MetaCoreId parentId) {
GameObjects_.push_back(MetaCoreGameObject{
MetaCoreIdGenerator::Generate(),
parentId,
name,
MetaCoreTransformComponent{},
std::nullopt,
std::nullopt,
std::nullopt
});
return GameObjects_.back();
}
MetaCoreGameObject* MetaCoreScene::FindGameObject(MetaCoreId objectId) {
for (MetaCoreGameObject& object : GameObjects_) {
if (object.Id == objectId) {
return &object;
}
}
return nullptr;
}
const MetaCoreGameObject* MetaCoreScene::FindGameObject(MetaCoreId objectId) const {
for (const MetaCoreGameObject& object : GameObjects_) {
if (object.Id == objectId) {
return &object;
}
}
return nullptr;
}
std::vector<MetaCoreGameObject>& MetaCoreScene::GetGameObjects() {
return GameObjects_;
}
const std::vector<MetaCoreGameObject>& MetaCoreScene::GetGameObjects() const {
return GameObjects_;
}
std::vector<MetaCoreId> MetaCoreScene::GetRootObjectIds() const {
std::vector<MetaCoreId> rootIds;
for (const MetaCoreGameObject& object : GameObjects_) {
if (object.ParentId == 0) {
rootIds.push_back(object.Id);
}
}
return rootIds;
}
std::vector<MetaCoreId> MetaCoreScene::GetChildrenOf(MetaCoreId parentId) const {
std::vector<MetaCoreId> childIds;
for (const MetaCoreGameObject& object : GameObjects_) {
if (object.ParentId == parentId) {
childIds.push_back(object.Id);
}
}
return childIds;
}
void MetaCoreScene::CollectPreorder(MetaCoreId objectId, std::vector<MetaCoreId>& output) const {
if (FindGameObject(objectId) == nullptr) {
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;
}
const MetaCoreGameObject* currentObject = FindGameObject(objectId);
while (currentObject != nullptr && currentObject->ParentId != 0) {
if (currentObject->ParentId == ancestorId) {
return true;
}
currentObject = FindGameObject(currentObject->ParentId);
}
return false;
}
bool MetaCoreScene::RenameGameObject(MetaCoreId objectId, const std::string& name) {
MetaCoreGameObject* object = FindGameObject(objectId);
if (object == nullptr || name.empty()) {
return false;
}
object->Name = name;
return true;
}
std::vector<MetaCoreId> MetaCoreScene::DeleteGameObjects(const std::vector<MetaCoreId>& objectIds) {
std::unordered_set<MetaCoreId> selectedIds;
for (MetaCoreId objectId : objectIds) {
if (FindGameObject(objectId) != nullptr) {
selectedIds.insert(objectId);
}
}
if (selectedIds.empty()) {
return {};
}
std::vector<MetaCoreId> rootIds;
rootIds.reserve(selectedIds.size());
for (MetaCoreId objectId : selectedIds) {
const MetaCoreGameObject* object = FindGameObject(objectId);
bool hasSelectedAncestor = false;
while (object != nullptr && object->ParentId != 0) {
if (selectedIds.contains(object->ParentId)) {
hasSelectedAncestor = true;
break;
}
object = FindGameObject(object->ParentId);
}
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());
std::erase_if(GameObjects_, [&](const MetaCoreGameObject& object) {
if (!idsToDelete.contains(object.Id)) {
return false;
}
deletedIds.push_back(object.Id);
return true;
});
return deletedIds;
}
std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<MetaCoreId>& objectIds) {
std::unordered_set<MetaCoreId> selectedIds;
for (MetaCoreId objectId : objectIds) {
if (FindGameObject(objectId) != nullptr) {
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;
}
const MetaCoreGameObject* object = FindGameObject(objectId);
bool hasSelectedAncestor = false;
while (object != nullptr && object->ParentId != 0) {
if (selectedIds.contains(object->ParentId)) {
hasSelectedAncestor = true;
break;
}
object = FindGameObject(object->ParentId);
}
if (!hasSelectedAncestor) {
rootIds.push_back(objectId);
}
}
std::vector<MetaCoreGameObject> clonedObjects;
std::vector<MetaCoreId> duplicatedRootIds;
const auto cloneSubtree = [&](const auto& self, MetaCoreId sourceId, MetaCoreId duplicatedParentId, bool isRoot) -> void {
const MetaCoreGameObject* sourceObject = FindGameObject(sourceId);
if (sourceObject == nullptr) {
return;
}
MetaCoreGameObject clonedObject = *sourceObject;
clonedObject.Id = MetaCoreIdGenerator::Generate();
clonedObject.ParentId = duplicatedParentId;
if (isRoot) {
clonedObject.Name = MetaCoreBuildCopyName(sourceObject->Name);
duplicatedRootIds.push_back(clonedObject.Id);
}
clonedObjects.push_back(std::move(clonedObject));
const MetaCoreId newObjectId = clonedObjects.back().Id;
for (MetaCoreId childId : GetChildrenOf(sourceId)) {
self(self, childId, newObjectId, false);
}
};
for (MetaCoreId rootId : rootIds) {
const MetaCoreGameObject* rootObject = FindGameObject(rootId);
if (rootObject == nullptr) {
continue;
}
cloneSubtree(cloneSubtree, rootId, rootObject->ParentId, true);
}
GameObjects_.insert(GameObjects_.end(), clonedObjects.begin(), clonedObjects.end());
return duplicatedRootIds;
}
bool MetaCoreScene::ReparentGameObjects(const std::vector<MetaCoreId>& objectIds, MetaCoreId newParentId, bool keepWorldTransform) {
if (newParentId != 0 && FindGameObject(newParentId) == nullptr) {
return false;
}
std::unordered_set<MetaCoreId> selectedIds;
for (MetaCoreId objectId : objectIds) {
if (FindGameObject(objectId) != nullptr) {
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;
}
const MetaCoreGameObject* object = FindGameObject(objectId);
bool hasSelectedAncestor = false;
while (object != nullptr && object->ParentId != 0) {
if (selectedIds.contains(object->ParentId)) {
hasSelectedAncestor = true;
break;
}
object = FindGameObject(object->ParentId);
}
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 {
const MetaCoreGameObject* object = FindGameObject(objectId);
if (object == nullptr) {
return glm::mat4(1.0F);
}
const glm::mat4 localMatrix = MetaCoreBuildTransformMatrix(object->Transform);
if (object->ParentId == 0) {
return localMatrix;
}
return self(self, object->ParentId) * 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 != nullptr) {
object->ParentId = 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 == nullptr) {
continue;
}
const auto matrixIterator = worldMatrices.find(rootId);
if (matrixIterator == worldMatrices.end()) {
continue;
}
const glm::mat4 newLocalMatrix = inverseParentWorldMatrix * matrixIterator->second;
MetaCoreApplyMatrixToTransform(newLocalMatrix, object->Transform);
}
}
return true;
}
MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
MetaCoreSceneSnapshot snapshot;
snapshot.GameObjects = GameObjects_;
return snapshot;
}
void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
GameObjects_ = snapshot.GameObjects;
MetaCoreId maxId = 0;
for (const MetaCoreGameObject& object : GameObjects_) {
maxId = std::max(maxId, object.Id);
}
MetaCoreIdGenerator::EnsureAbove(maxId);
}
MetaCoreScene MetaCoreCreateDefaultScene() {
MetaCoreScene scene;
MetaCoreGameObject& mainCamera = scene.CreateGameObject("Main Camera");
mainCamera.Camera = MetaCoreCameraComponent{};
mainCamera.Camera->IsPrimary = true;
mainCamera.Transform.Position = glm::vec3(0.0F, 2.2F, 6.5F);
mainCamera.Transform.RotationEulerDegrees = glm::vec3(-15.0F, 0.0F, 0.0F);
MetaCoreGameObject& directionalLight = scene.CreateGameObject("Directional Light");
directionalLight.Light = MetaCoreLightComponent{};
directionalLight.Transform.RotationEulerDegrees = glm::vec3(-45.0F, 30.0F, 0.0F);
MetaCoreGameObject& cube = scene.CreateGameObject("Cube");
cube.MeshRenderer = MetaCoreMeshRendererComponent{};
cube.Transform.Position = glm::vec3(0.0F, 0.5F, 0.0F);
MetaCoreGameObject& valve = scene.CreateGameObject("Valve");
valve.MeshRenderer = MetaCoreMeshRendererComponent{};
valve.Transform.Position = glm::vec3(-2.2F, 0.5F, 0.0F);
valve.MeshRenderer->BaseColor = glm::vec3(0.85F, 0.45F, 0.20F);
MetaCoreGameObject& tank = scene.CreateGameObject("Tank");
tank.MeshRenderer = MetaCoreMeshRendererComponent{};
tank.Transform.Position = glm::vec3(2.2F, 0.5F, 0.0F);
tank.Transform.Scale = glm::vec3(1.4F, 1.4F, 1.4F);
tank.MeshRenderer->BaseColor = glm::vec3(0.35F, 0.65F, 0.90F);
MetaCoreGameObject& alarmBeacon = scene.CreateGameObject("Alarm Beacon");
alarmBeacon.Light = MetaCoreLightComponent{};
alarmBeacon.Light->Intensity = 0.0F;
alarmBeacon.Light->Color = glm::vec3(1.0F, 0.15F, 0.1F);
alarmBeacon.Transform.Position = glm::vec3(0.0F, 2.5F, 0.0F);
return scene;
}
} // namespace MetaCore