431 lines
14 KiB
C++
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
|