Fix some stuff
This commit is contained in:
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -52,6 +52,10 @@ protected:
|
|||||||
bool frameLimit{false};
|
bool frameLimit{false};
|
||||||
float frameTime{0.f};
|
float frameTime{0.f};
|
||||||
float avgFPS{0.f};
|
float avgFPS{0.f};
|
||||||
|
|
||||||
|
bool resizePending = false;
|
||||||
|
std::chrono::steady_clock::time_point lastResizeTime{};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ public:
|
|||||||
|
|
||||||
VmaAllocator getAllocator() const { return allocator; }
|
VmaAllocator getAllocator() const { return allocator; }
|
||||||
|
|
||||||
|
vkb::Device getVkbDevice() const { return device; }
|
||||||
|
|
||||||
ImageID createImage(const vkutil::CreateImageInfo& createInfo, const std::string& debugName = "", void* pixelData = nullptr, ImageID imageId = NULL_IMAGE_ID);
|
ImageID createImage(const vkutil::CreateImageInfo& createInfo, const std::string& debugName = "", void* pixelData = nullptr, ImageID imageId = NULL_IMAGE_ID);
|
||||||
ImageID createDrawImage(VkFormat format, glm::ivec2 size, const std::string& debugName = "", ImageID imageId = NULL_IMAGE_ID);
|
ImageID createDrawImage(VkFormat format, glm::ivec2 size, const std::string& debugName = "", ImageID imageId = NULL_IMAGE_ID);
|
||||||
ImageID loadImageFromFile(const std::filesystem::path& path, VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT, bool mipMap = false, TextureIntent intent = TextureIntent::ColorSrgb);
|
ImageID loadImageFromFile(const std::filesystem::path& path, VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT, bool mipMap = false, TextureIntent intent = TextureIntent::ColorSrgb);
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ public:
|
|||||||
|
|
||||||
void cleanup(VkDevice device);
|
void cleanup(VkDevice device);
|
||||||
|
|
||||||
|
void setRenderWireframe(bool wireframe) {
|
||||||
|
m_renderWireframe = wireframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getRenderWireframe(){ return m_renderWireframe; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkPipelineLayout m_pipelineLayout;
|
VkPipelineLayout m_pipelineLayout;
|
||||||
@@ -43,6 +49,8 @@ private:
|
|||||||
std::uint32_t padding;
|
std::uint32_t padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool m_renderWireframe{false};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //MESHPIPELINE_H
|
#endif //MESHPIPELINE_H
|
||||||
|
|||||||
@@ -61,6 +61,14 @@ public:
|
|||||||
return *skinningPipeline;
|
return *skinningPipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setRenderWireframe(bool wireframe) {
|
||||||
|
meshPipeline->setRenderWireframe(wireframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getRenderWireframe() {
|
||||||
|
return meshPipeline->getRenderWireframe();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
|
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,13 @@
|
|||||||
#include <destrum/Graphics/SkeletalAnimation.h>
|
#include <destrum/Graphics/SkeletalAnimation.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ModelLoader {
|
namespace ModelLoader {
|
||||||
@@ -50,6 +52,21 @@ static glm::mat4 ToGLM(const aiMatrix4x4& m) {
|
|||||||
return glm::transpose(glm::make_mat4(&m.a1));
|
return glm::transpose(glm::make_mat4(&m.a1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static glm::vec3 ToGLM(const aiVector3D& v) {
|
||||||
|
return glm::vec3(v.x, v.y, v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::quat ToGLM(const aiQuaternion& q) {
|
||||||
|
// glm::quat constructor order is (w, x, y, z).
|
||||||
|
return glm::normalize(glm::quat(q.w, q.x, q.y, q.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::vec3 SafeNormalize(const glm::vec3& v, const glm::vec3& fallback) {
|
||||||
|
const float len2 = glm::dot(v, v);
|
||||||
|
if (len2 <= std::numeric_limits<float>::epsilon()) return fallback;
|
||||||
|
return v * (1.0f / std::sqrt(len2));
|
||||||
|
}
|
||||||
|
|
||||||
static void UpdateBounds(glm::vec3& mn, glm::vec3& mx, const glm::vec3& p) {
|
static void UpdateBounds(glm::vec3& mn, glm::vec3& mx, const glm::vec3& p) {
|
||||||
mn.x = std::min(mn.x, p.x);
|
mn.x = std::min(mn.x, p.x);
|
||||||
mn.y = std::min(mn.y, p.y);
|
mn.y = std::min(mn.y, p.y);
|
||||||
@@ -59,6 +76,47 @@ static void UpdateBounds(glm::vec3& mn, glm::vec3& mx, const glm::vec3& p) {
|
|||||||
mx.z = std::max(mx.z, p.z);
|
mx.z = std::max(mx.z, p.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NodeTRS {
|
||||||
|
glm::vec3 translation{0.0f, 0.0f, 0.0f};
|
||||||
|
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
glm::vec3 scale{1.0f, 1.0f, 1.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
static NodeTRS DecomposeNodeTransform(const aiNode* node) {
|
||||||
|
NodeTRS out{};
|
||||||
|
|
||||||
|
aiVector3D scaling;
|
||||||
|
aiVector3D translation;
|
||||||
|
aiQuaternion rotation;
|
||||||
|
node->mTransformation.Decompose(scaling, rotation, translation);
|
||||||
|
|
||||||
|
out.translation = ToGLM(translation);
|
||||||
|
out.rotation = ToGLM(rotation);
|
||||||
|
out.scale = ToGLM(scaling);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CollectNodeDefaults(const aiNode* node,
|
||||||
|
std::unordered_map<std::string, NodeTRS>& defaults) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
defaults[std::string(node->mName.C_Str())] = DecomposeNodeTransform(node);
|
||||||
|
|
||||||
|
for (unsigned int c = 0; c < node->mNumChildren; ++c)
|
||||||
|
CollectNodeDefaults(node->mChildren[c], defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, std::uint32_t>
|
||||||
|
BuildJointIndexMap(const Skeleton& skeleton) {
|
||||||
|
std::unordered_map<std::string, std::uint32_t> map;
|
||||||
|
map.reserve(skeleton.jointNames.size());
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < skeleton.jointNames.size(); ++i)
|
||||||
|
map[skeleton.jointNames[i]] = static_cast<std::uint32_t>(i);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Post-process flags ───────────────────────────────────────────────────────
|
// ─── Post-process flags ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
static constexpr unsigned int kImportFlags =
|
static constexpr unsigned int kImportFlags =
|
||||||
@@ -82,16 +140,17 @@ static const aiScene* LoadScene(Assimp::Importer& importer, const std::string& p
|
|||||||
// For skinned meshes, pass glm::mat4{1.f} so vertices stay in bind-pose space.
|
// For skinned meshes, pass glm::mat4{1.f} so vertices stay in bind-pose space.
|
||||||
// Skinning data is NOT populated here — call LoadSkinningData afterwards.
|
// Skinning data is NOT populated here — call LoadSkinningData afterwards.
|
||||||
static CPUMesh LoadAiMeshIntoCPUMesh(const aiMesh* mesh,
|
static CPUMesh LoadAiMeshIntoCPUMesh(const aiMesh* mesh,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const glm::mat4& world) {
|
const glm::mat4& world) {
|
||||||
CPUMesh out{};
|
CPUMesh out{};
|
||||||
out.name = name;
|
out.name = name;
|
||||||
|
|
||||||
const size_t vertexCount = mesh->mNumVertices;
|
const size_t vertexCount = mesh->mNumVertices;
|
||||||
out.vertices.resize(vertexCount);
|
out.vertices.resize(vertexCount);
|
||||||
|
|
||||||
const glm::mat3 nrmMat = glm::transpose(glm::inverse(glm::mat3(world)));
|
const glm::mat3 world3 = glm::mat3(world);
|
||||||
const glm::mat3 tanMat = glm::mat3(world);
|
const glm::mat3 nrmMat = glm::transpose(glm::inverse(world3));
|
||||||
|
const glm::mat3 tanMat = world3;
|
||||||
|
|
||||||
glm::vec3 mn{ std::numeric_limits<float>::infinity()};
|
glm::vec3 mn{ std::numeric_limits<float>::infinity()};
|
||||||
glm::vec3 mx{-std::numeric_limits<float>::infinity()};
|
glm::vec3 mx{-std::numeric_limits<float>::infinity()};
|
||||||
@@ -107,7 +166,8 @@ static CPUMesh LoadAiMeshIntoCPUMesh(const aiMesh* mesh,
|
|||||||
// Normal
|
// Normal
|
||||||
if (mesh->HasNormals()) {
|
if (mesh->HasNormals()) {
|
||||||
const aiVector3D& an = mesh->mNormals[i];
|
const aiVector3D& an = mesh->mNormals[i];
|
||||||
v.normal = glm::normalize(nrmMat * glm::vec3(an.x, an.y, an.z));
|
v.normal = SafeNormalize(nrmMat * glm::vec3(an.x, an.y, an.z),
|
||||||
|
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
} else {
|
} else {
|
||||||
v.normal = glm::vec3(0.0f, 1.0f, 0.0f);
|
v.normal = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
}
|
}
|
||||||
@@ -125,8 +185,10 @@ static CPUMesh LoadAiMeshIntoCPUMesh(const aiMesh* mesh,
|
|||||||
if (mesh->HasTangentsAndBitangents()) {
|
if (mesh->HasTangentsAndBitangents()) {
|
||||||
const aiVector3D& at = mesh->mTangents[i];
|
const aiVector3D& at = mesh->mTangents[i];
|
||||||
const aiVector3D& ab = mesh->mBitangents[i];
|
const aiVector3D& ab = mesh->mBitangents[i];
|
||||||
glm::vec3 t3 = glm::normalize(tanMat * glm::vec3(at.x, at.y, at.z));
|
glm::vec3 t3 = SafeNormalize(tanMat * glm::vec3(at.x, at.y, at.z),
|
||||||
glm::vec3 b3 = glm::normalize(tanMat * glm::vec3(ab.x, ab.y, ab.z));
|
glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
glm::vec3 b3 = SafeNormalize(tanMat * glm::vec3(ab.x, ab.y, ab.z),
|
||||||
|
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
glm::vec3 n3 = v.normal;
|
glm::vec3 n3 = v.normal;
|
||||||
float sign = (glm::dot(glm::cross(n3, t3), b3) < 0.0f) ? -1.0f : 1.0f;
|
float sign = (glm::dot(glm::cross(n3, t3), b3) < 0.0f) ? -1.0f : 1.0f;
|
||||||
v.tangent = glm::vec4(t3, sign);
|
v.tangent = glm::vec4(t3, sign);
|
||||||
@@ -182,6 +244,7 @@ static void AppendMesh(CPUMesh& dst, const CPUMesh& src) {
|
|||||||
|
|
||||||
static Skeleton LoadSkeleton(const aiScene* scene) {
|
static Skeleton LoadSkeleton(const aiScene* scene) {
|
||||||
Skeleton skeleton;
|
Skeleton skeleton;
|
||||||
|
skeleton.rootPreTransform = glm::mat4{1.0f};
|
||||||
|
|
||||||
// Pass 1: collect all bone names and their inverse bind matrices
|
// Pass 1: collect all bone names and their inverse bind matrices
|
||||||
// from every mesh in the scene.
|
// from every mesh in the scene.
|
||||||
@@ -223,7 +286,7 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
|||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
|
|
||||||
std::string name(node->mName.C_Str());
|
std::string name(node->mName.C_Str());
|
||||||
int myIdx = parentIdx;
|
int myIdx = parentIdx;
|
||||||
glm::mat4 myAccWorld = accWorld; // only used while still outside the bone hierarchy
|
glm::mat4 myAccWorld = accWorld; // only used while still outside the bone hierarchy
|
||||||
|
|
||||||
if (boneOffsets.count(name)) {
|
if (boneOffsets.count(name)) {
|
||||||
@@ -237,7 +300,7 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
|||||||
skeleton.jointNames.push_back(name);
|
skeleton.jointNames.push_back(name);
|
||||||
skeleton.inverseBindMatrices.push_back(boneOffsets[name]);
|
skeleton.inverseBindMatrices.push_back(boneOffsets[name]);
|
||||||
|
|
||||||
// Grow hierarchy arrays to accommodate this id
|
// Grow hierarchy arrays to accommodate this id.
|
||||||
while (skeleton.hierarchy.size() <= id)
|
while (skeleton.hierarchy.size() <= id)
|
||||||
skeleton.hierarchy.push_back({});
|
skeleton.hierarchy.push_back({});
|
||||||
skeleton.hierarchy[id].id = id;
|
skeleton.hierarchy[id].id = id;
|
||||||
@@ -251,10 +314,11 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
|||||||
skeleton.rootPreTransform = accWorld;
|
skeleton.rootPreTransform = accWorld;
|
||||||
glm::mat4& m = skeleton.rootPreTransform;
|
glm::mat4& m = skeleton.rootPreTransform;
|
||||||
|
|
||||||
// Normalize each basis vector to remove scale
|
// Normalize each basis vector to remove scale without creating
|
||||||
m[0] = glm::vec4(glm::normalize(glm::vec3(m[0])), 0.f);
|
// NaNs if an imported file contains a degenerate transform.
|
||||||
m[1] = glm::vec4(glm::normalize(glm::vec3(m[1])), 0.f);
|
m[0] = glm::vec4(SafeNormalize(glm::vec3(m[0]), glm::vec3(1.0f, 0.0f, 0.0f)), 0.f);
|
||||||
m[2] = glm::vec4(glm::normalize(glm::vec3(m[2])), 0.f);
|
m[1] = glm::vec4(SafeNormalize(glm::vec3(m[1]), glm::vec3(0.0f, 1.0f, 0.0f)), 0.f);
|
||||||
|
m[2] = glm::vec4(SafeNormalize(glm::vec3(m[2]), glm::vec3(0.0f, 0.0f, 1.0f)), 0.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we are inside the bone hierarchy the pre-transform is fully
|
// Once we are inside the bone hierarchy the pre-transform is fully
|
||||||
@@ -265,7 +329,7 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
|||||||
myAccWorld = accWorld * ToGLM(node->mTransformation);
|
myAccWorld = accWorld * ToGLM(node->mTransformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push children in reverse so left-most child is processed first
|
// Push children in reverse so left-most child is processed first.
|
||||||
for (int c = static_cast<int>(node->mNumChildren) - 1; c >= 0; --c)
|
for (int c = static_cast<int>(node->mNumChildren) - 1; c >= 0; --c)
|
||||||
stack.push_back({ node->mChildren[c], myIdx, myAccWorld });
|
stack.push_back({ node->mChildren[c], myIdx, myAccWorld });
|
||||||
}
|
}
|
||||||
@@ -279,119 +343,240 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
|
|||||||
// Populate cpuMesh.skinningData from the matching aiMesh.
|
// Populate cpuMesh.skinningData from the matching aiMesh.
|
||||||
// Must be called after LoadAiMeshIntoCPUMesh so vertices are already sized.
|
// Must be called after LoadAiMeshIntoCPUMesh so vertices are already sized.
|
||||||
static void LoadSkinningData(CPUMesh& cpuMesh,
|
static void LoadSkinningData(CPUMesh& cpuMesh,
|
||||||
const aiMesh* aiMesh,
|
const aiMesh* aiMesh,
|
||||||
const Skeleton& skeleton) {
|
const Skeleton& skeleton) {
|
||||||
if (!aiMesh->HasBones() || skeleton.joints.empty()) return;
|
if (!aiMesh->HasBones() || skeleton.joints.empty()) return;
|
||||||
|
|
||||||
cpuMesh.skinningData.assign(cpuMesh.vertices.size(),
|
cpuMesh.skinningData.assign(cpuMesh.vertices.size(),
|
||||||
CPUMesh::SkinningData{ {0, 0, 0, 0}, {0.f, 0.f, 0.f, 0.f} });
|
CPUMesh::SkinningData{ {0, 0, 0, 0}, {0.f, 0.f, 0.f, 0.f} });
|
||||||
|
|
||||||
|
const auto jointIndexByName = BuildJointIndexMap(skeleton);
|
||||||
std::vector<int> weightCount(cpuMesh.vertices.size(), 0);
|
std::vector<int> weightCount(cpuMesh.vertices.size(), 0);
|
||||||
|
|
||||||
for (unsigned int b = 0; b < aiMesh->mNumBones; ++b) {
|
for (unsigned int b = 0; b < aiMesh->mNumBones; ++b) {
|
||||||
const aiBone* bone = aiMesh->mBones[b];
|
const aiBone* bone = aiMesh->mBones[b];
|
||||||
std::string boneName(bone->mName.C_Str());
|
std::string boneName(bone->mName.C_Str());
|
||||||
|
|
||||||
// Find which joint index this bone maps to
|
const auto jointIt = jointIndexByName.find(boneName);
|
||||||
std::uint32_t jointIdx = 0;
|
if (jointIt == jointIndexByName.end()) continue;
|
||||||
bool found = false;
|
|
||||||
for (std::size_t j = 0; j < skeleton.jointNames.size(); ++j) {
|
const std::uint32_t jointIdx = jointIt->second;
|
||||||
if (skeleton.jointNames[j] == boneName) {
|
|
||||||
jointIdx = static_cast<std::uint32_t>(j);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) continue;
|
|
||||||
|
|
||||||
for (unsigned int w = 0; w < bone->mNumWeights; ++w) {
|
for (unsigned int w = 0; w < bone->mNumWeights; ++w) {
|
||||||
const unsigned int vertIdx = bone->mWeights[w].mVertexId;
|
const unsigned int vertIdx = bone->mWeights[w].mVertexId;
|
||||||
const float weight = bone->mWeights[w].mWeight;
|
if (vertIdx >= cpuMesh.skinningData.size()) continue;
|
||||||
const int slot = weightCount[vertIdx];
|
|
||||||
|
|
||||||
if (slot >= 4) continue; // aiProcess_LimitBoneWeights should prevent this
|
const float weight = bone->mWeights[w].mWeight;
|
||||||
|
const int slot = weightCount[vertIdx];
|
||||||
|
|
||||||
|
if (slot >= 4) continue; // aiProcess_LimitBoneWeights should prevent this.
|
||||||
|
|
||||||
cpuMesh.skinningData[vertIdx].jointIds[slot] = jointIdx;
|
cpuMesh.skinningData[vertIdx].jointIds[slot] = jointIdx;
|
||||||
cpuMesh.skinningData[vertIdx].weights[slot] = weight;
|
cpuMesh.skinningData[vertIdx].weights[slot] = weight;
|
||||||
++weightCount[vertIdx];
|
++weightCount[vertIdx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize weights so the shader does not accidentally shrink/explode
|
||||||
|
// vertices if the importer returned imperfect totals.
|
||||||
|
for (std::size_t i = 0; i < cpuMesh.skinningData.size(); ++i) {
|
||||||
|
auto& skin = cpuMesh.skinningData[i];
|
||||||
|
const float sum = skin.weights[0] + skin.weights[1] + skin.weights[2] + skin.weights[3];
|
||||||
|
|
||||||
|
if (sum > std::numeric_limits<float>::epsilon()) {
|
||||||
|
skin.weights[0] /= sum;
|
||||||
|
skin.weights[1] /= sum;
|
||||||
|
skin.weights[2] /= sum;
|
||||||
|
skin.weights[3] /= sum;
|
||||||
|
} else {
|
||||||
|
// Defensive fallback for malformed meshes with unweighted vertices.
|
||||||
|
skin.jointIds = {0, 0, 0, 0};
|
||||||
|
skin.weights = {1.f, 0.f, 0.f, 0.f};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Animation loading ────────────────────────────────────────────────────────
|
// ─── Animation loading ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static glm::vec3 SampleVectorKeys(const aiVectorKey* keys,
|
||||||
|
unsigned int count,
|
||||||
|
double tick,
|
||||||
|
const glm::vec3& fallback) {
|
||||||
|
if (!keys || count == 0) return fallback;
|
||||||
|
if (count == 1 || tick <= keys[0].mTime) return ToGLM(keys[0].mValue);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i + 1 < count; ++i) {
|
||||||
|
const double t0 = keys[i].mTime;
|
||||||
|
const double t1 = keys[i + 1].mTime;
|
||||||
|
|
||||||
|
if (tick <= t1) {
|
||||||
|
const double denom = t1 - t0;
|
||||||
|
const float alpha = denom > 0.0
|
||||||
|
? static_cast<float>((tick - t0) / denom)
|
||||||
|
: 0.0f;
|
||||||
|
|
||||||
|
return glm::mix(ToGLM(keys[i].mValue), ToGLM(keys[i + 1].mValue), alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToGLM(keys[count - 1].mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::quat SampleQuatKeys(const aiQuatKey* keys,
|
||||||
|
unsigned int count,
|
||||||
|
double tick,
|
||||||
|
const glm::quat& fallback) {
|
||||||
|
if (!keys || count == 0) return fallback;
|
||||||
|
if (count == 1 || tick <= keys[0].mTime) return ToGLM(keys[0].mValue);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i + 1 < count; ++i) {
|
||||||
|
const double t0 = keys[i].mTime;
|
||||||
|
const double t1 = keys[i + 1].mTime;
|
||||||
|
|
||||||
|
if (tick <= t1) {
|
||||||
|
const double denom = t1 - t0;
|
||||||
|
const float alpha = denom > 0.0
|
||||||
|
? static_cast<float>((tick - t0) / denom)
|
||||||
|
: 0.0f;
|
||||||
|
|
||||||
|
return glm::normalize(glm::slerp(ToGLM(keys[i].mValue),
|
||||||
|
ToGLM(keys[i + 1].mValue),
|
||||||
|
alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToGLM(keys[count - 1].mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUniqueKeyTimes(std::vector<double>& times,
|
||||||
|
const aiVectorKey* keys,
|
||||||
|
unsigned int count) {
|
||||||
|
if (!keys) return;
|
||||||
|
for (unsigned int i = 0; i < count; ++i)
|
||||||
|
times.push_back(keys[i].mTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUniqueKeyTimes(std::vector<double>& times,
|
||||||
|
const aiQuatKey* keys,
|
||||||
|
unsigned int count) {
|
||||||
|
if (!keys) return;
|
||||||
|
for (unsigned int i = 0; i < count; ++i)
|
||||||
|
times.push_back(keys[i].mTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SortAndUniqueKeyTimes(std::vector<double>& times) {
|
||||||
|
std::sort(times.begin(), times.end());
|
||||||
|
|
||||||
|
constexpr double epsilon = 1e-6;
|
||||||
|
times.erase(std::unique(times.begin(), times.end(),
|
||||||
|
[](double a, double b) { return std::abs(a - b) <= epsilon; }),
|
||||||
|
times.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static double GetMaxAnimationKeyTick(const aiAnimation* aiAnim) {
|
||||||
|
double maxTick = 0.0;
|
||||||
|
|
||||||
|
for (unsigned int c = 0; c < aiAnim->mNumChannels; ++c) {
|
||||||
|
const aiNodeAnim* ch = aiAnim->mChannels[c];
|
||||||
|
|
||||||
|
if (ch->mNumPositionKeys > 0)
|
||||||
|
maxTick = std::max(maxTick, ch->mPositionKeys[ch->mNumPositionKeys - 1].mTime);
|
||||||
|
if (ch->mNumRotationKeys > 0)
|
||||||
|
maxTick = std::max(maxTick, ch->mRotationKeys[ch->mNumRotationKeys - 1].mTime);
|
||||||
|
if (ch->mNumScalingKeys > 0)
|
||||||
|
maxTick = std::max(maxTick, ch->mScalingKeys[ch->mNumScalingKeys - 1].mTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxTick;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<SkeletalAnimation> LoadAnimations(const aiScene* scene,
|
static std::vector<SkeletalAnimation> LoadAnimations(const aiScene* scene,
|
||||||
const Skeleton& skeleton) {
|
const Skeleton& skeleton) {
|
||||||
std::vector<SkeletalAnimation> result;
|
std::vector<SkeletalAnimation> result;
|
||||||
if (!scene->HasAnimations()) return result;
|
if (!scene->HasAnimations() || skeleton.joints.empty()) return result;
|
||||||
|
|
||||||
|
const auto jointIndexByName = BuildJointIndexMap(skeleton);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, NodeTRS> nodeDefaults;
|
||||||
|
CollectNodeDefaults(scene->mRootNode, nodeDefaults);
|
||||||
|
|
||||||
|
result.reserve(scene->mNumAnimations);
|
||||||
|
|
||||||
for (unsigned int a = 0; a < scene->mNumAnimations; ++a) {
|
for (unsigned int a = 0; a < scene->mNumAnimations; ++a) {
|
||||||
const aiAnimation* aiAnim = scene->mAnimations[a];
|
const aiAnimation* aiAnim = scene->mAnimations[a];
|
||||||
const double tps = aiAnim->mTicksPerSecond > 0.0 ? aiAnim->mTicksPerSecond : 25.0;
|
const double tps = aiAnim->mTicksPerSecond > 0.0 ? aiAnim->mTicksPerSecond : 25.0;
|
||||||
|
|
||||||
|
const double durationTicks = aiAnim->mDuration > 0.0
|
||||||
|
? aiAnim->mDuration
|
||||||
|
: GetMaxAnimationKeyTick(aiAnim);
|
||||||
|
|
||||||
SkeletalAnimation anim;
|
SkeletalAnimation anim;
|
||||||
anim.name = aiAnim->mName.C_Str();
|
anim.name = aiAnim->mName.length > 0
|
||||||
anim.duration = static_cast<float>(aiAnim->mDuration / tps);
|
? std::string(aiAnim->mName.C_Str())
|
||||||
|
: ("Animation_" + std::to_string(a));
|
||||||
|
anim.duration = static_cast<float>(durationTicks / tps);
|
||||||
anim.looped = true;
|
anim.looped = true;
|
||||||
|
|
||||||
|
anim.tracks.reserve(aiAnim->mNumChannels);
|
||||||
|
|
||||||
for (unsigned int c = 0; c < aiAnim->mNumChannels; ++c) {
|
for (unsigned int c = 0; c < aiAnim->mNumChannels; ++c) {
|
||||||
const aiNodeAnim* ch = aiAnim->mChannels[c];
|
const aiNodeAnim* ch = aiAnim->mChannels[c];
|
||||||
std::string boneName(ch->mNodeName.C_Str());
|
const std::string boneName(ch->mNodeName.C_Str());
|
||||||
|
|
||||||
// Map bone name to joint index
|
const auto jointIt = jointIndexByName.find(boneName);
|
||||||
std::uint32_t jointIdx = 0;
|
if (jointIt == jointIndexByName.end()) continue;
|
||||||
bool found = false;
|
|
||||||
for (std::size_t j = 0; j < skeleton.jointNames.size(); ++j) {
|
|
||||||
if (skeleton.jointNames[j] == boneName) {
|
|
||||||
jointIdx = static_cast<std::uint32_t>(j);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) continue;
|
|
||||||
|
|
||||||
SkeletalAnimation::Track track;
|
const auto defaultIt = nodeDefaults.find(boneName);
|
||||||
track.jointIndex = jointIdx;
|
const NodeTRS defaults = defaultIt != nodeDefaults.end()
|
||||||
|
? defaultIt->second
|
||||||
|
: NodeTRS{};
|
||||||
|
|
||||||
// Position, rotation and scale channels can have different keyframe
|
// Position, rotation and scale channels can have different keyframe
|
||||||
// counts and independent time axes. Build the track from the position
|
// counts and independent time axes. Build a unified timeline from
|
||||||
// channel's timeline and pick the nearest rotation/scale key for each
|
// every key time, then sample/interpolate every TRS component at
|
||||||
// sample rather than assuming index alignment. This avoids corrupted
|
// that time.
|
||||||
// keyframes when counts differ.
|
//
|
||||||
const unsigned int numPos = ch->mNumPositionKeys;
|
// This fixes the old loader's bad assumptions:
|
||||||
const unsigned int numRot = ch->mNumRotationKeys;
|
// - rotation/scale keys might be absent;
|
||||||
const unsigned int numSca = ch->mNumScalingKeys;
|
// - scale-only tracks are valid;
|
||||||
|
// - key counts do not have to match;
|
||||||
|
// - key index k does not imply the same timestamp in each channel;
|
||||||
|
// - missing components must fall back to the node's bind/local pose,
|
||||||
|
// not zero translation or invalid array access.
|
||||||
|
std::vector<double> keyTicks;
|
||||||
|
keyTicks.reserve(ch->mNumPositionKeys + ch->mNumRotationKeys + ch->mNumScalingKeys);
|
||||||
|
|
||||||
// Use position keys to drive the timeline (most common case).
|
AppendUniqueKeyTimes(keyTicks, ch->mPositionKeys, ch->mNumPositionKeys);
|
||||||
// If there are no position keys fall back to rotation keys.
|
AppendUniqueKeyTimes(keyTicks, ch->mRotationKeys, ch->mNumRotationKeys);
|
||||||
const unsigned int numKeys = numPos > 0 ? numPos : numRot;
|
AppendUniqueKeyTimes(keyTicks, ch->mScalingKeys, ch->mNumScalingKeys);
|
||||||
|
SortAndUniqueKeyTimes(keyTicks);
|
||||||
|
|
||||||
for (unsigned int k = 0; k < numKeys; ++k) {
|
if (keyTicks.empty()) {
|
||||||
|
// Extremely defensive: an animation channel with no keys should
|
||||||
|
// not normally exist, but keep a stable bind-pose track instead
|
||||||
|
// of returning an empty/corrupt track.
|
||||||
|
keyTicks.push_back(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkeletalAnimation::Track track;
|
||||||
|
track.jointIndex = jointIt->second;
|
||||||
|
track.keyframes.reserve(keyTicks.size());
|
||||||
|
|
||||||
|
for (double tick : keyTicks) {
|
||||||
SkeletalAnimation::Keyframe kf;
|
SkeletalAnimation::Keyframe kf;
|
||||||
|
kf.time = static_cast<float>(tick / tps);
|
||||||
// Time comes from whichever channel drives this loop
|
kf.translation = SampleVectorKeys(ch->mPositionKeys,
|
||||||
if (numPos > 0) {
|
ch->mNumPositionKeys,
|
||||||
kf.time = static_cast<float>(ch->mPositionKeys[k].mTime / tps);
|
tick,
|
||||||
const auto& p = ch->mPositionKeys[k].mValue;
|
defaults.translation);
|
||||||
kf.translation = { p.x, p.y, p.z };
|
kf.rotation = SampleQuatKeys(ch->mRotationKeys,
|
||||||
} else {
|
ch->mNumRotationKeys,
|
||||||
kf.time = static_cast<float>(ch->mRotationKeys[k].mTime / tps);
|
tick,
|
||||||
kf.translation = { 0.f, 0.f, 0.f };
|
defaults.rotation);
|
||||||
}
|
kf.scale = SampleVectorKeys(ch->mScalingKeys,
|
||||||
|
ch->mNumScalingKeys,
|
||||||
// Nearest rotation key at this index
|
tick,
|
||||||
{
|
defaults.scale);
|
||||||
const unsigned int ri = std::min(k, numRot - 1);
|
|
||||||
const auto& r = ch->mRotationKeys[ri].mValue;
|
|
||||||
kf.rotation = glm::quat(r.w, r.x, r.y, r.z); // glm: (w,x,y,z)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nearest scale key at this index
|
|
||||||
{
|
|
||||||
const unsigned int si = std::min(k, numSca - 1);
|
|
||||||
const auto& s = ch->mScalingKeys[si].mValue;
|
|
||||||
kf.scale = { s.x, s.y, s.z };
|
|
||||||
}
|
|
||||||
|
|
||||||
track.keyframes.push_back(kf);
|
track.keyframes.push_back(kf);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-8
@@ -1,4 +1,5 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <SDL_vulkan.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <destrum/App.h>
|
#include <destrum/App.h>
|
||||||
|
|
||||||
@@ -66,6 +67,8 @@ void App::run() {
|
|||||||
InputManager::GetInstance().BeginFrame();
|
InputManager::GetInstance().BeginFrame();
|
||||||
camera.Update(dt);
|
camera.Update(dt);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_QUIT) {
|
if (event.type == SDL_QUIT) {
|
||||||
@@ -76,8 +79,11 @@ void App::run() {
|
|||||||
switch (event.window.event) {
|
switch (event.window.event) {
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
case SDL_WINDOWEVENT_RESIZED:
|
case SDL_WINDOWEVENT_RESIZED:
|
||||||
m_params.windowSize = { event.window.data1, event.window.data2 };
|
{
|
||||||
|
resizePending = true;
|
||||||
|
lastResizeTime = std::chrono::steady_clock::now();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (InputManager::GetInstance().ProcessEvent(event)) {
|
if (InputManager::GetInstance().ProcessEvent(event)) {
|
||||||
@@ -104,15 +110,33 @@ void App::run() {
|
|||||||
|
|
||||||
const float alpha = accumulator / fixedDt;
|
const float alpha = accumulator / fixedDt;
|
||||||
|
|
||||||
if (!gfxDevice.needsSwapchainRecreate()) {
|
if (gfxDevice.needsSwapchainRecreate() || resizePending) {
|
||||||
customDraw();
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
if (resizePending &&
|
||||||
|
now - lastResizeTime < std::chrono::milliseconds(100)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
SDL_Vulkan_GetDrawableSize(window, &w, &h);
|
||||||
|
|
||||||
|
if (w == 0 || h == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("Recreating swapchain to size: {}x{}", w, h);
|
||||||
|
|
||||||
|
gfxDevice.recreateSwapchain(w, h);
|
||||||
|
onWindowResize(w, h);
|
||||||
|
|
||||||
|
resizePending = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gfxDevice.needsSwapchainRecreate()) {
|
customDraw();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameLimit) {
|
if (frameLimit) {
|
||||||
auto sleepTime = Time::GetInstance().SleepDuration();
|
auto sleepTime = Time::GetInstance().SleepDuration();
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
|
|||||||
.imageCubeArray = VK_TRUE,
|
.imageCubeArray = VK_TRUE,
|
||||||
.geometryShader = VK_TRUE, // for im3d
|
.geometryShader = VK_TRUE, // for im3d
|
||||||
.depthClamp = VK_TRUE,
|
.depthClamp = VK_TRUE,
|
||||||
.samplerAnisotropy = VK_TRUE,
|
.fillModeNonSolid = VK_TRUE,
|
||||||
|
.samplerAnisotropy = VK_TRUE
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
|
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
|
||||||
@@ -63,11 +64,13 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
|
|||||||
.dynamicRendering = true,
|
.dynamicRendering = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
physicalDevice = vkb::PhysicalDeviceSelector{instance}
|
physicalDevice = vkb::PhysicalDeviceSelector{instance}
|
||||||
.set_minimum_version(1, 3)
|
.set_minimum_version(1, 3)
|
||||||
.set_required_features(deviceFeatures)
|
.set_required_features(deviceFeatures)
|
||||||
.set_required_features_12(features12)
|
.set_required_features_12(features12)
|
||||||
.set_required_features_13(features13)
|
.set_required_features_13(features13)
|
||||||
|
.add_required_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
||||||
.set_surface(surface)
|
.set_surface(surface)
|
||||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ void Pipeline::DefaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
|
|||||||
configInfo.depthStencilInfo.front = {}; // Optional
|
configInfo.depthStencilInfo.front = {}; // Optional
|
||||||
configInfo.depthStencilInfo.back = {}; // Optional
|
configInfo.depthStencilInfo.back = {}; // Optional
|
||||||
|
|
||||||
configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_POLYGON_MODE_EXT};
|
||||||
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||||
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
|
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
|
||||||
configInfo.dynamicStateInfo.dynamicStateCount = static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
|
configInfo.dynamicStateInfo.dynamicStateCount = static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ void MeshPipeline::draw(VkCommandBuffer cmd,
|
|||||||
};
|
};
|
||||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||||
|
|
||||||
|
vkCmdSetPolygonModeEXT(cmd, m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL);
|
||||||
|
|
||||||
auto prevMeshId = NULL_MESH_ID;
|
auto prevMeshId = NULL_MESH_ID;
|
||||||
|
|
||||||
const auto frustum = edge::createFrustumFromCamera(camera);
|
const auto frustum = edge::createFrustumFromCamera(camera);
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ void SkyboxPipeline::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camer
|
|||||||
skyboxRotation = glm::rotate(skyboxRotation, rotationSpeed * static_cast<float>(Time::GetInstance().DeltaTime()), glm::vec3(0.f, 1.f, 0.f));
|
skyboxRotation = glm::rotate(skyboxRotation, rotationSpeed * static_cast<float>(Time::GetInstance().DeltaTime()), glm::vec3(0.f, 1.f, 0.f));
|
||||||
|
|
||||||
pipeline->bind(cmd);
|
pipeline->bind(cmd);
|
||||||
|
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||||
|
|
||||||
gfxDevice.bindBindlessDescSet(cmd, pipelineLayout);
|
gfxDevice.bindBindlessDescSet(cmd, pipelineLayout);
|
||||||
|
|
||||||
const glm::mat3 r = glm::mat3(skyboxRotation);
|
const glm::mat3 r = glm::mat3(skyboxRotation);
|
||||||
|
|||||||
@@ -65,40 +65,72 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
|
|||||||
extent = m_swapchain.extent;
|
extent = m_swapchain.extent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) {
|
void Swapchain::recreateSwapchain(
|
||||||
assert(m_swapchain);
|
const GfxDevice& gfxDevice,
|
||||||
|
VkFormat format,
|
||||||
|
std::uint32_t width,
|
||||||
|
std::uint32_t height,
|
||||||
|
bool vSync)
|
||||||
|
{
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDevice device = gfxDevice.getDevice();
|
||||||
|
|
||||||
|
vkDeviceWaitIdle(device);
|
||||||
|
|
||||||
|
auto oldSwapchain = m_swapchain;
|
||||||
|
|
||||||
|
auto res = vkb::SwapchainBuilder{gfxDevice.getVkbDevice()}
|
||||||
|
.set_old_swapchain(oldSwapchain)
|
||||||
|
.set_desired_format(VkSurfaceFormatKHR{
|
||||||
|
.format = format,
|
||||||
|
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||||
|
})
|
||||||
|
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
|
||||||
|
.set_desired_present_mode(
|
||||||
|
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||||
|
.set_desired_extent(width, height)
|
||||||
|
.build();
|
||||||
|
|
||||||
assert(format == VK_FORMAT_B8G8R8A8_SRGB && "TODO: test other formats");
|
|
||||||
auto res = vkb::SwapchainBuilder{gfxDevice.getDevice()}
|
|
||||||
.set_old_swapchain(m_swapchain)
|
|
||||||
.set_desired_format(VkSurfaceFormatKHR{
|
|
||||||
.format = format,
|
|
||||||
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
|
||||||
})
|
|
||||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
|
|
||||||
.set_desired_present_mode(
|
|
||||||
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
|
||||||
.set_desired_extent(width, height)
|
|
||||||
.build();
|
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
throw std::runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"failed to create swapchain: error = {}, vk result = {}",
|
"failed to create swapchain: error = {}, vk result = {}",
|
||||||
res.full_error().type.message(),
|
res.full_error().type.message(),
|
||||||
string_VkResult(res.full_error().vk_result)));
|
string_VkResult(res.full_error().vk_result)));
|
||||||
}
|
}
|
||||||
vkb::destroy_swapchain(m_swapchain);
|
|
||||||
|
|
||||||
for (auto imageView: imageViews) {
|
for (auto sem : imageRenderSemaphores) {
|
||||||
vkDestroyImageView(m_gfxDevice->getDevice(), imageView, nullptr);
|
vkDestroySemaphore(device, sem, nullptr);
|
||||||
}
|
}
|
||||||
|
imageRenderSemaphores.clear();
|
||||||
|
|
||||||
|
for (auto imageView : imageViews) {
|
||||||
|
vkDestroyImageView(device, imageView, nullptr);
|
||||||
|
}
|
||||||
|
imageViews.clear();
|
||||||
|
|
||||||
|
vkb::destroy_swapchain(oldSwapchain);
|
||||||
|
|
||||||
m_swapchain = res.value();
|
m_swapchain = res.value();
|
||||||
|
|
||||||
images = m_swapchain.get_images().value();
|
images = m_swapchain.get_images().value();
|
||||||
imageViews = m_swapchain.get_image_views().value();
|
imageViews = m_swapchain.get_image_views().value();
|
||||||
|
|
||||||
dirty = false;
|
VkSemaphoreCreateInfo sci{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||||
|
};
|
||||||
|
|
||||||
|
imageRenderSemaphores.resize(images.size());
|
||||||
|
|
||||||
|
for (auto& sem : imageRenderSemaphores) {
|
||||||
|
VK_CHECK(vkCreateSemaphore(device, &sci, nullptr, &sem));
|
||||||
|
}
|
||||||
|
|
||||||
extent = m_swapchain.extent;
|
extent = m_swapchain.extent;
|
||||||
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::cleanup() {
|
void Swapchain::cleanup() {
|
||||||
@@ -126,6 +158,12 @@ void Swapchain::resetFences(int index) const {
|
|||||||
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
|
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SwapchainAcquireResult {
|
||||||
|
VkResult result = VK_SUCCESS;
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
uint32_t imageIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||||
std::uint32_t swapchainImageIndex{};
|
std::uint32_t swapchainImageIndex{};
|
||||||
const auto result = vkAcquireNextImageKHR(
|
const auto result = vkAcquireNextImageKHR(
|
||||||
|
|||||||
+130
-126
@@ -25,143 +25,147 @@ void LightKeeper::customInit() {
|
|||||||
|
|
||||||
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
|
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
|
||||||
camera.setAspectRatio(aspectRatio);
|
camera.setAspectRatio(aspectRatio);
|
||||||
//
|
|
||||||
// testMesh.name = "Test Mesh";
|
testMesh.name = "Test Mesh";
|
||||||
// auto list_of_models = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string());
|
auto list_of_models = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string());
|
||||||
// testMesh = list_of_models[0];
|
testMesh = list_of_models[0];
|
||||||
// testMeshID = meshCache.addMesh(gfxDevice, testMesh);
|
testMeshID = meshCache.addMesh(gfxDevice, testMesh);
|
||||||
// spdlog::info("TestMesh uploaded with id: {}", testMeshID);
|
spdlog::info("TestMesh uploaded with id: {}", testMeshID);
|
||||||
//
|
|
||||||
// auto testimgID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://kitty.png"));
|
auto testimgID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://kitty.png"));
|
||||||
// spdlog::info("Test image loaded with id: {}", testimgID);
|
spdlog::info("Test image loaded with id: {}", testimgID);
|
||||||
// testMaterialID = materialCache.addMaterial(gfxDevice, {
|
testMaterialID = materialCache.addMaterial(gfxDevice, {
|
||||||
// .baseColor = glm::vec3(1.f),
|
.baseColor = glm::vec3(1.f),
|
||||||
// .diffuseTexture = testimgID,
|
.diffuseTexture = testimgID,
|
||||||
// });
|
});
|
||||||
// spdlog::info("Test material created with id: {}", testMaterialID);
|
spdlog::info("Test material created with id: {}", testMaterialID);
|
||||||
//
|
|
||||||
// camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
||||||
//
|
|
||||||
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
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;
|
||||||
|
const float radius = 5.0f;
|
||||||
|
|
||||||
|
const float orbitRadius = 5.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
// auto childCube = std::make_shared<GameObject>(fmt::format("ChildCube{}", i));
|
||||||
|
//
|
||||||
|
// auto childMeshComp = childCube->AddComponent<MeshRendererComponent>();
|
||||||
|
// childMeshComp->SetMeshID(testMeshID);
|
||||||
|
// childMeshComp->SetMaterialID(testMaterialID);
|
||||||
|
//
|
||||||
|
// childCube->GetTransform().SetWorldScale(glm::vec3(0.1f));
|
||||||
|
//
|
||||||
|
// // Add orbit + self spin
|
||||||
|
// auto orbit = childCube->AddComponent<OrbitAndSpin>(orbitRadius, glm::vec3(0.0f));
|
||||||
|
// orbit->Randomize(1337u + (uint32_t)i); // stable random per index
|
||||||
|
//
|
||||||
|
// scene.Add(childCube);
|
||||||
|
}
|
||||||
|
// testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
|
||||||
|
//rotate 180 around X axis
|
||||||
|
// testCube->GetTransform().SetLocalRotation(glm::quat(glm::vec3(glm::radians(180.0f), 0.0f, 0.0f)));
|
||||||
//
|
//
|
||||||
// // auto testCube = std::make_shared<GameObject>("TestCube");
|
auto globeRoot = std::make_shared<GameObject>("GlobeRoot");
|
||||||
// // auto meshComp = testCube->AddComponent<MeshRendererComponent>();
|
globeRoot->GetTransform().SetWorldPosition(glm::vec3(0.0f));
|
||||||
// // meshComp->SetMeshID(testMeshID);
|
globeRoot->AddComponent<Spinner>(glm::vec3(0, 1, 0), 1.0f); // spin around Y, rad/sec
|
||||||
// // meshComp->SetMaterialID(testMaterialID);
|
scene.Add(globeRoot);
|
||||||
// const int count = 100;
|
|
||||||
// const float radius = 5.0f;
|
|
||||||
|
|
||||||
|
// scene.Add(testCube);
|
||||||
|
|
||||||
|
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
|
||||||
|
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
|
||||||
|
const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr");
|
||||||
//
|
//
|
||||||
// const float orbitRadius = 5.0f;
|
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png");
|
||||||
//
|
//
|
||||||
// for (int i = 0; i < count; ++i) {
|
const auto vertShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert");
|
||||||
// // auto childCube = std::make_shared<GameObject>(fmt::format("ChildCube{}", i));
|
const auto fragShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag");
|
||||||
// //
|
|
||||||
// // auto childMeshComp = childCube->AddComponent<MeshRendererComponent>();
|
|
||||||
// // childMeshComp->SetMeshID(testMeshID);
|
|
||||||
// // childMeshComp->SetMaterialID(testMaterialID);
|
|
||||||
// //
|
|
||||||
// // childCube->GetTransform().SetWorldScale(glm::vec3(0.1f));
|
|
||||||
// //
|
|
||||||
// // // Add orbit + self spin
|
|
||||||
// // auto orbit = childCube->AddComponent<OrbitAndSpin>(orbitRadius, glm::vec3(0.0f));
|
|
||||||
// // orbit->Randomize(1337u + (uint32_t)i); // stable random per index
|
|
||||||
// //
|
|
||||||
// // scene.Add(childCube);
|
|
||||||
// }
|
|
||||||
// // testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
|
|
||||||
// //rotate 180 around X axis
|
|
||||||
// // testCube->GetTransform().SetLocalRotation(glm::quat(glm::vec3(glm::radians(180.0f), 0.0f, 0.0f)));
|
|
||||||
// //
|
|
||||||
// auto globeRoot = std::make_shared<GameObject>("GlobeRoot");
|
|
||||||
// globeRoot->GetTransform().SetWorldPosition(glm::vec3(0.0f));
|
|
||||||
// globeRoot->AddComponent<Spinner>(glm::vec3(0, 1, 0), 1.0f); // spin around Y, rad/sec
|
|
||||||
// scene.Add(globeRoot);
|
|
||||||
//
|
//
|
||||||
|
skyboxCubemap = std::make_unique<CubeMap>();
|
||||||
|
skyboxCubemap->LoadCubeMap(skyboxID.generic_string());
|
||||||
|
skyboxCubemap->InitCubemapPipeline(vertShaderPath.generic_string(), fragShaderPath.generic_string());
|
||||||
|
skyboxCubemap->CreateCubeMap();
|
||||||
|
|
||||||
|
renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
const auto planeObj = std::make_shared<GameObject>("GroundPlane");
|
||||||
// // scene.Add(testCube);
|
const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
|
||||||
//
|
const auto planeModel = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string());
|
||||||
// // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
|
const auto planeMeshID = meshCache.addMesh(gfxDevice, planeModel[0]);
|
||||||
// // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
|
|
||||||
// const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr");
|
const auto planeTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://grass.png"));
|
||||||
// //
|
const auto planeMaterialID = materialCache.addMaterial(gfxDevice, {
|
||||||
// // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png");
|
.baseColor = glm::vec3(1.f),
|
||||||
// //
|
.textureFilteringMode = TextureFilteringMode::Nearest,
|
||||||
// const auto vertShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.vert");
|
.diffuseTexture = planeTextureID,
|
||||||
// const auto fragShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/cubemap.frag");
|
.name = "GroundPlaneMaterial",
|
||||||
// //
|
});
|
||||||
// skyboxCubemap = std::make_unique<CubeMap>();
|
planeMeshComp->SetMeshID(planeMeshID);
|
||||||
// skyboxCubemap->LoadCubeMap(skyboxID.generic_string());
|
planeMeshComp->SetMaterialID(planeMaterialID);
|
||||||
// skyboxCubemap->InitCubemapPipeline(vertShaderPath.generic_string(), fragShaderPath.generic_string());
|
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f));
|
||||||
// skyboxCubemap->CreateCubeMap();
|
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
|
||||||
//
|
scene.Add(planeObj);
|
||||||
// renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
|
|
||||||
//
|
|
||||||
// //
|
// At the bottom of customInit(), replace the incomplete CharObj block:
|
||||||
// const auto planeObj = std::make_shared<GameObject>("GroundPlane");
|
|
||||||
// const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
|
const auto CharObj = std::make_shared<GameObject>("Character");
|
||||||
// const auto planeModel = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string());
|
|
||||||
// const auto planeMeshID = meshCache.addMesh(gfxDevice, planeModel[0]);
|
auto charModel = ModelLoader::LoadSkinnedModel(
|
||||||
//
|
AssetFS::GetInstance().GetFullPath("engine://char2.fbx").generic_string()
|
||||||
// const auto planeTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://grass.png"));
|
);
|
||||||
// const auto planeMaterialID = materialCache.addMaterial(gfxDevice, {
|
|
||||||
// .baseColor = glm::vec3(1.f),
|
const auto charMeshID = meshCache.addMesh(gfxDevice, charModel.meshes[0]);
|
||||||
// .textureFilteringMode = TextureFilteringMode::Nearest,
|
|
||||||
// .diffuseTexture = planeTextureID,
|
const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://textures/T_T.png"));
|
||||||
// .name = "GroundPlaneMaterial",
|
const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
|
||||||
// });
|
.baseColor = glm::vec3(1.f),
|
||||||
// planeMeshComp->SetMeshID(planeMeshID);
|
.diffuseTexture = charTextureID,
|
||||||
// planeMeshComp->SetMaterialID(planeMaterialID);
|
.name = "CharacterMaterial",
|
||||||
// planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f));
|
});
|
||||||
// planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
|
|
||||||
// scene.Add(planeObj);
|
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
||||||
//
|
charMeshComp->SetMeshID(charMeshID);
|
||||||
//
|
charMeshComp->SetMaterialID(charMaterialID);
|
||||||
// // At the bottom of customInit(), replace the incomplete CharObj block:
|
|
||||||
//
|
|
||||||
// const auto CharObj = std::make_shared<GameObject>("Character");
|
const auto animator = CharObj->AddComponent<Animator>();
|
||||||
//
|
animator->setSkeleton(std::move(charModel.skeleton));
|
||||||
// auto charModel = ModelLoader::LoadSkinnedModel(
|
for (auto& clip : charModel.animations) {
|
||||||
// AssetFS::GetInstance().GetFullPath("engine://char2.fbx").generic_string()
|
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
||||||
// );
|
}
|
||||||
//
|
|
||||||
// const auto charMeshID = meshCache.addMesh(gfxDevice, charModel.meshes[0]);
|
for (const auto& clip : charModel.animations)
|
||||||
//
|
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||||
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
|
||||||
// const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
|
if (!charModel.animations.empty())
|
||||||
// .baseColor = glm::vec3(1.f),
|
// animator->play("Armature|Armature|mixamo.com");
|
||||||
// .diffuseTexture = charTextureID,
|
// // animator->play(charModel.animations[0].name);
|
||||||
// .name = "CharacterMaterial",
|
animator->play("Armature|Armature|Armature|main");
|
||||||
// });
|
// or: animator->play("Run", 0.2f); // 0.2s cross-fade
|
||||||
//
|
|
||||||
// const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
||||||
// charMeshComp->SetMeshID(charMeshID);
|
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
||||||
// charMeshComp->SetMaterialID(charMaterialID);
|
scene.Add(CharObj);
|
||||||
//
|
|
||||||
//
|
|
||||||
// const auto animator = CharObj->AddComponent<Animator>();
|
|
||||||
// animator->setSkeleton(std::move(charModel.skeleton));
|
|
||||||
// for (auto& clip : charModel.animations) {
|
|
||||||
// animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (const auto& clip : charModel.animations)
|
|
||||||
// spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
|
||||||
//
|
|
||||||
// if (!charModel.animations.empty())
|
|
||||||
// // 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);
|
|
||||||
// scene.Add(CharObj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightKeeper::customUpdate(float dt) {
|
void LightKeeper::customUpdate(float dt) {
|
||||||
camera.Update(dt);
|
camera.Update(dt);
|
||||||
SceneManager::GetInstance().Update();
|
SceneManager::GetInstance().Update();
|
||||||
|
|
||||||
|
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_1)) {
|
||||||
|
renderer.setRenderWireframe(!renderer.getRenderWireframe());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightKeeper::customDraw() {
|
void LightKeeper::customDraw() {
|
||||||
|
|||||||
Reference in New Issue
Block a user