temp
This commit is contained in:
2
TheChef
2
TheChef
Submodule TheChef updated: 3a7b137165...df0da96c38
@@ -47,6 +47,8 @@ set(SRC_FILES
|
||||
|
||||
"src/FS/AssetFS.cpp"
|
||||
"src/FS/Manifest.cpp"
|
||||
|
||||
"src/Util/DeltaTime.cpp"
|
||||
)
|
||||
|
||||
add_library(destrum ${SRC_FILES})
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,26 +4,33 @@
|
||||
|
||||
#include "bindless.glsl"
|
||||
|
||||
layout(location = 0) in vec2 uv; // from fullscreen triangle: 0..2 range
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(push_constant) uniform SkyboxPC {
|
||||
mat4 invViewProj; // inverse(Proj * View) (your current setup)
|
||||
vec4 cameraPos; // xyz = camera world position
|
||||
uint skyboxTextureId; // index into textureCubes[]
|
||||
mat4 invViewProj;
|
||||
vec4 skyboxRot[3];
|
||||
vec3 cameraPos;
|
||||
uint skyboxTextureId;
|
||||
} pcs;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 ndcXY = uv * 2.0 - 1.0;
|
||||
|
||||
vec4 ndc = vec4(ndcXY, 1.0, 1.0);
|
||||
|
||||
vec4 world = pcs.invViewProj * ndc;
|
||||
vec3 worldPos = world.xyz / world.w;
|
||||
|
||||
vec3 dir = normalize(worldPos - pcs.cameraPos.xyz);
|
||||
vec3 dir = normalize(worldPos - pcs.cameraPos);
|
||||
dir.y *= -1.0;
|
||||
|
||||
mat3 skyboxRot = mat3(
|
||||
pcs.skyboxRot[0].xyz,
|
||||
pcs.skyboxRot[1].xyz,
|
||||
pcs.skyboxRot[2].xyz
|
||||
);
|
||||
dir = skyboxRot * dir;
|
||||
|
||||
outColor = sampleTextureCubeLinear(pcs.skyboxTextureId, dir);
|
||||
}
|
||||
}
|
||||
@@ -14,27 +14,22 @@
|
||||
|
||||
class SkinningPipeline;
|
||||
|
||||
class Animator : public Component {
|
||||
class Animator final: public Component {
|
||||
public:
|
||||
explicit Animator(GameObject& parent);
|
||||
|
||||
// Component interface
|
||||
void Update() override;
|
||||
void ImGuiInspector() override;
|
||||
|
||||
// Animation control
|
||||
void addClip(std::shared_ptr<SkeletalAnimation> clip);
|
||||
void play(const std::string& name, float blendTime = 0.f);
|
||||
void stop();
|
||||
|
||||
bool isPlaying() const { return m_current.clip != nullptr; }
|
||||
const std::string& currentClipName() const { return m_currentClipName; }
|
||||
float currentTime() const { return m_current.time; }
|
||||
[[nodiscard]] bool isPlaying() const { return m_current.clip != nullptr; }
|
||||
[[nodiscard]] const std::string& currentClipName() const { return m_currentClipName; }
|
||||
[[nodiscard]] float currentTime() const { return m_current.time; }
|
||||
|
||||
// Called during draw command building
|
||||
std::size_t uploadJointMatrices(SkinningPipeline& pipeline,
|
||||
const Skeleton& skeleton,
|
||||
std::size_t frameIndex);
|
||||
std::size_t uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex);
|
||||
Skeleton* getSkeleton() {
|
||||
return &m_skeleton;
|
||||
}
|
||||
|
||||
@@ -32,12 +32,15 @@ private:
|
||||
std::unique_ptr<Pipeline> pipeline;
|
||||
ImageID skyboxTextureId{NULL_IMAGE_ID};
|
||||
|
||||
glm::mat4 skyboxRotation{1.f};
|
||||
|
||||
struct SkyboxPushConstants {
|
||||
glm::mat4 invViewProj;
|
||||
glm::vec4 cameraPos;
|
||||
glm::mat4 invViewProj;
|
||||
glm::vec4 skyboxRot[3];
|
||||
glm::vec3 cameraPos;
|
||||
std::uint32_t skyboxTextureId;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif //SKYBOXPIPELINE_H
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <destrum/Graphics/ids.h>
|
||||
|
||||
@@ -28,6 +29,8 @@ struct Skeleton {
|
||||
std::vector<Joint> joints;
|
||||
std::vector<std::string> jointNames;
|
||||
std::vector<int> parentIndex; // -1 = root, built once after loading
|
||||
|
||||
glm::mat4 rootPreTransform{1.f};
|
||||
};
|
||||
|
||||
inline void buildParentIndex(Skeleton& skeleton) {
|
||||
|
||||
31
destrum/include/destrum/Util/DeltaTime.h
Normal file
31
destrum/include/destrum/Util/DeltaTime.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef DELTATIME_H
|
||||
#define DELTATIME_H
|
||||
|
||||
#include <chrono>
|
||||
#include <destrum/Singleton.h>
|
||||
|
||||
class Time final: public Singleton<Time> {
|
||||
public:
|
||||
Time(const Time& other) = delete;
|
||||
Time(Time&& other) = delete;
|
||||
Time& operator=(const Time& other) = delete;
|
||||
Time& operator=(Time&& other) = delete;
|
||||
|
||||
[[nodiscard]] double FixedDeltaTime() const;
|
||||
[[nodiscard]] double DeltaTime() const;
|
||||
|
||||
[[nodiscard]] std::chrono::nanoseconds SleepDuration() const;
|
||||
|
||||
void Update();
|
||||
|
||||
private:
|
||||
friend class Singleton;
|
||||
|
||||
Time() = default;
|
||||
static constexpr double m_FixedDeltaTime{1.0 / 60.0};
|
||||
static constexpr double FPS{60};
|
||||
double m_DeltaTime{};
|
||||
std::chrono::high_resolution_clock::time_point m_PrevTime;
|
||||
};
|
||||
|
||||
#endif //DELTATIME_H
|
||||
@@ -12,6 +12,10 @@
|
||||
// - For skinned meshes, vertex positions are kept in local/bind-pose space
|
||||
// (identity world transform) so the skinning shader can apply joint matrices
|
||||
// correctly at runtime.
|
||||
// - rootPreTransform on Skeleton captures any non-bone ancestor transforms
|
||||
// (e.g. the Assimp-injected coordinate-system node for glTF/FBX) so that
|
||||
// computeJointMatrices can prepend it to root joints. This fixes the
|
||||
// 90-degree rotation + mirror that appears when those transforms are dropped.
|
||||
//
|
||||
// Link against: assimp (e.g. -lassimp)
|
||||
// Supports any format Assimp understands (.gltf, .glb, .fbx, .obj, …).
|
||||
@@ -198,19 +202,32 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
||||
// registering only nodes that correspond to actual bones.
|
||||
// This guarantees joints are ordered parent-before-child, which is required
|
||||
// by Animator::computeJointMatrices.
|
||||
//
|
||||
// Non-bone ancestor nodes (e.g. the Assimp coordinate-system root, or an
|
||||
// "Armature" node in Blender exports) are NOT registered as joints, but
|
||||
// their accumulated transform is carried forward so that the first real
|
||||
// bone joint inherits it via skeleton.rootPreTransform. Without this the
|
||||
// 90° rotation / mirror baked into those nodes is silently dropped, causing
|
||||
// skinned meshes to appear rotated and/or mirrored relative to static ones.
|
||||
JointId nextId = 0;
|
||||
|
||||
struct StackItem { const aiNode* node; int parentIdx; };
|
||||
std::vector<StackItem> stack{{ scene->mRootNode, -1 }};
|
||||
struct StackItem {
|
||||
const aiNode* node;
|
||||
int parentIdx;
|
||||
glm::mat4 accWorld; // accumulated transform of non-bone ancestors
|
||||
};
|
||||
std::vector<StackItem> stack{{ scene->mRootNode, -1, glm::mat4{1.f} }};
|
||||
|
||||
while (!stack.empty()) {
|
||||
auto [node, parentIdx] = stack.back();
|
||||
auto [node, parentIdx, accWorld] = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
std::string name(node->mName.C_Str());
|
||||
int myIdx = parentIdx; // default: pass parent through to children
|
||||
int myIdx = parentIdx;
|
||||
glm::mat4 myAccWorld = accWorld; // only used while still outside the bone hierarchy
|
||||
|
||||
if (boneOffsets.count(name)) {
|
||||
// ── This node IS a bone ──────────────────────────────────────────
|
||||
const JointId id = nextId++;
|
||||
myIdx = static_cast<int>(id);
|
||||
|
||||
@@ -220,19 +237,37 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
||||
skeleton.jointNames.push_back(name);
|
||||
skeleton.inverseBindMatrices.push_back(boneOffsets[name]);
|
||||
|
||||
// Grow hierarchy to accommodate this id
|
||||
// Grow hierarchy arrays to accommodate this id
|
||||
while (skeleton.hierarchy.size() <= id)
|
||||
skeleton.hierarchy.push_back({});
|
||||
skeleton.hierarchy[id].id = id;
|
||||
|
||||
skeleton.hierarchy[id].id = id; // record the node's own id
|
||||
|
||||
if (parentIdx >= 0)
|
||||
if (parentIdx >= 0) {
|
||||
skeleton.hierarchy[parentIdx].children.push_back(id);
|
||||
} else {
|
||||
// First bone encountered with no bone parent: capture any
|
||||
// non-bone ancestor transforms accumulated so far.
|
||||
// Animator::computeJointMatrices prepends this to root joints.
|
||||
skeleton.rootPreTransform = accWorld;
|
||||
glm::mat4& m = skeleton.rootPreTransform;
|
||||
|
||||
// Normalize each basis vector to remove scale
|
||||
m[0] = glm::vec4(glm::normalize(glm::vec3(m[0])), 0.f);
|
||||
m[1] = glm::vec4(glm::normalize(glm::vec3(m[1])), 0.f);
|
||||
m[2] = glm::vec4(glm::normalize(glm::vec3(m[2])), 0.f);
|
||||
}
|
||||
|
||||
// Once we are inside the bone hierarchy the pre-transform is fully
|
||||
// absorbed — children carry identity as their accWorld.
|
||||
myAccWorld = glm::mat4{1.f};
|
||||
} else {
|
||||
// ── Not a bone: accumulate this node's local transform ───────────
|
||||
myAccWorld = accWorld * ToGLM(node->mTransformation);
|
||||
}
|
||||
|
||||
// Push children in reverse so left-most child is processed first
|
||||
for (int c = static_cast<int>(node->mNumChildren) - 1; c >= 0; --c)
|
||||
stack.push_back({ node->mChildren[c], myIdx });
|
||||
stack.push_back({ node->mChildren[c], myIdx, myAccWorld });
|
||||
}
|
||||
|
||||
buildParentIndex(skeleton);
|
||||
@@ -318,11 +353,11 @@ static std::vector<SkeletalAnimation> LoadAnimations(const aiScene* scene,
|
||||
SkeletalAnimation::Track track;
|
||||
track.jointIndex = jointIdx;
|
||||
|
||||
// FIX: position, rotation and scale channels can have different
|
||||
// keyframe counts and independent time axes. Build the track from
|
||||
// the position channel's timeline and pick the nearest rotation/
|
||||
// scale key for each sample rather than assuming index alignment.
|
||||
// This avoids corrupted keyframes when counts differ.
|
||||
// Position, rotation and scale channels can have different keyframe
|
||||
// counts and independent time axes. Build the track from the position
|
||||
// channel's timeline and pick the nearest rotation/scale key for each
|
||||
// sample rather than assuming index alignment. This avoids corrupted
|
||||
// keyframes when counts differ.
|
||||
const unsigned int numPos = ch->mNumPositionKeys;
|
||||
const unsigned int numRot = ch->mNumRotationKeys;
|
||||
const unsigned int numSca = ch->mNumScalingKeys;
|
||||
@@ -464,6 +499,11 @@ struct SkinnedModel {
|
||||
// so they remain in bind-pose space. The skinning shader applies joint matrices
|
||||
// at runtime — baking the node transform into positions would break that.
|
||||
//
|
||||
// The skeleton carries a rootPreTransform that captures any non-bone ancestor
|
||||
// node transforms (coordinate-system correction nodes injected by Assimp for
|
||||
// glTF/FBX, Blender "Armature" nodes, etc.). Animator::computeJointMatrices
|
||||
// must prepend this to the global matrix of every root joint (parentIndex < 0).
|
||||
//
|
||||
// Usage:
|
||||
// auto model = ModelLoader::LoadSkinnedModel("character.glb");
|
||||
// MeshId id = meshCache.uploadMesh(model.meshes[0]);
|
||||
@@ -499,7 +539,7 @@ static SkinnedModel LoadSkinnedModel(const std::string& path) {
|
||||
? std::string(aiMesh->mName.C_Str())
|
||||
: ("mesh_" + std::to_string(node->mMeshes[m]));
|
||||
|
||||
// FIX: pass identity matrix for skinned meshes so vertices stay in
|
||||
// Pass identity matrix for skinned meshes so vertices stay in
|
||||
// bind-pose space. The inverse bind matrices and joint transforms
|
||||
// are already expressed in that space — baking the node world
|
||||
// transform into positions would offset everything incorrectly.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <destrum/App.h>
|
||||
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "glm/gtx/transform.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
@@ -35,41 +36,33 @@ void App::init(const AppParams& params) {
|
||||
gfxDevice.init(window, params.appName, false);
|
||||
|
||||
InputManager::GetInstance().Init();
|
||||
Time::GetInstance().Update();
|
||||
|
||||
customInit();
|
||||
}
|
||||
|
||||
void App::run() {
|
||||
using clock = std::chrono::steady_clock;
|
||||
Time::GetInstance().Update(); // initialize delta timing
|
||||
|
||||
const float targetHz = 60.0f;
|
||||
const float dt = 1.0f / targetHz;
|
||||
const float maxFrameTime = 0.25f; // clamp big hitches
|
||||
const int maxSteps = 5; // prevent spiral of death
|
||||
|
||||
auto prevTime = clock::now();
|
||||
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
|
||||
const int maxSteps = 5; // prevent spiral of death
|
||||
float accumulator = 0.0f;
|
||||
|
||||
isRunning = true;
|
||||
while (isRunning) {
|
||||
const auto frameStart = clock::now();
|
||||
float frameTimeSec = std::chrono::duration<float>(frameStart - prevTime).count();
|
||||
prevTime = frameStart;
|
||||
// ---- Update timing ---
|
||||
Time::GetInstance().Update();
|
||||
float dt = static_cast<float>(Time::GetInstance().DeltaTime());
|
||||
|
||||
if (frameTimeSec > 0.07f && frameTimeSec < 5.f) {
|
||||
spdlog::warn("Frame drop detected, time: {:.4f}s", frameTimeSec);
|
||||
}
|
||||
if (dt > 0.25f) dt = 0.25f;
|
||||
|
||||
if (frameTimeSec > maxFrameTime) frameTimeSec = maxFrameTime;
|
||||
if (frameTimeSec < 0.0f) frameTimeSec = 0.0f;
|
||||
|
||||
accumulator += frameTimeSec;
|
||||
|
||||
if (frameTimeSec > 0.0f) {
|
||||
const float newFPS = 1.0f / frameTimeSec;
|
||||
if (dt > 0.0f) {
|
||||
float newFPS = 1.0f / dt;
|
||||
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
|
||||
}
|
||||
|
||||
accumulator += dt;
|
||||
|
||||
InputManager::GetInstance().BeginFrame();
|
||||
camera.Update(dt);
|
||||
|
||||
@@ -92,63 +85,43 @@ void App::run() {
|
||||
}
|
||||
}
|
||||
if (!isRunning) break;
|
||||
|
||||
customUpdate(dt);
|
||||
|
||||
// ---- Swapchain resize check once per frame ----
|
||||
int steps = 0;
|
||||
while (accumulator >= fixedDt && steps < maxSteps) {
|
||||
// physics.Update(fixedDt);
|
||||
|
||||
SDL_SetWindowTitle(
|
||||
window,
|
||||
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str()
|
||||
);
|
||||
|
||||
accumulator -= fixedDt;
|
||||
steps++;
|
||||
}
|
||||
if (steps == maxSteps) accumulator = 0.0f;
|
||||
|
||||
const float alpha = accumulator / fixedDt;
|
||||
|
||||
if (!gfxDevice.needsSwapchainRecreate()) {
|
||||
customDraw();
|
||||
}
|
||||
|
||||
if (gfxDevice.needsSwapchainRecreate()) {
|
||||
spdlog::info("Recreating swapchain to size: {}x{}", m_params.windowSize.x, m_params.windowSize.y);
|
||||
gfxDevice.recreateSwapchain(m_params.windowSize.x, m_params.windowSize.y);
|
||||
onWindowResize(m_params.windowSize.x, m_params.windowSize.y);
|
||||
}
|
||||
|
||||
// ---- Fixed updates (no event polling inside) ----
|
||||
int steps = 0;
|
||||
while (accumulator >= dt && steps < maxSteps) {
|
||||
//Set window title to fps
|
||||
SDL_SetWindowTitle(
|
||||
window,
|
||||
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str());
|
||||
accumulator -= dt;
|
||||
steps++;
|
||||
}
|
||||
// If we hit the step cap, drop leftover time to recover smoothly
|
||||
if (steps == maxSteps) accumulator = 0.0f;
|
||||
|
||||
// Optional interpolation factor for rendering
|
||||
const float alpha = accumulator / dt;
|
||||
|
||||
// ---- Render ----
|
||||
if (!gfxDevice.needsSwapchainRecreate()) {
|
||||
// glm::mat4 objMatrix = glm::translate(glm::mat4(1.f), glm::vec3(0.f, -3.0f, 0.f));
|
||||
//
|
||||
// renderer.beginDrawing(gfxDevice);
|
||||
// renderer.drawMesh(testMeshID, glm::mat4(1.f), testMaterialID);
|
||||
// renderer.drawMesh(testMeshID, objMatrix, 0);
|
||||
// renderer.endDrawing();
|
||||
//
|
||||
// const auto cmd = gfxDevice.beginFrame();
|
||||
// const auto& drawImage = renderer.getDrawImage(gfxDevice);
|
||||
//
|
||||
// renderer.draw(cmd, gfxDevice, camera, GameRenderer::SceneData{
|
||||
// camera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f
|
||||
// });
|
||||
//
|
||||
// gfxDevice.endFrame(cmd, drawImage, {
|
||||
// .clearColor = {{0.f, 0.f, 0.5f, 1.f}},
|
||||
// .drawImageBlitRect = glm::ivec4{}
|
||||
// });
|
||||
customDraw();
|
||||
}
|
||||
|
||||
// ---- Frame cap (if you still want it) ----
|
||||
if (frameLimit) {
|
||||
const auto targetEnd = frameStart + std::chrono::duration_cast<clock::duration>(
|
||||
std::chrono::duration<float>(dt)
|
||||
);
|
||||
std::this_thread::sleep_until(targetEnd);
|
||||
|
||||
auto sleepTime = Time::GetInstance().SleepDuration();
|
||||
if (sleepTime.count() > 0) {
|
||||
std::this_thread::sleep_for(sleepTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,17 @@
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
Animator::Animator(GameObject& parent)
|
||||
: Component(parent, "Animator") {}
|
||||
|
||||
// ─── Component interface ──────────────────────────────────────────────────────
|
||||
|
||||
void Animator::Update() {
|
||||
if (!m_current.clip) return;
|
||||
|
||||
// Time delta comes from your engine's time system
|
||||
// const float dt = Time::GetDeltaTime();
|
||||
|
||||
const float dt = 0.016f; // ~60 FPS
|
||||
const float dt = Time::GetInstance().DeltaTime();
|
||||
|
||||
m_current.time += dt * m_current.speed;
|
||||
if (m_current.clip->looped)
|
||||
@@ -37,11 +33,6 @@ void Animator::Update() {
|
||||
m_previous = {};
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("Playing '{}': time = {:.2f}s, blend = {:.2f}",
|
||||
m_currentClipName.empty() ? "(none)" : m_currentClipName.c_str(),
|
||||
m_current.time,
|
||||
m_blendT);
|
||||
}
|
||||
|
||||
void Animator::ImGuiInspector() {
|
||||
@@ -65,8 +56,6 @@ void Animator::ImGuiInspector() {
|
||||
// }
|
||||
}
|
||||
|
||||
// ─── Animation control ────────────────────────────────────────────────────────
|
||||
|
||||
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
||||
m_clips[clip->name] = std::move(clip);
|
||||
}
|
||||
@@ -94,24 +83,18 @@ void Animator::stop() {
|
||||
m_currentClipName = {};
|
||||
}
|
||||
|
||||
// ─── Joint matrix upload ──────────────────────────────────────────────────────
|
||||
|
||||
std::size_t Animator::uploadJointMatrices(SkinningPipeline& pipeline,
|
||||
const Skeleton& skeleton,
|
||||
std::size_t frameIndex) {
|
||||
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
|
||||
auto matrices = computeJointMatrices(skeleton);
|
||||
return pipeline.appendJointMatrices(matrices, frameIndex);
|
||||
return ctx.renderer.getSkinningPipeline().appendJointMatrices(matrices, frameIndex);
|
||||
}
|
||||
|
||||
// ─── Private: pose evaluation ─────────────────────────────────────────────────
|
||||
|
||||
std::vector<glm::mat4> Animator::computeJointMatrices(const Skeleton& skeleton) {
|
||||
const std::size_t numJoints = skeleton.joints.size();
|
||||
std::vector<glm::mat4> global(numJoints, glm::mat4{1.f});
|
||||
std::vector<glm::mat4> result(numJoints, glm::mat4{1.f});
|
||||
|
||||
auto findTrack = [](const SkeletalAnimation* clip,
|
||||
std::uint32_t idx) -> const SkeletalAnimation::Track* {
|
||||
std::uint32_t idx) -> const SkeletalAnimation::Track* {
|
||||
if (!clip) return nullptr;
|
||||
for (auto& t : clip->tracks)
|
||||
if (t.jointIndex == idx) return &t;
|
||||
@@ -123,26 +106,31 @@ std::vector<glm::mat4> Animator::computeJointMatrices(const Skeleton& skeleton)
|
||||
glm::quat rot = glm::identity<glm::quat>();
|
||||
glm::vec3 sc = {1.f, 1.f, 1.f};
|
||||
|
||||
if (const auto* track = findTrack(m_current.clip, i)) {
|
||||
tr = sampleTranslation(*track, m_current.time);
|
||||
rot = sampleRotation (*track, m_current.time);
|
||||
sc = sampleScale (*track, m_current.time);
|
||||
}
|
||||
// Sample current clip if one is playing
|
||||
if (m_current.clip) {
|
||||
if (const auto* track = findTrack(m_current.clip, i)) {
|
||||
tr = sampleTranslation(*track, m_current.time);
|
||||
rot = sampleRotation (*track, m_current.time);
|
||||
sc = sampleScale (*track, m_current.time);
|
||||
}
|
||||
|
||||
if (m_previous.clip && m_blendT < 1.f) {
|
||||
if (const auto* prev = findTrack(m_previous.clip, i)) {
|
||||
tr = glm::mix (sampleTranslation(*prev, m_previous.time), tr, m_blendT);
|
||||
rot = glm::slerp(sampleRotation (*prev, m_previous.time), rot, m_blendT);
|
||||
sc = glm::mix (sampleScale (*prev, m_previous.time), sc, m_blendT);
|
||||
if (m_previous.clip && m_blendT < 1.f) {
|
||||
if (const auto* prev = findTrack(m_previous.clip, i)) {
|
||||
tr = glm::mix (sampleTranslation(*prev, m_previous.time), tr, m_blendT);
|
||||
rot = glm::slerp(sampleRotation (*prev, m_previous.time), rot, m_blendT);
|
||||
sc = glm::mix (sampleScale (*prev, m_previous.time), sc, m_blendT);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no clip: tr/rot/sc stay as identity → rest pose
|
||||
|
||||
glm::mat4 local = glm::translate(glm::mat4{1.f}, tr)
|
||||
* glm::mat4_cast(rot)
|
||||
* glm::scale(glm::mat4{1.f}, sc);
|
||||
|
||||
const int parent = skeleton.parentIndex[i];
|
||||
global[i] = (parent < 0) ? local : global[parent] * local;
|
||||
global[i] = (parent < 0) ? skeleton.rootPreTransform * local
|
||||
: global[parent] * local;
|
||||
|
||||
result[i] = global[i] * skeleton.inverseBindMatrices[i];
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ void MeshRendererComponent::Render(const RenderContext& ctx) {
|
||||
std::uint32_t frameIdx = GameState::GetInstance().Gfx().getCurrentFrameIndex();
|
||||
|
||||
const std::size_t jointMatricesStartIndex = animator->uploadJointMatrices(
|
||||
ctx.renderer.getSkinningPipeline(),
|
||||
*skeleton, // skeleton stored on GPUMesh (or pass from CPUMesh)
|
||||
ctx,
|
||||
*skeleton,
|
||||
frameIdx
|
||||
);
|
||||
|
||||
|
||||
@@ -10,15 +10,13 @@
|
||||
|
||||
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<float>();
|
||||
m_pitch = 0.0f;
|
||||
}
|
||||
|
||||
void Camera::Update(float deltaTime) {
|
||||
auto& input = InputManager::GetInstance();
|
||||
const auto& input = InputManager::GetInstance();
|
||||
|
||||
// --- tuning ---
|
||||
float moveSpeed = m_movementSpeed;
|
||||
if (input.IsKeyDown(SDL_SCANCODE_LSHIFT) || input.IsKeyDown(SDL_SCANCODE_RSHIFT)) {
|
||||
moveSpeed *= 2.0f;
|
||||
@@ -29,10 +27,7 @@ void Camera::Update(float deltaTime) {
|
||||
moveSpeed *= 3.0f;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Look Input (Keyboard & Controller)
|
||||
// =========================
|
||||
const float keyLookSpeed = glm::radians(120.0f);
|
||||
constexpr 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;
|
||||
@@ -46,13 +41,8 @@ void Camera::Update(float deltaTime) {
|
||||
m_pitch -= ry * padLookSpeed * deltaTime; // Inverted to match stick convention
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// =========================
|
||||
// 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);
|
||||
@@ -62,9 +52,6 @@ void Camera::Update(float deltaTime) {
|
||||
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));
|
||||
|
||||
// =========================
|
||||
// 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;
|
||||
@@ -105,18 +92,11 @@ void Camera::CalculateViewMatrix() {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Helpers to keep orientation consistent
|
||||
// ---------------------------------------------------------
|
||||
|
||||
void Camera::SetRotation(float yawRadians, float pitchRadians) {
|
||||
m_yaw = yawRadians;
|
||||
m_pitch = glm::clamp(pitchRadians, -glm::half_pi<float>() + 0.001f, glm::half_pi<float>() - 0.001f);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include <destrum/Graphics/Pipelines/SkyboxPipeline.h>
|
||||
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
SkyboxPipeline::SkyboxPipeline(): pipelineLayout{nullptr} {
|
||||
@@ -72,12 +75,19 @@ void SkyboxPipeline::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camer
|
||||
return;
|
||||
}
|
||||
|
||||
//rotate skybox slowly for visual interest
|
||||
const float rotationSpeed = 0.01f; // radians per second
|
||||
//Rotate over the Y axis
|
||||
skyboxRotation = glm::rotate(skyboxRotation, rotationSpeed * static_cast<float>(Time::GetInstance().DeltaTime()), glm::vec3(0.f, 1.f, 0.f));
|
||||
|
||||
pipeline->bind(cmd);
|
||||
gfxDevice.bindBindlessDescSet(cmd, pipelineLayout);
|
||||
|
||||
const glm::mat3 r = glm::mat3(skyboxRotation);
|
||||
const auto pcs = SkyboxPushConstants{
|
||||
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
|
||||
.cameraPos = glm::vec4{camera.GetPosition(), 1.f},
|
||||
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
|
||||
.skyboxRot = { glm::vec4(r[0], 0.f), glm::vec4(r[1], 0.f), glm::vec4(r[2], 0.f) },
|
||||
.cameraPos = camera.GetPosition(),
|
||||
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
|
||||
};
|
||||
vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(SkyboxPushConstants), &pcs);
|
||||
|
||||
@@ -5,18 +5,16 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Init.h>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
#include "volk.h"
|
||||
|
||||
|
||||
void Swapchain::initSync(VkDevice device)
|
||||
{
|
||||
const auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
void Swapchain::initSync(VkDevice device) {
|
||||
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
const auto semaphoreCreateInfo = VkSemaphoreCreateInfo{
|
||||
constexpr auto semaphoreCreateInfo = VkSemaphoreCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
@@ -57,7 +55,7 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
|
||||
};
|
||||
|
||||
for (auto& sem : imageRenderSemaphores) {
|
||||
for (auto& sem: imageRenderSemaphores) {
|
||||
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
|
||||
}
|
||||
|
||||
@@ -150,8 +148,8 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||
void Swapchain::submitAndPresent(
|
||||
VkCommandBuffer cmd,
|
||||
VkQueue graphicsQueue,
|
||||
uint32_t imageIndex, // from vkAcquireNextImageKHR
|
||||
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
|
||||
uint32_t imageIndex, // from vkAcquireNextImageKHR
|
||||
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
|
||||
{
|
||||
auto& frame = frames[frameIndex]; // ✅ per-frame
|
||||
|
||||
@@ -164,14 +162,14 @@ void Swapchain::submitAndPresent(
|
||||
};
|
||||
|
||||
VkSemaphoreSubmitInfo waitInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
|
||||
|
||||
VkSemaphoreSubmitInfo signalInfo =
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
|
||||
renderFinished); // ✅ signal semaphore (per-image)
|
||||
vkinit::semaphoreSubmitInfo(
|
||||
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
|
||||
renderFinished); // ✅ signal semaphore (per-image)
|
||||
|
||||
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
|
||||
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
|
||||
@@ -189,4 +187,4 @@ void Swapchain::submitAndPresent(
|
||||
VkResult res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
|
||||
else if (res != VK_SUCCESS) dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
24
destrum/src/Util/DeltaTime.cpp
Normal file
24
destrum/src/Util/DeltaTime.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
double Time::FixedDeltaTime() const {
|
||||
return m_FixedDeltaTime;
|
||||
}
|
||||
|
||||
double Time::DeltaTime() const {
|
||||
return m_DeltaTime;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds Time::SleepDuration() const {
|
||||
constexpr auto msPerFrame = std::chrono::milliseconds(static_cast<int>(1000.f / FPS));
|
||||
const std::chrono::nanoseconds sleepTime = (m_PrevTime + msPerFrame - std::chrono::high_resolution_clock::now());
|
||||
|
||||
return sleepTime;
|
||||
}
|
||||
|
||||
void Time::Update() {
|
||||
const auto currentTime = std::chrono::high_resolution_clock::now();
|
||||
m_DeltaTime = std::chrono::duration<double>(currentTime - m_PrevTime).count();
|
||||
m_PrevTime = currentTime;
|
||||
}
|
||||
@@ -44,10 +44,11 @@ void LightKeeper::customInit() {
|
||||
|
||||
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
||||
|
||||
auto testCube = std::make_shared<GameObject>("TestCube");
|
||||
auto meshComp = testCube->AddComponent<MeshRendererComponent>();
|
||||
meshComp->SetMeshID(testMeshID);
|
||||
meshComp->SetMaterialID(testMaterialID);const int count = 100;
|
||||
// auto testCube = std::make_shared<GameObject>("TestCube");
|
||||
// auto meshComp = testCube->AddComponent<MeshRendererComponent>();
|
||||
// meshComp->SetMeshID(testMeshID);
|
||||
// meshComp->SetMaterialID(testMaterialID);
|
||||
const int count = 100;
|
||||
const float radius = 5.0f;
|
||||
|
||||
const float orbitRadius = 5.0f;
|
||||
@@ -67,9 +68,9 @@ void LightKeeper::customInit() {
|
||||
//
|
||||
// scene.Add(childCube);
|
||||
}
|
||||
testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
|
||||
// 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)));
|
||||
// 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));
|
||||
@@ -78,7 +79,7 @@ void LightKeeper::customInit() {
|
||||
|
||||
|
||||
|
||||
scene.Add(testCube);
|
||||
// scene.Add(testCube);
|
||||
|
||||
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
|
||||
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
|
||||
@@ -96,7 +97,7 @@ void LightKeeper::customInit() {
|
||||
|
||||
renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
|
||||
|
||||
|
||||
//
|
||||
const auto planeObj = std::make_shared<GameObject>("GroundPlane");
|
||||
const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
|
||||
const auto planeModel = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string());
|
||||
@@ -104,10 +105,10 @@ void LightKeeper::customInit() {
|
||||
|
||||
const auto planeTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://grass.png"));
|
||||
const auto planeMaterialID = materialCache.addMaterial(gfxDevice, {
|
||||
.baseColor = glm::vec3(1.f),
|
||||
.textureFilteringMode = TextureFilteringMode::Nearest,
|
||||
.diffuseTexture = planeTextureID,
|
||||
.name = "GroundPlaneMaterial",
|
||||
.baseColor = glm::vec3(1.f),
|
||||
.textureFilteringMode = TextureFilteringMode::Nearest,
|
||||
.diffuseTexture = planeTextureID,
|
||||
.name = "GroundPlaneMaterial",
|
||||
});
|
||||
planeMeshComp->SetMeshID(planeMeshID);
|
||||
planeMeshComp->SetMaterialID(planeMaterialID);
|
||||
@@ -121,13 +122,12 @@ void LightKeeper::customInit() {
|
||||
const auto CharObj = std::make_shared<GameObject>("Character");
|
||||
|
||||
auto charModel = ModelLoader::LoadSkinnedModel(
|
||||
AssetFS::GetInstance().GetFullPath("engine://char.fbx").generic_string()
|
||||
AssetFS::GetInstance().GetFullPath("engine://char2.fbx").generic_string()
|
||||
);
|
||||
|
||||
const auto charMeshID = meshCache.addMesh(gfxDevice, charModel.meshes[0]);
|
||||
|
||||
const auto charTextureID = gfxDevice.loadImageFromFile(
|
||||
AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
||||
const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
||||
const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
|
||||
.baseColor = glm::vec3(1.f),
|
||||
.diffuseTexture = charTextureID,
|
||||
@@ -146,16 +146,16 @@ void LightKeeper::customInit() {
|
||||
}
|
||||
|
||||
for (const auto& clip : charModel.animations)
|
||||
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||
|
||||
if (!charModel.animations.empty())
|
||||
// animator->play(charModel.animations[0].name);
|
||||
// animator->play("Armature|main");
|
||||
animator->play("Armature|mixamo.com");
|
||||
// animator->play("Armature|Armature|mixamo.com");
|
||||
// // animator->play(charModel.animations[0].name);
|
||||
animator->play("Armature|Armature|Armature|main");
|
||||
// or: animator->play("Run", 0.2f); // 0.2s cross-fade
|
||||
|
||||
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
||||
CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
||||
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
||||
scene.Add(CharObj);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user