feat: add debug rendering for other types of colliders / split game into more logical functions / files
This commit is contained in:
@@ -51,6 +51,7 @@ set(SRC_FILES
|
||||
|
||||
"src/Graphics/BindlessSetManager.cpp"
|
||||
"src/Graphics/Camera.cpp"
|
||||
"src/Graphics/DebugDraw.cpp"
|
||||
"src/Graphics/ComputePipeline.cpp"
|
||||
"src/Graphics/Frustum.cpp"
|
||||
"src/Graphics/GfxDevice.cpp"
|
||||
@@ -77,6 +78,7 @@ set(SRC_FILES
|
||||
"src/Graphics/Resources/GPUImage.cpp"
|
||||
"src/Graphics/Resources/NBuffer.cpp"
|
||||
"src/Graphics/Resources/Cubemap.cpp"
|
||||
"src/Graphics/Resources/PrimitiveMeshes.cpp"
|
||||
|
||||
"src/Graphics/Pipelines/MeshPipeline.cpp"
|
||||
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
||||
@@ -96,6 +98,7 @@ set(SRC_FILES
|
||||
|
||||
"src/Scene/Scene.cpp"
|
||||
"src/Scene/SceneManager.cpp"
|
||||
"src/Scene/ModelSpawner.cpp"
|
||||
|
||||
"src/FS/AssetFS.cpp"
|
||||
"src/FS/Manifest.cpp"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Blender 4.0.2 MTL File: 'None'
|
||||
# www.blender.org
|
||||
|
||||
newmtl place_holder
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.500000
|
||||
d 1.000000
|
||||
illum 2
|
||||
Ps 0.000000
|
||||
Pc 0.000000
|
||||
Pcr 0.030000
|
||||
aniso 0.000000
|
||||
anisor 0.000000
|
||||
map_Kd texture_diffuse.png
|
||||
map_Bump -bm 1.000000 texture_normal.png
|
||||
map_Pr texture_roughness.png
|
||||
map_Pm texture_metallic.png
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 706 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 526 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -21,6 +21,8 @@ public:
|
||||
}
|
||||
|
||||
|
||||
void DrawDebugLines(const glm::vec4& color) const override;
|
||||
|
||||
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
|
||||
return PhysicsShapeDesc::Box(m_HalfExtents, m_CenterOffset, m_IsTrigger);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void DrawDebugLines(const glm::vec4& color) const override;
|
||||
|
||||
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
|
||||
return PhysicsShapeDesc::Capsule(m_Radius, m_Height, m_CenterOffset, m_IsTrigger);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <destrum/ObjectModel/Component.h>
|
||||
#include <destrum/Physics/PhysicsTypes.h>
|
||||
@@ -15,11 +16,14 @@ public:
|
||||
void ImGuiInspector() override;
|
||||
|
||||
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
|
||||
virtual void DrawDebugLines(const glm::vec4& color) const = 0;
|
||||
|
||||
[[nodiscard]] bool IsTrigger() const { return m_IsTrigger; }
|
||||
void SetTrigger(bool trigger) { m_IsTrigger = trigger; NotifyPhysicsChanged(); }
|
||||
|
||||
[[nodiscard]] const glm::vec3& GetCenterOffset() const { return m_CenterOffset; }
|
||||
[[nodiscard]] glm::vec3 GetWorldCenter() const;
|
||||
[[nodiscard]] glm::quat GetWorldRotation() const;
|
||||
void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; NotifyPhysicsChanged(); }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
return "SphereCollider";
|
||||
}
|
||||
|
||||
void DrawDebugLines(const glm::vec4& color) const override;
|
||||
|
||||
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
|
||||
return PhysicsShapeDesc::Sphere(m_Radius, m_CenterOffset, m_IsTrigger);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef DEBUGDRAW_H
|
||||
#define DEBUGDRAW_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
namespace DebugDraw {
|
||||
void Arc(const glm::vec3& center, const glm::vec3& axisA, const glm::vec3& axisB, float radius,
|
||||
float fromAngle, float toAngle, const glm::vec4& color, int segments = 16);
|
||||
void Circle(const glm::vec3& center, const glm::vec3& axisA, const glm::vec3& axisB, float radius,
|
||||
const glm::vec4& color, int segments = 32);
|
||||
|
||||
void Box(const glm::vec3& center, const glm::quat& rotation, const glm::vec3& halfExtents, const glm::vec4& color);
|
||||
void Sphere(const glm::vec3& center, float radius, const glm::vec4& color);
|
||||
|
||||
void Capsule(const glm::vec3& center, const glm::quat& rotation, float radius, float height, const glm::vec4& color);
|
||||
}
|
||||
|
||||
#endif // DEBUGDRAW_H
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef PRIMITIVEMESHES_H
|
||||
#define PRIMITIVEMESHES_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <destrum/Graphics/Resources/CPUMesh.h>
|
||||
|
||||
namespace PrimitiveMeshes {
|
||||
[[nodiscard]] CPUMesh MakeBox(const glm::vec3& size);
|
||||
}
|
||||
|
||||
#endif // PRIMITIVEMESHES_H
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef MODELSPAWNER_H
|
||||
#define MODELSPAWNER_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class GameObject;
|
||||
class GfxDevice;
|
||||
class RenderResources;
|
||||
class Scene;
|
||||
|
||||
namespace ModelDoc {
|
||||
struct LoadOptions;
|
||||
}
|
||||
|
||||
namespace ModelSpawner {
|
||||
// Loads a model and creates a root GameObject with one child per mesh primitive, using the model's PBR materials
|
||||
GameObject* SpawnStaticModel(
|
||||
Scene& scene,
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
std::string_view reference,
|
||||
const ModelDoc::LoadOptions& options,
|
||||
const std::string& name);
|
||||
}
|
||||
|
||||
#endif // MODELSPAWNER_H
|
||||
@@ -1,9 +1,14 @@
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
|
||||
#include <destrum/Graphics/DebugDraw.h>
|
||||
#include <destrum/Util/ImGuiUtils.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
void BoxCollider::DrawDebugLines(const glm::vec4& color) const {
|
||||
DebugDraw::Box(GetWorldCenter(), GetWorldRotation(), m_HalfExtents, color);
|
||||
}
|
||||
|
||||
nlohmann::json BoxCollider::Serialize() const {
|
||||
auto data = SerializeCollider();
|
||||
data["halfExtents"] = {m_HalfExtents.x, m_HalfExtents.y, m_HalfExtents.z};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
#include <destrum/Components/Physics/CapsuleCollider.h>
|
||||
|
||||
#include <destrum/Graphics/DebugDraw.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
void CapsuleCollider::DrawDebugLines(const glm::vec4& color) const {
|
||||
DebugDraw::Capsule(GetWorldCenter(), GetWorldRotation(), m_Radius, m_Height, color);
|
||||
}
|
||||
|
||||
void CapsuleCollider::ImGuiInspector() {
|
||||
Collider::ImGuiInspector();
|
||||
|
||||
|
||||
@@ -2,8 +2,18 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Util/ImGuiUtils.h>
|
||||
|
||||
glm::vec3 Collider::GetWorldCenter() const {
|
||||
Transform& transform = GetTransform();
|
||||
return transform.GetWorldPosition() + transform.GetWorldRotation() * m_CenterOffset;
|
||||
}
|
||||
|
||||
glm::quat Collider::GetWorldRotation() const {
|
||||
return GetTransform().GetWorldRotation();
|
||||
}
|
||||
|
||||
void Collider::ImGuiInspector() {
|
||||
bool isTrigger = m_IsTrigger;
|
||||
if (ImGui::Checkbox("Is trigger", &isTrigger)) {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
|
||||
#include <destrum/Graphics/DebugDraw.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
void SphereCollider::DrawDebugLines(const glm::vec4& color) const {
|
||||
DebugDraw::Sphere(GetWorldCenter(), m_Radius, color);
|
||||
}
|
||||
|
||||
nlohmann::json SphereCollider::Serialize() const {
|
||||
auto data = SerializeCollider();
|
||||
data["radius"] = m_Radius;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <destrum/Graphics/DebugDraw.h>
|
||||
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
void DebugDraw::Arc(const glm::vec3& center, const glm::vec3& axisA, const glm::vec3& axisB, float radius,
|
||||
float fromAngle, float toAngle, const glm::vec4& color, int segments)
|
||||
{
|
||||
auto& lines = LineRenderingManager::GetInstance();
|
||||
segments = std::max(segments, 1);
|
||||
|
||||
const auto pointAt = [&](float angle) {
|
||||
return center + (axisA * std::cos(angle) + axisB * std::sin(angle)) * radius;
|
||||
};
|
||||
|
||||
glm::vec3 previous = pointAt(fromAngle);
|
||||
for (int i = 1; i <= segments; ++i) {
|
||||
const float t = static_cast<float>(i) / static_cast<float>(segments);
|
||||
const glm::vec3 next = pointAt(fromAngle + (toAngle - fromAngle) * t);
|
||||
lines.SubmitLine(previous, next, color);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDraw::Circle(const glm::vec3& center, const glm::vec3& axisA, const glm::vec3& axisB, float radius,
|
||||
const glm::vec4& color, int segments)
|
||||
{
|
||||
Arc(center, axisA, axisB, radius, 0.0f, glm::two_pi<float>(), color, segments);
|
||||
}
|
||||
|
||||
void DebugDraw::Box(const glm::vec3& center, const glm::quat& rotation, const glm::vec3& halfExtents,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
// Corner i has +x when bit 0 is set, +y for bit 1 and +z for bit 2
|
||||
std::array<glm::vec3, 8> corners{};
|
||||
for (std::size_t i = 0; i < corners.size(); ++i) {
|
||||
const glm::vec3 sign{
|
||||
(i & 1) != 0 ? 1.0f : -1.0f,
|
||||
(i & 2) != 0 ? 1.0f : -1.0f,
|
||||
(i & 4) != 0 ? 1.0f : -1.0f,
|
||||
};
|
||||
corners[i] = center + rotation * (sign * halfExtents);
|
||||
}
|
||||
|
||||
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}},
|
||||
}};
|
||||
|
||||
auto& lines = LineRenderingManager::GetInstance();
|
||||
for (const auto& edge : edges) {
|
||||
lines.SubmitLine(corners[edge[0]], corners[edge[1]], color);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDraw::Sphere(const glm::vec3& center, float radius, const glm::vec4& color)
|
||||
{
|
||||
const glm::vec3 x{1.0f, 0.0f, 0.0f};
|
||||
const glm::vec3 y{0.0f, 1.0f, 0.0f};
|
||||
const glm::vec3 z{0.0f, 0.0f, 1.0f};
|
||||
Circle(center, x, z, radius, color);
|
||||
Circle(center, x, y, radius, color);
|
||||
Circle(center, z, y, radius, color);
|
||||
}
|
||||
|
||||
void DebugDraw::Capsule(const glm::vec3& center, const glm::quat& rotation, float radius, float height,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
const glm::vec3 right = rotation * glm::vec3{1.0f, 0.0f, 0.0f};
|
||||
const glm::vec3 up = rotation * glm::vec3{0.0f, 1.0f, 0.0f};
|
||||
const glm::vec3 forward = rotation * glm::vec3{0.0f, 0.0f, 1.0f};
|
||||
|
||||
const float halfCylinder = std::max(height * 0.5f - radius, 0.0f);
|
||||
const glm::vec3 top = center + up * halfCylinder;
|
||||
const glm::vec3 bottom = center - up * halfCylinder;
|
||||
|
||||
Circle(top, right, forward, radius, color);
|
||||
Circle(bottom, right, forward, radius, color);
|
||||
|
||||
auto& lines = LineRenderingManager::GetInstance();
|
||||
for (const glm::vec3& side : {right, -right, forward, -forward}) {
|
||||
lines.SubmitLine(top + side * radius, bottom + side * radius, color);
|
||||
}
|
||||
|
||||
constexpr float pi = glm::pi<float>();
|
||||
Arc(top, right, up, radius, 0.0f, pi, color);
|
||||
Arc(top, forward, up, radius, 0.0f, pi, color);
|
||||
Arc(bottom, right, up, radius, pi, glm::two_pi<float>(), color);
|
||||
Arc(bottom, forward, up, radius, pi, glm::two_pi<float>(), color);
|
||||
}
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
@@ -263,7 +266,12 @@ MaterialID RenderResources::addModelMaterial(
|
||||
material.emissiveColor = sourceMaterial.emissiveColor;
|
||||
|
||||
material.diffuseTexture = loadTexture({Semantic::BaseColor, Semantic::Diffuse}, TextureIntent::ColorSrgb);
|
||||
material.normalTexture = loadTexture({Semantic::Normal}, TextureIntent::DataLinear);
|
||||
// Blender's OBJ exporter writes normal maps as map_Bump, which assimp imports as a height map
|
||||
std::string extension = std::filesystem::path{sourceMaterial.assetReference.path}.extension().string();
|
||||
std::ranges::transform(extension, extension.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
material.normalTexture = extension == ".obj"
|
||||
? loadTexture({Semantic::Normal, Semantic::Height}, TextureIntent::DataLinear)
|
||||
: loadTexture({Semantic::Normal}, TextureIntent::DataLinear);
|
||||
material.metallicTexture = loadTexture({Semantic::MetallicRoughness, Semantic::Metallic}, TextureIntent::DataLinear);
|
||||
material.roughnessTexture = loadTexture({Semantic::MetallicRoughness, Semantic::Roughness}, TextureIntent::DataLinear);
|
||||
material.occlusionTexture = loadTexture({Semantic::AmbientOcclusion}, TextureIntent::DataLinear);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
CPUMesh PrimitiveMeshes::MakeBox(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;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
||||
@@ -440,13 +440,13 @@ public:
|
||||
{
|
||||
ShapeRefC shape = CreateJoltShape(desc.shape);
|
||||
|
||||
// Jolt's offset shape stores the offset from the shape to the center
|
||||
// of mass, so negate the component's center offset. This keeps the
|
||||
// body transform aligned with the owning GameObject.
|
||||
// Move the shape itself relative to the body origin, so the body transform stays aligned with
|
||||
// the owning GameObject (an offset center of mass shape would only move the center of mass)
|
||||
if (glm::length2(desc.shape.centerOffset) > 0.0000001f)
|
||||
{
|
||||
OffsetCenterOfMassShapeSettings offsetSettings(
|
||||
ToJoltVec3(-desc.shape.centerOffset),
|
||||
RotatedTranslatedShapeSettings offsetSettings(
|
||||
ToJoltVec3(desc.shape.centerOffset),
|
||||
Quat::sIdentity(),
|
||||
shape.GetPtr());
|
||||
ShapeSettings::ShapeResult result = offsetSettings.Create();
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <destrum/Scene/ModelSpawner.h>
|
||||
|
||||
#include <destrum/Assets/AssetManager.h>
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/ModelDocUtils.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
GameObject* ModelSpawner::SpawnStaticModel(
|
||||
Scene& scene,
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
std::string_view reference,
|
||||
const ModelDoc::LoadOptions& options,
|
||||
const std::string& name)
|
||||
{
|
||||
const auto model = AssetManager::GetInstance().LoadModel(reference, options);
|
||||
ModelDocUtils::LogModelDocSummary(model, name);
|
||||
|
||||
std::vector<MaterialID> materialIDs;
|
||||
for (const auto& material : model.materials) {
|
||||
materialIDs.push_back(resources.addModelMaterial(gfxDevice, material));
|
||||
}
|
||||
|
||||
GameObject* root = scene.CreateGameObject(name);
|
||||
for (const auto& primitive : model.primitives) {
|
||||
GameObject* part = scene.CreateGameObject(primitive.nodeName.empty() ? primitive.mesh.name : primitive.nodeName);
|
||||
part->GetTransform().SetParent(&root->GetTransform(), false);
|
||||
|
||||
auto* meshRenderer = part->AddComponent<MeshRendererComponent>();
|
||||
meshRenderer->SetMeshID(resources.meshes().addMesh(gfxDevice, primitive.mesh));
|
||||
meshRenderer->SetMaterialID(primitive.materialIndex < materialIDs.size()
|
||||
? materialIDs[primitive.materialIndex]
|
||||
: resources.materials().getPlaceholderMaterialId());
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -6,6 +6,10 @@ set(GAME_SRC
|
||||
src/main.cpp
|
||||
|
||||
src/Lightkeeper.cpp
|
||||
src/LightkeeperDebugUi.cpp
|
||||
|
||||
src/scenes/MainScene.cpp
|
||||
src/scenes/Experiments.cpp
|
||||
|
||||
src/components/GameComponentRegistry.cpp
|
||||
src/components/FirstPersonController.cpp
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "destrum/Graphics/Resources/Cubemap.h"
|
||||
#include "destrum/ObjectModel/GameObject.h"
|
||||
|
||||
#include <scenes/MainScene.h>
|
||||
|
||||
class CameraComponent;
|
||||
class FirstPersonController;
|
||||
class LightKeeper final : public App {
|
||||
@@ -26,31 +28,33 @@ public:
|
||||
void customWindowsMenu() override;
|
||||
|
||||
private:
|
||||
void drawDebugMenuBar();
|
||||
void saveCurrentScene();
|
||||
void loadScene();
|
||||
void createEmptyScene();
|
||||
void submitBoxColliderDebugLines();
|
||||
void setupSkybox();
|
||||
[[nodiscard]] CameraComponent* getSceneCamera() const;
|
||||
[[nodiscard]] bool isPlayerViewActive() const;
|
||||
|
||||
// LightkeeperDebugUi.cpp
|
||||
void updateDebugUi();
|
||||
void drawDebugMenuBar();
|
||||
void drawTestWindow();
|
||||
void spawnBall();
|
||||
void saveCurrentScene();
|
||||
void loadScene();
|
||||
void createEmptyScene();
|
||||
void findPlayer();
|
||||
void submitDebugDraws();
|
||||
void submitColliderDebugLines();
|
||||
|
||||
Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)};
|
||||
|
||||
MeshID sphereMesh;
|
||||
MaterialID sphereMaterial;
|
||||
|
||||
ImageID texID;
|
||||
|
||||
std::unique_ptr<CubeMap> skyboxCubemap;
|
||||
MainSceneObjects mainScene{};
|
||||
|
||||
GameObject* capybara = nullptr;
|
||||
GameObject* m_Player = nullptr;
|
||||
FirstPersonController* m_FPSController = nullptr;
|
||||
ImageID debugQuadTexture{NULL_IMAGE_ID};
|
||||
|
||||
char scenePath[512]{"scenes/debug_scene.json"};
|
||||
char newSceneName[128]{"EmptyScene"};
|
||||
std::string sceneStatus;
|
||||
bool renderBoxColliders{false};
|
||||
bool renderColliders{false};
|
||||
bool renderQuads{true};
|
||||
bool renderDebugLines{true};
|
||||
bool showTestWindow{true};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef LIGHTKEEPER_MAINSCENE_H
|
||||
#define LIGHTKEEPER_MAINSCENE_H
|
||||
|
||||
#include <destrum/Graphics/ids.h>
|
||||
|
||||
class FirstPersonController;
|
||||
class GameObject;
|
||||
class GfxDevice;
|
||||
class RenderResources;
|
||||
class Scene;
|
||||
|
||||
// What the app keeps a hold of after building the scene
|
||||
struct MainSceneObjects {
|
||||
GameObject* player{nullptr};
|
||||
FirstPersonController* playerController{nullptr};
|
||||
|
||||
// Used by the debug "Spawn ball" button
|
||||
MeshID sphereMesh{};
|
||||
MaterialID sphereMaterial{};
|
||||
};
|
||||
|
||||
// Sun, ground, parkour course, models, trigger zone and the player
|
||||
MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources);
|
||||
|
||||
#endif // LIGHTKEEPER_MAINSCENE_H
|
||||
+18
-861
@@ -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();
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
#include "Lightkeeper.h"
|
||||
|
||||
#include <destrum/Components/CameraComponent.h>
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Components/Physics/SphereCollider.h>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
#include <destrum/Input/InputManager.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <components/FirstPersonController.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "imgui.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;
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::updateDebugUi()
|
||||
{
|
||||
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1))
|
||||
{
|
||||
renderer.setRenderWireframe(!renderer.getRenderWireframe());
|
||||
}
|
||||
|
||||
if (!m_params.showDebugUi) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawDebugMenuBar();
|
||||
if (showTestWindow) {
|
||||
drawTestWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::customWindowsMenu()
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Test", nullptr, &showTestWindow);
|
||||
}
|
||||
|
||||
void LightKeeper::drawTestWindow()
|
||||
{
|
||||
ImGui::Begin("Test", &showTestWindow);
|
||||
if (ImGui::Button("Spawn ball"))
|
||||
{
|
||||
spawnBall();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LightKeeper::spawnBall()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().GetCurrentScene();
|
||||
|
||||
GameObject* sphere = scene.CreateGameObject("Sphere");
|
||||
sphere->AddComponent<SphereCollider>(1.5f);
|
||||
sphere->AddComponent<Rigidbody>()->SetMass(1000);
|
||||
|
||||
auto* meshRenderComp = sphere->AddComponent<MeshRendererComponent>();
|
||||
meshRenderComp->SetMaterialID(mainScene.sphereMaterial);
|
||||
meshRenderComp->SetMeshID(mainScene.sphereMesh);
|
||||
|
||||
sphere->GetTransform().SetWorldPosition((std::rand() % 2) - 0.5f, 100, (std::rand() % 2) - 0.5f);
|
||||
sphere->GetTransform().SetWorldScale(glm::vec3{0.015f});
|
||||
|
||||
scene.GetPhysics().RefreshGameObject(*sphere);
|
||||
}
|
||||
|
||||
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("Colliders", nullptr, &renderColliders);
|
||||
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();
|
||||
}
|
||||
findPlayer();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
findPlayer();
|
||||
}
|
||||
|
||||
// Loading or switching scenes replaces the player, so look it up again instead of keeping a dangling pointer
|
||||
void LightKeeper::findPlayer()
|
||||
{
|
||||
mainScene.player = nullptr;
|
||||
mainScene.playerController = nullptr;
|
||||
|
||||
auto& sceneManager = SceneManager::GetInstance();
|
||||
if (sceneManager.GetActiveScenes().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& object : sceneManager.GetCurrentScene().GetObjects()) {
|
||||
if (object == nullptr || object->IsBeingDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
if (auto* controller = object->GetComponent<FirstPersonController>()) {
|
||||
mainScene.player = object.get();
|
||||
mainScene.playerController = controller;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::submitDebugDraws()
|
||||
{
|
||||
submitColliderDebugLines();
|
||||
|
||||
if (renderDebugLines) {
|
||||
LineRenderingManager::GetInstance().SubmitLine({0, 0, 0}, {0, 10, 0}, {255, 0, 0, 255});
|
||||
}
|
||||
|
||||
if (renderQuads && debugQuadTexture != NULL_IMAGE_ID) {
|
||||
QuadRenderingManager::GetInstance().SubmitQuad(
|
||||
glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f,
|
||||
glm::vec4{1.0f}, debugQuadTexture);
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers orange, then colored by body type: static blue, dynamic green, kinematic purple
|
||||
void LightKeeper::submitColliderDebugLines()
|
||||
{
|
||||
if (!renderColliders) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& sceneManager = SceneManager::GetInstance();
|
||||
if (sceneManager.GetActiveScenes().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& object : sceneManager.GetCurrentScene().GetObjects()) {
|
||||
if (object == nullptr || object->IsBeingDestroyed() || !object->IsActiveInHierarchy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* rigidbody = object->GetComponent<Rigidbody>();
|
||||
const RigidbodyType type = rigidbody != nullptr ? rigidbody->GetType() : RigidbodyType::Static;
|
||||
|
||||
for (const auto& component : object->GetComponents()) {
|
||||
const auto* collider = dynamic_cast<const Collider*>(component.get());
|
||||
if (collider == nullptr || collider->IsBeingDestroyed() || !collider->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glm::vec4 color{0.1f, 0.8f, 1.0f, 1.0f};
|
||||
if (collider->IsTrigger()) {
|
||||
color = {1.0f, 0.75f, 0.1f, 1.0f};
|
||||
} else if (type == RigidbodyType::Dynamic) {
|
||||
color = {0.3f, 1.0f, 0.3f, 1.0f};
|
||||
} else if (type == RigidbodyType::Kinematic) {
|
||||
color = {0.8f, 0.4f, 1.0f, 1.0f};
|
||||
}
|
||||
collider->DrawDebugLines(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
// Old scene setup experiments, moved here from LightKeeper::customInit as reference.
|
||||
// They were written against older APIs (scene.Add, gfxDevice.loadImageFromFile, ...) and the locals
|
||||
// of customInit (planeModel, staticModelOptions, sphereMesh, ...), so they don't compile as is.
|
||||
#if 0
|
||||
|
||||
// ---- Skybox images that were tried --------------------------------------------------------------
|
||||
|
||||
// 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().GetFullPath("engine://textures/test-skybox.png");
|
||||
|
||||
// ---- Capybara texture -----------------------------------------------------------------------------
|
||||
|
||||
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
||||
|
||||
// ---- Multi mesh model, one GameObject per primitive ----------------------------------------------
|
||||
|
||||
// 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);
|
||||
// }
|
||||
|
||||
// ---- characterMedium with run.fbx animations -----------------------------------------------------
|
||||
|
||||
// {
|
||||
// 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));
|
||||
// }
|
||||
|
||||
// ---- Physics cube stacks -------------------------------------------------------------------------
|
||||
|
||||
// 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);
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// ---- Textured sphere material --------------------------------------------------------------------
|
||||
|
||||
// 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"),
|
||||
// });
|
||||
|
||||
// ---- 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});
|
||||
// }
|
||||
|
||||
// ---- customCleanup ---------------------------------------------------------------------------------
|
||||
|
||||
// auto device = gfxDevice.getDevice().device;
|
||||
|
||||
// vkDeviceWaitIdle(device);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,289 @@
|
||||
#include <scenes/MainScene.h>
|
||||
|
||||
#include <destrum/Assets/AssetManager.h>
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Components/LightComponent.h>
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/Components/Physics/BoxCollider.h>
|
||||
#include <destrum/Components/Physics/CapsuleCollider.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/Scene/ModelSpawner.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Util/ModelDocUtils.h>
|
||||
|
||||
#include <components/FirstPersonController.h>
|
||||
#include <components/TriggerLoggerComponent.h>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
// The visible ground plane is at y = -1
|
||||
constexpr float GroundHeight = -1.0f;
|
||||
|
||||
ModelDoc::LoadOptions StaticModelOptions()
|
||||
{
|
||||
ModelDoc::LoadOptions options{};
|
||||
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||
options.loadMaterials = true;
|
||||
options.loadSkeleton = false;
|
||||
options.loadAnimations = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
void CreateSun(Scene& scene)
|
||||
{
|
||||
// Directional lights shine down their local -Z: tilt it 50 degrees down, then turn it around the up axis
|
||||
GameObject* 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);
|
||||
}
|
||||
|
||||
void CreateGround(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
const 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 planeTexturePath = ModelDocUtils::PickTexturePath(
|
||||
planeModel,
|
||||
planePrimitive,
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://grass.jpg"));
|
||||
|
||||
const MaterialID planeMaterialID = resources.materials().addMaterial({
|
||||
.baseColor = ModelDocUtils::GetImportedBaseColor(planeModel, planePrimitive),
|
||||
.textureFilteringMode = TextureFilteringMode::Nearest,
|
||||
.diffuseTexture = resources.loadImageFromFile(gfxDevice, planeTexturePath),
|
||||
.name = ModelDocUtils::GetImportedMaterialName(planeModel, planePrimitive, "GroundPlaneMaterial"),
|
||||
});
|
||||
|
||||
GameObject* planeObj = scene.CreateGameObject("GroundPlane");
|
||||
auto* planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
|
||||
planeMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, planePrimitive.mesh));
|
||||
planeMeshComp->SetMaterialID(planeMaterialID);
|
||||
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight, 0.f));
|
||||
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
|
||||
|
||||
// The plane mesh is flat, so the floor collider sits right below it
|
||||
GameObject* groundObj = scene.CreateGameObject("GroundCollider");
|
||||
groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight - 0.5f, 0.f));
|
||||
groundObj->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
|
||||
groundObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||
scene.GetPhysics().RegisterGameObject(*groundObj);
|
||||
}
|
||||
|
||||
// Kenney prototype textures, texture_01 is a 1 x 1 meter grid
|
||||
MaterialID AddPrototypeMaterial(GfxDevice& gfxDevice, RenderResources& resources,
|
||||
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));
|
||||
}
|
||||
|
||||
GameObject* CreateBox(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources,
|
||||
const std::string& name, const glm::vec3& position, const glm::vec3& size,
|
||||
MaterialID materialID, RigidbodyType type)
|
||||
{
|
||||
GameObject* box = scene.CreateGameObject(name);
|
||||
box->GetTransform().SetWorldPosition(position);
|
||||
|
||||
auto* boxMeshComp = box->AddComponent<MeshRendererComponent>();
|
||||
boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, PrimitiveMeshes::MakeBox(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;
|
||||
}
|
||||
|
||||
// Every platform is reachable with a jump from the previous one
|
||||
void CreateParkourCourse(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
const MaterialID platformMaterial = AddPrototypeMaterial(gfxDevice, resources, "Orange", 0.6f);
|
||||
const MaterialID goalMaterial = AddPrototypeMaterial(gfxDevice, resources, "Green", 0.6f);
|
||||
const MaterialID wallMaterial = AddPrototypeMaterial(gfxDevice, resources, "Dark", 0.9f);
|
||||
const MaterialID crateMaterial = AddPrototypeMaterial(gfxDevice, resources, "Purple", 0.35f);
|
||||
|
||||
// Platforms stand on the ground and are given by their top height
|
||||
const auto createPlatform = [&](const std::string& name, const glm::vec2& center, float top,
|
||||
const glm::vec2& footprint, MaterialID materialID)
|
||||
{
|
||||
const float height = top - GroundHeight;
|
||||
CreateBox(scene, gfxDevice, resources, name,
|
||||
glm::vec3{center.x, top - height * 0.5f, center.y},
|
||||
glm::vec3{footprint.x, height, footprint.y},
|
||||
materialID, RigidbodyType::Static);
|
||||
};
|
||||
createPlatform("Parkour_Step", {0.0f, 4.0f}, -0.5f, {2.0f, 2.0f}, platformMaterial);
|
||||
createPlatform("Parkour_Block", {0.0f, 7.0f}, 0.4f, {2.0f, 2.0f}, platformMaterial);
|
||||
createPlatform("Parkour_Pillar1", {3.5f, 8.5f}, 1.2f, {1.5f, 1.5f}, platformMaterial);
|
||||
createPlatform("Parkour_Pillar2", {6.5f, 7.5f}, 2.0f, {1.2f, 1.2f}, platformMaterial);
|
||||
CreateBox(scene, gfxDevice, resources, "Parkour_Beam", {6.5f, 1.85f, 4.0f}, {0.6f, 0.3f, 5.0f},
|
||||
platformMaterial, RigidbodyType::Static);
|
||||
createPlatform("Parkour_Goal", {6.5f, -0.5f}, 2.8f, {2.5f, 2.5f}, goalMaterial);
|
||||
|
||||
CreateBox(scene, gfxDevice, resources, "Wall", {-5.0f, 0.5f, 0.0f}, {0.5f, 3.0f, 8.0f},
|
||||
wallMaterial, RigidbodyType::Static);
|
||||
CreateBox(scene, gfxDevice, resources, "LowWall", {-2.5f, -0.6f, -4.0f}, {4.0f, 0.8f, 0.4f},
|
||||
wallMaterial, RigidbodyType::Static);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
CreateBox(scene, gfxDevice, resources, "Crate" + std::to_string(i),
|
||||
{-2.5f, -0.6f + 0.81f * static_cast<float>(i), 2.0f},
|
||||
glm::vec3{0.8f}, crateMaterial, RigidbodyType::Dynamic);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
GameObject* helmetObj = ModelSpawner::SpawnStaticModel(
|
||||
scene, gfxDevice, resources,
|
||||
"engine://flight-helmet/FlightHelmet.gltf",
|
||||
StaticModelOptions(),
|
||||
"FlightHelmet");
|
||||
helmetObj->GetTransform().SetWorldPosition(glm::vec3(2.f, GroundHeight, 0.f));
|
||||
helmetObj->GetTransform().SetWorldScale(glm::vec3(2.f));
|
||||
}
|
||||
|
||||
void CreateTree(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
constexpr float scale = 3.0f;
|
||||
GameObject* treeObj = ModelSpawner::SpawnStaticModel(
|
||||
scene, gfxDevice, resources,
|
||||
"engine://tree/base.obj",
|
||||
StaticModelOptions(),
|
||||
"Tree");
|
||||
treeObj->GetTransform().SetWorldPosition(glm::vec3(-7.f, GroundHeight + scale, 7.f));
|
||||
treeObj->GetTransform().SetWorldScale(glm::vec3(scale));
|
||||
|
||||
// The trunk is about 0.14 units thick and runs up through the foliage, colliders are in world units
|
||||
treeObj->AddComponent<CapsuleCollider>(0.15f * scale, 2.0f * scale);
|
||||
treeObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||
scene.GetPhysics().RegisterGameObject(*treeObj);
|
||||
}
|
||||
|
||||
void CreateCapybara(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
constexpr auto modelReference = "engine://cotw-capybara-male/source/capybara.fbx";
|
||||
|
||||
ModelDoc::LoadOptions options{};
|
||||
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||
options.loadMaterials = true;
|
||||
options.loadSkeleton = true;
|
||||
options.loadAnimations = true;
|
||||
|
||||
auto charModel = AssetManager::GetInstance().LoadModel(modelReference, options);
|
||||
ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
|
||||
|
||||
const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(charModel, modelReference);
|
||||
|
||||
// The model stores an absolute texture path from another machine, so fall back to the shipped texture
|
||||
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 MaterialID charMaterialID = resources.materials().addMaterial({
|
||||
.baseColor = ModelDocUtils::GetImportedBaseColor(charModel, charPrimitive),
|
||||
.diffuseTexture = resources.loadImageFromFile(gfxDevice, charTexturePath),
|
||||
.name = ModelDocUtils::GetImportedMaterialName(charModel, charPrimitive, "CharacterMaterial"),
|
||||
});
|
||||
|
||||
GameObject* charObj = scene.CreateGameObject("Character");
|
||||
auto* charMeshComp = charObj->AddComponent<MeshRendererComponent>();
|
||||
charMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, charPrimitive.mesh));
|
||||
charMeshComp->SetMaterialID(charMaterialID);
|
||||
|
||||
auto* animator = charObj->AddComponent<Animator>();
|
||||
animator->setSkeleton(std::move(charModel.skeleton));
|
||||
for (auto& clip : charModel.animations)
|
||||
{
|
||||
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// Logs everything that enters or leaves it, spheres from the "Spawn ball" button fall through it
|
||||
void CreateTriggerZone(Scene& scene)
|
||||
{
|
||||
GameObject* triggerObj = scene.CreateGameObject("TriggerBox");
|
||||
triggerObj->AddComponent<BoxCollider>(glm::vec3{3.0f, 3.0f, 3.0f})->SetTrigger(true);
|
||||
triggerObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||
triggerObj->AddComponent<TriggerLoggerComponent>();
|
||||
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
|
||||
}
|
||||
|
||||
void CreatePlayer(Scene& scene, MainSceneObjects& objects)
|
||||
{
|
||||
GameObject* 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>();
|
||||
|
||||
// Facing the parkour course
|
||||
auto* controller = playerObj->AddComponent<FirstPersonController>();
|
||||
controller->SetYaw(glm::half_pi<float>());
|
||||
scene.GetPhysics().RegisterGameObject(*playerObj);
|
||||
|
||||
objects.player = playerObj;
|
||||
objects.playerController = controller;
|
||||
}
|
||||
|
||||
void LoadSpawnBallResources(GfxDevice& gfxDevice, RenderResources& resources, MainSceneObjects& objects)
|
||||
{
|
||||
const auto sphereModel = AssetManager::GetInstance().LoadModel("engine://sphere.fbx", StaticModelOptions());
|
||||
ModelDocUtils::LogModelDocSummary(sphereModel, "sphere.fbx");
|
||||
const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(sphereModel, "engine://sphere.fbx");
|
||||
|
||||
objects.sphereMesh = resources.meshes().addMesh(gfxDevice, spherePrimitive.mesh);
|
||||
objects.sphereMaterial = resources.materials().addSimpleColorMaterial({0.5f, 0.3f, 0.8f}, "Blue");
|
||||
}
|
||||
}
|
||||
|
||||
MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
MainSceneObjects objects{};
|
||||
|
||||
scene.GetEnvironment().fogDensity = 0.01f;
|
||||
|
||||
CreateSun(scene);
|
||||
CreateGround(scene, gfxDevice, resources);
|
||||
CreateParkourCourse(scene, gfxDevice, resources);
|
||||
CreateFlightHelmet(scene, gfxDevice, resources);
|
||||
CreateTree(scene, gfxDevice, resources);
|
||||
CreateCapybara(scene, gfxDevice, resources);
|
||||
CreateTriggerZone(scene);
|
||||
CreatePlayer(scene, objects);
|
||||
LoadSpawnBallResources(gfxDevice, resources, objects);
|
||||
|
||||
return objects;
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <destrum/Components/Physics/Collider.h>
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Components/CameraComponent.h>
|
||||
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Components/LightComponent.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Graphics/Material.h>
|
||||
@@ -429,6 +431,85 @@ namespace {
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testCapsuleColliderDebugLines()
|
||||
{
|
||||
GameObject object{"capsule"};
|
||||
object.GetTransform().SetWorldPosition({1.0f, 2.0f, 3.0f});
|
||||
object.GetTransform().SetWorldScale(glm::vec3{5.0f});
|
||||
auto* capsule = object.AddComponent<CapsuleCollider>(0.4f, 1.8f);
|
||||
capsule->SetCenterOffset({0.0f, 0.5f, 0.0f});
|
||||
|
||||
auto& lines = LineRenderingManager::GetInstance();
|
||||
lines.ClearLines();
|
||||
capsule->DrawDebugLines(glm::vec4{1.0f});
|
||||
|
||||
const glm::vec3 center{1.0f, 2.5f, 3.0f};
|
||||
float top = -1000.0f;
|
||||
float bottom = 1000.0f;
|
||||
bool inside = true;
|
||||
for (const Line& line : lines.GetLines()) {
|
||||
for (const glm::vec3& point : {line.start, line.end}) {
|
||||
const glm::vec3 local = point - center;
|
||||
top = std::max(top, local.y);
|
||||
bottom = std::min(bottom, local.y);
|
||||
inside = inside && glm::length(glm::vec2{local.x, local.z}) <= 0.4f + 0.001f;
|
||||
}
|
||||
}
|
||||
lines.ClearLines();
|
||||
|
||||
check(inside && close(top, 0.9f) && close(bottom, -0.9f),
|
||||
"capsule debug lines must match the collider shape in world units, ignoring the object scale");
|
||||
}
|
||||
|
||||
void testColliderCenterOffset()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("collider-offset");
|
||||
|
||||
GameObject* box = scene.CreateGameObject("offset box");
|
||||
box->AddComponent<BoxCollider>(glm::vec3{0.5f})->SetCenterOffset({0.0f, 5.0f, 0.0f});
|
||||
box->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||
scene.CommitPendingAdditions();
|
||||
scene.GetPhysics().RefreshGameObject(*box);
|
||||
|
||||
PhysicsRaycastHit hit{};
|
||||
const bool found = box->GetComponent<Rigidbody>()->GetPhysicsWorld()->Raycast(
|
||||
{0.0f, 10.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, 20.0f, hit);
|
||||
check(found && std::abs(hit.distance - 4.5f) < 0.01f,
|
||||
"a collider center offset must move the shape, not just the center of mass");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testPrimitiveBoxMesh()
|
||||
{
|
||||
const glm::vec3 size{4.0f, 1.0f, 2.0f};
|
||||
const CPUMesh box = PrimitiveMeshes::MakeBox(size);
|
||||
|
||||
check(box.vertices.size() == 24 && box.indices.size() == 36, "a box must have 6 faces of 2 triangles");
|
||||
check(box.minPos == -size * 0.5f && box.maxPos == size * 0.5f, "a box must be centered on the origin");
|
||||
|
||||
float maxU = 0.0f;
|
||||
bool outward = true;
|
||||
for (const auto& vertex : box.vertices) {
|
||||
if (vertex.normal == glm::vec3{0.0f, 0.0f, 1.0f}) {
|
||||
maxU = std::max(maxU, vertex.uv_x);
|
||||
}
|
||||
outward = outward && glm::dot(vertex.position, vertex.normal) > 0.0f;
|
||||
}
|
||||
check(close(maxU, size.x), "box uvs must be in meters so textures tile at the same size");
|
||||
check(outward, "box normals must point away from the center");
|
||||
|
||||
bool counterClockwise = true;
|
||||
for (std::size_t i = 0; i < box.indices.size(); i += 3) {
|
||||
const auto& a = box.vertices[box.indices[i]];
|
||||
const auto& b = box.vertices[box.indices[i + 1]];
|
||||
const auto& c = box.vertices[box.indices[i + 2]];
|
||||
const glm::vec3 faceNormal = glm::cross(b.position - a.position, c.position - a.position);
|
||||
counterClockwise = counterClockwise && glm::dot(faceNormal, a.normal) > 0.0f;
|
||||
}
|
||||
check(counterClockwise, "box triangles must be counter clockwise seen from outside");
|
||||
}
|
||||
|
||||
void testFrozenRotationCapsule()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("frozen-capsule");
|
||||
@@ -1047,6 +1128,9 @@ int main()
|
||||
testTriggerZone();
|
||||
testSceneCurrentCamera();
|
||||
testFrozenRotationCapsule();
|
||||
testPrimitiveBoxMesh();
|
||||
testColliderCenterOffset();
|
||||
testCapsuleColliderDebugLines();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user