Add very experimental skeleton loading
Added functions to extract Havok pak files, and extract some skeleton data from an externally processed Havok XML file. This still requires you to use the proprietary Havok SDK though, but I'll think of a solution in the future.
This commit is contained in:
parent
a7751a50bb
commit
68ecdca929
5 changed files with 199 additions and 4 deletions
|
@ -72,7 +72,8 @@ add_library(libxiv STATIC
|
||||||
src/installextract.cpp
|
src/installextract.cpp
|
||||||
src/patch.cpp
|
src/patch.cpp
|
||||||
src/exlparser.cpp
|
src/exlparser.cpp
|
||||||
src/mdlparser.cpp)
|
src/mdlparser.cpp
|
||||||
|
src/havokxmlparser.cpp)
|
||||||
target_include_directories(libxiv PUBLIC include PRIVATE src)
|
target_include_directories(libxiv PUBLIC include PRIVATE src)
|
||||||
target_link_libraries(libxiv PUBLIC ${LIBRARIES})
|
target_link_libraries(libxiv PUBLIC ${LIBRARIES} pugixml)
|
||||||
target_link_directories(libxiv PUBLIC ${LIB_DIRS})
|
target_link_directories(libxiv PUBLIC ${LIB_DIRS})
|
|
@ -26,6 +26,8 @@ public:
|
||||||
*/
|
*/
|
||||||
void extractFile(std::string_view dataFilePath, std::string_view outPath);
|
void extractFile(std::string_view dataFilePath, std::string_view outPath);
|
||||||
|
|
||||||
|
void extractSkeleton();
|
||||||
|
|
||||||
std::optional<EXH> readExcelSheet(std::string_view name);
|
std::optional<EXH> readExcelSheet(std::string_view name);
|
||||||
|
|
||||||
std::vector<std::string> getAllSheetNames();
|
std::vector<std::string> getAllSheetNames();
|
||||||
|
|
27
include/havokxmlparser.h
Normal file
27
include/havokxmlparser.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
struct Bone {
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
Bone* parent = nullptr;
|
||||||
|
|
||||||
|
std::array<float, 16> localTransform, finalTransform;
|
||||||
|
|
||||||
|
std::array<float, 3> position;
|
||||||
|
std::array<float, 4> rotation;
|
||||||
|
std::array<float, 3> scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Skeleton {
|
||||||
|
std::vector<Bone> bones;
|
||||||
|
Bone* root_bone = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This reads a havok xml scene file, which is generated from your preferred assetcc.exe.
|
||||||
|
*/
|
||||||
|
Skeleton parseHavokXML(const std::string_view path);
|
|
@ -106,7 +106,7 @@ void GameData::extractFile(std::string_view dataFilePath, std::string_view outPa
|
||||||
// TODO: handle platforms other than win32
|
// TODO: handle platforms other than win32
|
||||||
auto indexFilename = calculateFilename(categoryToID[category], getExpansionID(repository), 0, "win32", "index");
|
auto indexFilename = calculateFilename(categoryToID[category], getExpansionID(repository), 0, "win32", "index");
|
||||||
|
|
||||||
fmt::print("calculated index filename: ");
|
fmt::print("calculated index filename: {}\n", indexFilename);
|
||||||
|
|
||||||
// TODO: handle hashes in index2 files (we can read them but it's not setup yet.)
|
// TODO: handle hashes in index2 files (we can read them but it's not setup yet.)
|
||||||
auto indexFile = readIndexFile(dataDirectory + "/" + repository + "/" + indexFilename);
|
auto indexFile = readIndexFile(dataDirectory + "/" + repository + "/" + indexFilename);
|
||||||
|
@ -445,4 +445,56 @@ std::optional<EXH> GameData::readExcelSheet(std::string_view name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameData::extractSkeleton() {
|
||||||
|
std::string path = fmt::format("chara/human/c0201/skeleton/base/b0001/skl_c0201b0001.sklb");
|
||||||
|
|
||||||
|
extractFile(path, "test.skel");
|
||||||
|
|
||||||
|
FILE* file = fopen("test.skel", "rb");
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
size_t end = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
int32_t magic;
|
||||||
|
fread(&magic, sizeof(int32_t), 1, file);
|
||||||
|
|
||||||
|
int32_t format;
|
||||||
|
fread(&format, sizeof(int32_t), 1, file);
|
||||||
|
|
||||||
|
fseek(file, sizeof(uint16_t), SEEK_CUR);
|
||||||
|
|
||||||
|
if(magic != 0x736B6C62)
|
||||||
|
fmt::print("INVALID SKLB magic");
|
||||||
|
|
||||||
|
size_t dataOffset = 0;
|
||||||
|
|
||||||
|
switch(format) {
|
||||||
|
case 0x31323030:
|
||||||
|
fread(&dataOffset, sizeof(int16_t), 1, file);
|
||||||
|
break;
|
||||||
|
case 0x31333030:
|
||||||
|
case 0x31333031:
|
||||||
|
fseek(file, sizeof(uint16_t), SEEK_CUR);
|
||||||
|
fread(&dataOffset, sizeof(int16_t), 1, file);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fmt::print("INVALID SKLB format {}", format);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, dataOffset, SEEK_SET);
|
||||||
|
|
||||||
|
fmt::print("data offset: {}\n", dataOffset);
|
||||||
|
|
||||||
|
std::vector<uint8_t> havokData(end - dataOffset);
|
||||||
|
fread(havokData.data(), havokData.size(), 1, file);
|
||||||
|
|
||||||
|
FILE* newFile = fopen("test.sklb.havok", "wb");
|
||||||
|
fwrite(havokData.data(), havokData.size(), 1, newFile);
|
||||||
|
|
||||||
|
fclose(newFile);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
113
src/havokxmlparser.cpp
Normal file
113
src/havokxmlparser.cpp
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#include "havokxmlparser.h"
|
||||||
|
|
||||||
|
#include <pugixml.hpp>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void walkSkeleton(Skeleton& skeleton, Bone *pBone, int level = 0);
|
||||||
|
|
||||||
|
inline std::vector<std::string> tokenize(const std::string_view string, const std::string_view& delimiters) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
|
||||||
|
const size_t length = string.length();
|
||||||
|
size_t lastPos = 0;
|
||||||
|
|
||||||
|
while(lastPos < length + 1) {
|
||||||
|
size_t pos = string.find_first_of(delimiters, lastPos);
|
||||||
|
if(pos == std::string_view::npos)
|
||||||
|
pos = length;
|
||||||
|
|
||||||
|
if(pos != lastPos)
|
||||||
|
tokens.emplace_back(string.data() + lastPos, pos - lastPos);
|
||||||
|
|
||||||
|
lastPos = pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
Skeleton parseHavokXML(const std::string_view path) {
|
||||||
|
Skeleton skeleton;
|
||||||
|
|
||||||
|
pugi::xml_document doc;
|
||||||
|
doc.load_file(path.data());
|
||||||
|
|
||||||
|
pugi::xpath_node build_tool = doc.select_node("//hkobject[@name=\"#0052\"]/hkparam[@name=\"bones\"]");
|
||||||
|
|
||||||
|
auto bonesNode = build_tool.node();
|
||||||
|
|
||||||
|
fmt::print("num bones: {}\n", bonesNode.attribute("numelements").as_int());
|
||||||
|
|
||||||
|
skeleton.bones.reserve(bonesNode.attribute("numelements").as_int());
|
||||||
|
|
||||||
|
for(auto node : bonesNode.children()) {
|
||||||
|
pugi::xpath_node name_node = node.select_node("./hkparam[@name=\"name\"]");
|
||||||
|
//fmt::print("{}\n", node.print());
|
||||||
|
name_node.node().print(std::cout);
|
||||||
|
|
||||||
|
Bone bone;
|
||||||
|
bone.name = name_node.node().text().as_string();
|
||||||
|
|
||||||
|
fmt::print("bone {}\n", bone.name);
|
||||||
|
|
||||||
|
skeleton.bones.push_back(bone);
|
||||||
|
}
|
||||||
|
pugi::xpath_node parentNode = doc.select_node("//hkparam[@name=\"parentIndices\"]");
|
||||||
|
|
||||||
|
std::string text = parentNode.node().text().as_string();
|
||||||
|
|
||||||
|
auto parentIndices = tokenize(text, " ");
|
||||||
|
|
||||||
|
for(int i = 0; i < parentIndices.size(); i++) {
|
||||||
|
auto indice = std::atoi(parentIndices[i].c_str());
|
||||||
|
if(indice != -1)
|
||||||
|
skeleton.bones[i].parent = &skeleton.bones[indice];
|
||||||
|
else
|
||||||
|
skeleton.root_bone = &skeleton.bones[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
walkSkeleton(skeleton, skeleton.root_bone);
|
||||||
|
|
||||||
|
pugi::xpath_node build_tool2 = doc.select_node("//hkobject[@name=\"#0052\"]/hkparam[@name=\"referencePose\"]");
|
||||||
|
|
||||||
|
fmt::print("num ref poses: {}\n", build_tool2.node().attribute("numelements").as_int());
|
||||||
|
|
||||||
|
auto matrices = tokenize(build_tool2.node().text().as_string(), "\n");
|
||||||
|
int i = 0;
|
||||||
|
for(auto matrix : matrices) {
|
||||||
|
size_t firstParenthesis = matrix.find_first_of('(');
|
||||||
|
if(firstParenthesis != std::string::npos) {
|
||||||
|
matrix = matrix.substr(firstParenthesis);
|
||||||
|
std::replace(matrix.begin(), matrix.end(), ')', ' ');
|
||||||
|
matrix.erase(std::remove(matrix.begin(), matrix.end(), '('), matrix.end());
|
||||||
|
fmt::print("{}\n", matrix);
|
||||||
|
|
||||||
|
auto tokens = tokenize(matrix, " ");
|
||||||
|
std::array<float, 3> position = {std::stof(tokens[0]), std::stof(tokens[1]), std::stof(tokens[2])};
|
||||||
|
std::array<float, 4> rotation = {std::stof(tokens[3]), std::stof(tokens[4]), std::stof(tokens[5]), std::stof(tokens[6])};
|
||||||
|
std::array<float, 3> scale = {std::stof(tokens[7]), std::stof(tokens[8]), std::stof(tokens[9])};
|
||||||
|
|
||||||
|
skeleton.bones[i].position = position;
|
||||||
|
skeleton.bones[i].rotation = rotation;
|
||||||
|
skeleton.bones[i].scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return skeleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void walkSkeleton(Skeleton& skeleton, Bone *pBone, int level) {
|
||||||
|
for(int i = 0; i < level; i++)
|
||||||
|
fmt::print(" ");
|
||||||
|
|
||||||
|
fmt::print("- {}\n", pBone->name);
|
||||||
|
|
||||||
|
for(auto& bone : skeleton.bones) {
|
||||||
|
if(bone.parent == pBone) {
|
||||||
|
walkSkeleton(skeleton, &bone, level++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue