Working on new animations

This commit is contained in:
Vladislav Khorev committed 2026-04-22 18:53:27 +03:00
1 parent fe01a5d1b2
commit 52c4ad8ee0
33 files changed
+417521 -113

No files matched your search

+14 -3
View File
@@ -5,11 +5,11 @@ Convert a text-based bone animation file to the BSAF binary format.
Usage:
python convert_anim_to_binary.py <input.txt> <output.bin>
Binary format (BSAF v1) -- all values little-endian:
Binary format (BSAF v2) -- all values little-endian:
HEADER
4 bytes magic "BSAF"
uint32 version (1)
uint32 version (2)
BONES
uint32 numBones
@@ -21,6 +21,11 @@ Binary format (BSAF v1) -- all values little-endian:
uint32 numChildren
numChildren x int32 childIndices
BONE NAMES (v2+)
per bone:
uint32 nameLen
nameLen bytes UTF-8 name (no terminator)
VERTICES
uint32 numVertices
numVertices x 3 x float positions
@@ -248,7 +253,7 @@ def convert(input_path, output_path):
with open(output_path, 'wb') as out:
# Header
out.write(b'BSAF')
out.write(struct.pack('<I', 1))
out.write(struct.pack('<I', 2))
# Bones
out.write(struct.pack('<I', num_bones))
@@ -262,6 +267,12 @@ def convert(input_path, output_path):
for c in b['children']:
out.write(struct.pack('<i', c))
# Bone names (v2+)
for name in bone_names:
name_bytes = name.encode('utf-8')
out.write(struct.pack('<I', len(name_bytes)))
out.write(name_bytes)
# Vertices
out.write(struct.pack('<I', num_vertices))
for v in vertices:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+64
View File
@@ -0,0 +1,64 @@
===Vertices (Split by UV/Normal): 41
V 0: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.77503, 0.750033)
V 1: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.808365, 0.500022)
V 2: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.808365, 0.750033)
V 3: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.866701, 0.496276)
V 4: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.833366, 0.746287)
V 5: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.833366, 0.496276)
V 6: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.833366, 0.496276)
V 7: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.866701, 0.0)
V 8: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.866701, 0.496276)
V 9: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.500022, 0.5)
V 10: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.750033, 0.0)
V 11: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.750033, 0.5)
V 12: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.77503, 0.500022)
V 13: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.808365, 0.0)
V 14: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.808365, 0.500022)
V 15: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.500022, 1.0)
V 16: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.750033, 0.5)
V 17: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.750033, 1.0)
V 18: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.8667, 0.250011)
V 19: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.891702, 0.0)
V 20: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.891702, 0.250011)
V 21: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.166674, 0.0)
V 22: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.250011, 0.666784)
V 23: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.0, 0.666784)
V 24: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.250011, 0.666784)
V 25: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.333348, 0.0)
V 26: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.500022, 0.666784)
V 27: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.77503, 0.68218)
V 28: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.750033, 0.681722)
V 29: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.77503, 0.0)
V 30: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.808365, 0.666696)
V 31: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.820865, 0.0)
V 32: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.833366, 0.666696)
V 33: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.77503, 0.500022)
V 34: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.866701, 0.746287)
V 35: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.833366, 0.0)
V 36: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.500022, 0.0)
V 37: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.77503, 0.0)
V 38: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.500022, 0.5)
V 39: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.8667, 0.0)
V 40: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.250011, 0.0)
===Triangles (Indices): 20
Tri: 0 1 2
Tri: 3 4 5
Tri: 6 7 8
Tri: 9 10 11
Tri: 12 13 14
Tri: 15 16 17
Tri: 18 19 20
Tri: 21 22 23
Tri: 24 25 26
Tri: 27 28 29
Tri: 30 31 32
Tri: 0 33 1
Tri: 3 34 4
Tri: 6 35 7
Tri: 9 36 10
Tri: 12 37 13
Tri: 15 38 16
Tri: 18 39 19
Tri: 21 40 22
Tri: 24 40 25
+64
View File
@@ -0,0 +1,64 @@
===Vertices (Split by UV/Normal): 41
V 0: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.77503, 0.750033)
V 1: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.808365, 0.500022)
V 2: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.808365, 0.750033)
V 3: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.866701, 0.496276)
V 4: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.833366, 0.746287)
V 5: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.833366, 0.496276)
V 6: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.833366, 0.496276)
V 7: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.866701, 0.0)
V 8: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.866701, 0.496276)
V 9: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.500022, 0.5)
V 10: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.750033, 0.0)
V 11: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.750033, 0.5)
V 12: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.77503, 0.500022)
V 13: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.808365, 0.0)
V 14: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.808365, 0.500022)
V 15: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.500022, 1.0)
V 16: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.750033, 0.5)
V 17: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.750033, 1.0)
V 18: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.8667, 0.250011)
V 19: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.891702, 0.0)
V 20: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.891702, 0.250011)
V 21: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.166674, 0.0)
V 22: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.250011, 0.666784)
V 23: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.0, 0.666784)
V 24: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.250011, 0.666784)
V 25: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.333348, 0.0)
V 26: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.500022, 0.666784)
V 27: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.77503, 0.68218)
V 28: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.750033, 0.681722)
V 29: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.77503, 0.0)
V 30: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.808365, 0.666696)
V 31: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.820865, 0.0)
V 32: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.833366, 0.666696)
V 33: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.77503, 0.500022)
V 34: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.866701, 0.746287)
V 35: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.833366, 0.0)
V 36: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.500022, 0.0)
V 37: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.77503, 0.0)
V 38: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.500022, 0.5)
V 39: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.8667, 0.0)
V 40: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.250011, 0.0)
===Triangles (Indices): 20
Tri: 0 1 2
Tri: 3 4 5
Tri: 6 7 8
Tri: 9 10 11
Tri: 12 13 14
Tri: 15 16 17
Tri: 18 19 20
Tri: 21 22 23
Tri: 24 25 26
Tri: 27 28 29
Tri: 30 31 32
Tri: 0 33 1
Tri: 3 34 4
Tri: 6 35 7
Tri: 9 36 10
Tri: 12 37 13
Tri: 15 38 16
Tri: 18 39 19
Tri: 21 40 22
Tri: 24 40 25
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+20 -1
View File
@@ -211,6 +211,7 @@ namespace ZL
startBones = bones;
currentBones = bones;
this->boneNames = boneNames;
std::getline(f, tempLine); //vertice count
int numberVertices;
@@ -597,7 +598,7 @@ namespace ZL
if (std::memcmp(magic, "BSAF", 4) != 0)
throw std::runtime_error("Invalid binary animation file (bad magic)");
uint32_t version = readUint32();
if (version != 1)
if (version != 2)
throw std::runtime_error("Unsupported binary animation file version");
// ---- Bones ----
@@ -630,6 +631,15 @@ namespace ZL
startBones = bones;
currentBones = bones;
// ---- Bone names ----
boneNames.resize(numBones);
for (uint32_t i = 0; i < numBones; i++)
{
uint32_t nameLen = readUint32();
boneNames[i].resize(nameLen);
if (nameLen > 0) readRaw(&boneNames[i][0], nameLen);
}
// ---- Vertices ----
uint32_t numVertices = readUint32();
std::vector<Vector3f> vertices(numVertices);
@@ -728,6 +738,15 @@ namespace ZL
}
}
int BoneSystem::findBoneIndex(const std::string& name) const
{
for (size_t i = 0; i < boneNames.size(); i++)
{
if (boneNames[i] == name) return static_cast<int>(i);
}
return -1;
}
void GpuBoneData::PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight)
{
size_t vertexCount = verticesBoneWeight.size();
+3
View File
@@ -53,6 +53,7 @@ namespace ZL
std::vector<Bone> startBones;
std::vector<Bone> currentBones;
std::vector<std::string> boneNames;
std::vector<Animation> animations;
int startingFrame = 0;
@@ -62,6 +63,8 @@ namespace ZL
void Interpolate(int frame);
int findBoneIndex(const std::string& name) const;
// GPU skinning: compute skinning matrices without modifying the mesh
//void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
};
+870
View File
@@ -0,0 +1,870 @@
#include "BoneAnimatedModelNew.h"
#include <regex>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cstring>
namespace ZL
{
#ifdef EMSCRIPTEN
using std::min;
using std::max;
#endif
extern int getIndexByValue(const std::string& name, const std::vector<std::string>& words);
static std::string trimRight(const std::string& s)
{
size_t end = s.size();
while (end > 0 && (s[end - 1] == '\r' || s[end - 1] == '\n' || s[end - 1] == ' ' || s[end - 1] == '\t'))
end--;
return s.substr(0, end);
}
void BoneSystemNew::LoadFromFile(const std::string& fileName, const std::string& ZIPFileName)
{
std::ifstream filestream;
std::istringstream zipStream;
if (!ZIPFileName.empty())
{
std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
std::string fileContents(fileData.begin(), fileData.end());
zipStream.str(fileContents);
}
else
{
filestream.open(fileName);
}
std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);
static const std::regex pattern_count(R"(\d+)");
static const std::regex pattern_float(R"([-]?\d+\.\d+)");
static const std::regex pattern_int(R"([-]?\d+)");
static const std::regex pattern_boneChildren(R"(\'([^\']+)\')");
static const std::regex pattern_bone_weight(R"(\'([^\']+)\'.*?([-]?\d+\.\d+))");
std::smatch match;
std::string tempLine;
// ---- Armature matrix (4 lines after the header) ----
std::getline(f, tempLine); // === Armature Matrix ===
armatureMatrix = Matrix4f::Identity();
for (int r = 0; r < 4; r++)
{
std::getline(f, tempLine);
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
if (floatValues.size() >= 4)
{
for (int c = 0; c < 4; c++)
armatureMatrix.data()[r + c * 4] = floatValues[c];
}
}
// ---- Bones ----
std::getline(f, tempLine); // === Armature Bones: N
int numberBones;
if (std::regex_search(tempLine, match, pattern_count)) {
numberBones = std::stoi(match.str());
}
else {
throw std::runtime_error("Armature bones count not found");
}
std::vector<Bone> bones;
std::vector<std::string> boneNames;
std::vector<std::string> boneParentNames;
std::unordered_map<std::string, std::vector<std::string>> boneChildren;
bones.resize(numberBones);
boneNames.resize(numberBones);
boneParentNames.resize(numberBones);
for (int i = 0; i < numberBones; i++)
{
std::getline(f, tempLine); // Bone: name
std::string boneName = trimRight(tempLine.substr(6));
boneNames[i] = boneName;
std::getline(f, tempLine); // HEAD_LOCAL
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
bones[i].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
std::getline(f, tempLine); // TAIL_LOCAL
std::getline(f, tempLine); // Length
if (std::regex_search(tempLine, match, pattern_float)) {
bones[i].boneLength = std::stof(match.str());
}
else {
throw std::runtime_error("Bone length not found");
}
// 3x3 matrix
for (int r = 0; r < 3; r++)
{
std::getline(f, tempLine);
b = tempLine.cbegin();
e = tempLine.cend();
floatValues.clear();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
bones[i].boneMatrixWorld.data()[r] = floatValues[0];
bones[i].boneMatrixWorld.data()[r + 1 * 3] = floatValues[1];
bones[i].boneMatrixWorld.data()[r + 2 * 3] = floatValues[2];
}
std::getline(f, tempLine); // Parent
std::string parentLine = trimRight(tempLine);
if (parentLine == " Parent: None")
{
bones[i].parent = -1;
}
else
{
boneParentNames[i] = parentLine.substr(10);
}
std::getline(f, tempLine); // Children
auto bc = tempLine.cbegin();
auto ec = tempLine.cend();
while (std::regex_search(bc, ec, match, pattern_boneChildren)) {
boneChildren[boneName].push_back(match.str(1));
bc = match.suffix().first;
}
}
// Resolve parent/child indices
for (int i = 0; i < numberBones; i++)
{
const std::string& boneName = boneNames[i];
const std::string& boneParent = boneParentNames[i];
if (boneParent == "" || boneParent == "None")
bones[i].parent = -1;
else
bones[i].parent = getIndexByValue(boneParent, boneNames);
for (size_t j = 0; j < boneChildren[boneName].size(); j++)
bones[i].children.push_back(getIndexByValue(boneChildren[boneName][j], boneNames));
}
startBones = bones;
currentBones = bones;
// ---- Multi-mesh header ----
std::getline(f, tempLine); // === TOTAL MESHES TO EXPORT: N ===
int numberMeshes;
if (std::regex_search(tempLine, match, pattern_count)) {
numberMeshes = std::stoi(match.str());
}
else {
throw std::runtime_error("Total meshes count not found");
}
for (int meshIdx = 0; meshIdx < numberMeshes; meshIdx++)
{
// === Mesh Object: Name ===
std::getline(f, tempLine);
std::string meshHeader = trimRight(tempLine);
// Extract mesh name: strip "=== Mesh Object: " prefix and " ===" suffix
const std::string prefix = "=== Mesh Object: ";
const std::string suffix = " ===";
std::string meshName;
if (meshHeader.size() >= prefix.size() + suffix.size() &&
meshHeader.compare(0, prefix.size(), prefix) == 0 &&
meshHeader.compare(meshHeader.size() - suffix.size(), suffix.size(), suffix) == 0)
{
meshName = meshHeader.substr(prefix.size(),
meshHeader.size() - prefix.size() - suffix.size());
}
else
{
throw std::runtime_error("Invalid mesh header: " + meshHeader);
}
MeshBoneData meshData;
// Vertices
std::getline(f, tempLine); // ===Vertices: N
int numberVertices;
if (std::regex_search(tempLine, match, pattern_count)) {
numberVertices = std::stoi(match.str());
}
else {
throw std::runtime_error("Vertex count not found for mesh " + meshName);
}
std::vector<Vector3f> vertices(numberVertices);
for (int i = 0; i < numberVertices; i++)
{
std::getline(f, tempLine);
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
vertices[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
}
// UV coordinates
std::getline(f, tempLine); // ===UV Coordinates:
std::getline(f, tempLine); // Face count: M
int numberFaces;
if (std::regex_search(tempLine, match, pattern_count)) {
numberFaces = std::stoi(match.str());
}
else {
throw std::runtime_error("Face count not found for mesh " + meshName);
}
std::vector<std::array<Vector2f, 3>> uvCoords(numberFaces);
for (int i = 0; i < numberFaces; i++)
{
std::getline(f, tempLine); // Face X
std::getline(f, tempLine); // UV Count: 3
int uvCount;
if (std::regex_search(tempLine, match, pattern_count)) {
uvCount = std::stoi(match.str());
}
else {
throw std::runtime_error("UV count not found");
}
if (uvCount != 3)
throw std::runtime_error("more than 3 uvs");
for (int j = 0; j < 3; j++)
{
std::getline(f, tempLine); // UV <Vector (u, v)>
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
if (floatValues.size() != 2)
throw std::runtime_error("more than 2 uvs---");
uvCoords[i][j] = Vector2f{ floatValues[0], floatValues[1] };
}
}
// Normals
std::getline(f, tempLine); // ===Normals:
std::vector<Vector3f> normals(numberVertices);
for (int i = 0; i < numberVertices; i++)
{
std::getline(f, tempLine);
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
normals[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
}
// Triangles
std::getline(f, tempLine); // ===Triangles: M
int numberTriangles;
if (std::regex_search(tempLine, match, pattern_count)) {
numberTriangles = std::stoi(match.str());
}
else {
throw std::runtime_error("Triangle count not found for mesh " + meshName);
}
std::vector<std::array<int, 3>> triangles(numberTriangles);
for (int i = 0; i < numberTriangles; i++)
{
std::getline(f, tempLine);
std::vector<int> intValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_int)) {
intValues.push_back(std::stoi(match.str()));
b = match.suffix().first;
}
triangles[i] = { intValues[0], intValues[1], intValues[2] };
}
// Vertex weights
std::getline(f, tempLine); // === Vertex Weights (Max 5 bones per vertex) ===
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight(numberVertices);
for (int i = 0; i < numberVertices; i++)
{
std::getline(f, tempLine); // Vertex X:
std::getline(f, tempLine); // Vertex groups: K
int boneCount;
if (std::regex_search(tempLine, match, pattern_count)) {
boneCount = std::stoi(match.str());
}
else {
throw std::runtime_error("Vertex group count not found");
}
if (boneCount > MAX_BONE_COUNT)
throw std::runtime_error("more than 5 bones");
float sumWeights = 0;
for (int j = 0; j < boneCount; j++)
{
std::getline(f, tempLine);
if (std::regex_search(tempLine, match, pattern_bone_weight)) {
std::string word = match.str(1);
double weight = std::stod(match.str(2));
int boneNumber = getIndexByValue(word, boneNames);
localVerticesBoneWeight[i][j].boneIndex = boneNumber;
localVerticesBoneWeight[i][j].weight = static_cast<float>(weight);
sumWeights += static_cast<float>(weight);
}
else {
throw std::runtime_error("No match found in bone weight line");
}
}
for (int j = 0; j < boneCount; j++)
localVerticesBoneWeight[i][j].weight /= sumWeights;
}
// Build per-triangle expanded mesh
for (int i = 0; i < numberTriangles; i++)
{
meshData.mesh.PositionData.push_back(vertices[triangles[i][0]]);
meshData.mesh.PositionData.push_back(vertices[triangles[i][1]]);
meshData.mesh.PositionData.push_back(vertices[triangles[i][2]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][0]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][1]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][2]);
}
meshData.startMesh = meshData.mesh;
meshes[meshName] = std::move(meshData);
meshNamesOrdered.push_back(meshName);
}
std::cout << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl;
// ---- Animation Keyframes ----
std::getline(f, tempLine); // === Animation Keyframes ===
std::getline(f, tempLine); // === Bone Transforms per Keyframe ===
std::getline(f, tempLine); // Keyframes: N
int numberKeyFrames;
if (std::regex_search(tempLine, match, pattern_count)) {
numberKeyFrames = std::stoi(match.str());
}
else {
throw std::runtime_error("Keyframe count not found");
}
animations.resize(1);
animations[0].keyFrames.resize(numberKeyFrames);
for (int i = 0; i < numberKeyFrames; i++)
{
std::getline(f, tempLine); // Frame: N
int numberFrame;
if (std::regex_search(tempLine, match, pattern_count)) {
numberFrame = std::stoi(match.str());
}
else {
throw std::runtime_error("Frame number not found");
}
animations[0].keyFrames[i].frame = numberFrame;
animations[0].keyFrames[i].bones.resize(numberBones);
for (int j = 0; j < numberBones; j++)
{
std::getline(f, tempLine); // Bone: name
std::string boneName = trimRight(tempLine.substr(8));
int boneNumber = getIndexByValue(boneName, boneNames);
animations[0].keyFrames[i].bones[boneNumber] = startBones[boneNumber];
std::getline(f, tempLine); // Location
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
animations[0].keyFrames[i].bones[boneNumber].boneStartWorld =
Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
std::getline(f, tempLine); // Rotation
std::getline(f, tempLine); // Matrix:
for (int r = 0; r < 4; r++)
{
std::getline(f, tempLine);
b = tempLine.cbegin();
e = tempLine.cend();
floatValues.clear();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r] = floatValues[0];
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 1 * 4] = floatValues[1];
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 2 * 4] = floatValues[2];
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[r + 3 * 4] = floatValues[3];
}
}
}
if (startBones.size() > MAX_GPU_BONES)
{
std::cout << "Warning: model has " << startBones.size()
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
}
}
void BoneSystemNew::LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName)
{
std::vector<char> fileData;
if (!ZIPFileName.empty())
{
fileData = readFileFromZIP(fileName, ZIPFileName);
}
else
{
std::ifstream f(fileName, std::ios::binary | std::ios::ate);
if (!f.is_open()) throw std::runtime_error("Failed to open binary file: " + fileName);
std::streamsize fileSize = f.tellg();
f.seekg(0);
fileData.resize(static_cast<size_t>(fileSize));
f.read(fileData.data(), fileSize);
}
const char* ptr = fileData.data();
const char* end = ptr + fileData.size();
auto readRaw = [&](void* dst, size_t n) {
if (ptr + n > end) throw std::runtime_error("Unexpected EOF in binary animation file");
std::memcpy(dst, ptr, n);
ptr += n;
};
auto readUint32 = [&]() -> uint32_t { uint32_t v; readRaw(&v, 4); return v; };
auto readInt32 = [&]() -> int32_t { int32_t v; readRaw(&v, 4); return v; };
auto readFloat = [&]() -> float { float v; readRaw(&v, 4); return v; };
auto readVec3 = [&]() -> Vector3f { return Vector3f{readFloat(), readFloat(), readFloat()}; };
auto readVec2 = [&]() -> Vector2f { return Vector2f{readFloat(), readFloat()}; };
auto readString = [&]() -> std::string {
uint32_t len = readUint32();
std::string s(len, '\0');
if (len > 0) readRaw(&s[0], len);
return s;
};
// Header
char magic[4];
readRaw(magic, 4);
if (std::memcmp(magic, "BSMF", 4) != 0)
throw std::runtime_error("Invalid multi-mesh binary animation file (bad magic)");
uint32_t version = readUint32();
if (version != 1)
throw std::runtime_error("Unsupported multi-mesh binary animation file version");
// Armature matrix (row-major in file, stored with stride-4 into Matrix4f)
{
float m[16];
for (int k = 0; k < 16; k++) m[k] = readFloat();
armatureMatrix = Matrix4f::Identity();
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
armatureMatrix.data()[r + c * 4] = m[r * 4 + c];
}
// Bones
uint32_t numBones = readUint32();
std::vector<Bone> bones(numBones);
for (uint32_t i = 0; i < numBones; i++)
{
bones[i].boneStartWorld = readVec3();
bones[i].boneLength = readFloat();
float m[9];
for (int j = 0; j < 9; j++) m[j] = readFloat();
bones[i].boneMatrixWorld = Matrix4f::Zero();
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
bones[i].boneMatrixWorld.data()[r + c * 3] = m[r * 3 + c];
bones[i].parent = readInt32();
uint32_t numChildren = readUint32();
bones[i].children.resize(numChildren);
for (uint32_t j = 0; j < numChildren; j++)
bones[i].children[j] = readInt32();
}
startBones = bones;
currentBones = bones;
// Meshes
uint32_t numMeshes = readUint32();
for (uint32_t meshIdx = 0; meshIdx < numMeshes; meshIdx++)
{
std::string meshName = readString();
MeshBoneData meshData;
uint32_t numVertices = readUint32();
std::vector<Vector3f> vertices(numVertices);
for (uint32_t i = 0; i < numVertices; i++)
vertices[i] = readVec3();
uint32_t numFaces = readUint32();
std::vector<std::array<Vector2f, 3>> uvCoords(numFaces);
for (uint32_t i = 0; i < numFaces; i++)
for (int j = 0; j < 3; j++)
uvCoords[i][j] = readVec2();
std::vector<Vector3f> normals(numVertices);
for (uint32_t i = 0; i < numVertices; i++)
normals[i] = readVec3();
uint32_t numTriangles = readUint32();
std::vector<std::array<int, 3>> triangles(numTriangles);
for (uint32_t i = 0; i < numTriangles; i++)
triangles[i] = { readInt32(), readInt32(), readInt32() };
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight(numVertices);
for (uint32_t i = 0; i < numVertices; i++)
{
uint32_t numGroups = readUint32();
float sumWeights = 0;
for (uint32_t j = 0; j < numGroups; j++)
{
int boneIdx = readInt32();
float weight = readFloat();
if (j < MAX_BONE_COUNT)
{
localVerticesBoneWeight[i][j].boneIndex = boneIdx;
localVerticesBoneWeight[i][j].weight = weight;
sumWeights += weight;
}
}
uint32_t cap = (numGroups < MAX_BONE_COUNT) ? numGroups : MAX_BONE_COUNT;
for (uint32_t j = 0; j < cap; j++)
localVerticesBoneWeight[i][j].weight /= sumWeights;
}
for (uint32_t i = 0; i < numTriangles; i++)
{
meshData.mesh.PositionData.push_back(vertices[triangles[i][0]]);
meshData.mesh.PositionData.push_back(vertices[triangles[i][1]]);
meshData.mesh.PositionData.push_back(vertices[triangles[i][2]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
meshData.verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][0]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][1]);
meshData.mesh.TexCoordData.push_back(uvCoords[i][2]);
}
meshData.startMesh = meshData.mesh;
meshes[meshName] = std::move(meshData);
meshNamesOrdered.push_back(meshName);
}
// Animation Keyframes
uint32_t numKeyframes = readUint32();
animations.resize(1);
animations[0].keyFrames.resize(numKeyframes);
for (uint32_t i = 0; i < numKeyframes; i++)
{
animations[0].keyFrames[i].frame = readInt32();
animations[0].keyFrames[i].bones.resize(numBones);
for (uint32_t j = 0; j < numBones; j++)
{
animations[0].keyFrames[i].bones[j] = startBones[j];
animations[0].keyFrames[i].bones[j].boneStartWorld = readVec3();
float m[16];
for (int k = 0; k < 16; k++) m[k] = readFloat();
for (int r = 0; r < 4; r++)
for (int c = 0; c < 4; c++)
animations[0].keyFrames[i].bones[j].boneMatrixWorld.data()[r + c * 4] = m[r * 4 + c];
}
}
if (startBones.size() > MAX_GPU_BONES)
{
std::cout << "Warning: model has " << startBones.size()
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
}
}
void BoneSystemNew::Interpolate(int frame)
{
int startingKeyFrame = -1;
for (int i = 0; i < static_cast<int>(animations[0].keyFrames.size()) - 1; i++)
{
int oldFrame = animations[0].keyFrames[i].frame;
int nextFrame = animations[0].keyFrames[i + 1].frame;
if (frame >= oldFrame && frame < nextFrame)
{
startingKeyFrame = i;
break;
}
}
if (startingKeyFrame == -1)
{
std::cout << "Exception here: frame number is out of range of keyframes. Frame: " << frame << std::endl;
throw std::runtime_error("Exception here");
}
int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
float t = (modifiedFrameNumber + 0.f) / diffFrames;
std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
std::vector<Matrix4f> skinningMatrixForEachBone(currentBones.size());
for (size_t i = 0; i < currentBones.size(); i++)
{
currentBones[i].boneStartWorld(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
currentBones[i].boneStartWorld(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
currentBones[i].boneStartWorld(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
Eigen::Quaternionf result = q1.slerp(t, q2);
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
currentBones[i].boneMatrixWorld = Eigen::Matrix4f::Identity();
currentBones[i].boneMatrixWorld.block<3, 3>(0, 0) = boneMatrixWorld3;
currentBones[i].boneMatrixWorld.block<3, 1>(0, 3) = currentBones[i].boneStartWorld;
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
skinningMatrixForEachBone[i] = currentBones[i].boneMatrixWorld * startBoneMatrixWorld4.inverse();
}
for (const auto& name : meshNamesOrdered)
{
auto it = meshes.find(name);
if (it == meshes.end()) continue;
MeshBoneData& md = it->second;
for (size_t i = 0; i < md.mesh.PositionData.size(); i++)
{
Vector4f originalPos = {
md.startMesh.PositionData[i](0),
md.startMesh.PositionData[i](1),
md.startMesh.PositionData[i](2), 1.0f };
Vector4f finalPos = Vector4f{ 0.f, 0.f, 0.f, 0.f };
bool vMoved = false;
for (int j = 0; j < MAX_BONE_COUNT; j++)
{
if (md.verticesBoneWeight[i][j].weight != 0)
{
if (md.verticesBoneWeight[i][j].boneIndex == -1)
{
std::cout << "Exception here: bone index is -1 but weight is > 0" << std::endl;
throw std::runtime_error("Bones loaded incorrectly - bone index is -1 but weight is > 0");
}
vMoved = true;
finalPos = finalPos + (skinningMatrixForEachBone[md.verticesBoneWeight[i][j].boneIndex] * originalPos) * md.verticesBoneWeight[i][j].weight;
}
}
if (!vMoved) finalPos = originalPos;
md.mesh.PositionData[i](0) = finalPos(0);
md.mesh.PositionData[i](1) = finalPos(1);
md.mesh.PositionData[i](2) = finalPos(2);
}
}
}
void MeshGpuSkinningData::prepareGpuSkinningVBOs(MeshBoneData& meshData)
{
if (gpuSkinningPrepared) return;
gpuBoneData.PrepareGpuSkinningData(meshData.verticesBoneWeight);
bindPoseMutable.AssignFrom(meshData.startMesh);
auto& gpu = gpuBoneData;
boneIndices0VBO = std::make_shared<VBOHolder>();
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices0.size() * sizeof(Eigen::Vector4f),
gpu.boneIndices0.data(), GL_STATIC_DRAW);
boneIndices1VBO = std::make_shared<VBOHolder>();
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices1.size() * sizeof(Eigen::Vector2f),
gpu.boneIndices1.data(), GL_STATIC_DRAW);
boneWeights0VBO = std::make_shared<VBOHolder>();
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights0.size() * sizeof(Eigen::Vector4f),
gpu.boneWeights0.data(), GL_STATIC_DRAW);
boneWeights1VBO = std::make_shared<VBOHolder>();
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights1.size() * sizeof(Eigen::Vector2f),
gpu.boneWeights1.data(), GL_STATIC_DRAW);
gpuSkinningPrepared = true;
}
void MeshGpuSkinningData::RenderVBO(Renderer& renderer)
{
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
CheckGlError(__FILE__, __LINE__);
if (bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
CheckGlError(__FILE__, __LINE__);
}
if (bindPoseMutable.normalVBO) {
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.normalVBO->getBuffer());
renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
CheckGlError(__FILE__, __LINE__);
} else {
renderer.DisableVertexAttribArray("vNormal");
CheckGlError(__FILE__, __LINE__);
}
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
}
void GpuSkinningShaderDataNew::prepareGpuSkinningVBOs(BoneSystemNew& model)
{
for (const auto& name : model.meshNamesOrdered)
{
auto it = model.meshes.find(name);
if (it == model.meshes.end()) continue;
perMesh[name].prepareGpuSkinningVBOs(it->second);
}
}
void GpuSkinningShaderDataNew::RenderVBO(Renderer& renderer, const std::vector<std::string>& meshNamesOrdered)
{
for (const auto& name : meshNamesOrdered)
{
auto it = perMesh.find(name);
if (it == perMesh.end()) continue;
it->second.RenderVBO(renderer);
}
}
void GpuSkinningShaderDataNew::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame)
{
int startingKeyFrame = -1;
for (size_t i = 0; i < keyFrames.size() - 1; i++)
{
int oldFrame = keyFrames[i].frame;
int nextFrame = keyFrames[i + 1].frame;
if (frame >= oldFrame && frame < nextFrame)
{
startingKeyFrame = static_cast<int>(i);
break;
}
}
skinningMatrices.resize(startBones.size());
if (startingKeyFrame == -1)
{
for (auto& m : skinningMatrices) m = Matrix4f::Identity();
return;
}
int modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
int diffFrames = keyFrames[startingKeyFrame + 1].frame - keyFrames[startingKeyFrame].frame;
float t = (modifiedFrameNumber + 0.f) / diffFrames;
const std::vector<Bone>& oneFrameBones = keyFrames[startingKeyFrame].bones;
const std::vector<Bone>& nextFrameBones = keyFrames[startingKeyFrame + 1].bones;
for (size_t i = 0; i < startBones.size(); i++)
{
Vector3f interpPos;
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
Eigen::Quaternionf result = q1.slerp(t, q2);
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
Matrix4f startBoneMatrixWorld4 = keyFrames[0].bones[i].boneMatrixWorld;
skinningMatrices[i] = currentBoneMatrixWorld4 * startBoneMatrixWorld4.inverse();
}
}
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include "BoneAnimatedModel.h"
#include <unordered_map>
namespace ZL
{
struct MeshBoneData
{
VertexDataStruct mesh;
VertexDataStruct startMesh;
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> verticesBoneWeight;
};
struct BoneSystemNew
{
std::unordered_map<std::string, MeshBoneData> meshes;
std::vector<std::string> meshNamesOrdered;
Matrix4f armatureMatrix;
std::vector<Bone> startBones;
std::vector<Bone> currentBones;
std::vector<Animation> animations;
int startingFrame = 0;
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
void Interpolate(int frame);
};
struct MeshGpuSkinningData
{
GpuBoneData gpuBoneData;
VertexRenderStruct bindPoseMutable;
std::shared_ptr<VBOHolder> boneIndices0VBO;
std::shared_ptr<VBOHolder> boneIndices1VBO;
std::shared_ptr<VBOHolder> boneWeights0VBO;
std::shared_ptr<VBOHolder> boneWeights1VBO;
bool gpuSkinningPrepared = false;
void prepareGpuSkinningVBOs(MeshBoneData& meshData);
void RenderVBO(Renderer& renderer);
};
struct GpuSkinningShaderDataNew
{
std::unordered_map<std::string, MeshGpuSkinningData> perMesh;
std::vector<Eigen::Matrix4f> skinningMatrices;
void prepareGpuSkinningVBOs(BoneSystemNew& model);
void RenderVBO(Renderer& renderer, const std::vector<std::string>& meshNamesOrdered);
void ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame);
};
struct BoneAnimationDataNew
{
BoneSystemNew model;
float currentFrame = 0.f;
int lastFrame = -1;
int totalFrames = 1;
GpuSkinningShaderDataNew gpuSkinningShaderData;
};
};
+154 -70
View File
@@ -7,6 +7,8 @@
namespace ZL {
const float ATTACK_COOLDOWN_TIME = 3.0f;
extern float x;
extern float y;
void Character::loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile) {
auto& data = animations[state];
@@ -79,13 +81,68 @@ AnimationState Character::resolveActiveState() const {
}
void Character::update(int64_t deltaMs) {
//std::cout << "update called with deltaMs: " << deltaMs << std::endl;
// Move toward walk target on the XZ plane
Eigen::Vector3f activeTarget = walkTarget;
//weaponInitialRotation = Eigen::AngleAxisf(x/180.0, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialRotation = Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialPosition = Eigen::Vector3f(0, 0.09, 0.016);
/*if (deltaMs > 200)
{
deltaMs = 200;
}*/
Eigen::Vector3f activeTarget;
Eigen::Vector3f lookTarget;
if (attackTarget == nullptr)
{
battle_state = 0;
attack = 0;
}
if (attackTarget != nullptr)
{
float distToGhost = (attackTarget->position - position).norm();
if (distToGhost >= 10.f)
{
if (isPlayer)
{
setTarget(attackTarget->position);
}
battle_state = 0;
}
else if (distToGhost < 10.0f && distToGhost >= 1.f) {
setTarget(attackTarget->position);
battle_state = 0;
}
else
{
battle_state = 1;
}
}
if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) {
activeTarget = pathWaypoints[currentWaypointIndex];
}
else
{
activeTarget = walkTarget;
}
if (battle_state == 0)
{
lookTarget = activeTarget;
}
else
{
lookTarget = attackTarget->position;
}
targetFacingAngle = facingAngle;
if (currentState == AnimationState::WALK || currentState == AnimationState::STAND)
{
Eigen::Vector3f toTarget = activeTarget - position;
toTarget.y() = 0.f;
float dist = toTarget.norm();
@@ -96,21 +153,15 @@ void Character::update(int64_t deltaMs) {
if (moveAmount >= dist) {
position = activeTarget;
position.y() = 0.f;
} else {
}
else {
position += dir * moveAmount;
}
targetFacingAngle = atan2(dir.x(), -dir.z());
if (battle_state == 0 && currentState == AnimationState::STAND)
{
currentState = AnimationState::WALK;
}
} else {
if (battle_state == 0 &&
currentState == AnimationState::WALK
)
{
else {
currentState = AnimationState::STAND;
}
const bool hasNextWaypoint = !pathWaypoints.empty() && currentWaypointIndex + 1 < pathWaypoints.size();
if (hasNextWaypoint) {
@@ -126,66 +177,15 @@ void Character::update(int64_t deltaMs) {
onArrivedCallback = nullptr;
cb();
}
}
if (battle_state == 1 && attack == 1 && attackTarget != nullptr)
{
targetFacingAngle = atan2(attackTarget->position.x() - position.x(), -(attackTarget->position.z() - position.z()));
}
// Rotate toward target facing angle at constant angular speed
float angleDiff = targetFacingAngle - facingAngle;
while (angleDiff > static_cast<float>(M_PI)) angleDiff -= 2.f * static_cast<float>(M_PI);
while (angleDiff < -static_cast<float>(M_PI)) angleDiff += 2.f * static_cast<float>(M_PI);
float rotStep = rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotStep) {
facingAngle = targetFacingAngle;
} else {
facingAngle += (angleDiff > 0.f ? rotStep : -rotStep);
}
if (canAttack && attackTarget != nullptr)
{
float distToGhost = (attackTarget->position - position).norm();
if (distToGhost >= 10.f)
{
if (isPlayer)
{
setTarget(attackTarget->position);
}
if (battle_state != 0)
{
battle_state = 0;
}
}
else if (distToGhost < 10.0f && distToGhost >= 1.f) {
setTarget(attackTarget->position);
if (battle_state != 0)
{
battle_state = 0;
}
}
else
{
setTarget(position);
if (battle_state != 1)
{
battle_state = 1;
}
}
}
if (attackTarget == nullptr)
{
if (battle_state != 0)
{
battle_state = 0;
attack = 0;
targetFacingAngle = facingAngle;
}
}
if (battle_state == 1)
{
targetFacingAngle = atan2(lookTarget.x() - position.x(), -(lookTarget.z() - position.z()));
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
currentState = AnimationState::STAND_TO_ACTION;
resetAnim = true;
@@ -218,6 +218,19 @@ void Character::update(int64_t deltaMs) {
}
}
// Rotate toward target facing angle at constant angular speed
float angleDiff = targetFacingAngle - facingAngle;
while (angleDiff > static_cast<float>(M_PI)) angleDiff -= 2.f * static_cast<float>(M_PI);
while (angleDiff < -static_cast<float>(M_PI)) angleDiff += 2.f * static_cast<float>(M_PI);
float rotStep = rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotStep) {
facingAngle = targetFacingAngle;
} else {
facingAngle += (angleDiff > 0.f ? rotStep : -rotStep);
}
auto it = animations.find(currentState);
if (it == animations.end()) return;
@@ -229,7 +242,22 @@ void Character::update(int64_t deltaMs) {
anim.currentFrame = 0;
}
anim.currentFrame += static_cast<float>(deltaMs) / 24.f;
//std::cout << "Current animation frame: " << anim.currentFrame << " / " << anim.totalFrames << " -- " << anim.lastFrame << std::endl;
if (static_cast<int>(anim.currentFrame) >= 20 && currentState == AnimationState::STAND_TO_ACTION)
{
showWeapon = true;
}
else if (static_cast<int>(anim.currentFrame) <= 32 && currentState == AnimationState::ACTION_TO_STAND)
{
showWeapon = true;
}
else
{
showWeapon = false;
}
int frms = anim.model.animations[0].keyFrames[anim.model.animations[0].keyFrames.size() - 1].frame;
if (static_cast<int>(anim.currentFrame) >= frms) {
anim.currentFrame = anim.model.startingFrame;
@@ -255,9 +283,8 @@ void Character::update(int64_t deltaMs) {
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
if (useGpuSkinning) {
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
} else {
if (!useGpuSkinning) {
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
}
anim.lastFrame = static_cast<int>(anim.currentFrame);
@@ -294,6 +321,9 @@ void Character::draw(Renderer& renderer) {
renderer.DrawVertexRenderStruct(modelMutable);
renderer.PopMatrix();
drawAttachedWeapon(renderer);
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
@@ -353,6 +383,12 @@ void Character::drawGpuSkinning(Renderer& renderer) {
anim.gpuSkinningShaderData.RenderVBO(renderer);
renderer.PopMatrix();
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
@@ -385,6 +421,38 @@ bool Character::prepareGpuSkinning()
return true;
}
void Character::drawAttachedWeapon(Renderer& renderer)
{
if (!showWeapon) return;
if (weaponMesh.data.PositionData.size() == 0 || !weaponTexture) return;
auto it = animations.find(resolveActiveState());
if (it == animations.end()) return;
auto& anim = it->second;
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
int boneIdx = anim.model.findBoneIndex(weaponAttachBoneName);
if (boneIdx < 0) return;
const Eigen::Matrix4f& bindBone = anim.model.animations[0].keyFrames[0].bones[boneIdx].boneMatrixWorld;
Eigen::Matrix4f currentBone = anim.gpuSkinningShaderData.skinningMatrices[boneIdx] * bindBone;
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix(Eigen::Vector3f(currentBone.block<3, 1>(0, 3)));
renderer.RotateMatrix(Eigen::Matrix3f(currentBone.block<3, 3>(0, 0)));
renderer.TranslateMatrix(weaponInitialPosition);
renderer.RotateMatrix(weaponInitialRotation);
glBindTexture(GL_TEXTURE_2D, weaponTexture->getTexID());
renderer.DrawVertexRenderStruct(weaponMesh);
renderer.PopMatrix();
}
// ==================== Shadow depth pass ====================
void Character::drawShadowDepth(Renderer& renderer) {
@@ -412,8 +480,9 @@ void Character::drawShadowDepthCpu(Renderer& renderer) {
modelMutable.AssignFrom(anim.model.mesh);
modelMutable.RefreshVBO();
renderer.DrawVertexRenderStruct(modelMutable);
renderer.PopMatrix();
drawAttachedWeapon(renderer);
}
void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
@@ -453,6 +522,8 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
renderer.PopMatrix();
renderer.shaderManager.PopShader();
drawAttachedWeapon(renderer);
CheckGlError(__FILE__, __LINE__);
}
@@ -500,6 +571,12 @@ void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lig
renderer.DrawVertexRenderStruct(modelMutable);
renderer.PopMatrix();
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
@@ -553,9 +630,16 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
CheckGlError(__FILE__, __LINE__);
it->second.gpuSkinningShaderData.RenderVBO(renderer);
renderer.PopMatrix();
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
CheckGlError(__FILE__, __LINE__);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
+11
View File
@@ -75,6 +75,13 @@ public:
float interactionRadius = 0.0f;
VertexRenderStruct weaponMesh;
std::shared_ptr<Texture> weaponTexture;
Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity();
Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
std::string weaponAttachBoneName = "RightHand";
bool showWeapon = true;
private:
@@ -99,6 +106,10 @@ private:
bool prepareGpuSkinning();
// Draws the weapon mesh attached to weaponAttachBoneName.
// Caller must ensure the right shader is active with matching uniforms.
void drawAttachedWeapon(Renderer& renderer);
// GPU skinning: draw using shader-based skinning
void drawGpuSkinning(Renderer& renderer);
// Shadow: draw into depth map (no texture, no projection push)
+10
View File
@@ -503,10 +503,20 @@ namespace ZL
currentLocation->dialogueSystem.startDialogue("test_cutscene_pan_dialogue");
break;
case SDLK_o:
y = y + 0.002;
break;
case SDLK_k:
y = y - 0.002;
break;
case SDLK_p:
x = x + 0.002;
break;
case SDLK_l:
x = x - 0.002;
break;
case SDLK_RETURN:
+44 -15
View File
@@ -41,23 +41,50 @@ namespace ZL
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
auto playerTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
//auto playerTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
auto playerTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/UniV_Grid_2K_Base_color.png", CONST_ZIP_FILE));
player = std::make_unique<Character>();
/*
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE);
*/
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/new/gg_stand_idle001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/new/gg_walk001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/new/gg_stand_to_action001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/new/gg_action_to_stand001.anim", CONST_ZIP_FILE);
player->weaponTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/white.png", CONST_ZIP_FILE));
//player->weaponMesh.AssignFrom(LoadFromTextFile02("resources/w/gg/knife001.txt", CONST_ZIP_FILE));
player->weaponMesh.data = LoadFromTextFile02("resources/w/gg/knife002.txt", CONST_ZIP_FILE);
player->weaponMesh.data.Scale(0.1f);
//player->weaponMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
player->weaponMesh.RefreshVBO();
player->weaponAttachBoneName = "RightHand";
player->weaponInitialRotation = Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitZ()).toRotationMatrix();
player->weaponInitialPosition = Eigen::Vector3f(0, 0.09, 0.016);
player->setTexture(playerTexture);
player->walkSpeed = 3.0f;
player->rotationSpeed = 8.0f;
player->modelScale = 1.f;
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
player->modelCorrectionRotation = Eigen::Quaternionf(
Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())
*
Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitX())
);
player->canAttack = true;
player->isPlayer = true;
@@ -295,14 +322,6 @@ namespace ZL
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
/*
glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
renderer.DrawVertexRenderStruct(fireboxMesh);
glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
renderer.DrawVertexRenderStruct(inaiMesh);
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
renderer.DrawVertexRenderStruct(benchMesh);
*/
for (auto& intObj : interactiveObjects) {
if (intObj.isActive) {
intObj.draw(renderer);
@@ -499,7 +518,14 @@ namespace ZL
player->update(delta);
dialogueSystem.update(static_cast<int>(delta), player->position);
}
for (auto& npc : npcs) npc->update(delta);
for (auto& npc : npcs)
{
if (npc->canAttack)
{
npc->update(delta);
}
//npc->update(delta);
}
@@ -573,6 +599,7 @@ namespace ZL
targetInteractiveObject = clickedObject;
player->setTarget(clickedObject->position);
player->attackTarget = nullptr;
std::cout << "[CLICK] Player moving to object..." << std::endl;
}
else {
@@ -602,6 +629,10 @@ namespace ZL
{
player->attackTarget = clickedNpc;
}
else
{
player->attackTarget = nullptr;
}
}
}
@@ -611,10 +642,8 @@ namespace ZL
Eigen::Vector3f hit = camPos + rayDir * t;
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK)
{
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
}
player->attackTarget = nullptr;
}
else {
std::cout << "[CLICK] No valid target found" << std::endl;