1053 lines
33 KiB
C++
1053 lines
33 KiB
C++
#include "BoneAnimatedModel.h"
|
|
#include <regex>
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
|
|
namespace ZL
|
|
{
|
|
|
|
int getIndexByValue(const std::string& name, const std::vector<std::string>& words)
|
|
{
|
|
for (int i = 0; i < words.size(); i++)
|
|
{
|
|
if (words[i] == name)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
|
|
std::cout << "Bone name not found: " << name << std::endl;
|
|
throw std::runtime_error("Bone name not found: " + name);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
void BoneSystem::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);
|
|
|
|
//Skip first 5 lines
|
|
std::string tempLine;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
std::getline(f, tempLine);
|
|
}
|
|
std::getline(f, tempLine);
|
|
|
|
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;
|
|
|
|
int numberBones;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberBones = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
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);
|
|
std::string boneName = tempLine.substr(6);
|
|
|
|
boneNames[i] = boneName;
|
|
|
|
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;
|
|
}
|
|
|
|
bones[i].boneStartWorld = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
|
|
|
|
|
|
std::getline(f, tempLine); //skip tail
|
|
|
|
std::getline(f, tempLine); //len
|
|
|
|
if (std::regex_search(tempLine, match, pattern_float)) {
|
|
std::string len_str = match.str();
|
|
bones[i].boneLength = std::stof(len_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
//---------- matrix begin
|
|
|
|
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()[0] = floatValues[0];
|
|
bones[i].boneMatrixWorld.data()[0 + 1 * 3] = floatValues[1];
|
|
bones[i].boneMatrixWorld.data()[0 + 2 * 3] = floatValues[2];
|
|
|
|
|
|
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()[1] = floatValues[0];
|
|
bones[i].boneMatrixWorld.data()[1 + 1 * 3] = floatValues[1];
|
|
bones[i].boneMatrixWorld.data()[1 + 2 * 3] = floatValues[2];
|
|
|
|
|
|
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()[2] = floatValues[0];
|
|
bones[i].boneMatrixWorld.data()[2 + 1 * 3] = floatValues[1];
|
|
bones[i].boneMatrixWorld.data()[2 + 2 * 3] = floatValues[2];
|
|
|
|
//----------- matrix end
|
|
std::getline(f, tempLine); //parent
|
|
|
|
if (tempLine == " Parent: None")
|
|
{
|
|
bones[i].parent = -1;
|
|
}
|
|
else
|
|
{
|
|
std::string boneParent = tempLine.substr(10);
|
|
boneParentNames[i] = boneParent;
|
|
}
|
|
|
|
std::getline(f, tempLine); //children
|
|
|
|
b = tempLine.cbegin();
|
|
e = tempLine.cend();
|
|
while (std::regex_search(b, e, match, pattern_boneChildren)) {
|
|
|
|
boneChildren[boneName].push_back(match.str(1));
|
|
b = match.suffix().first;
|
|
}
|
|
|
|
}
|
|
|
|
std::cout << "bone names count : " << boneNames.size() << std::endl;
|
|
|
|
//Now process all the bones:
|
|
for (int i = 0; i < numberBones; i++)
|
|
{
|
|
std::string boneName = boneNames[i];
|
|
std::string boneParent = boneParentNames[i];
|
|
if (boneParent == "" || boneParent == "None")
|
|
{
|
|
bones[i].parent = -1;
|
|
}
|
|
else
|
|
{
|
|
bones[i].parent = getIndexByValue(boneParent, boneNames);
|
|
}
|
|
|
|
for (int j = 0; j < boneChildren[boneName].size(); j++)
|
|
{
|
|
bones[i].children.push_back(getIndexByValue(boneChildren[boneName][j], boneNames));
|
|
}
|
|
}
|
|
|
|
startBones = bones;
|
|
currentBones = bones;
|
|
|
|
std::getline(f, tempLine); //vertice count
|
|
int numberVertices;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberVertices = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
std::vector<Vector3f> vertices;
|
|
|
|
vertices.resize(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]};
|
|
}
|
|
|
|
|
|
//==== process uv and normals begin
|
|
|
|
std::getline(f, tempLine); //===UV Coordinates:
|
|
|
|
std::getline(f, tempLine); //triangle count
|
|
int numberTriangles;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberTriangles = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
|
|
// Now process UVs
|
|
std::vector<std::array<Vector2f, 3>> uvCoords;
|
|
|
|
uvCoords.resize(numberTriangles);
|
|
|
|
for (int i = 0; i < numberTriangles; i++)
|
|
{
|
|
std::getline(f, tempLine); //Face 0
|
|
|
|
int uvCount;
|
|
std::getline(f, tempLine);
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
uvCount = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
if (uvCount != 3)
|
|
{
|
|
throw std::runtime_error("more than 3 uvs");
|
|
}
|
|
|
|
std::vector<float> floatValues;
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
std::getline(f, tempLine); //UV <Vector (-0.3661, -1.1665)>
|
|
|
|
auto b = tempLine.cbegin();
|
|
auto 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;
|
|
}
|
|
|
|
if (floatValues.size() != 2)
|
|
{
|
|
throw std::runtime_error("more than 2 uvs---");
|
|
}
|
|
|
|
uvCoords[i][j] = Vector2f{ floatValues[0],floatValues[1] };
|
|
}
|
|
}
|
|
|
|
std::getline(f, tempLine); //===Normals:
|
|
|
|
std::vector<Vector3f> normals;
|
|
|
|
normals.resize(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] };
|
|
}
|
|
|
|
//==== process uv and normals end
|
|
|
|
std::getline(f, tempLine); //triangle count.
|
|
//numberTriangles; //Need to check if new value is the same as was read before
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberTriangles = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
std::vector<std::array<int, 3>> triangles;
|
|
|
|
triangles.resize(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] };
|
|
}
|
|
|
|
std::getline(f, tempLine);//=== Vertex Weights ===
|
|
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight;
|
|
localVerticesBoneWeight.resize(numberVertices);
|
|
|
|
for (int i = 0; i < numberVertices; i++)
|
|
{
|
|
std::getline(f, tempLine); //skip Vertex 0:
|
|
std::getline(f, tempLine); //vertex group count
|
|
int boneCount;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
boneCount = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
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); //Group: 'Bone', Weight: 0.9929084181785583
|
|
if (std::regex_search(tempLine, match, pattern_bone_weight)) {
|
|
std::string word = match.str(1);
|
|
double weight = std::stod(match.str(2));
|
|
//std::cout << "Bone name: " << word << ", weight: " << weight << std::endl;
|
|
int boneNumber = getIndexByValue(word, boneNames);
|
|
localVerticesBoneWeight[i][j].boneIndex = boneNumber;
|
|
localVerticesBoneWeight[i][j].weight = weight;
|
|
sumWeights += weight;
|
|
}
|
|
else {
|
|
throw std::runtime_error("No match found in the input string.");
|
|
}
|
|
}
|
|
|
|
//Normalize weights:
|
|
for (int j = 0; j < boneCount; j++)
|
|
{
|
|
localVerticesBoneWeight[i][j].weight = localVerticesBoneWeight[i][j].weight / sumWeights;
|
|
}
|
|
|
|
}
|
|
|
|
std::getline(f, tempLine);//=== Animation Keyframes ===
|
|
std::getline(f, tempLine);//=== Bone Transforms per Keyframe ===
|
|
|
|
std::getline(f, tempLine);
|
|
int numberKeyFrames;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberKeyFrames = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
animations.resize(1);
|
|
|
|
animations[0].keyFrames.resize(numberKeyFrames);
|
|
|
|
for (int i = 0; i < numberKeyFrames; i++)
|
|
{
|
|
std::getline(f, tempLine);
|
|
int numberFrame;
|
|
|
|
if (std::regex_search(tempLine, match, pattern_count)) {
|
|
std::string number_str = match.str();
|
|
numberFrame = std::stoi(number_str);
|
|
}
|
|
else {
|
|
throw std::runtime_error("No number found in the input string.");
|
|
}
|
|
|
|
animations[0].keyFrames[i].frame = numberFrame;
|
|
|
|
animations[0].keyFrames[i].bones.resize(numberBones);
|
|
|
|
for (int j = 0; j < numberBones; j++)
|
|
{
|
|
std::getline(f, tempLine);
|
|
std::string boneName = tempLine.substr(8);
|
|
|
|
int boneNumber = getIndexByValue(boneName, boneNames);
|
|
animations[0].keyFrames[i].bones[boneNumber] = startBones[boneNumber];
|
|
|
|
std::getline(f, tempLine); // Location: <Vector (0.0000, 0.0000, -0.0091)>
|
|
|
|
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
|
|
|
|
//=============== Matrix begin ==================
|
|
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()[0] = floatValues[0];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[0 + 1 * 4] = floatValues[1];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[0 + 2 * 4] = floatValues[2];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[0 + 3 * 4] = floatValues[3];
|
|
|
|
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()[1] = floatValues[0];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[1 + 1 * 4] = floatValues[1];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[1 + 2 * 4] = floatValues[2];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[1 + 3 * 4] = floatValues[3];
|
|
|
|
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()[2] = floatValues[0];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[2 + 1 * 4] = floatValues[1];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[2 + 2 * 4] = floatValues[2];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[2 + 3 * 4] = floatValues[3];
|
|
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()[3] = floatValues[0];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[3 + 1 * 4] = floatValues[1];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[3 + 2 * 4] = floatValues[2];
|
|
animations[0].keyFrames[i].bones[boneNumber].boneMatrixWorld.data()[3 + 3 * 4] = floatValues[3];
|
|
|
|
//=============== Matrix end ==================
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now let's process bone weights and vertices
|
|
|
|
for (int i = 0; i < numberTriangles; i++)
|
|
{
|
|
|
|
mesh.PositionData.push_back(vertices[triangles[i][0]]);
|
|
mesh.PositionData.push_back(vertices[triangles[i][1]]);
|
|
mesh.PositionData.push_back(vertices[triangles[i][2]]);
|
|
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
|
|
|
|
mesh.TexCoordData.push_back(uvCoords[i][0]);
|
|
mesh.TexCoordData.push_back(uvCoords[i][1]);
|
|
mesh.TexCoordData.push_back(uvCoords[i][2]);
|
|
}
|
|
|
|
startMesh = mesh;
|
|
|
|
|
|
if (startBones.size() > MAX_GPU_BONES)
|
|
{
|
|
std::cout << "Warning: model has " << startBones.size()
|
|
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
|
|
//throw std::runtime_error("Too many bones for GPU skinning");
|
|
}
|
|
}
|
|
|
|
void BoneSystem::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();
|
|
|
|
auto readRaw = [&](void* dst, size_t n) {
|
|
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()}; };
|
|
|
|
// Header
|
|
char magic[4];
|
|
readRaw(magic, 4);
|
|
if (std::memcmp(magic, "BSAF", 4) != 0)
|
|
throw std::runtime_error("Invalid binary animation file (bad magic)");
|
|
uint32_t version = readUint32();
|
|
if (version != 1)
|
|
throw std::runtime_error("Unsupported binary animation file version");
|
|
|
|
// ---- 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();
|
|
|
|
// 3x3 matrix (row-major in file).
|
|
// Stored with stride-3 into Matrix4f to match the text loader.
|
|
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;
|
|
|
|
// ---- Vertices ----
|
|
uint32_t numVertices = readUint32();
|
|
std::vector<Vector3f> vertices(numVertices);
|
|
for (uint32_t i = 0; i < numVertices; i++)
|
|
vertices[i] = readVec3();
|
|
|
|
// ---- UV Coordinates ----
|
|
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();
|
|
|
|
// ---- Normals (read but not currently used by mesh) ----
|
|
std::vector<Vector3f> normals(numVertices);
|
|
for (uint32_t i = 0; i < numVertices; i++)
|
|
normals[i] = readVec3();
|
|
|
|
// ---- Triangles ----
|
|
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() };
|
|
|
|
// ---- Vertex Weights ----
|
|
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;
|
|
}
|
|
}
|
|
// Normalize weights
|
|
uint32_t cap = (numGroups < MAX_BONE_COUNT) ? numGroups : MAX_BONE_COUNT;
|
|
for (uint32_t j = 0; j < cap; j++)
|
|
localVerticesBoneWeight[i][j].weight /= sumWeights;
|
|
}
|
|
|
|
// ---- 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();
|
|
|
|
// 4x4 matrix (row-major in file, stored with stride-4 into Matrix4f)
|
|
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];
|
|
}
|
|
}
|
|
|
|
// ---- Build per-triangle mesh (same expansion as text loader) ----
|
|
for (uint32_t i = 0; i < numTriangles; i++)
|
|
{
|
|
mesh.PositionData.push_back(vertices[triangles[i][0]]);
|
|
mesh.PositionData.push_back(vertices[triangles[i][1]]);
|
|
mesh.PositionData.push_back(vertices[triangles[i][2]]);
|
|
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
|
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
|
|
|
|
mesh.TexCoordData.push_back(uvCoords[i][0]);
|
|
mesh.TexCoordData.push_back(uvCoords[i][1]);
|
|
mesh.TexCoordData.push_back(uvCoords[i][2]);
|
|
}
|
|
|
|
startMesh = mesh;
|
|
|
|
if (startBones.size() > MAX_GPU_BONES)
|
|
{
|
|
std::cout << "Warning: model has " << startBones.size()
|
|
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
|
|
//throw std::runtime_error("Too many bones for GPU skinning");
|
|
}
|
|
}
|
|
|
|
void GpuBoneData::PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight)
|
|
{
|
|
size_t vertexCount = verticesBoneWeight.size();
|
|
boneIndices0.resize(vertexCount);
|
|
boneIndices1.resize(vertexCount);
|
|
boneWeights0.resize(vertexCount);
|
|
boneWeights1.resize(vertexCount);
|
|
|
|
for (size_t i = 0; i < vertexCount; i++)
|
|
{
|
|
boneIndices0[i] = Vector4f(
|
|
static_cast<float>(max(0, verticesBoneWeight[i][0].boneIndex)),
|
|
static_cast<float>(max(0, verticesBoneWeight[i][1].boneIndex)),
|
|
static_cast<float>(max(0, verticesBoneWeight[i][2].boneIndex)),
|
|
static_cast<float>(max(0, verticesBoneWeight[i][3].boneIndex))
|
|
);
|
|
boneIndices1[i] = Vector2f(
|
|
static_cast<float>(max(0, verticesBoneWeight[i][4].boneIndex)),
|
|
static_cast<float>(max(0, verticesBoneWeight[i][5].boneIndex))
|
|
);
|
|
boneWeights0[i] = Vector4f(
|
|
verticesBoneWeight[i][0].weight,
|
|
verticesBoneWeight[i][1].weight,
|
|
verticesBoneWeight[i][2].weight,
|
|
verticesBoneWeight[i][3].weight
|
|
);
|
|
boneWeights1[i] = Vector2f(
|
|
verticesBoneWeight[i][4].weight,
|
|
verticesBoneWeight[i][5].weight
|
|
);
|
|
}
|
|
|
|
prepared = true;
|
|
}
|
|
/*
|
|
void BoneSystem::ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const
|
|
{
|
|
int startingKeyFrame = -1;
|
|
for (size_t i = 0; i < 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 = static_cast<int>(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (startingKeyFrame == -1)
|
|
{
|
|
outMatrices.resize(startBones.size());
|
|
for (auto& m : outMatrices) m = Matrix4f::Identity();
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
const std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
|
|
const std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
|
|
|
|
outMatrices.resize(startBones.size());
|
|
|
|
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 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
|
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
|
|
|
outMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
|
}
|
|
}
|
|
*/
|
|
void BoneSystem::Interpolate(int frame)
|
|
{
|
|
int startingFrame = -1;
|
|
for (int i = 0; i < 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)
|
|
{
|
|
startingFrame = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (startingFrame == -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[startingFrame].frame;
|
|
|
|
int diffFrames = animations[0].keyFrames[startingFrame + 1].frame - animations[0].keyFrames[startingFrame].frame;
|
|
|
|
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
|
|
|
std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingFrame].bones;
|
|
std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingFrame+1].bones;
|
|
|
|
std::vector<Matrix4f> skinningMatrixForEachBone;
|
|
skinningMatrixForEachBone.resize(currentBones.size());
|
|
|
|
|
|
for (int 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;
|
|
|
|
oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
|
|
|
Matrix3f nextFrameBonesMatrix;
|
|
|
|
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 q1_norm = q1.normalized();
|
|
Eigen::Quaternionf q2_norm = q2.normalized();
|
|
|
|
Eigen::Quaternionf result = q1_norm.slerp(t, q2_norm);
|
|
|
|
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
|
|
|
|
currentBones[i].boneMatrixWorld = Eigen::Matrix4f::Identity();
|
|
|
|
// Копируем 3x3 матрицу в верхний левый угол
|
|
currentBones[i].boneMatrixWorld.block<3, 3>(0, 0) = boneMatrixWorld3;
|
|
|
|
// Копируем позицию в последний столбец (первые 3 элемента)
|
|
currentBones[i].boneMatrixWorld.block<3, 1>(0, 3) = currentBones[i].boneStartWorld;
|
|
|
|
|
|
Matrix4f currentBoneMatrixWorld4 = currentBones[i].boneMatrixWorld;
|
|
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
|
|
|
Matrix4f inverstedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
|
|
|
skinningMatrixForEachBone[i] = currentBoneMatrixWorld4 * inverstedStartBoneMatrixWorld4;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < mesh.PositionData.size(); i++)
|
|
{
|
|
Vector4f originalPos = {
|
|
startMesh.PositionData[i](0),
|
|
startMesh.PositionData[i](1),
|
|
startMesh.PositionData[i](2), 1.0};
|
|
|
|
Vector4f finalPos = Vector4f{0.f, 0.f, 0.f, 0.f};
|
|
|
|
bool vMoved = false;
|
|
|
|
for (int j = 0; j < MAX_BONE_COUNT; j++)
|
|
{
|
|
if (verticesBoneWeight[i][j].weight != 0)
|
|
{
|
|
if (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[verticesBoneWeight[i][j].boneIndex] * originalPos) * verticesBoneWeight[i][j].weight;
|
|
|
|
}
|
|
}
|
|
|
|
if (!vMoved)
|
|
{
|
|
finalPos = originalPos;
|
|
}
|
|
|
|
mesh.PositionData[i](0) = finalPos(0);
|
|
mesh.PositionData[i](1) = finalPos(1);
|
|
mesh.PositionData[i](2) = finalPos(2);
|
|
}
|
|
|
|
}
|
|
|
|
void GpuSkinningShaderData::prepareGpuSkinningVBOs(BoneSystem& model) {
|
|
if (gpuSkinningPrepared)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gpuBoneData.PrepareGpuSkinningData(model.verticesBoneWeight);
|
|
|
|
// Upload bind-pose mesh (static, done once)
|
|
bindPoseMutable.AssignFrom(model.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 GpuSkinningShaderData::RenderVBO(Renderer& renderer)
|
|
{
|
|
// Bind position and texcoord VBOs
|
|
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
|
|
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
|
|
|
if (bindPoseMutable.texCoordVBO) {
|
|
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
|
|
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
|
}
|
|
|
|
// Bind bone index VBOs
|
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
|
|
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
|
|
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
|
|
|
// Bind bone weight VBOs
|
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
|
|
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
|
|
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
|
|
|
|
}
|
|
|
|
void GpuSkinningShaderData::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;
|
|
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
|
|
|
skinningMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
|
}
|
|
}
|
|
|
|
|
|
} |