MODEL LOADING BABY WORKS

This commit is contained in:
2026-01-20 00:13:54 +01:00
parent 87dcbb50ec
commit b9878f2a06
31 changed files with 755 additions and 252 deletions

View File

@@ -11,3 +11,15 @@ endif()
add_subdirectory(destrum) add_subdirectory(destrum)
add_subdirectory(lightkeeper) add_subdirectory(lightkeeper)
add_subdirectory(TheChef) add_subdirectory(TheChef)
add_custom_target(CleanupAssets)
add_dependencies(CleanupAssets
_internal_clean_game_assets
_internal_clean_engine_assets
)
add_custom_target(CookAssets)
add_dependencies(CookAssets
_internal_cook_game_assets
_internal_cook_engine_assets
)

1
TheChef Submodule

Submodule TheChef added at faaf7fa120

View File

@@ -6,6 +6,8 @@ set(SRC_FILES
"src/Components/MeshRendererComponent.cpp" "src/Components/MeshRendererComponent.cpp"
"src/Components/Rotator.cpp" "src/Components/Rotator.cpp"
"src/Components/Spinner.cpp"
"src/Components/OrbitAndSpin.cpp"
"src/Graphics/BindlessSetManager.cpp" "src/Graphics/BindlessSetManager.cpp"
"src/Graphics/Camera.cpp" "src/Graphics/Camera.cpp"
@@ -38,6 +40,7 @@ set(SRC_FILES
"src/FS/AssetFS.cpp" "src/FS/AssetFS.cpp"
"src/FS/Manifest.cpp"
) )
add_library(destrum ${SRC_FILES}) add_library(destrum ${SRC_FILES})
@@ -63,6 +66,7 @@ target_link_libraries(destrum
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
spdlog::spdlog spdlog::spdlog
stb::image stb::image
tinygltf
PRIVATE PRIVATE
freetype::freetype freetype::freetype
@@ -98,15 +102,23 @@ target_compile_definitions(destrum
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src") set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src")
set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime") set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime")
set(OUTPUT_ENGINE_ASSETS_DIR "${CMAKE_BINARY_DIR}/assets/engine")
add_custom_target(cook_assets_clean add_custom_command(TARGET destrum POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUTPUT_ENGINE_ASSETS_DIR}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/assets"
COMMAND ${CMAKE_COMMAND} -E create_symlink "${ASSETS_RUNTIME_DIR}" "${OUTPUT_ENGINE_ASSETS_DIR}"
VERBATIM
)
add_custom_target(_internal_clean_engine_assets
COMMAND TheChef COMMAND TheChef
--input "${ASSETS_SRC_DIR}" --input "${ASSETS_SRC_DIR}"
--output "${ASSETS_RUNTIME_DIR}" --output "${ASSETS_RUNTIME_DIR}"
--clean --clean
) )
add_custom_target(cook_assets ALL add_custom_target(_internal_cook_engine_assets ALL
COMMAND TheChef COMMAND TheChef
--input "${ASSETS_SRC_DIR}" --input "${ASSETS_SRC_DIR}"
--output "${ASSETS_RUNTIME_DIR}" --output "${ASSETS_RUNTIME_DIR}"

View File

@@ -1 +0,0 @@
Hello world!

View File

@@ -0,0 +1,48 @@
#ifndef ORBITANDSPIN_H
#define ORBITANDSPIN_H
#include <destrum/ObjectModel/Component.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
class OrbitAndSpin final : public Component {
public:
OrbitAndSpin(
GameObject& parent,
float radius,
glm::vec3 center = glm::vec3(0.0f)
)
: Component(parent, "OrbitAndSpin")
, m_Radius(radius)
, m_Center(center)
{}
// Call after constructing if you want deterministic randomness per object
void Randomize(uint32_t seed);
void Update() override;
// optional setters
void SetRadius(float r) { m_Radius = r; }
void SetCenter(glm::vec3 c) { m_Center = c; }
private:
void BuildOrbitBasis(); // builds m_U/m_V from m_OrbitAxis
float m_Radius = 5.0f;
glm::vec3 m_Center{0.0f};
// orbit
glm::vec3 m_OrbitAxis{0,1,0}; // normal of the orbit plane
float m_OrbitSpeed = 1.0f; // rad/sec
float m_OrbitAngle = 0.0f; // current angle
float m_OrbitPhase = 0.0f; // starting offset
glm::vec3 m_U{1,0,0}; // orbit basis axis 1
glm::vec3 m_V{0,0,1}; // orbit basis axis 2
// self spin
glm::vec3 m_SpinAxis{0,1,0};
float m_SpinSpeed = 2.0f; // rad/sec
};
#endif //ORBITANDSPIN_H

View File

@@ -4,6 +4,8 @@
#include <destrum/ObjectModel/Component.h> #include <destrum/ObjectModel/Component.h>
#include <destrum/ObjectModel/Transform.h> #include <destrum/ObjectModel/Transform.h>
#include <glm/glm.hpp>
class Rotator final : public Component { class Rotator final : public Component {
public: public:
explicit Rotator(GameObject& parent, float distance, float speed); explicit Rotator(GameObject& parent, float distance, float speed);
@@ -15,15 +17,29 @@ public:
void Update() override; void Update() override;
void SetDistance(float distance) { m_Distance = distance; } void SetDistance(float distance);
void SetSpeed(float speed) { m_Speed = speed; } void SetSpeed(float speed) { m_Speed = speed; }
void SetRotatePosition(const glm::vec3& position) { GetTransform().SetLocalPosition(position); }
// Pivot (the point you orbit around) in LOCAL space
void SetPivotPosition(const glm::vec3& pivot);
// Axis to orbit around (in LOCAL space)
void SetAxis(const glm::vec3& axis);
// Backwards-compatible name if you were using this already
void SetRotatePosition(const glm::vec3& pivot) { SetPivotPosition(pivot); }
private:
static glm::vec3 MakePerpendicularUnitVector(const glm::vec3& axis);
private: private:
float m_Distance{}; float m_Distance{};
float m_Speed{}; float m_Speed{};
float m_CurrentAngle{}; float m_CurrentAngle{};
glm::vec3 m_OriginalPosition{};
glm::vec3 m_Pivot{}; // center of rotation (local)
glm::vec3 m_Axis{0, 0, 1}; // rotation axis (local, normalized)
glm::vec3 m_InitialOffset{}; // from pivot -> object at angle 0 (local)
}; };
#endif // ROTATOR_H #endif // ROTATOR_H

View File

@@ -0,0 +1,25 @@
#ifndef SPINNER_H
#define SPINNER_H
#include <destrum/ObjectModel/Component.h>
#include <glm/glm.hpp>
class Spinner final : public Component {
public:
explicit Spinner(GameObject& parent, glm::vec3 axis, float speedRadPerSec)
: Component(parent, "Spinner")
, m_Axis(glm::normalize(axis))
, m_Speed(speedRadPerSec) {}
void Update() override;
void SetAxis(const glm::vec3& axis) { m_Axis = glm::normalize(axis); }
void SetSpeed(float speedRadPerSec) { m_Speed = speedRadPerSec; }
private:
glm::vec3 m_Axis{0,1,0};
float m_Speed = 1.0f;
float m_Angle = 0.0f;
};
#endif //SPINNER_H

View File

@@ -7,9 +7,14 @@
#include <destrum/Singleton.h> #include <destrum/Singleton.h>
#include <destrum/FS/Manifest.h>
struct FSMount { struct FSMount {
std::string scheme; // "engine", "game" std::string scheme; // "engine", "game"
std::filesystem::path root; std::filesystem::path root;
std::optional<AssetManifest> manifest;
}; };
class AssetFS final: public Singleton<AssetFS> { class AssetFS final: public Singleton<AssetFS> {
@@ -21,6 +26,8 @@ public:
[[nodiscard]] std::filesystem::path GetFullPath(std::string_view vpath) const; [[nodiscard]] std::filesystem::path GetFullPath(std::string_view vpath) const;
[[nodiscard]] std::filesystem::path GetCookedPathForFile(std::string_view vpath) const;
private: private:
static std::vector<uint8_t> ReadFile(const std::filesystem::path& fullPath); static std::vector<uint8_t> ReadFile(const std::filesystem::path& fullPath);

View File

@@ -0,0 +1,32 @@
#ifndef MANIFEST_H
#define MANIFEST_H
#include <string>
#include <optional>
#include <unordered_map>
struct ManifestAsset {
std::string src; // "shaders/mesh.frag"
std::optional<std::string> out; // "shaders/mesh.frag.spv" or null
std::string type; // "shader_spirv", "texture", etc
int64_t mtime_epoch_ns = 0;
uint64_t size_bytes = 0;
};
struct AssetManifest {
uint32_t version = 0;
// Fast lookup by src path
std::unordered_map<std::string, ManifestAsset> assetsBySrc;
const ManifestAsset* FindBySrc(std::string_view src) const {
auto it = assetsBySrc.find(std::string(src));
return it != assetsBySrc.end() ? &it->second : nullptr;
}
};
namespace FS {
AssetManifest LoadAssetManifest(const std::filesystem::path& manifestPath);
}
#endif //MANIFEST_H

View File

@@ -94,12 +94,23 @@ static std::vector<CPUMesh::Vertex> vertices = {
}; };
static std::vector<uint32_t> indices = { static std::vector<uint32_t> indices = {
0, 1, 2, 2, 3, 0, // Front (+Z) // Front (+Z)
4, 5, 6, 6, 7, 4, // Back (-Z) <-- fixed 0, 2, 1, 0, 3, 2,
8, 9,10, 10,11, 8, // Right (+X)
12,13,14, 14,15,12, // Left (-X) // Back (-Z)
16,17,18, 18,19,16, // Top (+Y) 4, 6, 5, 4, 7, 6,
20,21,22, 22,23,20 // Bottom(-Y)
// Right (+X)
8,10, 9, 8,11,10,
// Left (-X)
12,14,13, 12,15,14,
// Top (+Y)
16,18,17, 16,19,18,
// Bottom (-Y)
20,22,21, 20,23,22
}; };

View File

@@ -1,15 +1,28 @@
#ifndef MODELLOADER_H #ifndef MODELLOADER_H
#define MODELLOADER_H #define MODELLOADER_H
namespace ModelLoader {
// CPUMesh loader with tinygltf // CPUMesh loader with tinygltf
// - Loads first scene (or default scene), iterates nodes, extracts mesh primitives. // - Loads first scene (or default scene), iterates nodes, extracts mesh primitives.
// - Handles POSITION/NORMAL/TANGENT/TEXCOORD_0 and indices. // - Handles POSITION/NORMAL/TANGENT/TEXCOORD_0 and indices.
// - Computes minPos/maxPos. // - Computes minPos/maxPos.
// - Optionally merges primitives into a single CPUMesh per glTF mesh. // - Can return per-primitive meshes, or merged-per-gltf-mesh meshes.
// - IMPORTANT FIX: Applies node transforms (TRS / matrix) so models don't appear flipped/rotated.
//
// NOTE: Defining TINYGLTF_IMPLEMENTATION in a header can cause ODR / multiple-definition issues
// if included in more than one translation unit. Best practice: put the TINYGLTF_IMPLEMENTATION
// define in exactly one .cpp. I keep it here because your original file did, but you may want
// to move it.
#define TINYGLTF_IMPLEMENTATION
#define TINYGLTF_NO_STB_IMAGE_WRITE
#include <tiny_gltf.h> #include <tiny_gltf.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> // translate/scale
#include <glm/gtc/quaternion.hpp> // quat
#include <glm/gtx/quaternion.hpp> // mat4_cast
#include <destrum/Graphics/Resources/Mesh.h>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@@ -20,25 +33,9 @@ namespace ModelLoader {
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
#include <iostream> #include <iostream>
#include <algorithm>
struct CPUMesh { namespace ModelLoader {
std::vector<std::uint32_t> indices;
struct Vertex {
glm::vec3 position;
float uv_x{};
glm::vec3 normal;
float uv_y{};
glm::vec4 tangent;
};
std::vector<Vertex> vertices;
std::string name;
glm::vec3 minPos;
glm::vec3 maxPos;
};
// -------------------- helpers -------------------- // -------------------- helpers --------------------
@@ -84,13 +81,10 @@ namespace ModelLoader {
size_t stride = bv.byteStride ? size_t(bv.byteStride) : packedStride; size_t stride = bv.byteStride ? size_t(bv.byteStride) : packedStride;
const size_t start = const size_t start = size_t(bv.byteOffset) + size_t(accessor.byteOffset);
size_t(bv.byteOffset) + size_t(accessor.byteOffset); // Conservative bounds check; don't hard-fail on weird stride but catches obvious issues.
if (start > buf.data.size()) {
if (start + accessor.count * stride > buf.data.size() + 0ull) { throw std::runtime_error("Accessor start is out of buffer bounds");
// Note: This check is conservative; stride can be larger than packed.
// Still useful to catch obvious corrupt files.
// If you want strict validation, check per-element.
} }
const std::uint8_t* ptr = buf.data.data() + start; const std::uint8_t* ptr = buf.data.data() + start;
@@ -104,8 +98,6 @@ namespace ModelLoader {
return v; return v;
} }
// Reads VEC3 float accessor element i into glm::vec3.
// Allows input component type float only for simplicity (common in glTF).
static glm::vec3 ReadVec3Float(const std::uint8_t* base, size_t stride, size_t i) { static glm::vec3 ReadVec3Float(const std::uint8_t* base, size_t stride, size_t i) {
const std::uint8_t* p = base + i * stride; const std::uint8_t* p = base + i * stride;
const float x = ReadAs<float>(p + 0); const float x = ReadAs<float>(p + 0);
@@ -155,15 +147,62 @@ namespace ModelLoader {
mx.z = std::max(mx.z, p.z); mx.z = std::max(mx.z, p.z);
} }
// Build a node local matrix from either node.matrix or TRS.
// glTF stores rotation as quaternion [x,y,z,w].
static glm::mat4 NodeLocalMatrix(const tinygltf::Node& node) {
if (node.matrix.size() == 16) {
glm::mat4 m(1.0f);
// glTF is column-major; GLM is column-major -> fill columns.
for (int c = 0; c < 4; ++c)
for (int r = 0; r < 4; ++r)
m[c][r] = static_cast<float>(node.matrix[c * 4 + r]);
return m;
}
glm::vec3 t(0.0f);
if (node.translation.size() == 3) {
t = glm::vec3(
static_cast<float>(node.translation[0]),
static_cast<float>(node.translation[1]),
static_cast<float>(node.translation[2])
);
}
glm::quat q(1.0f, 0.0f, 0.0f, 0.0f); // w,x,y,z
if (node.rotation.size() == 4) {
q = glm::quat(
static_cast<float>(node.rotation[3]), // w
static_cast<float>(node.rotation[0]), // x
static_cast<float>(node.rotation[1]), // y
static_cast<float>(node.rotation[2]) // z
);
}
glm::vec3 s(1.0f);
if (node.scale.size() == 3) {
s = glm::vec3(
static_cast<float>(node.scale[0]),
static_cast<float>(node.scale[1]),
static_cast<float>(node.scale[2])
);
}
const glm::mat4 T = glm::translate(glm::mat4(1.0f), t);
const glm::mat4 R = glm::toMat4(q);
const glm::mat4 S = glm::scale(glm::mat4(1.0f), s);
return T * R * S;
}
// -------------------- primitive extraction -------------------- // -------------------- primitive extraction --------------------
static CPUMesh LoadPrimitiveIntoCPUMesh(const tinygltf::Model& model, static CPUMesh LoadPrimitiveIntoCPUMesh(const tinygltf::Model& model,
const tinygltf::Primitive& prim, const tinygltf::Primitive& prim,
const std::string& nameForMesh) { const std::string& nameForMesh,
const glm::mat4& world) {
CPUMesh out{}; CPUMesh out{};
out.name = nameForMesh; out.name = nameForMesh;
// POSITION is required for our vertex buffer // POSITION is required
auto itPos = prim.attributes.find("POSITION"); auto itPos = prim.attributes.find("POSITION");
if (itPos == prim.attributes.end()) { if (itPos == prim.attributes.end()) {
throw std::runtime_error("Primitive has no POSITION attribute"); throw std::runtime_error("Primitive has no POSITION attribute");
@@ -184,7 +223,7 @@ namespace ModelLoader {
if (auto it = prim.attributes.find("NORMAL"); it != prim.attributes.end()) { if (auto it = prim.attributes.find("NORMAL"); it != prim.attributes.end()) {
accNormal = &model.accessors.at(it->second); accNormal = &model.accessors.at(it->second);
if (accNormal->componentType != TINYGLTF_COMPONENT_TYPE_FLOAT || accNormal->type != TINYGLTF_TYPE_VEC3) if (accNormal->componentType != TINYGLTF_COMPONENT_TYPE_FLOAT || accNormal->type != TINYGLTF_TYPE_VEC3)
accNormal = nullptr; // ignore unsupported accNormal = nullptr;
} }
if (auto it = prim.attributes.find("TANGENT"); it != prim.attributes.end()) { if (auto it = prim.attributes.find("TANGENT"); it != prim.attributes.end()) {
accTangent = &model.accessors.at(it->second); accTangent = &model.accessors.at(it->second);
@@ -211,7 +250,7 @@ namespace ModelLoader {
auto p = GetAccessorDataPtrAndStride(model, *accNormal); auto p = GetAccessorDataPtrAndStride(model, *accNormal);
nrmBase = p.first; nrmBase = p.first;
nrmStride = p.second; nrmStride = p.second;
if (size_t(accNormal->count) != vertexCount) accNormal = nullptr; // mismatch -> ignore if (size_t(accNormal->count) != vertexCount) accNormal = nullptr;
} }
if (accTangent) { if (accTangent) {
auto p = GetAccessorDataPtrAndStride(model, *accTangent); auto p = GetAccessorDataPtrAndStride(model, *accTangent);
@@ -229,23 +268,31 @@ namespace ModelLoader {
// Allocate vertices // Allocate vertices
out.vertices.resize(vertexCount); out.vertices.resize(vertexCount);
// Bounds init // Bounds init (in world space, because we transform positions)
glm::vec3 mn{std::numeric_limits<float>::infinity()}; glm::vec3 mn{std::numeric_limits<float>::infinity()};
glm::vec3 mx{-std::numeric_limits<float>::infinity()}; glm::vec3 mx{-std::numeric_limits<float>::infinity()};
// Fill vertices // Normal matrix
const glm::mat3 nrmMat = glm::transpose(glm::inverse(glm::mat3(world)));
const glm::mat3 tanMat = glm::mat3(world);
for (size_t i = 0; i < vertexCount; ++i) { for (size_t i = 0; i < vertexCount; ++i) {
CPUMesh::Vertex v{}; CPUMesh::Vertex v{};
v.position = ReadVec3Float(posBase, posStride, i); // Position
const glm::vec3 pLocal = ReadVec3Float(posBase, posStride, i);
v.position = glm::vec3(world * glm::vec4(pLocal, 1.0f));
UpdateBounds(mn, mx, v.position); UpdateBounds(mn, mx, v.position);
// Normal
if (accNormal) { if (accNormal) {
v.normal = ReadVec3Float(nrmBase, nrmStride, i); const glm::vec3 nLocal = ReadVec3Float(nrmBase, nrmStride, i);
v.normal = glm::normalize(nrmMat * nLocal);
} else { } else {
v.normal = glm::vec3(0.0f, 1.0f, 0.0f); v.normal = glm::vec3(0.0f, 1.0f, 0.0f);
} }
// UV
if (accUV0) { if (accUV0) {
glm::vec2 uv = ReadVec2Float(uvBase, uvStride, i); glm::vec2 uv = ReadVec2Float(uvBase, uvStride, i);
v.uv_x = uv.x; v.uv_x = uv.x;
@@ -255,8 +302,11 @@ namespace ModelLoader {
v.uv_y = 0.0f; v.uv_y = 0.0f;
} }
// Tangent
if (accTangent) { if (accTangent) {
v.tangent = ReadVec4Float(tanBase, tanStride, i); glm::vec4 t = ReadVec4Float(tanBase, tanStride, i);
glm::vec3 t3 = glm::normalize(tanMat * glm::vec3(t));
v.tangent = glm::vec4(t3, t.w); // keep handedness in w
} else { } else {
v.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); v.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
} }
@@ -267,7 +317,7 @@ namespace ModelLoader {
out.minPos = (vertexCount > 0) ? mn : glm::vec3(0.0f); out.minPos = (vertexCount > 0) ? mn : glm::vec3(0.0f);
out.maxPos = (vertexCount > 0) ? mx : glm::vec3(0.0f); out.maxPos = (vertexCount > 0) ? mx : glm::vec3(0.0f);
// Indices (optional; if absent, build linear indices) // Indices
if (prim.indices >= 0) { if (prim.indices >= 0) {
const tinygltf::Accessor& accIdx = model.accessors.at(prim.indices); const tinygltf::Accessor& accIdx = model.accessors.at(prim.indices);
if (accIdx.type != TINYGLTF_TYPE_SCALAR) { if (accIdx.type != TINYGLTF_TYPE_SCALAR) {
@@ -279,11 +329,16 @@ namespace ModelLoader {
out.indices[i] = ReadIndexAsU32(model, accIdx, i); out.indices[i] = ReadIndexAsU32(model, accIdx, i);
} }
} else { } else {
// Non-indexed primitive: make it indexed
out.indices.resize(vertexCount); out.indices.resize(vertexCount);
for (size_t i = 0; i < vertexCount; ++i) out.indices[i] = static_cast<std::uint32_t>(i); for (size_t i = 0; i < vertexCount; ++i) out.indices[i] = static_cast<std::uint32_t>(i);
} }
// If your renderer expects opposite winding vs glTF, you can flip triangle order.
// Keep what you had:
// for (size_t i = 0; i + 2 < out.indices.size(); i += 3) {
// std::swap(out.indices[i + 1], out.indices[i + 2]);
// }
return out; return out;
} }
@@ -301,7 +356,6 @@ namespace ModelLoader {
dst.minPos = src.minPos; dst.minPos = src.minPos;
dst.maxPos = src.maxPos; dst.maxPos = src.maxPos;
} else { } else {
// update bounds using src bounds (cheap)
dst.minPos = glm::vec3( dst.minPos = glm::vec3(
std::min(dst.minPos.x, src.minPos.x), std::min(dst.minPos.x, src.minPos.x),
std::min(dst.minPos.y, src.minPos.y), std::min(dst.minPos.y, src.minPos.y),
@@ -317,6 +371,11 @@ namespace ModelLoader {
// -------------------- public API -------------------- // -------------------- public API --------------------
struct NodeStackItem {
int nodeIdx;
glm::mat4 parentWorld;
};
// Option A: return one CPUMesh per *primitive* encountered in the scene // Option A: return one CPUMesh per *primitive* encountered in the scene
static std::vector<CPUMesh> LoadGLTF_CPUMeshes_PerPrimitive(const std::string& path) { static std::vector<CPUMesh> LoadGLTF_CPUMeshes_PerPrimitive(const std::string& path) {
tinygltf::TinyGLTF loader; tinygltf::TinyGLTF loader;
@@ -339,22 +398,31 @@ namespace ModelLoader {
std::vector<CPUMesh> result; std::vector<CPUMesh> result;
const tinygltf::Scene& scene = model.scenes.at(sceneIndex); const tinygltf::Scene& scene = model.scenes.at(sceneIndex);
// Traverse nodes and pull mesh primitives (ignoring node transforms in this basic example) std::vector<NodeStackItem> stack;
std::vector<int> stack(scene.nodes.begin(), scene.nodes.end()); stack.reserve(scene.nodes.size());
while (!stack.empty()) { for (int n : scene.nodes) stack.push_back({n, glm::mat4(1.0f)});
int nodeIdx = stack.back();
stack.pop_back();
const tinygltf::Node& node = model.nodes.at(nodeIdx);
for (int child: node.children) stack.push_back(child); while (!stack.empty()) {
NodeStackItem it = stack.back();
stack.pop_back();
const tinygltf::Node& node = model.nodes.at(it.nodeIdx);
const glm::mat4 local = NodeLocalMatrix(node);
const glm::mat4 world = it.parentWorld * local;
for (int child : node.children) stack.push_back({child, world});
if (node.mesh < 0) continue; if (node.mesh < 0) continue;
const tinygltf::Mesh& mesh = model.meshes.at(node.mesh); const tinygltf::Mesh& mesh = model.meshes.at(node.mesh);
for (size_t p = 0; p < mesh.primitives.size(); ++p) { for (size_t p = 0; p < mesh.primitives.size(); ++p) {
const tinygltf::Primitive& prim = mesh.primitives[p]; const tinygltf::Primitive& prim = mesh.primitives[p];
CPUMesh cpu = LoadPrimitiveIntoCPUMesh(model, prim, mesh.name.empty() ? ("mesh_" + std::to_string(node.mesh)) : mesh.name); CPUMesh cpu = LoadPrimitiveIntoCPUMesh(
// Optionally make name unique per primitive model,
prim,
mesh.name.empty() ? ("mesh_" + std::to_string(node.mesh)) : mesh.name,
world
);
cpu.name += "_prim" + std::to_string(p); cpu.name += "_prim" + std::to_string(p);
result.push_back(std::move(cpu)); result.push_back(std::move(cpu));
} }
@@ -363,7 +431,8 @@ namespace ModelLoader {
return result; return result;
} }
// Option B: return one CPUMesh per *glTF mesh*, merging all primitives of that mesh into one CPUMesh // Option B: return one CPUMesh per *glTF mesh instance*, merging all primitives of that mesh into one CPUMesh.
// Note: If the same glTF mesh is instanced by multiple nodes, you'll get one merged CPUMesh PER NODE instance.
static std::vector<CPUMesh> LoadGLTF_CPUMeshes_MergedPerMesh(const std::string& path) { static std::vector<CPUMesh> LoadGLTF_CPUMeshes_MergedPerMesh(const std::string& path) {
tinygltf::TinyGLTF loader; tinygltf::TinyGLTF loader;
tinygltf::Model model; tinygltf::Model model;
@@ -385,14 +454,19 @@ namespace ModelLoader {
std::vector<CPUMesh> result; std::vector<CPUMesh> result;
const tinygltf::Scene& scene = model.scenes.at(sceneIndex); const tinygltf::Scene& scene = model.scenes.at(sceneIndex);
std::vector<int> stack(scene.nodes.begin(), scene.nodes.end()); std::vector<NodeStackItem> stack;
stack.reserve(scene.nodes.size());
for (int n : scene.nodes) stack.push_back({n, glm::mat4(1.0f)});
while (!stack.empty()) { while (!stack.empty()) {
int nodeIdx = stack.back(); NodeStackItem it = stack.back();
stack.pop_back(); stack.pop_back();
const tinygltf::Node& node = model.nodes.at(nodeIdx);
for (int child: node.children) stack.push_back(child); const tinygltf::Node& node = model.nodes.at(it.nodeIdx);
const glm::mat4 local = NodeLocalMatrix(node);
const glm::mat4 world = it.parentWorld * local;
for (int child : node.children) stack.push_back({child, world});
if (node.mesh < 0) continue; if (node.mesh < 0) continue;
const tinygltf::Mesh& mesh = model.meshes.at(node.mesh); const tinygltf::Mesh& mesh = model.meshes.at(node.mesh);
@@ -404,9 +478,9 @@ namespace ModelLoader {
bool any = false; bool any = false;
for (const tinygltf::Primitive& prim : mesh.primitives) { for (const tinygltf::Primitive& prim : mesh.primitives) {
CPUMesh part = LoadPrimitiveIntoCPUMesh(model, prim, merged.name); CPUMesh part = LoadPrimitiveIntoCPUMesh(model, prim, merged.name, world);
if (!any) { if (!any) {
merged = part; merged = std::move(part);
any = true; any = true;
} else { } else {
AppendMesh(merged, part); AppendMesh(merged, part);
@@ -418,6 +492,7 @@ namespace ModelLoader {
return result; return result;
} }
}
} // namespace ModelLoader
#endif // MODELLOADER_H #endif // MODELLOADER_H

View File

@@ -153,4 +153,5 @@ void App::run() {
} }
void App::cleanup() { void App::cleanup() {
customCleanup();
} }

View File

@@ -0,0 +1,69 @@
#include <destrum/Components/OrbitAndSpin.h>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/norm.hpp>
#include <random>
#include <cmath>
#include "destrum/ObjectModel/Transform.h"
static glm::vec3 RandomUnitVector(std::mt19937& rng)
{
// uniform on sphere
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
float z = dist(rng) * 2.0f - 1.0f; // -1..1
float a = dist(rng) * 6.28318530718f; // 0..2pi
float r = std::sqrt(std::max(0.0f, 1.0f - z*z));
return glm::normalize(glm::vec3(r*std::cos(a), z, r*std::sin(a)));
}
void OrbitAndSpin::Randomize(uint32_t seed)
{
std::mt19937 rng(seed);
// speeds + phase
std::uniform_real_distribution<float> orbitSpeedDist(0.2f, 1.5f);
std::uniform_real_distribution<float> spinSpeedDist(0.5f, 6.0f);
std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530718f);
m_OrbitAxis = RandomUnitVector(rng);
m_SpinAxis = RandomUnitVector(rng);
m_OrbitSpeed = orbitSpeedDist(rng);
m_SpinSpeed = spinSpeedDist(rng);
m_OrbitPhase = phaseDist(rng);
BuildOrbitBasis();
}
void OrbitAndSpin::BuildOrbitBasis()
{
m_OrbitAxis = glm::normalize(m_OrbitAxis);
// pick any vector not parallel to axis
glm::vec3 any = (std::abs(m_OrbitAxis.y) < 0.99f) ? glm::vec3(0,1,0) : glm::vec3(1,0,0);
m_U = glm::normalize(glm::cross(any, m_OrbitAxis));
m_V = glm::normalize(glm::cross(m_OrbitAxis, m_U));
}
void OrbitAndSpin::Update()
{
// If your engine provides dt via a global/time service, use that instead.
// Since your Spinner takes dt indirectly, I'm assuming Component::Update()
// is called once per frame and you can access dt somewhere globally.
//
// If you CAN pass dt into Update, change signature to Update(float dt).
float dt = 1.0f / 60.0f;
// orbit
m_OrbitAngle += m_OrbitSpeed * dt;
float a = m_OrbitAngle + m_OrbitPhase;
glm::vec3 offset = (m_U * std::cos(a) + m_V * std::sin(a)) * m_Radius;
GetTransform().SetWorldPosition(m_Center + offset);
// self spin (local rotation)
glm::quat dq = glm::angleAxis(m_SpinSpeed * dt, glm::normalize(m_SpinAxis));
auto current = GetTransform().GetLocalRotation(); // adapt to your API
GetTransform().SetLocalRotation(glm::normalize(dq * current));
}

View File

@@ -1,17 +1,95 @@
#include <destrum/Components/Rotator.h> #include <destrum/Components/Rotator.h>
Rotator::Rotator(GameObject& parent, float distance, float speed): #include <glm/gtx/rotate_vector.hpp> // glm::rotate(vec3, angle, axis)
Component(parent, "Rotator"), #include <glm/gtc/epsilon.hpp>
m_Distance(distance), #include <cmath>
m_Speed(speed), #include <glm/gtc/quaternion.hpp> // glm::quat, glm::angleAxis
m_CurrentAngle(0), #include <glm/gtx/quaternion.hpp> // operator*(quat, vec3)
m_OriginalPosition(GetTransform().GetWorldPosition())
{}
void Rotator::Update() { glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis)
m_CurrentAngle += m_Speed * static_cast<float>(0.001); {
const float x = cos(m_CurrentAngle) * m_Distance; // Pick any vector that is not parallel to axis, then cross to get perpendicular.
const float y = sin(m_CurrentAngle) * m_Distance; const glm::vec3 a = glm::normalize(axis);
GetTransform().SetLocalPosition(m_OriginalPosition + glm::vec3(x, y, 0)); const glm::vec3 ref = (std::abs(a.y) < 0.99f) ? glm::vec3(0, 1, 0) : glm::vec3(1, 0, 0);
glm::vec3 perp = glm::cross(a, ref);
const float len2 = glm::dot(perp, perp);
if (len2 < 1e-8f)
return glm::vec3(1, 0, 0); // fallback
return perp / std::sqrt(len2);
}
Rotator::Rotator(GameObject& parent, float distance, float speed)
: Component(parent, "Rotator")
, m_Distance(distance)
, m_Speed(speed)
, m_CurrentAngle(0.0f)
{
// Orbit around where we started (LOCAL), similar to your old behavior.
m_Pivot = GetTransform().GetLocalPosition();
// Default axis is Z (so this behaves like your old XY circle by default).
m_Axis = glm::vec3(0, 0, 1);
// Choose an initial offset that is perpendicular to the axis, with the requested radius.
const glm::vec3 perp = MakePerpendicularUnitVector(m_Axis);
m_InitialOffset = perp * m_Distance;
// Optional: if you'd rather keep the *current* position as the starting point on the orbit:
// m_InitialOffset = GetTransform().GetLocalPosition() - m_Pivot;
// m_Distance = glm::length(m_InitialOffset);
}
void Rotator::SetPivotPosition(const glm::vec3& pivot)
{
m_Pivot = pivot;
// Recompute offset based on current position so it doesn't “jump”.
m_InitialOffset = GetTransform().GetLocalPosition() - m_Pivot;
const float len = glm::length(m_InitialOffset);
if (len > 1e-6f)
m_Distance = len;
else
m_InitialOffset = MakePerpendicularUnitVector(m_Axis) * m_Distance;
}
void Rotator::SetAxis(const glm::vec3& axis)
{
const float len2 = glm::dot(axis, axis);
if (len2 < 1e-8f)
return; // ignore invalid axis
m_Axis = glm::normalize(axis);
// Ensure the offset is perpendicular to the axis (remove any parallel component).
m_InitialOffset -= m_Axis * glm::dot(m_Axis, m_InitialOffset);
const float offLen = glm::length(m_InitialOffset);
if (offLen > 1e-6f)
m_InitialOffset = (m_InitialOffset / offLen) * m_Distance;
else
m_InitialOffset = MakePerpendicularUnitVector(m_Axis) * m_Distance;
}
void Rotator::SetDistance(float distance)
{
m_Distance = distance;
const float offLen = glm::length(m_InitialOffset);
if (offLen > 1e-6f)
m_InitialOffset = (m_InitialOffset / offLen) * m_Distance;
else
m_InitialOffset = MakePerpendicularUnitVector(m_Axis) * m_Distance;
}
void Rotator::Update()
{
// Replace 0.001f with your engine delta time if you have one.
m_CurrentAngle += m_Speed * 0.001f;
const glm::quat q = glm::angleAxis(m_CurrentAngle, glm::normalize(m_Axis));
const glm::vec3 rotatedOffset = q * m_InitialOffset;
GetTransform().SetLocalPosition(m_Pivot + rotatedOffset);
} }

View File

@@ -0,0 +1,26 @@
#include <destrum/Components/Spinner.h>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include "destrum/ObjectModel/Transform.h"
void Spinner::Update()
{
// Replace with your engine dt if you have it available in Component.
const float dt = 1.0f / 60.0f;
m_Angle += m_Speed * dt;
// If you already have SetLocalRotation / SetWorldRotation, use that.
// Here I'm assuming you can set rotation as a quaternion or Euler somewhere.
// If not, tell me your Transform rotation API and Ill adjust.
const glm::quat q = glm::angleAxis(m_Angle, m_Axis);
// Example APIs you might have:
// GetTransform().SetLocalRotation(q);
// or GetTransform().SetWorldRotation(q);
GetTransform().SetLocalRotation(q);
}

View File

@@ -12,7 +12,17 @@ void AssetFS::Init(std::filesystem::path exeDir) {
void AssetFS::Mount(std::string scheme, std::filesystem::path root) { void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", scheme, root.string()); spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", scheme, root.string());
mounts.push_back({std::move(scheme), std::move(root)});
FSMount m;
m.scheme = std::move(scheme);
m.root = std::move(root);
const auto manifestPath = m.root / "manifest.json";
if (std::filesystem::exists(manifestPath)) {
m.manifest = FS::LoadAssetManifest(manifestPath);
}
mounts.push_back(std::move(m));
} }
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) { std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
@@ -51,6 +61,37 @@ std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
throw std::runtime_error("mount not found"); throw std::runtime_error("mount not found");
} }
std::filesystem::path AssetFS::GetCookedPathForFile(std::string_view vpath) const {
assert(initialized && "AssetFS not initialized");
const auto pos = vpath.find("://");
if (pos == std::string_view::npos)
throw std::runtime_error("bad vpath");
const std::string scheme(vpath.substr(0, pos));
const std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
const std::string relStr = rel.generic_string();
for (const auto& m : mounts) {
if (m.scheme != scheme) continue;
// If we have a manifest, consult it
if (m.manifest) {
if (const ManifestAsset* asset = m.manifest->FindBySrc(relStr)) {
if (asset->out) {
return m.root / *asset->out;
}
}
}
// Fallback to raw file
return m.root / rel;
}
throw std::runtime_error("mount not found");
return {};
}
std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) { std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
std::ifstream file(fullPath, std::ios::binary); std::ifstream file(fullPath, std::ios::binary);
if (!file) { if (!file) {

View File

@@ -0,0 +1,43 @@
#include <filesystem>
#include <destrum/FS/Manifest.h>
#include <fstream>
#include <nlohmann/json.hpp>
AssetManifest FS::LoadAssetManifest(const std::filesystem::path& manifestPath) {
std::ifstream f(manifestPath);
if (!f) {
throw std::runtime_error("Failed to open manifest: " + manifestPath.string());
}
nlohmann::json j;
f >> j;
AssetManifest manifest;
manifest.version = j.value("version", 0);
if (!j.contains("assets") || !j["assets"].is_array()) {
throw std::runtime_error("Invalid manifest format: missing assets array");
}
for (const auto& a : j["assets"]) {
ManifestAsset asset;
asset.src = a.at("src").get<std::string>();
asset.type = a.at("type").get<std::string>();
asset.mtime_epoch_ns = a.value("mtime_epoch_ns", 0);
asset.size_bytes = a.value("size_bytes", 0);
if (a.contains("out") && !a["out"].is_null()) {
asset.out = a["out"].get<std::string>();
}
// Normalize to forward slashes for cross-platform matching
std::filesystem::path p(asset.src);
asset.src = p.generic_string();
manifest.assetsBySrc.emplace(asset.src, std::move(asset));
}
return manifest;
}

View File

@@ -6,11 +6,13 @@
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/string_cast.hpp> #include <glm/gtx/string_cast.hpp>
#include <glm/gtx/norm.hpp>
#include "glm/gtx/norm.hpp" Camera::Camera(const glm::vec3& position, const glm::vec3& up)
: m_position{position}, m_up{up} {
// Initialize yaw to -90 degrees so the camera faces -Z by default
Camera::Camera(const glm::vec3& position, const glm::vec3& up): m_position{position}, m_up{up} { m_yaw = -glm::half_pi<float>();
m_pitch = 0.0f;
} }
void Camera::Update(float deltaTime) { void Camera::Update(float deltaTime) {
@@ -22,166 +24,126 @@ void Camera::Update(float deltaTime) {
moveSpeed *= 2.0f; moveSpeed *= 2.0f;
} }
// Controller speed boost (pad0 LB)
const SDL_JoystickID pad0 = input.GetPadInstanceId(0); const SDL_JoystickID pad0 = input.GetPadInstanceId(0);
if (pad0 >= 0 && input.IsPadButtonDown(pad0, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) { if (pad0 >= 0 && input.IsPadButtonDown(pad0, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) {
moveSpeed *= 3.0f; moveSpeed *= 3.0f;
} }
// Clamp pitch like your old code
m_pitch = glm::clamp(m_pitch, -glm::half_pi<float>() + 0.01f, glm::half_pi<float>() - 0.01f);
// ========================= // =========================
// Movement (Keyboard) // Look Input (Keyboard & Controller)
// ========================= // =========================
glm::vec3 move(0.0f); const float keyLookSpeed = glm::radians(120.0f);
if (input.IsKeyDown(SDL_SCANCODE_W)) move += m_forward;
if (input.IsKeyDown(SDL_SCANCODE_S)) move -= m_forward;
if (input.IsKeyDown(SDL_SCANCODE_D)) move += m_right;
if (input.IsKeyDown(SDL_SCANCODE_A)) move -= m_right;
if (input.IsKeyDown(SDL_SCANCODE_Q)) move += m_up;
if (input.IsKeyDown(SDL_SCANCODE_E)) move -= m_up;
if (glm::length2(move) > 0.0f) {
move = glm::normalize(move);
m_position += move * (moveSpeed * deltaTime);
}
// =========================
// Movement (Controller)
// =========================
if (pad0 >= 0) {
const float lx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_LEFTX); // [-1..1]
const float ly = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_LEFTY); // [-1..1]
// SDL Y is typically +down, so invert for "forward"
glm::vec3 padMove(0.0f);
padMove += m_forward * (-ly);
padMove += m_right * ( lx);
// Triggers for vertical movement (optional)
// SDL controller triggers are axes too: 0..1-ish after normalization in our helper, but signless.
// With our NormalizeAxis, triggers will sit near 0 until pressed (depending on mapping).
// If your NormalizeAxis maps triggers weirdly, swap to raw event value approach.
const float lt = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
const float rt = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
const float vertical = (rt - lt);
padMove += m_up * vertical;
if (glm::length2(padMove) > 0.0001f) {
// do NOT normalize: preserve analog magnitude for smooth movement
m_position += padMove * (moveSpeed * deltaTime);
}
}
// =========================
// Look (Keyboard arrows only)
// =========================
// Use radians/sec so framerate-independent
const float keyLookSpeed = glm::radians(120.0f); // degrees per second
if (input.IsKeyDown(SDL_SCANCODE_UP)) m_pitch += keyLookSpeed * deltaTime; if (input.IsKeyDown(SDL_SCANCODE_UP)) m_pitch += keyLookSpeed * deltaTime;
if (input.IsKeyDown(SDL_SCANCODE_DOWN)) m_pitch -= keyLookSpeed * deltaTime; if (input.IsKeyDown(SDL_SCANCODE_DOWN)) m_pitch -= keyLookSpeed * deltaTime;
if (input.IsKeyDown(SDL_SCANCODE_LEFT)) m_yaw -= keyLookSpeed * deltaTime; if (input.IsKeyDown(SDL_SCANCODE_LEFT)) m_yaw -= keyLookSpeed * deltaTime;
if (input.IsKeyDown(SDL_SCANCODE_RIGHT)) m_yaw += keyLookSpeed * deltaTime; if (input.IsKeyDown(SDL_SCANCODE_RIGHT)) m_yaw += keyLookSpeed * deltaTime;
// =========================
// Look (Controller right stick)
// =========================
if (pad0 >= 0) { if (pad0 >= 0) {
const float rx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTX); const float rx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTX);
const float ry = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTY); const float ry = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTY);
const float padLookSpeed = 2.2f;
const float padLookSpeed = 2.2f; // radians/sec at full deflection
m_yaw += rx * padLookSpeed * deltaTime; m_yaw += rx * padLookSpeed * deltaTime;
m_pitch += ry * padLookSpeed * deltaTime; m_pitch -= ry * padLookSpeed * deltaTime; // Inverted to match stick convention
} }
// Clamp pitch again after modifications // Clamp pitch to prevent flipping over the top
m_pitch = glm::clamp(m_pitch, -glm::half_pi<float>() + 0.01f, glm::half_pi<float>() - 0.01f); m_pitch = glm::clamp(m_pitch, -glm::half_pi<float>() + 0.01f, glm::half_pi<float>() - 0.01f);
// Recompute basis from yaw/pitch (same convention you used) // =========================
const glm::mat4 yawMatrix = glm::rotate(glm::mat4(1.0f), -m_yaw, glm::vec3(0, 1, 0)); // Update Basis Vectors
const glm::mat4 pitchMatrix = glm::rotate(glm::mat4(1.0f), m_pitch, glm::vec3(0, 0, 1)); // =========================
const glm::mat4 rotation = yawMatrix * pitchMatrix; // Standard Spherical to Cartesian coordinates (Y-Up, Right-Handed)
glm::vec3 front;
front.x = cos(m_yaw) * cos(m_pitch);
front.y = sin(m_pitch);
front.z = sin(m_yaw) * cos(m_pitch);
m_forward = glm::normalize(glm::vec3(rotation * glm::vec4(1, 0, 0, 0))); // +X forward m_forward = glm::normalize(front);
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); // World Up
m_up = glm::normalize(glm::cross(m_right, m_forward)); m_up = glm::normalize(glm::cross(m_right, m_forward));
// keep target mode off when manually controlled // =========================
m_useTarget = false; // Movement Input
// =========================
glm::vec3 move(0.0f);
if (input.IsKeyDown(SDL_SCANCODE_W)) move += m_forward;
if (input.IsKeyDown(SDL_SCANCODE_S)) move -= m_forward;
if (input.IsKeyDown(SDL_SCANCODE_D)) move += m_right;
if (input.IsKeyDown(SDL_SCANCODE_A)) move -= m_right;
if (input.IsKeyDown(SDL_SCANCODE_Q)) move += glm::vec3(0, 1, 0); // Absolute Up
if (input.IsKeyDown(SDL_SCANCODE_E)) move -= glm::vec3(0, 1, 0); // Absolute Down
if (glm::length2(move) > 0.0f) {
m_position += glm::normalize(move) * (moveSpeed * deltaTime);
}
// Controller Movement
if (pad0 >= 0) {
const float lx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_LEFTX);
const float ly = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_LEFTY);
const float lt = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
const float rt = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
glm::vec3 padMove = (m_forward * -ly) + (m_right * lx) + (glm::vec3(0, 1, 0) * (lt - rt));
if (glm::length2(padMove) > 0.0001f) {
m_position += padMove * (moveSpeed * deltaTime);
}
}
m_useTarget = false;
CalculateProjectionMatrix(); CalculateProjectionMatrix();
CalculateViewMatrix(); CalculateViewMatrix();
} }
void Camera::CalculateViewMatrix() { void Camera::CalculateViewMatrix() {
if (m_useTarget) { if (m_useTarget) {
m_forward = glm::normalize(m_target - m_position); m_viewMatrix = glm::lookAt(m_position, m_target, glm::vec3(0, 1, 0));
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
m_up = glm::normalize(glm::cross(m_right, m_forward));
m_viewMatrix = glm::lookAt(m_position, m_target, m_up);
} else { } else {
m_viewMatrix = glm::lookAt(m_position, m_position + m_forward, m_up); m_viewMatrix = glm::lookAt(m_position, m_position + m_forward, m_up);
} }
m_invMatrix = glm::inverse(m_viewMatrix); m_invMatrix = glm::inverse(m_viewMatrix);
} }
void Camera::CalculateProjectionMatrix() { void Camera::CalculateProjectionMatrix() {
// RH_ZO: Right-Handed, Zero-to-One depth (Vulkan/D3D standard)
m_projectionMatrix = glm::perspectiveRH_ZO(glm::radians(fovAngle), m_aspectRatio, m_zNear, m_zFar); m_projectionMatrix = glm::perspectiveRH_ZO(glm::radians(fovAngle), m_aspectRatio, m_zNear, m_zFar);
// CRITICAL VULKAN FIX: Flip Y-axis
// This keeps the world upright and fixes winding order issues
m_projectionMatrix[1][1] *= -1;
} }
void Camera::ClearTarget() { // ---------------------------------------------------------
m_useTarget = false; // Helpers to keep orientation consistent
m_forward = glm::normalize(m_target - m_position); // ---------------------------------------------------------
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
m_up = glm::normalize(glm::cross(m_right, m_forward));
}
void Camera::SetTarget(const glm::vec3& target) {
m_target = target;
m_useTarget = true;
// m_forward = glm::normalize(m_target - m_position);
// m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
// m_up = glm::normalize(glm::cross(m_right, m_forward));
}
void Camera::Target(const glm::vec3& target) {
glm::vec3 directionToTarget = glm::normalize(target - m_position);
m_forward = glm::normalize(target - m_position);
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
m_up = glm::normalize(glm::cross(m_right, m_forward));
m_viewMatrix = glm::lookAt(m_position, m_position + m_forward, m_up);
m_invMatrix = glm::inverse(m_viewMatrix);
}
void Camera::SetRotation(float yawRadians, float pitchRadians) { void Camera::SetRotation(float yawRadians, float pitchRadians) {
m_yaw = yawRadians; m_yaw = yawRadians;
m_pitch = glm::clamp( m_pitch = glm::clamp(pitchRadians, -glm::half_pi<float>() + 0.001f, glm::half_pi<float>() - 0.001f);
pitchRadians,
-glm::half_pi<float>() + 0.001f,
glm::half_pi<float>() - 0.001f
);
// Yaw around world Y, pitch around local Z (same convention you used) glm::vec3 front;
const glm::mat4 yawMatrix = glm::rotate(glm::mat4(1.0f), -m_yaw, glm::vec3(0, 1, 0)); front.x = cos(m_yaw) * cos(m_pitch);
const glm::mat4 pitchMatrix = glm::rotate(glm::mat4(1.0f), m_pitch, glm::vec3(0, 0, 1)); front.y = sin(m_pitch);
const glm::mat4 rotation = yawMatrix * pitchMatrix; front.z = sin(m_yaw) * cos(m_pitch);
m_forward = glm::normalize(front);
// Forward is +X in your camera space
m_forward = glm::normalize(glm::vec3(rotation * glm::vec4(1, 0, 0, 0)));
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
m_up = glm::normalize(glm::cross(m_right, m_forward)); m_up = glm::normalize(glm::cross(m_right, m_forward));
m_useTarget = false; // rotation overrides target mode
} }
void Camera::SetRotation(const glm::vec2& yawPitchRadians) { void Camera::SetRotation(const glm::vec2& yawPitchRadians) {
SetRotation(yawPitchRadians.x, yawPitchRadians.y); SetRotation(yawPitchRadians.x, yawPitchRadians.y);
} }
void Camera::SetTarget(const glm::vec3& target) {
}
void Camera::ClearTarget() {
}
void Camera::Target(const glm::vec3& target) {
m_target = target;
m_useTarget = true;
m_forward = glm::normalize(target - m_position);
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0)));
m_up = glm::normalize(glm::cross(m_right, m_forward));
}

View File

@@ -147,7 +147,7 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
void GfxDevice::recreateSwapchain(int width, int height) { void GfxDevice::recreateSwapchain(int width, int height) {
assert(width != 0 && height != 0); assert(width != 0 && height != 0);
waitIdle(); waitIdle();
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, false); swapchain.recreateSwapchain(*this, swapchainFormat, width, height, true);
} }
VkCommandBuffer GfxDevice::beginFrame() { VkCommandBuffer GfxDevice::beginFrame() {

View File

@@ -2,6 +2,7 @@
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h> #include <stb_image.h>
#include <tiny_gltf.h>
ImageData::~ImageData() ImageData::~ImageData()
{ {

View File

@@ -134,6 +134,12 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st
CreateShaderModule(vertCode, &m_vertShaderModule); CreateShaderModule(vertCode, &m_vertShaderModule);
// vkutil::addDebugLabel(
// m_device.getDevice(),
// m_fragShaderModule,
// VertFileName.c_str()
// );
VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
@@ -155,6 +161,13 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st
CreateShaderModule(fragCode, &m_fragShaderModule); CreateShaderModule(fragCode, &m_fragShaderModule);
// vkutil::addDebugLabel(
// m_device.getDevice(),
// m_fragShaderModule,
// FragFileName.c_str()
// );
VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;

View File

@@ -10,17 +10,10 @@ MeshPipeline::~MeshPipeline() {
void MeshPipeline::init(GfxDevice& gfxDevice, VkFormat drawImageFormat, VkFormat depthImageFormat) { void MeshPipeline::init(GfxDevice& gfxDevice, VkFormat drawImageFormat, VkFormat depthImageFormat) {
const auto& device = gfxDevice.getDevice(); const auto& device = gfxDevice.getDevice();
// const auto vertexShader = vkutil::loadShaderModule(AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.vert.spv"), device); const auto vertexShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
// const auto fragShader = vkutil::loadShaderModule(AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.frag.spv"), device); const auto fragShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.frag");
const auto vertexShader = AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.vert.spv");
const auto fragShader = AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.frag.spv");
// vkutil::addDebugLabel(device, vertexShader, "mesh.vert");
// vkutil::addDebugLabel(device, vertexShader, "mesh.frag");
const auto bufferRange = VkPushConstantRange{ const auto bufferRange = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
.offset = 0, .offset = 0,

View File

@@ -91,6 +91,16 @@ void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
vkSetDebugUtilsObjectNameEXT(device, &nameInfo); vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
} }
void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_SHADER_MODULE,
.objectHandle = (std::uint64_t)shaderModule,
.pObjectName = label,
};
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) { void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{ const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,

View File

@@ -8,7 +8,7 @@
// #include "Managers/ResourceManager.h" // #include "Managers/ResourceManager.h"
GameObject::~GameObject() { GameObject::~GameObject() {
spdlog::debug("GameObject destroyed: {}", GetName()); // spdlog::debug("GameObject destroyed: {}", GetName());
} }
void GameObject::SetActiveDirty() { void GameObject::SetActiveDirty() {

View File

@@ -9,12 +9,11 @@ Object::~Object() {
if (!m_BeingDestroyed) { if (!m_BeingDestroyed) {
assert(false && "Objects destructor called before destroy"); assert(false && "Objects destructor called before destroy");
} }
spdlog::debug("Object Destroyed: {}", m_Name); // spdlog::debug("Object Destroyed: {}", m_Name);
} }
void Object::Destroy() { void Object::Destroy() {
std::cout << "Marked Object for destruction: " << m_Name << std::endl; // spdlog::debug("Object marked for destruction: {}", m_Name);
spdlog::debug("Object Destroyed: {}", m_Name);
m_BeingDestroyed = true; m_BeingDestroyed = true;
} }

View File

@@ -22,31 +22,28 @@ target_include_directories(lightkeeper PRIVATE "${CMAKE_CURRENT_LIST_DIR}/includ
target_link_libraries(lightkeeper PRIVATE destrum::destrum) target_link_libraries(lightkeeper PRIVATE destrum::destrum)
#symlink_assets(lightkeeper) set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src")
set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime")
set(LK_SHADER_SRC "${CMAKE_CURRENT_LIST_DIR}/assets_src/shaders") set(OUTPUT_GAME_ASSETS_DIR "${CMAKE_BINARY_DIR}/assets/game")
set(LK_SHADER_OUT "${CMAKE_CURRENT_LIST_DIR}/assets_runtime/shaders")
include(../cmake/compile_shaders.cmake)
compile_glsl_to_spv(lightkeeper "${LK_SHADER_SRC}" "${LK_SHADER_OUT}" LK_SPV)
add_dependencies(lightkeeper lightkeeper_shaders)
set(ENGINE_ASSETS_SRC "${CMAKE_SOURCE_DIR}/destrum/assets_runtime")
set(GAME_ASSETS_SRC "${CMAKE_SOURCE_DIR}/lightkeeper/assets_runtime")
set(ENGINE_ASSETS_DST "$<TARGET_FILE_DIR:lightkeeper>/assets/engine")
set(GAME_ASSETS_DST "$<TARGET_FILE_DIR:lightkeeper>/assets/game")
add_custom_command(TARGET lightkeeper POST_BUILD add_custom_command(TARGET lightkeeper POST_BUILD
# ensure parent dir exists COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_GAME_ASSETS_DIR}"
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:lightkeeper>/assets" COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUTPUT_GAME_ASSETS_DIR}"
COMMAND ${CMAKE_COMMAND} -E create_symlink "${ASSETS_RUNTIME_DIR}" "${OUTPUT_GAME_ASSETS_DIR}"
# remove destinations if they already exist (dir OR symlink)
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ENGINE_ASSETS_DST}"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${GAME_ASSETS_DST}"
# create symlinks
COMMAND ${CMAKE_COMMAND} -E create_symlink "${ENGINE_ASSETS_SRC}" "${ENGINE_ASSETS_DST}"
COMMAND ${CMAKE_COMMAND} -E create_symlink "${GAME_ASSETS_SRC}" "${GAME_ASSETS_DST}"
VERBATIM VERBATIM
) )
add_custom_target(_internal_clean_game_assets
COMMAND TheChef
--input "${ASSETS_SRC_DIR}"
--output "${ASSETS_RUNTIME_DIR}"
--clean
)
add_custom_target(_internal_cook_game_assets ALL
COMMAND TheChef
--input "${ASSETS_SRC_DIR}"
--output "${ASSETS_RUNTIME_DIR}"
DEPENDS TheChef
)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

View File

@@ -7,10 +7,12 @@
#include "destrum/Components/MeshRendererComponent.h" #include "destrum/Components/MeshRendererComponent.h"
#include "destrum/Components/Rotator.h" #include "destrum/Components/Rotator.h"
#include "destrum/Components/Spinner.h"
#include "destrum/Components/OrbitAndSpin.h"
#include "destrum/ObjectModel/GameObject.h" #include "destrum/ObjectModel/GameObject.h"
#include "destrum/Util/ModelLoader.h"
LightKeeper::LightKeeper(): App(), renderer(meshCache, materialCache) { LightKeeper::LightKeeper(): App(), renderer(meshCache, materialCache) {
} }
LightKeeper::~LightKeeper() { LightKeeper::~LightKeeper() {
@@ -27,13 +29,15 @@ void LightKeeper::customInit() {
std::string fileStr(file.begin(), file.end()); std::string fileStr(file.begin(), file.end());
spdlog::info("Read from assetfstest.txt: {}", fileStr); spdlog::info("Read from assetfstest.txt: {}", fileStr);
testMesh.name = "Test Mesh"; testMesh.name = "Test Mesh";
testMesh.vertices = vertices; // testMesh.vertices = vertices;
testMesh.indices = indices; // testMesh.indices = indices;
auto list_of_models = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string());
testMesh = list_of_models[0];
testMeshID = meshCache.addMesh(gfxDevice, testMesh); testMeshID = meshCache.addMesh(gfxDevice, testMesh);
spdlog::info("TestMesh uploaded with id: {}", testMeshID); spdlog::info("TestMesh uploaded with id: {}", testMeshID);
const auto testimgpath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png"); const auto testimgpath = AssetFS::GetInstance().GetFullPath("game://kitty.png");
auto testimgID = gfxDevice.loadImageFromFile(testimgpath); auto testimgID = gfxDevice.loadImageFromFile(testimgpath);
spdlog::info("Test image loaded with id: {}", testimgID); spdlog::info("Test image loaded with id: {}", testimgID);
testMaterialID = materialCache.addMaterial(gfxDevice, { testMaterialID = materialCache.addMaterial(gfxDevice, {
@@ -50,6 +54,35 @@ void LightKeeper::customInit() {
auto meshComp = testCube->AddComponent<MeshRendererComponent>(); auto meshComp = testCube->AddComponent<MeshRendererComponent>();
meshComp->SetMeshID(testMeshID); meshComp->SetMeshID(testMeshID);
meshComp->SetMaterialID(testMaterialID); meshComp->SetMaterialID(testMaterialID);
testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
//rotate 180 around X axis
testCube->GetTransform().SetLocalRotation(glm::quat(glm::vec3(glm::radians(180.0f), 0.0f, 0.0f)));
//
auto globeRoot = std::make_shared<GameObject>("GlobeRoot");
globeRoot->GetTransform().SetWorldPosition(glm::vec3(0.0f));
globeRoot->AddComponent<Spinner>(glm::vec3(0, 1, 0), 1.0f); // spin around Y, rad/sec
scene.Add(globeRoot);
const int count = 100;
const float radius = 5.0f;
const float orbitRadius = 5.0f;
for (int i = 0; i < count; ++i) {
auto childCube = std::make_shared<GameObject>(fmt::format("ChildCube{}", i));
auto childMeshComp = childCube->AddComponent<MeshRendererComponent>();
childMeshComp->SetMeshID(testMeshID);
childMeshComp->SetMaterialID(testMaterialID);
childCube->GetTransform().SetWorldScale(glm::vec3(0.1f));
// Add orbit + self spin
auto orbit = childCube->AddComponent<OrbitAndSpin>(orbitRadius, glm::vec3(0.0f));
orbit->Randomize(1337u + (uint32_t)i); // stable random per index
scene.Add(childCube);
}
// testCube->AddComponent<Rotator>(10, 5); // testCube->AddComponent<Rotator>(10, 5);
@@ -63,7 +96,6 @@ void LightKeeper::customUpdate(float dt) {
} }
void LightKeeper::customDraw() { void LightKeeper::customDraw() {
renderer.beginDrawing(gfxDevice); renderer.beginDrawing(gfxDevice);
const RenderContext ctx{ const RenderContext ctx{