feat: add debug rendering for other types of colliders / split game into more logical functions / files

This commit is contained in:
2026-10-16 15:08:10 +02:00
parent 4c9b77c948
commit 469bb287b7
32 changed files with 42809 additions and 882 deletions
+18 -861
View File
@@ -1,110 +1,11 @@
#include "Lightkeeper.h"
#include <array>
#include <cmath>
#include <destrum/Components/CameraComponent.h>
#include <destrum/FS/AssetFS.h>
#include <destrum/Assets/AssetManager.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
#include "glm/gtc/constants.hpp"
#include "glm/gtc/quaternion.hpp"
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/SceneSerializer.h>
#include "destrum/Components/MeshRendererComponent.h"
#include "destrum/Components/Rotator.h"
#include "destrum/Components/Spinner.h"
#include "destrum/Components/OrbitAndSpin.h"
#include "destrum/ObjectModel/GameObject.h"
#include "destrum/Util/ModelDoc.h"
#include "destrum/Components/Animator.h"
#include "destrum/Components/LightComponent.h"
#include "destrum/Components/CameraComponent.h"
#include <components/FirstPersonController.h>
#include <components/TriggerLoggerComponent.h>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <string_view>
#include "imgui.h"
#include "components/GameComponentRegistry.h"
#include "components/PrintComponent.h"
#include "destrum/Components/Physics/SphereCollider.h"
#include "destrum/Util/ModelDocUtils.h"
namespace {
std::filesystem::path ResolveScenePath(
std::string_view value,
const std::filesystem::path& exeDir) {
if (value.empty()) {
throw std::invalid_argument("Scene path cannot be empty");
}
if (value.find("://") != std::string_view::npos) {
return AssetFS::GetInstance().GetFullPath(value);
}
const std::filesystem::path path{value};
return path.is_absolute() ? path : exeDir / path;
}
// Box centered on the origin. UVs are in meters, so tiling textures keep the same size on every box
CPUMesh MakeBoxMesh(const glm::vec3& size)
{
struct Face {
glm::vec3 normal;
glm::vec3 tangent;
};
constexpr std::array<Face, 6> faces{{
{{1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}},
{{-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
{{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}},
}};
constexpr std::array<glm::vec2, 4> corners{{
{-1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f},
}};
CPUMesh mesh{};
mesh.name = "Box";
mesh.minPos = -size * 0.5f;
mesh.maxPos = size * 0.5f;
for (const Face& face : faces) {
const glm::vec3 bitangent = glm::cross(face.normal, face.tangent);
const float width = glm::dot(glm::abs(face.tangent), size);
const float height = glm::dot(glm::abs(bitangent), size);
const auto first = static_cast<std::uint32_t>(mesh.vertices.size());
for (const glm::vec2& corner : corners) {
// v runs against the bitangent so images are upright on the side faces (w = -1 to match)
mesh.vertices.push_back(CPUMesh::Vertex{
.position = (face.normal + face.tangent * corner.x + bitangent * corner.y) * size * 0.5f,
.uv_x = (corner.x + 1.0f) * 0.5f * width,
.normal = face.normal,
.uv_y = (1.0f - corner.y) * 0.5f * height,
.tangent = glm::vec4{face.tangent, -1.0f},
});
}
for (const std::uint32_t index : {0u, 1u, 2u, 0u, 2u, 3u}) {
mesh.indices.push_back(first + index);
}
}
return mesh;
}
}
#include <components/GameComponentRegistry.h>
LightKeeper::LightKeeper() : App()
{
@@ -129,538 +30,29 @@ void LightKeeper::customInit()
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
camera.setAspectRatio(aspectRatio);
ModelDoc::LoadOptions staticModelOptions{};
staticModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
staticModelOptions.loadMaterials = true;
staticModelOptions.loadSkeleton = false;
staticModelOptions.loadAnimations = false;
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
setupSkybox();
auto& scene = SceneManager::GetInstance().CreateScene("Main");
scene.GetEnvironment().fogDensity = 0.01f;
mainScene = BuildMainScene(scene, gfxDevice, resources);
// Directional lights shine down their local -Z: tilt it 50 degrees down, then turn it around the up axis
const auto sunObj = scene.CreateGameObject("Sun");
sunObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 10.f, 0.f));
sunObj->GetTransform().SetWorldRotation(
glm::angleAxis(glm::radians(35.f), glm::vec3{0.f, 1.f, 0.f}) *
glm::angleAxis(glm::radians(-50.f), glm::vec3{1.f, 0.f, 0.f}));
auto* sun = sunObj->AddComponent<LightComponent>(LightType::Directional);
sun->SetColor(glm::vec3{1.0f, 0.95f, 0.85f});
sun->SetIntensity(3.0f);
sun->SetCastShadows(true);
debugQuadTexture = resources.loadImageFromFile(
gfxDevice, AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png"));
}
// scene.Add(testCube);
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
// const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr");
const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("engine://DJI_0001_stitch.jpg");
//
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png");
//
void LightKeeper::setupSkybox()
{
const auto skyboxPath = AssetFS::GetInstance().GetCookedPathForFile("engine://DJI_0001_stitch.jpg");
const auto vertShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert");
const auto fragShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag");
//
skyboxCubemap = std::make_unique<CubeMap>();
skyboxCubemap->LoadCubeMap(gfxDevice, resources, skyboxID.generic_string());
skyboxCubemap->LoadCubeMap(gfxDevice, resources, skyboxPath.generic_string());
skyboxCubemap->InitCubemapPipeline(gfxDevice, resources, vertShaderPath.generic_string(), fragShaderPath.generic_string());
skyboxCubemap->CreateCubeMap(gfxDevice, resources);
renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
const auto planeObj = scene.CreateGameObject("GroundPlane");
const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
auto planeModel = AssetManager::GetInstance().LoadModel("game://plane.glb", staticModelOptions);
ModelDocUtils::LogModelDocSummary(planeModel, "plane.glb");
const auto& planePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(planeModel, "game://plane.glb");
const auto planeMeshID = resources.meshes().addMesh(gfxDevice, planePrimitive.mesh);
const auto planeTexturePath = ModelDocUtils::PickTexturePath(
planeModel,
planePrimitive,
AssetFS::GetInstance().GetFullPath("game://grass.png")
);
const auto planeTextureID = resources.loadImageFromFile(gfxDevice, planeTexturePath);
const auto planeMaterialID = resources.materials().addMaterial({
.baseColor = ModelDocUtils::GetImportedBaseColor(
planeModel, planePrimitive),
.textureFilteringMode = TextureFilteringMode::Nearest,
.diffuseTexture = planeTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
planeModel, planePrimitive, "GroundPlaneMaterial"),
});
planeMeshComp->SetMeshID(planeMeshID);
planeMeshComp->SetMaterialID(planeMaterialID);
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f));
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
// Kenney prototype textures, texture_01 is a 1 x 1 meter grid
const auto addPrototypeMaterial = [&](const std::string& color, float roughness)
{
Material material = Material::SimpleColor(glm::vec3{1.0f}, "Prototype" + color);
material.roughnessFactor = roughness;
material.textureFilteringMode = TextureFilteringMode::Anisotropic;
material.diffuseTexture = resources.loadImageFromFile(
gfxDevice, AssetFS::GetInstance().GetFullPath("engine://kenny/" + color + "/texture_01.png"));
return resources.materials().addMaterial(std::move(material));
};
const auto createBox = [&](const std::string& name, const glm::vec3& position, const glm::vec3& size,
MaterialID materialID, RigidbodyType type)
{
const auto box = scene.CreateGameObject(name);
box->GetTransform().SetWorldPosition(position);
const auto boxMeshComp = box->AddComponent<MeshRendererComponent>();
boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, MakeBoxMesh(size)));
boxMeshComp->SetMaterialID(materialID);
box->AddComponent<BoxCollider>(size * 0.5f);
auto* boxRb = box->AddComponent<Rigidbody>();
boxRb->SetType(type);
boxRb->SetMass(10.0f);
scene.GetPhysics().RegisterGameObject(*box);
return box;
};
// The plane mesh is flat at y = 0, so the floor collider sits right below it
const auto groundObj = scene.CreateGameObject("GroundCollider");
groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.5f, 0.f));
groundObj->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
groundObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
scene.GetPhysics().RegisterGameObject(*groundObj);
// Parkour course, every platform is reachable with a jump from the previous one
const MaterialID platformMaterial = addPrototypeMaterial("Orange", 0.6f);
const MaterialID goalMaterial = addPrototypeMaterial("Green", 0.6f);
const MaterialID wallMaterial = addPrototypeMaterial("Dark", 0.9f);
const MaterialID crateMaterial = addPrototypeMaterial("Purple", 0.35f);
const auto createPlatform = [&](const std::string& name, const glm::vec3& center, float top, const glm::vec3& size,
MaterialID materialID)
{
const float height = top - (-1.0f);
createBox(name, glm::vec3{center.x, top - height * 0.5f, center.z}, glm::vec3{size.x, height, size.z},
materialID, RigidbodyType::Static);
};
createPlatform("Parkour_Step", {0.0f, 0.0f, 4.0f}, -0.5f, {2.0f, 0.0f, 2.0f}, platformMaterial);
createPlatform("Parkour_Block", {0.0f, 0.0f, 7.0f}, 0.4f, {2.0f, 0.0f, 2.0f}, platformMaterial);
createPlatform("Parkour_Pillar1", {3.5f, 0.0f, 8.5f}, 1.2f, {1.5f, 0.0f, 1.5f}, platformMaterial);
createPlatform("Parkour_Pillar2", {6.5f, 0.0f, 7.5f}, 2.0f, {1.2f, 0.0f, 1.2f}, platformMaterial);
createBox("Parkour_Beam", {6.5f, 1.85f, 4.0f}, {0.6f, 0.3f, 5.0f}, platformMaterial, RigidbodyType::Static);
createPlatform("Parkour_Goal", {6.5f, 0.0f, -0.5f}, 2.8f, {2.5f, 0.0f, 2.5f}, goalMaterial);
createBox("Wall", {-5.0f, 0.5f, 0.0f}, {0.5f, 3.0f, 8.0f}, wallMaterial, RigidbodyType::Static);
createBox("LowWall", {-2.5f, -0.6f, -4.0f}, {4.0f, 0.8f, 0.4f}, wallMaterial, RigidbodyType::Static);
for (int i = 0; i < 4; ++i)
{
createBox("Crate" + std::to_string(i),
{-2.5f, -0.6f + 0.81f * static_cast<float>(i), 2.0f},
glm::vec3{0.8f}, crateMaterial, RigidbodyType::Dynamic);
}
const auto helmetObj = scene.CreateGameObject("FlightHelmet");
helmetObj->GetTransform().SetWorldPosition(glm::vec3(2.f, -1.f, 0.f));
helmetObj->GetTransform().SetWorldScale(glm::vec3(2.f));
const auto helmetModel = AssetManager::GetInstance().LoadModel(
"engine://flight-helmet/FlightHelmet.gltf",
staticModelOptions);
ModelDocUtils::LogModelDocSummary(helmetModel, "FlightHelmet.gltf");
std::vector<MaterialID> helmetMaterialIDs;
for (const auto& material : helmetModel.materials)
{
helmetMaterialIDs.push_back(resources.addModelMaterial(gfxDevice, material));
}
for (const auto& primitive : helmetModel.primitives)
{
const auto partObj = scene.CreateGameObject(primitive.nodeName);
partObj->GetTransform().SetParent(&helmetObj->GetTransform(), false);
const auto partMeshComp = partObj->AddComponent<MeshRendererComponent>();
partMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, primitive.mesh));
partMeshComp->SetMaterialID(primitive.materialIndex < helmetMaterialIDs.size()
? helmetMaterialIDs[primitive.materialIndex]
: resources.materials().getPlaceholderMaterialId());
}
const auto CharObj = scene.CreateGameObject("Character");
capybara = CharObj;
ModelDoc::LoadOptions characterModelOptions{};
characterModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
characterModelOptions.loadMaterials = true;
characterModelOptions.loadSkeleton = true;
characterModelOptions.loadAnimations = true;
auto charModel = AssetManager::GetInstance().LoadModel(
"engine://cotw-capybara-male/source/capybara.fbx",
characterModelOptions
);
ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
charModel,
"engine://cotw-capybara-male/source/capybara.fbx"
);
const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
const auto charTexturePath = ModelDocUtils::PickTexturePath(
charModel,
charPrimitive,
AssetFS::GetInstance().GetFullPath(
"engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
);
const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath);
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
const auto charMaterialID = resources.materials().addMaterial({
.baseColor = ModelDocUtils::GetImportedBaseColor(
charModel, charPrimitive),
.diffuseTexture = charTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
charModel, charPrimitive, "CharacterMaterial"),
});
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
charMeshComp->SetMeshID(charMeshID);
charMeshComp->SetMaterialID(charMaterialID);
const auto animator = CharObj->AddComponent<Animator>();
animator->setSkeleton(std::move(charModel.skeleton));
std::string firstAnimationName;
for (auto& clip : charModel.animations)
{
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
if (firstAnimationName.empty())
firstAnimationName = clip.name;
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
}
if (!firstAnimationName.empty())
{
spdlog::info("Playing animation: '{}'", firstAnimationName);
animator->play(firstAnimationName);
}
animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
// ModelDoc::LoadOptions options{};
// options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
//
// auto model = ModelDoc::LoadModel(
// AssetFS::GetInstance()
// .GetFullPath("engine://multiple_object_test.fbx")
// .generic_string(),
// options
// );
//
// ModelDocUtils::LogModelDocSummary(model, "engine://multiple_object_test.fbx");
//
// auto root = std::make_shared<GameObject>("MultiMeshModel");
// root->GetTransform().SetWorldPosition(glm::vec3(0.1f, 0.1f, 0.1f));
// scene.Add(root);
//
// for (std::size_t i = 0; i < model.primitives.size(); ++i) {
// const auto &primitive = model.primitives[i];
//
// const auto meshID = meshCache.addMesh(gfxDevice, primitive.mesh);
//
// const auto texturePath = ModelDocUtils::PickTexturePath(
// model,
// primitive,
// AssetFS::GetInstance().GetFullPath("engine://textures/white.png")
// );
//
// const auto textureID = gfxDevice.loadImageFromFile(texturePath);
//
// const auto materialID = materialCache.addMaterial(gfxDevice, {
// .baseColor = ModelDocUtils::GetImportedBaseColor(
// model, primitive),
// .diffuseTexture = textureID,
// .name = ModelDocUtils::GetImportedMaterialName(
// model,
// primitive,
// primitive.mesh.name + "_Material"
// ),
// });
//
// auto part = std::make_shared<GameObject>(
// primitive.mesh.name.empty()
// ? "Primitive_" + std::to_string(i)
// : primitive.mesh.name
// );
//
// auto meshComp = part->AddComponent<MeshRendererComponent>();
// meshComp->SetMeshID(meshID);
// meshComp->SetMaterialID(materialID);
//
// scene.Add(part);
// }
// {
// const auto CharObj = scene.CreateGameObject("Character");
//
// ModelDoc::LoadOptions characterOptions{};
// characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
// characterOptions.loadMaterials = true;
// characterOptions.loadSkeleton = true;
// characterOptions.loadAnimations = false;
//
// auto charModel = AssetManager::GetInstance().LoadModel("engine://characterMedium.fbx", characterOptions);
//
// const auto& charPrimitive =
// ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
// charModel,
// "engine://characterMedium.fbx"
// );
//
// const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
//
// const auto charTextureID = resources.loadImageFromFile(gfxDevice,
// AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png")
// );
//
// const auto charMaterialID = resources.materials().addMaterial({
// .baseColor = ModelDocUtils::GetImportedBaseColor(
// charModel, charPrimitive),
// .diffuseTexture = charTextureID,
// .name = ModelDocUtils::GetImportedMaterialName(
// charModel,
// charPrimitive,
// "CharacterMaterial"
// ),
// });
//
// const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
// charMeshComp->SetMeshID(charMeshID);
// charMeshComp->SetMaterialID(charMaterialID);
//
// const auto animator = CharObj->AddComponent<Animator>();
// animator->setSkeleton(charModel.skeleton);
//
// auto runClips = AssetManager::GetInstance().LoadAnimationClips(
// "engine://run.fbx",
// charModel.skeleton
// );
//
// for (auto& clip : runClips)
// {
// spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
// animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
// }
//
// if (!runClips.empty())
// {
// animator->play("Root|Run");
// }
//
// CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f));
// CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0));
// }
//
// auto cubeModel = ModelDoc::LoadModel(
// AssetFS::GetInstance()
// .GetFullPath("engine://cube.fbx")
// .generic_string(),
// staticModelOptions
// );
// ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx");
//
// const auto& cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx");
//
//
// const auto eliasTextuerPath = ModelDocUtils::PickTexturePath(
// cubeModel,
// cubePrimitive,
// AssetFS::GetInstance().GetFullPath("game://grass.png")
// );
//
// const auto eliasTextueID = resources.loadImageFromFile(gfxDevice, eliasTextuerPath);
// const auto eliasMaterialID = resources.materials().addMaterial({
// .baseColor = ModelDocUtils::GetImportedBaseColor(
// planeModel, planePrimitive),
// .textureFilteringMode =
// TextureFilteringMode::Anisotropic,
// .diffuseTexture = eliasTextueID,
// .name = ModelDocUtils::GetImportedMaterialName(
// planeModel, planePrimitive, "GroundPlaneMaterial"),
// });
//
// const auto cubeMeshID = resources.meshes().addMesh(gfxDevice, cubePrimitive.mesh);
// //
// // for (int i{0}; i < 100; i++)
// // {
// // auto cube = std::make_shared<GameObject>("Cube");
// //
// // cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
// // cube->AddComponent<Rigidbody>();
// //
// // auto meshComp = cube->AddComponent<MeshRendererComponent>();
// // meshComp->SetMeshID(cubeMeshID);
// // meshComp->SetMaterialID(eliasMaterialID);
// //
// // cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, i, 0.0f));
// // cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
// //
// // scene.Add(cube);
// // scene.GetPhysics().RegisterGameObject(*cube);
// // }
//
// // const int cubeCount = 10;
// // const float spacing = 1.0f;
// //
// // for (int x = 0; x < cubeCount; x++)
// // {
// // for (int y = 0; y < 3; y++)
// // {
// // for (int z = 0; z < cubeCount; z++)
// // {
// // auto cube = std::make_shared<GameObject>("Cube");
// //
// // cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
// // cube->AddComponent<Rigidbody>();
// //
// // auto meshComp = cube->AddComponent<MeshRendererComponent>();
// // meshComp->SetMeshID(cubeMeshID);
// // meshComp->SetMaterialID(eliasMaterialID);
// //
// // cube->GetTransform().SetWorldPosition(glm::vec3(
// // (x - cubeCount / 2.0f) * spacing,
// // y * spacing + 0,
// // (z - cubeCount / 2.0f) * spacing
// // ));
// //
// // cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
// //
// // scene.Add(cube);
// // scene.GetPhysics().RegisterGameObject(*cube);
// // }
// // }
// // }
// {
auto sphereModel = AssetManager::GetInstance().LoadModel("engine://sphere.fbx", staticModelOptions);
ModelDocUtils::LogModelDocSummary(sphereModel, "sphere.fbx");
//
const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(sphereModel, "game://sphere.fbx");
//
//
// const auto sphereTexturePath = ModelDocUtils::PickTexturePath(
// sphereModel,
// spherePrimitive,
// AssetFS::GetInstance().GetFullPath("game://238882.png")
// );
//
// const auto sphereTextureID = resources.loadImageFromFile(gfxDevice, sphereTexturePath);
// sphereMaterial = resources.materials().addMaterial({
// .baseColor = ModelDocUtils::GetImportedBaseColor(planeModel, planePrimitive),
// .textureFilteringMode =TextureFilteringMode::Anisotropic,
// .diffuseTexture = sphereTextureID,
// .name = ModelDocUtils::GetImportedMaterialName(
// planeModel, planePrimitive,
// "GroundPlaneMaterial"),
// });
//
sphereMesh = resources.meshes().addMesh(gfxDevice, spherePrimitive.mesh);
//
// }
sphereMaterial = resources.materials().addSimpleColorMaterial({0.5f, 0.3f, 0.8f}, "Blue");
// Trigger zone: a static box with a TriggerLoggerComponent.
// Spheres spawned via the "Spawn ball" button fall through it.
auto triggerObj = scene.CreateGameObject("TriggerBox");
auto triggerBox = triggerObj->AddComponent<BoxCollider>(glm::vec3{3.0f, 3.0f, 3.0f});
triggerBox->SetTrigger(true);
auto triggerRb = triggerObj->AddComponent<Rigidbody>();
triggerRb->SetType(RigidbodyType::Static);
triggerObj->AddComponent<TriggerLoggerComponent>();
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
// Forward+ test lights: a dim sun and a ring of colored point lights orbiting the origin
// auto sunObj = scene.CreateGameObject("Sun");
// auto* sun = sunObj->AddComponent<LightComponent>(LightType::Directional);
// sun->SetColor(glm::vec3{1.0f, 0.95f, 0.85f});
// sun->SetIntensity(1.0f);
// sun->SetCastShadows(true);
// sunObj->GetTransform().SetWorldRotation(glm::vec3{-50.0f, 30.0f, 0.0f});
//
// // static shadow casters, so the sun's shadows are visible without spawning balls
// for (int i = 0; i < 3; i++)
// {
// auto casterObj = scene.CreateGameObject("ShadowCaster " + std::to_string(i));
// auto* casterMesh = casterObj->AddComponent<MeshRendererComponent>();
// casterMesh->SetMeshID(sphereMesh);
// casterMesh->SetMaterialID(sphereMaterial);
// casterObj->GetTransform().SetWorldPosition(glm::vec3{-4.0f + 4.0f * static_cast<float>(i), 0.5f, 3.0f});
// casterObj->GetTransform().SetWorldScale(glm::vec3{0.015f});
// }
//
// constexpr int pointLightCount = 32;
// constexpr float pointLightRingRadius = 6.0f;
// for (int i = 0; i < pointLightCount; i++)
// {
// const float t = static_cast<float>(i) / static_cast<float>(pointLightCount);
// const float angle = t * glm::two_pi<float>();
//
// auto lightObj = scene.CreateGameObject("PointLight " + std::to_string(i));
// lightObj->GetTransform().SetWorldPosition(glm::vec3{
// std::cos(angle) * pointLightRingRadius,
// -0.25f,
// std::sin(angle) * pointLightRingRadius
// });
//
// auto* light = lightObj->AddComponent<LightComponent>(LightType::Point);
// light->SetColor(glm::vec3{
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * t),
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * (t + 0.33f)),
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * (t + 0.67f))
// });
// light->SetIntensity(1.0f);
// light->SetRange(3.0f);
//
// auto* orbit = lightObj->AddComponent<Rotator>(pointLightRingRadius, 0.5f);
// orbit->SetAxis(glm::vec3{0.0f, 1.0f, 0.0f});
// orbit->SetPivotPosition(glm::vec3{0.0f, -0.25f, 0.0f});
// }
auto playerObj = scene.CreateGameObject("Player");
playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 0.0f, -6.0f));
playerObj->AddComponent<CapsuleCollider>(0.4f, 1.8f);
playerObj->AddComponent<Rigidbody>();
auto* fps = playerObj->AddComponent<FirstPersonController>();
fps->SetYaw(glm::half_pi<float>());
m_Player = playerObj;
m_FPSController = fps;
scene.GetPhysics().RegisterGameObject(*playerObj);
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
texID = resources.loadImageFromFile(gfxDevice, texPath);
}
void LightKeeper::customUpdate(float dt)
@@ -674,44 +66,12 @@ void LightKeeper::customUpdate(float dt)
SceneManager::GetInstance().Update(dt);
SceneManager::GetInstance().LateUpdate(dt);
if (m_params.showDebugUi) {
drawDebugMenuBar();
}
LineRenderingManager::GetInstance().SubmitLine({0, 0, 0}, {0, 10, 0}, {255, 0, 0, 255});
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1))
{
renderer.setRenderWireframe(!renderer.getRenderWireframe());
}
if (m_params.showDebugUi && showTestWindow) {
ImGui::Begin("Test", &showTestWindow);
if (ImGui::Button("SPawn ball"))
{
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
sphere->AddComponent<SphereCollider>(1.5f);
auto rb = sphere->AddComponent<Rigidbody>();
rb->SetMass(1000);
auto meshRenderComp = sphere->AddComponent<MeshRendererComponent>();
meshRenderComp->SetMaterialID(sphereMaterial);
meshRenderComp->SetMeshID(sphereMesh);
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
sphere->GetTransform().SetWorldScale(glm::vec3{0.015f});
SceneManager::GetInstance().GetCurrentScene().GetPhysics().RefreshGameObject(*sphere);
}
ImGui::End();
}
updateDebugUi();
}
bool LightKeeper::isPlayerViewActive() const
{
return m_FPSController != nullptr && m_FPSController->IsMouseCaptured();
return mainScene.playerController != nullptr && mainScene.playerController->IsMouseCaptured();
}
CameraComponent* LightKeeper::getSceneCamera() const
@@ -723,201 +83,6 @@ CameraComponent* LightKeeper::getSceneCamera() const
return activeScenes.front()->GetCurrentCamera();
}
void LightKeeper::customWindowsMenu()
{
ImGui::Separator();
ImGui::MenuItem("Test", nullptr, &showTestWindow);
}
void LightKeeper::drawDebugMenuBar()
{
if (!ImGui::BeginMainMenuBar()) {
return;
}
if (ImGui::BeginMenu("File")) {
ImGui::TextDisabled("Scene");
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("Scene path", scenePath, sizeof(scenePath));
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("New scene name", newSceneName, sizeof(newSceneName));
ImGui::Separator();
if (ImGui::MenuItem("New Empty Scene")) {
createEmptyScene();
}
if (ImGui::MenuItem("Load")) {
loadScene();
}
if (ImGui::MenuItem("Save")) {
saveCurrentScene();
}
if (!sceneStatus.empty()) {
ImGui::Separator();
ImGui::TextWrapped("%s", sceneStatus.c_str());
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Render")) {
ImGui::MenuItem("Box Colliders", nullptr, &renderBoxColliders);
ImGui::MenuItem("2D Quads", nullptr, &renderQuads);
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
bool lightHeatmap = renderer.getRenderLightHeatmap();
if (ImGui::MenuItem("Forward+ Light Heatmap", nullptr, &lightHeatmap)) {
renderer.setRenderLightHeatmap(lightHeatmap);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Camera")) {
ImGui::MenuItem("Free Camera", nullptr, &useFreeCamera);
ImGui::Separator();
auto& scene = SceneManager::GetInstance().GetCurrentScene();
const auto cameras = scene.GetCameras();
if (cameras.empty()) {
ImGui::TextDisabled("No cameras in the scene");
}
for (CameraComponent* sceneCamera : cameras) {
const GameObject* owner = sceneCamera->GetGameObject();
ImGui::PushID(owner);
if (ImGui::MenuItem(owner->GetName().c_str(), nullptr, !useFreeCamera && sceneCamera->IsCurrent())) {
scene.SetCurrentCamera(sceneCamera);
useFreeCamera = false;
}
ImGui::PopID();
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
void LightKeeper::saveCurrentScene()
{
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
if (SceneSerializer::Save(
SceneManager::GetInstance().GetCurrentScene(),
path)) {
sceneStatus = "Saved scene to " + path.string();
} else {
sceneStatus = "Failed to save scene to " + path.string();
}
} catch (const std::exception& exception) {
sceneStatus = std::string{"Save failed: "} + exception.what();
}
}
void LightKeeper::loadScene()
{
try {
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
if (SceneSerializer::Load(
SceneManager::GetInstance().GetCurrentScene(),
path)) {
sceneStatus = "Loaded scene from " + path.string();
} else {
sceneStatus = "Failed to load scene from " + path.string();
}
} catch (const std::exception& exception) {
sceneStatus = std::string{"Load failed: "} + exception.what();
}
}
void LightKeeper::createEmptyScene()
{
try {
const std::string name = newSceneName[0] != '\0'
? newSceneName
: "EmptyScene";
auto& sceneManager = SceneManager::GetInstance();
const int newSceneIndex = sceneManager.GetSceneCount();
sceneManager.CreateScene(name);
sceneManager.SwitchScene(newSceneIndex);
sceneStatus = "Created and switched to scene '" + name + "'";
} catch (const std::exception& exception) {
sceneStatus = std::string{"New scene failed: "} + exception.what();
}
}
void LightKeeper::submitBoxColliderDebugLines()
{
if (!renderBoxColliders) {
return;
}
auto& sceneManager = SceneManager::GetInstance();
if (sceneManager.GetSceneCount() == 0) {
return;
}
Scene& scene = sceneManager.GetCurrentScene();
auto& lineManager = LineRenderingManager::GetInstance();
constexpr std::array<std::array<std::size_t, 2>, 12> edges{{
{{0, 1}}, {{1, 3}}, {{3, 2}}, {{2, 0}},
{{4, 5}}, {{5, 7}}, {{7, 6}}, {{6, 4}},
{{0, 4}}, {{1, 5}}, {{2, 6}}, {{3, 7}},
}};
for (const auto& objectPtr : scene.GetObjects()) {
if (!objectPtr || objectPtr->IsBeingDestroyed() ||
!objectPtr->IsActiveInHierarchy()) {
continue;
}
const auto* boxCollider = objectPtr->GetComponent<BoxCollider>();
if (boxCollider == nullptr) {
continue;
}
// Collider dimensions are already in world units. Physics does not
// apply the GameObject scale when constructing the shape.
const glm::vec3 center = boxCollider->GetCenterOffset();
const glm::vec3 halfExtents = boxCollider->GetHalfExtents();
const std::array<glm::vec3, 8> localCorners{
center + glm::vec3{-halfExtents.x, -halfExtents.y, -halfExtents.z},
center + glm::vec3{ halfExtents.x, -halfExtents.y, -halfExtents.z},
center + glm::vec3{-halfExtents.x, halfExtents.y, -halfExtents.z},
center + glm::vec3{ halfExtents.x, halfExtents.y, -halfExtents.z},
center + glm::vec3{-halfExtents.x, -halfExtents.y, halfExtents.z},
center + glm::vec3{ halfExtents.x, -halfExtents.y, halfExtents.z},
center + glm::vec3{-halfExtents.x, halfExtents.y, halfExtents.z},
center + glm::vec3{ halfExtents.x, halfExtents.y, halfExtents.z},
};
Transform& transform = objectPtr->GetTransform();
const glm::vec3 worldPosition = transform.GetWorldPosition();
const glm::quat worldRotation = transform.GetWorldRotation();
std::array<glm::vec3, 8> worldCorners{};
for (std::size_t index = 0; index < localCorners.size(); ++index) {
worldCorners[index] = worldPosition + worldRotation * localCorners[index];
}
const glm::vec4 color = boxCollider->IsTrigger()
? glm::vec4{1.0f, 0.75f, 0.1f, 1.0f}
: glm::vec4{0.1f, 0.8f, 1.0f, 1.0f};
for (const auto& edge : edges) {
lineManager.SubmitLine(
worldCorners[edge[0]],
worldCorners[edge[1]],
color);
}
}
}
void LightKeeper::customDraw()
{
const auto cmd = gfxDevice.beginFrame();
@@ -926,17 +91,13 @@ void LightKeeper::customDraw()
}
renderer.beginDrawing(gfxDevice);
submitBoxColliderDebugLines();
QuadRenderingManager::GetInstance().SubmitQuad(
glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f,
glm::vec4{1.0f}, texID);
submitDebugDraws();
// The player camera is placed here, after the fixed steps, so it can be interpolated between them
if (isPlayerViewActive())
{
camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch());
camera.m_position = m_FPSController->GetEyePosition();
camera.SetRotation(mainScene.playerController->GetYaw(), mainScene.playerController->GetPitch());
camera.m_position = mainScene.playerController->GetEyePosition();
camera.CalculateViewMatrix();
camera.CalculateProjectionMatrix();
}
@@ -977,10 +138,6 @@ void LightKeeper::customDraw()
void LightKeeper::customCleanup()
{
// auto device = gfxDevice.getDevice().device;
// vkDeviceWaitIdle(device);
gfxDevice.waitIdle();
SceneManager::GetInstance().Destroy();