diff --git a/CMakeLists.txt b/CMakeLists.txt index b92082c..f76378a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,16 @@ endif() add_subdirectory(destrum) add_subdirectory(lightkeeper) -add_subdirectory(TheChef) \ No newline at end of file +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 +) \ No newline at end of file diff --git a/TheChef b/TheChef new file mode 160000 index 0000000..faaf7fa --- /dev/null +++ b/TheChef @@ -0,0 +1 @@ +Subproject commit faaf7fa12094f4aac8cb8e1082565096c005f51c diff --git a/destrum/CMakeLists.txt b/destrum/CMakeLists.txt index e525605..ec77056 100644 --- a/destrum/CMakeLists.txt +++ b/destrum/CMakeLists.txt @@ -6,6 +6,8 @@ set(SRC_FILES "src/Components/MeshRendererComponent.cpp" "src/Components/Rotator.cpp" + "src/Components/Spinner.cpp" + "src/Components/OrbitAndSpin.cpp" "src/Graphics/BindlessSetManager.cpp" "src/Graphics/Camera.cpp" @@ -38,6 +40,7 @@ set(SRC_FILES "src/FS/AssetFS.cpp" + "src/FS/Manifest.cpp" ) add_library(destrum ${SRC_FILES}) @@ -63,6 +66,7 @@ target_link_libraries(destrum nlohmann_json::nlohmann_json spdlog::spdlog stb::image + tinygltf PRIVATE freetype::freetype @@ -98,15 +102,23 @@ target_compile_definitions(destrum set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src") 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 --input "${ASSETS_SRC_DIR}" --output "${ASSETS_RUNTIME_DIR}" --clean ) -add_custom_target(cook_assets ALL +add_custom_target(_internal_cook_engine_assets ALL COMMAND TheChef --input "${ASSETS_SRC_DIR}" --output "${ASSETS_RUNTIME_DIR}" diff --git a/destrum/assets_runtime/assetfstest.txt b/destrum/assets_runtime/assetfstest.txt deleted file mode 100644 index 6769dd6..0000000 --- a/destrum/assets_runtime/assetfstest.txt +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/destrum/include/destrum/Components/OrbitAndSpin.h b/destrum/include/destrum/Components/OrbitAndSpin.h new file mode 100644 index 0000000..08221d2 --- /dev/null +++ b/destrum/include/destrum/Components/OrbitAndSpin.h @@ -0,0 +1,48 @@ +#ifndef ORBITANDSPIN_H +#define ORBITANDSPIN_H + +#include +#include +#include + +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 diff --git a/destrum/include/destrum/Components/Rotator.h b/destrum/include/destrum/Components/Rotator.h index fb6b3e3..6efbdd9 100644 --- a/destrum/include/destrum/Components/Rotator.h +++ b/destrum/include/destrum/Components/Rotator.h @@ -4,6 +4,8 @@ #include #include +#include + class Rotator final : public Component { public: explicit Rotator(GameObject& parent, float distance, float speed); @@ -15,15 +17,29 @@ public: void Update() override; - void SetDistance(float distance) { m_Distance = distance; } + void SetDistance(float distance); 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: float m_Distance{}; float m_Speed{}; 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 diff --git a/destrum/include/destrum/Components/Spinner.h b/destrum/include/destrum/Components/Spinner.h new file mode 100644 index 0000000..04b6062 --- /dev/null +++ b/destrum/include/destrum/Components/Spinner.h @@ -0,0 +1,25 @@ +#ifndef SPINNER_H +#define SPINNER_H + +#include +#include + +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 diff --git a/destrum/include/destrum/FS/AssetFS.h b/destrum/include/destrum/FS/AssetFS.h index 5f6657c..98ee034 100644 --- a/destrum/include/destrum/FS/AssetFS.h +++ b/destrum/include/destrum/FS/AssetFS.h @@ -7,9 +7,14 @@ #include +#include + + struct FSMount { std::string scheme; // "engine", "game" std::filesystem::path root; + std::optional manifest; + }; class AssetFS final: public Singleton { @@ -21,6 +26,8 @@ public: [[nodiscard]] std::filesystem::path GetFullPath(std::string_view vpath) const; + [[nodiscard]] std::filesystem::path GetCookedPathForFile(std::string_view vpath) const; + private: static std::vector ReadFile(const std::filesystem::path& fullPath); diff --git a/destrum/include/destrum/FS/Manifest.h b/destrum/include/destrum/FS/Manifest.h new file mode 100644 index 0000000..ecc7ad1 --- /dev/null +++ b/destrum/include/destrum/FS/Manifest.h @@ -0,0 +1,32 @@ +#ifndef MANIFEST_H +#define MANIFEST_H + +#include +#include +#include + +struct ManifestAsset { + std::string src; // "shaders/mesh.frag" + std::optional 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 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 diff --git a/destrum/include/destrum/Graphics/Resources/Mesh.h b/destrum/include/destrum/Graphics/Resources/Mesh.h index c4821a9..b8b5684 100644 --- a/destrum/include/destrum/Graphics/Resources/Mesh.h +++ b/destrum/include/destrum/Graphics/Resources/Mesh.h @@ -94,12 +94,23 @@ static std::vector vertices = { }; static std::vector indices = { - 0, 1, 2, 2, 3, 0, // Front (+Z) - 4, 5, 6, 6, 7, 4, // Back (-Z) <-- fixed - 8, 9,10, 10,11, 8, // Right (+X) - 12,13,14, 14,15,12, // Left (-X) - 16,17,18, 18,19,16, // Top (+Y) - 20,21,22, 22,23,20 // Bottom(-Y) + // Front (+Z) + 0, 2, 1, 0, 3, 2, + + // Back (-Z) + 4, 6, 5, 4, 7, 6, + + // 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 }; diff --git a/destrum/include/destrum/Util/ModelLoader.h b/destrum/include/destrum/Util/ModelLoader.h index a8ead79..0a83040 100644 --- a/destrum/include/destrum/Util/ModelLoader.h +++ b/destrum/include/destrum/Util/ModelLoader.h @@ -1,15 +1,28 @@ #ifndef MODELLOADER_H #define MODELLOADER_H -namespace ModelLoader { - // CPUMesh loader with tinygltf - // - Loads first scene (or default scene), iterates nodes, extracts mesh primitives. - // - Handles POSITION/NORMAL/TANGENT/TEXCOORD_0 and indices. - // - Computes minPos/maxPos. - // - Optionally merges primitives into a single CPUMesh per glTF mesh. +// CPUMesh loader with tinygltf +// - Loads first scene (or default scene), iterates nodes, extracts mesh primitives. +// - Handles POSITION/NORMAL/TANGENT/TEXCOORD_0 and indices. +// - Computes minPos/maxPos. +// - 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 + #include +#include // translate/scale +#include // quat +#include // mat4_cast + +#include #include #include @@ -20,25 +33,9 @@ namespace ModelLoader { #include #include #include +#include - struct CPUMesh { - std::vector indices; - - struct Vertex { - glm::vec3 position; - float uv_x{}; - glm::vec3 normal; - float uv_y{}; - glm::vec4 tangent; - }; - - std::vector vertices; - - std::string name; - - glm::vec3 minPos; - glm::vec3 maxPos; - }; +namespace ModelLoader { // -------------------- helpers -------------------- @@ -84,13 +81,10 @@ namespace ModelLoader { size_t stride = bv.byteStride ? size_t(bv.byteStride) : packedStride; - const size_t start = - size_t(bv.byteOffset) + size_t(accessor.byteOffset); - - if (start + accessor.count * stride > buf.data.size() + 0ull) { - // 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 size_t start = 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()) { + throw std::runtime_error("Accessor start is out of buffer bounds"); } const std::uint8_t* ptr = buf.data.data() + start; @@ -104,8 +98,6 @@ namespace ModelLoader { 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) { const std::uint8_t* p = base + i * stride; const float x = ReadAs(p + 0); @@ -155,15 +147,62 @@ namespace ModelLoader { 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(node.matrix[c * 4 + r]); + return m; + } + + glm::vec3 t(0.0f); + if (node.translation.size() == 3) { + t = glm::vec3( + static_cast(node.translation[0]), + static_cast(node.translation[1]), + static_cast(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(node.rotation[3]), // w + static_cast(node.rotation[0]), // x + static_cast(node.rotation[1]), // y + static_cast(node.rotation[2]) // z + ); + } + + glm::vec3 s(1.0f); + if (node.scale.size() == 3) { + s = glm::vec3( + static_cast(node.scale[0]), + static_cast(node.scale[1]), + static_cast(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 -------------------- static CPUMesh LoadPrimitiveIntoCPUMesh(const tinygltf::Model& model, - const tinygltf::Primitive& prim, - const std::string& nameForMesh) { + const tinygltf::Primitive& prim, + const std::string& nameForMesh, + const glm::mat4& world) { CPUMesh out{}; out.name = nameForMesh; - // POSITION is required for our vertex buffer + // POSITION is required auto itPos = prim.attributes.find("POSITION"); if (itPos == prim.attributes.end()) { 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()) { accNormal = &model.accessors.at(it->second); 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()) { accTangent = &model.accessors.at(it->second); @@ -211,7 +250,7 @@ namespace ModelLoader { auto p = GetAccessorDataPtrAndStride(model, *accNormal); nrmBase = p.first; nrmStride = p.second; - if (size_t(accNormal->count) != vertexCount) accNormal = nullptr; // mismatch -> ignore + if (size_t(accNormal->count) != vertexCount) accNormal = nullptr; } if (accTangent) { auto p = GetAccessorDataPtrAndStride(model, *accTangent); @@ -229,23 +268,31 @@ namespace ModelLoader { // Allocate vertices out.vertices.resize(vertexCount); - // Bounds init + // Bounds init (in world space, because we transform positions) glm::vec3 mn{std::numeric_limits::infinity()}; glm::vec3 mx{-std::numeric_limits::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) { 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); + // Normal if (accNormal) { - v.normal = ReadVec3Float(nrmBase, nrmStride, i); + const glm::vec3 nLocal = ReadVec3Float(nrmBase, nrmStride, i); + v.normal = glm::normalize(nrmMat * nLocal); } else { v.normal = glm::vec3(0.0f, 1.0f, 0.0f); } + // UV if (accUV0) { glm::vec2 uv = ReadVec2Float(uvBase, uvStride, i); v.uv_x = uv.x; @@ -255,8 +302,11 @@ namespace ModelLoader { v.uv_y = 0.0f; } + // Tangent 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 { 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.maxPos = (vertexCount > 0) ? mx : glm::vec3(0.0f); - // Indices (optional; if absent, build linear indices) + // Indices if (prim.indices >= 0) { const tinygltf::Accessor& accIdx = model.accessors.at(prim.indices); if (accIdx.type != TINYGLTF_TYPE_SCALAR) { @@ -279,11 +329,16 @@ namespace ModelLoader { out.indices[i] = ReadIndexAsU32(model, accIdx, i); } } else { - // Non-indexed primitive: make it indexed out.indices.resize(vertexCount); for (size_t i = 0; i < vertexCount; ++i) out.indices[i] = static_cast(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; } @@ -293,7 +348,7 @@ namespace ModelLoader { dst.vertices.insert(dst.vertices.end(), src.vertices.begin(), src.vertices.end()); dst.indices.reserve(dst.indices.size() + src.indices.size()); - for (std::uint32_t idx: src.indices) { + for (std::uint32_t idx : src.indices) { dst.indices.push_back(baseVertex + idx); } @@ -301,7 +356,6 @@ namespace ModelLoader { dst.minPos = src.minPos; dst.maxPos = src.maxPos; } else { - // update bounds using src bounds (cheap) dst.minPos = glm::vec3( std::min(dst.minPos.x, src.minPos.x), std::min(dst.minPos.y, src.minPos.y), @@ -317,6 +371,11 @@ namespace ModelLoader { // -------------------- public API -------------------- + struct NodeStackItem { + int nodeIdx; + glm::mat4 parentWorld; + }; + // Option A: return one CPUMesh per *primitive* encountered in the scene static std::vector LoadGLTF_CPUMeshes_PerPrimitive(const std::string& path) { tinygltf::TinyGLTF loader; @@ -339,22 +398,31 @@ namespace ModelLoader { std::vector result; const tinygltf::Scene& scene = model.scenes.at(sceneIndex); - // Traverse nodes and pull mesh primitives (ignoring node transforms in this basic example) - std::vector stack(scene.nodes.begin(), scene.nodes.end()); - while (!stack.empty()) { - int nodeIdx = stack.back(); - stack.pop_back(); - const tinygltf::Node& node = model.nodes.at(nodeIdx); + std::vector stack; + stack.reserve(scene.nodes.size()); + for (int n : scene.nodes) stack.push_back({n, glm::mat4(1.0f)}); - 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; const tinygltf::Mesh& mesh = model.meshes.at(node.mesh); for (size_t p = 0; p < mesh.primitives.size(); ++p) { const tinygltf::Primitive& prim = mesh.primitives[p]; - CPUMesh cpu = LoadPrimitiveIntoCPUMesh(model, prim, mesh.name.empty() ? ("mesh_" + std::to_string(node.mesh)) : mesh.name); - // Optionally make name unique per primitive + CPUMesh cpu = LoadPrimitiveIntoCPUMesh( + model, + prim, + mesh.name.empty() ? ("mesh_" + std::to_string(node.mesh)) : mesh.name, + world + ); cpu.name += "_prim" + std::to_string(p); result.push_back(std::move(cpu)); } @@ -363,7 +431,8 @@ namespace ModelLoader { 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 LoadGLTF_CPUMeshes_MergedPerMesh(const std::string& path) { tinygltf::TinyGLTF loader; tinygltf::Model model; @@ -385,14 +454,19 @@ namespace ModelLoader { std::vector result; const tinygltf::Scene& scene = model.scenes.at(sceneIndex); - std::vector stack(scene.nodes.begin(), scene.nodes.end()); + std::vector stack; + stack.reserve(scene.nodes.size()); + for (int n : scene.nodes) stack.push_back({n, glm::mat4(1.0f)}); while (!stack.empty()) { - int nodeIdx = stack.back(); + NodeStackItem it = stack.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; const tinygltf::Mesh& mesh = model.meshes.at(node.mesh); @@ -403,10 +477,10 @@ namespace ModelLoader { merged.maxPos = glm::vec3(-std::numeric_limits::infinity()); bool any = false; - for (const tinygltf::Primitive& prim: mesh.primitives) { - CPUMesh part = LoadPrimitiveIntoCPUMesh(model, prim, merged.name); + for (const tinygltf::Primitive& prim : mesh.primitives) { + CPUMesh part = LoadPrimitiveIntoCPUMesh(model, prim, merged.name, world); if (!any) { - merged = part; + merged = std::move(part); any = true; } else { AppendMesh(merged, part); @@ -418,6 +492,7 @@ namespace ModelLoader { return result; } -} -#endif //MODELLOADER_H +} // namespace ModelLoader + +#endif // MODELLOADER_H diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index 563d2f6..6e33578 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -153,4 +153,5 @@ void App::run() { } void App::cleanup() { + customCleanup(); } diff --git a/destrum/src/Components/OrbitAndSpin.cpp b/destrum/src/Components/OrbitAndSpin.cpp new file mode 100644 index 0000000..404e580 --- /dev/null +++ b/destrum/src/Components/OrbitAndSpin.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +#include "destrum/ObjectModel/Transform.h" + +static glm::vec3 RandomUnitVector(std::mt19937& rng) +{ + // uniform on sphere + std::uniform_real_distribution 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 orbitSpeedDist(0.2f, 1.5f); + std::uniform_real_distribution spinSpeedDist(0.5f, 6.0f); + std::uniform_real_distribution 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)); +} diff --git a/destrum/src/Components/Rotator.cpp b/destrum/src/Components/Rotator.cpp index 49d3a3a..2a6d193 100644 --- a/destrum/src/Components/Rotator.cpp +++ b/destrum/src/Components/Rotator.cpp @@ -1,17 +1,95 @@ #include -Rotator::Rotator(GameObject& parent, float distance, float speed): -Component(parent, "Rotator"), -m_Distance(distance), -m_Speed(speed), -m_CurrentAngle(0), -m_OriginalPosition(GetTransform().GetWorldPosition()) -{} +#include // glm::rotate(vec3, angle, axis) +#include +#include +#include // glm::quat, glm::angleAxis +#include // operator*(quat, vec3) -void Rotator::Update() { - m_CurrentAngle += m_Speed * static_cast(0.001); - const float x = cos(m_CurrentAngle) * m_Distance; - const float y = sin(m_CurrentAngle) * m_Distance; - GetTransform().SetLocalPosition(m_OriginalPosition + glm::vec3(x, y, 0)); +glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis) +{ + // Pick any vector that is not parallel to axis, then cross to get perpendicular. + const glm::vec3 a = glm::normalize(axis); + 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); } diff --git a/destrum/src/Components/Spinner.cpp b/destrum/src/Components/Spinner.cpp new file mode 100644 index 0000000..c9793fb --- /dev/null +++ b/destrum/src/Components/Spinner.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#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 I’ll 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); +} diff --git a/destrum/src/FS/AssetFS.cpp b/destrum/src/FS/AssetFS.cpp index 8a676ef..184a90e 100644 --- a/destrum/src/FS/AssetFS.cpp +++ b/destrum/src/FS/AssetFS.cpp @@ -12,7 +12,17 @@ void AssetFS::Init(std::filesystem::path exeDir) { void AssetFS::Mount(std::string scheme, std::filesystem::path root) { 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 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"); } +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 AssetFS::ReadFile(const std::filesystem::path& fullPath) { std::ifstream file(fullPath, std::ios::binary); if (!file) { diff --git a/destrum/src/FS/Manifest.cpp b/destrum/src/FS/Manifest.cpp new file mode 100644 index 0000000..fb787dc --- /dev/null +++ b/destrum/src/FS/Manifest.cpp @@ -0,0 +1,43 @@ +#include +#include + +#include +#include + + +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(); + asset.type = a.at("type").get(); + 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(); + } + + // 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; +} diff --git a/destrum/src/Graphics/Camera.cpp b/destrum/src/Graphics/Camera.cpp index 6e912b5..17a3169 100644 --- a/destrum/src/Graphics/Camera.cpp +++ b/destrum/src/Graphics/Camera.cpp @@ -6,11 +6,13 @@ #include #include #include +#include -#include "glm/gtx/norm.hpp" - - -Camera::Camera(const glm::vec3& position, const glm::vec3& up): m_position{position}, m_up{up} { +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 + m_yaw = -glm::half_pi(); + m_pitch = 0.0f; } void Camera::Update(float deltaTime) { @@ -22,166 +24,126 @@ void Camera::Update(float deltaTime) { moveSpeed *= 2.0f; } - // Controller speed boost (pad0 LB) const SDL_JoystickID pad0 = input.GetPadInstanceId(0); if (pad0 >= 0 && input.IsPadButtonDown(pad0, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) { moveSpeed *= 3.0f; } - // Clamp pitch like your old code - m_pitch = glm::clamp(m_pitch, -glm::half_pi() + 0.01f, glm::half_pi() - 0.01f); - // ========================= - // Movement (Keyboard) + // Look Input (Keyboard & Controller) // ========================= - 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 += 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 - + const float keyLookSpeed = glm::radians(120.0f); 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_LEFT)) m_yaw -= keyLookSpeed * deltaTime; if (input.IsKeyDown(SDL_SCANCODE_RIGHT)) m_yaw += keyLookSpeed * deltaTime; - // ========================= - // Look (Controller right stick) - // ========================= if (pad0 >= 0) { const float rx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTX); const float ry = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTY); - - const float padLookSpeed = 2.2f; // radians/sec at full deflection + const float padLookSpeed = 2.2f; 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() + 0.01f, glm::half_pi() - 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)); - const glm::mat4 pitchMatrix = glm::rotate(glm::mat4(1.0f), m_pitch, glm::vec3(0, 0, 1)); - const glm::mat4 rotation = yawMatrix * pitchMatrix; + // ========================= + // Update Basis Vectors + // ========================= + // 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_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); + m_forward = glm::normalize(front); + 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)); - // 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(); CalculateViewMatrix(); } void Camera::CalculateViewMatrix() { if (m_useTarget) { - 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)); - m_viewMatrix = glm::lookAt(m_position, m_target, m_up); + m_viewMatrix = glm::lookAt(m_position, m_target, glm::vec3(0, 1, 0)); } else { m_viewMatrix = glm::lookAt(m_position, m_position + m_forward, m_up); } - m_invMatrix = glm::inverse(m_viewMatrix); } 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); + + // 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; - 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); -} +// --------------------------------------------------------- +// Helpers to keep orientation consistent +// --------------------------------------------------------- void Camera::SetRotation(float yawRadians, float pitchRadians) { m_yaw = yawRadians; - m_pitch = glm::clamp( - pitchRadians, - -glm::half_pi() + 0.001f, - glm::half_pi() - 0.001f - ); + m_pitch = glm::clamp(pitchRadians, -glm::half_pi() + 0.001f, glm::half_pi() - 0.001f); - // Yaw around world Y, pitch around local Z (same convention you used) - const glm::mat4 yawMatrix = glm::rotate(glm::mat4(1.0f), -m_yaw, glm::vec3(0, 1, 0)); - const glm::mat4 pitchMatrix = glm::rotate(glm::mat4(1.0f), m_pitch, glm::vec3(0, 0, 1)); - const glm::mat4 rotation = yawMatrix * pitchMatrix; - - // Forward is +X in your camera space - m_forward = glm::normalize(glm::vec3(rotation * glm::vec4(1, 0, 0, 0))); + 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(front); m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); m_up = glm::normalize(glm::cross(m_right, m_forward)); - - m_useTarget = false; // rotation overrides target mode } void Camera::SetRotation(const glm::vec2& yawPitchRadians) { 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)); +} diff --git a/destrum/src/Graphics/GfxDevice.cpp b/destrum/src/Graphics/GfxDevice.cpp index 774fb85..82b4e29 100644 --- a/destrum/src/Graphics/GfxDevice.cpp +++ b/destrum/src/Graphics/GfxDevice.cpp @@ -147,7 +147,7 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync) void GfxDevice::recreateSwapchain(int width, int height) { assert(width != 0 && height != 0); waitIdle(); - swapchain.recreateSwapchain(*this, swapchainFormat, width, height, false); + swapchain.recreateSwapchain(*this, swapchainFormat, width, height, true); } VkCommandBuffer GfxDevice::beginFrame() { diff --git a/destrum/src/Graphics/ImageLoader.cpp b/destrum/src/Graphics/ImageLoader.cpp index e4fd2ab..e5f2b19 100644 --- a/destrum/src/Graphics/ImageLoader.cpp +++ b/destrum/src/Graphics/ImageLoader.cpp @@ -2,6 +2,7 @@ #define STB_IMAGE_IMPLEMENTATION #include +#include ImageData::~ImageData() { diff --git a/destrum/src/Graphics/Pipeline.cpp b/destrum/src/Graphics/Pipeline.cpp index 38882ac..45c0dd4 100644 --- a/destrum/src/Graphics/Pipeline.cpp +++ b/destrum/src/Graphics/Pipeline.cpp @@ -134,6 +134,12 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st CreateShaderModule(vertCode, &m_vertShaderModule); + // vkutil::addDebugLabel( + // m_device.getDevice(), + // m_fragShaderModule, + // VertFileName.c_str() + // ); + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 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); + // vkutil::addDebugLabel( + // m_device.getDevice(), + // m_fragShaderModule, + // FragFileName.c_str() + // ); + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; diff --git a/destrum/src/Graphics/Pipelines/MeshPipeline.cpp b/destrum/src/Graphics/Pipelines/MeshPipeline.cpp index 7051af1..a9b2614 100644 --- a/destrum/src/Graphics/Pipelines/MeshPipeline.cpp +++ b/destrum/src/Graphics/Pipelines/MeshPipeline.cpp @@ -10,17 +10,10 @@ MeshPipeline::~MeshPipeline() { void MeshPipeline::init(GfxDevice& gfxDevice, VkFormat drawImageFormat, VkFormat depthImageFormat) { const auto& device = gfxDevice.getDevice(); - // const auto vertexShader = vkutil::loadShaderModule(AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.vert.spv"), device); - // const auto fragShader = vkutil::loadShaderModule(AssetFS::GetInstance().GetFullPath("engine://shaders/mesh.frag.spv"), device); + const auto vertexShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert"); + 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{ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, .offset = 0, diff --git a/destrum/src/Graphics/Util.cpp b/destrum/src/Graphics/Util.cpp index e50f128..f58e702 100644 --- a/destrum/src/Graphics/Util.cpp +++ b/destrum/src/Graphics/Util.cpp @@ -91,6 +91,16 @@ void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) { 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) { const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, diff --git a/destrum/src/ObjectModel/GameObject.cpp b/destrum/src/ObjectModel/GameObject.cpp index 46dd39e..9a8abc6 100644 --- a/destrum/src/ObjectModel/GameObject.cpp +++ b/destrum/src/ObjectModel/GameObject.cpp @@ -8,7 +8,7 @@ // #include "Managers/ResourceManager.h" GameObject::~GameObject() { - spdlog::debug("GameObject destroyed: {}", GetName()); + // spdlog::debug("GameObject destroyed: {}", GetName()); } void GameObject::SetActiveDirty() { diff --git a/destrum/src/ObjectModel/Object.cpp b/destrum/src/ObjectModel/Object.cpp index 027b153..4c5f245 100644 --- a/destrum/src/ObjectModel/Object.cpp +++ b/destrum/src/ObjectModel/Object.cpp @@ -9,12 +9,11 @@ Object::~Object() { if (!m_BeingDestroyed) { assert(false && "Objects destructor called before destroy"); } - spdlog::debug("Object Destroyed: {}", m_Name); + // spdlog::debug("Object Destroyed: {}", m_Name); } void Object::Destroy() { - std::cout << "Marked Object for destruction: " << m_Name << std::endl; - spdlog::debug("Object Destroyed: {}", m_Name); + // spdlog::debug("Object marked for destruction: {}", m_Name); m_BeingDestroyed = true; } diff --git a/lightkeeper/CMakeLists.txt b/lightkeeper/CMakeLists.txt index fbe3f77..a58b311 100644 --- a/lightkeeper/CMakeLists.txt +++ b/lightkeeper/CMakeLists.txt @@ -22,31 +22,28 @@ target_include_directories(lightkeeper PRIVATE "${CMAKE_CURRENT_LIST_DIR}/includ target_link_libraries(lightkeeper PRIVATE destrum::destrum) -#symlink_assets(lightkeeper) - -set(LK_SHADER_SRC "${CMAKE_CURRENT_LIST_DIR}/assets_src/shaders") -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 "$/assets/engine") -set(GAME_ASSETS_DST "$/assets/game") +set(ASSETS_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_src") +set(ASSETS_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/assets_runtime") +set(OUTPUT_GAME_ASSETS_DIR "${CMAKE_BINARY_DIR}/assets/game") add_custom_command(TARGET lightkeeper POST_BUILD - # ensure parent dir exists - COMMAND ${CMAKE_COMMAND} -E make_directory "$/assets" - - # 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}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_GAME_ASSETS_DIR}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUTPUT_GAME_ASSETS_DIR}" + COMMAND ${CMAKE_COMMAND} -E create_symlink "${ASSETS_RUNTIME_DIR}" "${OUTPUT_GAME_ASSETS_DIR}" 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 +) + diff --git a/lightkeeper/assets_src/kitty.glb b/lightkeeper/assets_src/kitty.glb new file mode 100644 index 0000000..e9ad353 Binary files /dev/null and b/lightkeeper/assets_src/kitty.glb differ diff --git a/lightkeeper/assets_src/kitty.png b/lightkeeper/assets_src/kitty.png new file mode 100644 index 0000000..36fe4d4 Binary files /dev/null and b/lightkeeper/assets_src/kitty.png differ diff --git a/lightkeeper/assets_src/tano.png b/lightkeeper/assets_src/tano.png new file mode 100644 index 0000000..c7f9cd6 Binary files /dev/null and b/lightkeeper/assets_src/tano.png differ diff --git a/lightkeeper/assets_src/test.glb b/lightkeeper/assets_src/test.glb new file mode 100644 index 0000000..4073ccd Binary files /dev/null and b/lightkeeper/assets_src/test.glb differ diff --git a/lightkeeper/src/Lightkeeper.cpp b/lightkeeper/src/Lightkeeper.cpp index dd52110..b095ade 100644 --- a/lightkeeper/src/Lightkeeper.cpp +++ b/lightkeeper/src/Lightkeeper.cpp @@ -7,10 +7,12 @@ #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/ModelLoader.h" LightKeeper::LightKeeper(): App(), renderer(meshCache, materialCache) { - } LightKeeper::~LightKeeper() { @@ -27,13 +29,15 @@ void LightKeeper::customInit() { std::string fileStr(file.begin(), file.end()); spdlog::info("Read from assetfstest.txt: {}", fileStr); testMesh.name = "Test Mesh"; - testMesh.vertices = vertices; - testMesh.indices = indices; + // testMesh.vertices = vertices; + // 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); 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); spdlog::info("Test image loaded with id: {}", testimgID); testMaterialID = materialCache.addMaterial(gfxDevice, { @@ -50,6 +54,35 @@ void LightKeeper::customInit() { auto meshComp = testCube->AddComponent(); meshComp->SetMeshID(testMeshID); meshComp->SetMaterialID(testMaterialID); + testCube->AddComponent(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("GlobeRoot"); + globeRoot->GetTransform().SetWorldPosition(glm::vec3(0.0f)); + globeRoot->AddComponent(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(fmt::format("ChildCube{}", i)); + + auto childMeshComp = childCube->AddComponent(); + childMeshComp->SetMeshID(testMeshID); + childMeshComp->SetMaterialID(testMaterialID); + + childCube->GetTransform().SetWorldScale(glm::vec3(0.1f)); + + // Add orbit + self spin + auto orbit = childCube->AddComponent(orbitRadius, glm::vec3(0.0f)); + orbit->Randomize(1337u + (uint32_t)i); // stable random per index + + scene.Add(childCube); + } // testCube->AddComponent(10, 5); @@ -63,7 +96,6 @@ void LightKeeper::customUpdate(float dt) { } void LightKeeper::customDraw() { - renderer.beginDrawing(gfxDevice); const RenderContext ctx{