temp
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user