chore: Drop support for SWTOR assets
This doesn't really add to the project.
This commit is contained in:
parent
013f746cff
commit
df8fec4193
11 changed files with 5 additions and 1152 deletions
|
@ -236,21 +236,6 @@ set_target_properties(librender PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINA
|
|||
|
||||
## END librender static library
|
||||
|
||||
## libtor static library
|
||||
|
||||
set(TOR_HEADERS
|
||||
src/experimental/tor/gr2file.h
|
||||
src/experimental/tor/jbafile.h)
|
||||
|
||||
set(TOR_SOURCES
|
||||
src/experimental/tor/gr2file.cpp
|
||||
src/experimental/tor/jbafile.cpp)
|
||||
|
||||
add_library(libtor STATIC ${TOR_HEADERS} ${TOR_SOURCES})
|
||||
set_target_properties(libtor PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
## END libtor static library
|
||||
|
||||
## libaudio static library
|
||||
|
||||
set(AUDIO_HEADERS
|
||||
|
@ -686,7 +671,7 @@ add_executable(reone ${REONE_HEADERS} ${REONE_SOURCES})
|
|||
set_target_properties(reone PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
target_link_libraries(reone PRIVATE
|
||||
libmp libgame libscript libgui libscene libvideo libaudio libtor librender libresource libcommon
|
||||
libmp libgame libscript libgui libscene libvideo libaudio librender libresource libcommon
|
||||
libs3tc
|
||||
${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_SYSTEM_LIBRARY}
|
||||
GLEW::GLEW
|
||||
|
@ -751,7 +736,7 @@ if(BUILD_TESTS)
|
|||
foreach(TEST_FILE ${TEST_FILES})
|
||||
get_filename_component(TEST_NAME "${TEST_FILE}" NAME_WE)
|
||||
add_executable(test_${TEST_NAME} ${TEST_FILE})
|
||||
target_link_libraries(test_${TEST_NAME} PRIVATE libgame libscript libresource libcommon ${Boost_FILESYSTEM_LIBRARY})
|
||||
target_link_libraries(test_${TEST_NAME} PRIVATE libgame libscript libresource libcommon ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(test_${TEST_NAME} PRIVATE SDL2::SDL2)
|
||||
|
|
|
@ -1 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2021-02-08T10:55:18.929Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36" etag="Jn16g3SQKS4mHqmAybD-" version="14.3.0" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">3Zlbd6IwEMc/jY/2cPP2uK292x53e/H0aU9KRshuIDSEqv30GzQoSjyrrYhVfAj/hJD8ZjIToGafBeNLjiL/jmGgNcvA45rdrVmW6VhWLf0beKKUdsuZKR4nWGkL4YF8gBINpSYEQ7zUUDBGBYmWRZeFIbhiSUOcs9FysyGjy3eNkAcF4cFFtKgOCBb+TG1brYV+BcTzszubzc6sJkBZYzWT2EeYjXKSfV6zzzhjYlYKxmdAU3oZl8H1ZEB7f5uXNz/jN/R0evt4/1yfdXaxzSXzKXAIxae7TkbDm+jtGXcGHzAJ78c/ux/Duj3r+h3RRPFScxWTDKDHWRKpZsAFjHVmQ69Zc2PDwZpzgtL3gAUg+ERep3q3Oqof5Xb1duZPo4URrabS/LwBsxEg5TjevPMFHFlQfLZgZRVYwTgCTgI5UUQL3KS7RGkxCegFR4Esno58IuAhQm6qj+SCk5ovAjmKrplWq6mZhpFWZJOy07O1BsiDXm/iIuj/cjQ7J52l39exjh57j78j87af+Hd95+5lMnix65lhc1wpeQ2itUADhpNUO0WUeKEUKAzloE9jCZaEXm961rWMXAtXIgIuhRQgkcHhh6oQbMUG20DV2GOtQ5vL/uzYJ007fxScO3WCglEazknDyR8lubrWJILxPdpk5w4/T2nfyhLOoQbolfhsmo3KA3R7H6zWW+kTMbY0FKYmV4GbTKcWl5yqhpREV/Lka2mrXSFSfZ4qLkUOLISDzlPtNQbYNE9pFrU+HJZGvaGnXk8fItZ78vdk71QHXzuDInu5DeCIk9JjyEZxYxdpy9A8VzQcDWTbKcvFmzrKXopwY++e++5O/JuiV6B9FhNBmPbK3kqDgGA8Hc82NtpmZdgrZus0GxXHpZbOaC4LAgnkQILSbtA7q0um3dljWPLCQYwDmFzBE3DeQ716o6fZ3Ej2KMGEHRf64kuQytEX34GkGQFCDPt8NqyC/V5DjpZ98V3dlH3MEu4ezC60pKBTPf3i7l/SjyWqI0NvNg8u6Gi3oV5Cjhx89T6v3ZnGLieROHL2us8NpbH3/9Qj0g/7v26uQ68rd5cX8KR/BftOMOxzk7M33i2nPN7ydPGRblqX+9Zpn/8D</diagram></mxfile>
|
||||
<mxfile host="app.diagrams.net" modified="2021-03-08T05:27:07.122Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36" etag="n5N0cfmXNYK2PiidXptk" version="14.4.4" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">3Zlbc+IgFMc/jY+ZyU2rj2vt3Xbs9uL0qUPDMWGXhJSQGvvplxjiLdixl6jrjA/wBwn8zuFwQhrOcZidcRQH1wwDbdgmzhpOr2Hblmvbjfxn4olSOo5VKD4nWGlz4Y68gxJNpaYEQ7LUUTBGBYmXRY9FEXhiSUOcs/FytxGjy0+NkQ8V4c5DtKoOCRZBobbto7l+DsQPyidbrU7REqKys1pJEiDMxguSc9JwjjljoiiF2THQnF7JZXgxGdL+39bZ5W3yih66V/c3j0Yx2Oln/jJbAodIfHnodDy6jF8fcWf4DpPoJrvtvY+MZjH0G6Kp4kXJC0ecSJMVixaTkqRcf5wX05CechTKYnccEAF3MfJyfSw9SGqBCKmsWXmzAm6ZZt5QYnby2oZrUmt/Ay4gW7CoWuMZsBAEn8guqrW0lvJXwzJLTxzPzd8sewULpndcJSLlcv5s8DlWWVBkP0HZqVCuwPU5S+NvMpltIvRSDmt+zKqzAqutYWU7GlaWXRcru8IKshg4CSUTRLfulB/ac2OnNHRc3bq4ju/798+xdTVIg+uBe/00GT45sxksb/UwXks0ZDjNtS6ixI+kQGEkZ9lNJFkS+f1prWebCz08iQ24FHIyRMbgX6pBsBUj1AXaspY9ulnlntu9wr1Zlzu7+7r1V8OkpUFlt7a69dtfZvUh+q+z2h0Kq4ICMvDS6ZTrPplHlMTnsvKJgNheg3pHSPUBsLoVObAI9joAbg62bN1d/NNTr2aYU+pG/hKw3pP/T/bunsFv6Y58Pw8QG3OfUf0R8hS9AB2whAjCtP/sr3QICcbT+XzzfFxrM2flHOy0mjs22pHOaB4LQwlkT7bLz6B3V3OQdmeLG8aPhgkOYXIOD8B5H/WNZl9z7Er2KMWEHRb66ovfztFX3/vymwiIsCR12Oy3GnK07Kv3E1P2CUu5tzf5UU1BZ/f0q3mppJ9IVAeG3mrtXdDRXn/6KTlw8Lv3eW1mmnicxOLA2euuWGtjH/wxYjKIBr8vLyK/J7PLU3gwtBeBbwTDNpOcrfE+cuvjLavzzz/TtoWvaM7JPw==</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
|
@ -1,554 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gr2file.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include "glm/gtc/type_ptr.hpp"
|
||||
|
||||
#include "../../common/log.h"
|
||||
#include "../../common/streamutil.h"
|
||||
#include "../../render/textures.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "jbafile.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::render;
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tor {
|
||||
|
||||
static bool g_debugSkeleton = false;
|
||||
|
||||
Gr2File::Gr2File(string resRef, vector<shared_ptr<Animation>> animations, shared_ptr<Model> skeleton) :
|
||||
BinaryFile(4, "GAWB"),
|
||||
_resRef(move(resRef)),
|
||||
_animations(move(animations)),
|
||||
_skeleton(move(skeleton)) {
|
||||
}
|
||||
|
||||
void Gr2File::doLoad() {
|
||||
// Adapted from multiple sources:
|
||||
//
|
||||
// https://github.com/SWTOR-Extractors-Modders-Dataminers/Granny2-Plug-In-Blender-2.8x/blob/v1.0.0.1/io_scene_gr2/import_gr2.py
|
||||
// https://forum.xentax.com/viewtopic.php?f=16&t=9703&start=30#p94880
|
||||
// https://forum.xentax.com/viewtopic.php?f=16&t=11317&start=15#p128702
|
||||
|
||||
seek(0x10);
|
||||
|
||||
uint32_t numCachedOffsets = readUint32();
|
||||
_fileType = static_cast<FileType>(readUint32());
|
||||
_numMeshes = readUint16();
|
||||
_numMaterials = readUint16();
|
||||
_numBones = readUint16();
|
||||
_numAttachments = readUint16();
|
||||
|
||||
seek(0x50);
|
||||
|
||||
uint32_t offsetCachedOffsets = readUint32();
|
||||
_offsetMeshHeader = readUint32();
|
||||
_offsetMaterialHeader = readUint32();
|
||||
_offsetBoneStructure = readUint32();
|
||||
_offsetAttachments = readUint32();
|
||||
|
||||
loadMaterials();
|
||||
loadMeshes();
|
||||
loadSkeletonBones();
|
||||
loadAttachments();
|
||||
loadModel();
|
||||
}
|
||||
|
||||
void Gr2File::loadMeshes() {
|
||||
for (uint16_t i = 0; i < _numMeshes; ++i) {
|
||||
seek(_offsetMeshHeader + i * 0x28);
|
||||
_meshes.push_back(readMesh());
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<Gr2File::Gr2Mesh> Gr2File::readMesh() {
|
||||
uint32_t offsetName = readUint32();
|
||||
|
||||
auto mesh = make_unique<Gr2Mesh>();
|
||||
mesh->header.name = readCStringAt(offsetName);
|
||||
|
||||
ignore(4);
|
||||
|
||||
mesh->header.numPieces = readUint16();
|
||||
mesh->header.numUsedBones = readUint16();
|
||||
mesh->header.vertexMask = readUint16();
|
||||
mesh->header.vertexSize = readUint16();
|
||||
mesh->header.numVertices = readUint32();
|
||||
mesh->header.numIndices = readUint32();
|
||||
mesh->header.offsetVertices = readUint32();
|
||||
mesh->header.offsetPieces = readUint32();
|
||||
mesh->header.offsetIndices = readUint32();
|
||||
mesh->header.offsetBones = readUint32();
|
||||
|
||||
for (uint16_t i = 0; i < mesh->header.numPieces; ++i) {
|
||||
seek(mesh->header.offsetPieces + i * 0x30);
|
||||
mesh->pieces.push_back(readMeshPiece());
|
||||
}
|
||||
|
||||
mesh->mesh = readModelMesh(*mesh);
|
||||
|
||||
for (uint16_t i = 0; i < mesh->header.numUsedBones; ++i) {
|
||||
seek(mesh->header.offsetBones + i * 0x1c);
|
||||
mesh->bones.push_back(readMeshBone());
|
||||
}
|
||||
|
||||
return move(mesh);
|
||||
}
|
||||
|
||||
unique_ptr<Gr2File::MeshPiece> Gr2File::readMeshPiece() {
|
||||
auto piece = make_unique<MeshPiece>();
|
||||
piece->startFaceIdx = readUint32();
|
||||
piece->numFaces = readUint32();
|
||||
piece->materialIndex = readUint32();
|
||||
piece->pieceIndex = readUint32();
|
||||
|
||||
ignore(0x24); // bounding box
|
||||
|
||||
return move(piece);
|
||||
}
|
||||
|
||||
static float convertByteToFloat(uint8_t value) {
|
||||
return 2.0f * value / 255.0f - 1.0f;
|
||||
}
|
||||
|
||||
static float convertHalfFloatToFloat(uint16_t value) {
|
||||
uint32_t sign = (value & 0x8000) ? 1 : 0;
|
||||
uint32_t exponent = ((value & 0x7c00) >> 10) - 16;
|
||||
uint32_t mantissa = value & 0x03ff;
|
||||
uint32_t tmp = ((sign << 31) | ((exponent + 127) << 23) | (mantissa << 13));
|
||||
float result = 2.0f * *reinterpret_cast<float *>(&tmp);
|
||||
return result;
|
||||
}
|
||||
|
||||
static glm::vec3 computeBitangent(float sign, const glm::vec3 &normal, const glm::vec3 &tangent) {
|
||||
return sign * glm::cross(tangent, normal);
|
||||
}
|
||||
|
||||
unique_ptr<ModelMesh> Gr2File::readModelMesh(const Gr2Mesh &mesh) {
|
||||
int unkFlags = mesh.header.vertexMask & ~0x1f2;
|
||||
if (unkFlags != 0) {
|
||||
warn(boost::format("GR2: unrecognized vertex flags: 0x%x") % unkFlags);
|
||||
}
|
||||
|
||||
Mesh::VertexOffsets offsets;
|
||||
|
||||
vector<float> vertices;
|
||||
seek(mesh.header.offsetVertices);
|
||||
vector<uint8_t> gr2Vertices(readArray<uint8_t>(static_cast<size_t>(mesh.header.numVertices) * mesh.header.vertexSize));
|
||||
const uint8_t *gr2VerticesPtr = &gr2Vertices[0];
|
||||
for (uint32_t i = 0; i < mesh.header.numVertices; ++i) {
|
||||
int stride = 0;
|
||||
int gr2Stride = 0;
|
||||
|
||||
// Vertex coordinates
|
||||
vertices.push_back(*reinterpret_cast<const float *>(gr2VerticesPtr + gr2Stride + 0));
|
||||
vertices.push_back(*reinterpret_cast<const float *>(gr2VerticesPtr + gr2Stride + 4));
|
||||
vertices.push_back(*reinterpret_cast<const float *>(gr2VerticesPtr + gr2Stride + 8));
|
||||
stride += 3 * sizeof(float);
|
||||
gr2Stride += 3 * sizeof(float);
|
||||
|
||||
// Bone weights and indices
|
||||
if (mesh.header.vertexMask & 0x100) {
|
||||
vertices.push_back(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 0) / 255.0f);
|
||||
vertices.push_back(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 1) / 255.0f);
|
||||
vertices.push_back(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 2) / 255.0f);
|
||||
vertices.push_back(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 3) / 255.0f);
|
||||
vertices.push_back(static_cast<float>(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 4)));
|
||||
vertices.push_back(static_cast<float>(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 5)));
|
||||
vertices.push_back(static_cast<float>(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 6)));
|
||||
vertices.push_back(static_cast<float>(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 7)));
|
||||
offsets.boneWeights = stride;
|
||||
offsets.boneIndices = stride + 4 * sizeof(float);
|
||||
stride += 8 * sizeof(float);
|
||||
gr2Stride += 8;
|
||||
}
|
||||
|
||||
// Normal and tangent space
|
||||
if (mesh.header.vertexMask & 0x2) {
|
||||
glm::vec3 normal;
|
||||
normal.x = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 0));
|
||||
normal.y = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 1));
|
||||
normal.z = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 2));
|
||||
vertices.push_back(normal.x);
|
||||
vertices.push_back(normal.y);
|
||||
vertices.push_back(normal.z);
|
||||
|
||||
glm::vec3 tangent;
|
||||
tangent.x = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 4));
|
||||
tangent.y = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 5));
|
||||
tangent.z = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 6));
|
||||
float sign = convertByteToFloat(*reinterpret_cast<const uint8_t *>(gr2VerticesPtr + gr2Stride + 7));
|
||||
vertices.push_back(tangent.x);
|
||||
vertices.push_back(tangent.y);
|
||||
vertices.push_back(tangent.z);
|
||||
|
||||
glm::vec3 bitangent(computeBitangent(sign, normal, tangent));
|
||||
vertices.push_back(bitangent.x);
|
||||
vertices.push_back(bitangent.y);
|
||||
vertices.push_back(bitangent.z);
|
||||
|
||||
offsets.normals = stride;
|
||||
offsets.tangents = stride + 3 * sizeof(float);
|
||||
offsets.bitangents = stride + 6 * sizeof(float);
|
||||
stride += 9 * sizeof(float);
|
||||
gr2Stride += 8;
|
||||
}
|
||||
|
||||
// Color
|
||||
if (mesh.header.vertexMask & 0x10) {
|
||||
gr2Stride += 4;
|
||||
}
|
||||
|
||||
// Texture 1 coordinates
|
||||
if (mesh.header.vertexMask & 0x20) {
|
||||
vertices.push_back(convertHalfFloatToFloat(*reinterpret_cast<const uint16_t *>(gr2VerticesPtr + gr2Stride + 0)));
|
||||
vertices.push_back(-1.0f * convertHalfFloatToFloat(*reinterpret_cast<const uint16_t *>(gr2VerticesPtr + gr2Stride + 2)));
|
||||
offsets.texCoords1 = stride;
|
||||
stride += 2 * sizeof(float);
|
||||
gr2Stride += 2 * sizeof(uint16_t);
|
||||
}
|
||||
|
||||
// Texture 2 coordinates
|
||||
if (mesh.header.vertexMask & 0x40) {
|
||||
gr2Stride += 4;
|
||||
}
|
||||
|
||||
// Texture 3 coordinates
|
||||
if (mesh.header.vertexMask & 0x80) {
|
||||
gr2Stride += 4;
|
||||
}
|
||||
|
||||
gr2VerticesPtr += mesh.header.vertexSize;
|
||||
offsets.stride = stride;
|
||||
}
|
||||
|
||||
vector<uint16_t> indices;
|
||||
seek(mesh.header.offsetIndices);
|
||||
for (uint16_t i = 0; i < mesh.header.numPieces; ++i) {
|
||||
vector<uint16_t> pieceIndices(readArray<uint16_t>(3 * mesh.pieces[i]->numFaces));
|
||||
indices.insert(indices.end(), pieceIndices.begin(), pieceIndices.end());
|
||||
}
|
||||
|
||||
auto simpleMesh = make_unique<Mesh>(mesh.header.numVertices, move(vertices), move(indices), move(offsets));
|
||||
simpleMesh->computeAABB();
|
||||
|
||||
auto modelMesh = make_unique<ModelMesh>(move(simpleMesh));
|
||||
modelMesh->setRender(true);
|
||||
modelMesh->setShadow(true);
|
||||
modelMesh->setDiffuseColor(glm::vec3(0.8f));
|
||||
modelMesh->setAmbientColor(glm::vec3(0.2f));
|
||||
|
||||
// Fill mesh textures from materials
|
||||
if (!_materials.empty()) {
|
||||
// TODO: add support for multiple materials
|
||||
string materialResRef(_materials[0]);
|
||||
if (materialResRef == "default") {
|
||||
materialResRef = _resRef + "_v01";
|
||||
}
|
||||
shared_ptr<ByteArray> matData(Resources::instance().get(materialResRef, ResourceType::Mat));
|
||||
if (matData) {
|
||||
pt::ptree tree;
|
||||
pt::read_xml(*wrap(matData), tree);
|
||||
|
||||
string diffuseResRef;
|
||||
string bumpmapResRef;
|
||||
|
||||
for (auto &material : tree.get_child("Material")) {
|
||||
if (material.first != "input") continue;
|
||||
|
||||
string semantic(material.second.get("semantic", ""));
|
||||
string type(material.second.get("type", ""));
|
||||
|
||||
if (type != "texture") continue;
|
||||
|
||||
string value(material.second.get("value", ""));
|
||||
int lastSlashIdx = static_cast<int>(value.find_last_of('\\'));
|
||||
if (lastSlashIdx != -1) {
|
||||
value = value.substr(lastSlashIdx + 1ll);
|
||||
}
|
||||
if (semantic == "DiffuseMap") {
|
||||
diffuseResRef = value;
|
||||
} else if (semantic == "RotationMap1") {
|
||||
bumpmapResRef = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!diffuseResRef.empty()) {
|
||||
modelMesh->setDiffuseTexture(Textures::instance().get(diffuseResRef, TextureUsage::Diffuse));
|
||||
if (!bumpmapResRef.empty()) {
|
||||
modelMesh->setBumpmapTexture(Textures::instance().get(bumpmapResRef, TextureUsage::Bumpmap), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return move(modelMesh);
|
||||
}
|
||||
|
||||
unique_ptr<Gr2File::MeshBone> Gr2File::readMeshBone() {
|
||||
uint32_t offsetName = readUint32();
|
||||
|
||||
auto bone = make_unique<MeshBone>();
|
||||
bone->name = readCStringAt(offsetName);
|
||||
bone->bounds = readArray<float>(6);
|
||||
|
||||
return move(bone);
|
||||
}
|
||||
|
||||
void Gr2File::loadMaterials() {
|
||||
if (_numMaterials == 0) {
|
||||
for (auto &mesh : _meshes) {
|
||||
if (mesh->header.vertexMask & 0x20) {
|
||||
for (uint16_t i = 0; i < mesh->header.numPieces; ++i) {
|
||||
_materials.push_back(str(boost::format("%s.%03d") % mesh->header.name % i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
seek(_offsetMaterialHeader);
|
||||
for (uint16_t i = 0; i < _numMaterials; ++i) {
|
||||
uint32_t offsetName = readUint32();
|
||||
_materials.push_back(readCStringAt(offsetName));
|
||||
}
|
||||
}
|
||||
|
||||
void Gr2File::loadSkeletonBones() {
|
||||
for (uint16_t i = 0; i < _numBones; ++i) {
|
||||
seek(_offsetBoneStructure + i * 0x88);
|
||||
_bones.push_back(readSkeletonBone());
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<Gr2File::SkeletonBone> Gr2File::readSkeletonBone() {
|
||||
uint32_t offsetName = readUint32();
|
||||
uint32_t parentIndex = readUint32();
|
||||
|
||||
ignore(0x40);
|
||||
|
||||
vector<float> rootToBoneValues(readArray<float>(16));
|
||||
|
||||
auto bone = make_unique<SkeletonBone>();
|
||||
bone->name = readCStringAt(offsetName);
|
||||
bone->parentIndex = parentIndex;
|
||||
bone->rootToBone = glm::make_mat4(&rootToBoneValues[0]);
|
||||
|
||||
return move(bone);
|
||||
}
|
||||
|
||||
void Gr2File::loadAttachments() {
|
||||
for (uint16_t i = 0; i < _numAttachments; ++i) {
|
||||
seek(_offsetAttachments + i * 0x48);
|
||||
_attachments.push_back(readAttachment());
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<Gr2File::Attachment> Gr2File::readAttachment() {
|
||||
uint32_t offsetName = readUint32();
|
||||
uint32_t offsetBoneName = readUint32();
|
||||
vector<float> transformValues(readArray<float>(16));
|
||||
|
||||
auto attachment = make_unique<Attachment>();
|
||||
attachment->name = readCStringAt(offsetName);
|
||||
attachment->boneName = readCStringAt(offsetBoneName);
|
||||
attachment->transform = glm::make_mat4(&transformValues[0]);
|
||||
|
||||
return move(attachment);
|
||||
}
|
||||
|
||||
static glm::mat4 getModelMatrix() {
|
||||
glm::mat4 transform(1.0f);
|
||||
transform *= glm::mat4_cast(glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||
transform *= glm::mat4_cast(glm::angleAxis(glm::half_pi<float>(), glm::vec3(1.0f, 0.0, 0.0f)));
|
||||
transform = glm::scale(transform, glm::vec3(10.0f));
|
||||
return move(transform);
|
||||
}
|
||||
|
||||
static shared_ptr<Mesh> getBoneMesh() {
|
||||
vector<float> vertices {
|
||||
-0.005f, -0.005f, -0.005f,
|
||||
-0.005f, -0.005f, 0.005f,
|
||||
-0.005f, 0.005f, 0.005f,
|
||||
-0.005f, 0.005f, -0.005f,
|
||||
0.005f, 0.005f, 0.005f,
|
||||
0.005f, 0.005f, -0.005f,
|
||||
0.005f, -0.005f, -0.005f,
|
||||
0.005f, -0.005f, 0.005f
|
||||
};
|
||||
vector<uint16_t> indices {
|
||||
0, 1, 2, 2, 3, 0,
|
||||
2, 4, 5, 5, 3, 2,
|
||||
1, 7, 4, 4, 2, 1,
|
||||
0, 6, 7, 7, 1, 0,
|
||||
7, 6, 5, 5, 4, 7,
|
||||
6, 0, 3, 3, 5, 6
|
||||
};
|
||||
Mesh::VertexOffsets offsets { 0, -1, -1, -1, -1, -1, -1, -1, 3 * sizeof(float) };
|
||||
|
||||
return make_shared<Mesh>(8, move(vertices), move(indices), move(offsets));
|
||||
}
|
||||
|
||||
void Gr2File::loadModel() {
|
||||
static shared_ptr<Mesh> boneMesh(getBoneMesh());
|
||||
|
||||
shared_ptr<ModelNode> rootNode;
|
||||
int nodeIndex = 0;
|
||||
|
||||
if (_fileType == FileType::Skeleton) {
|
||||
rootNode = make_shared<ModelNode>(nodeIndex++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
rootNode->setAbsoluteTransform(getModelMatrix());
|
||||
|
||||
shared_ptr<ModelMesh> modelMesh;
|
||||
if (g_debugSkeleton) {
|
||||
modelMesh = make_shared<ModelMesh>(boneMesh);
|
||||
modelMesh->setRender(true);
|
||||
modelMesh->setAmbientColor(glm::vec3(1.0f));
|
||||
modelMesh->setDiffuseColor(glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
// Convert bones to model nodes
|
||||
vector<shared_ptr<ModelNode>> nodes;
|
||||
for (uint16_t i = 0; i < _numBones; ++i) {
|
||||
auto node = make_shared<ModelNode>(nodeIndex);
|
||||
node->setNodeNumber(nodeIndex);
|
||||
node->setName(_bones[i]->name);
|
||||
node->setMesh(modelMesh);
|
||||
node->setAbsoluteTransform(rootNode->absoluteTransform() * glm::inverse(_bones[i]->rootToBone));
|
||||
nodes.push_back(move(node));
|
||||
++nodeIndex;
|
||||
}
|
||||
|
||||
// Reparent model nodes
|
||||
for (uint16_t i = 0; i < _numBones; ++i) {
|
||||
if (_bones[i]->parentIndex == 0xffffffff) {
|
||||
rootNode->addChild(nodes[i]);
|
||||
} else {
|
||||
nodes[_bones[i]->parentIndex]->addChild(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// If skeleton is present, use it as the base
|
||||
if (_skeleton) {
|
||||
rootNode = _skeleton->rootNode();
|
||||
nodeIndex = _skeleton->maxNodeIndex() + 1;
|
||||
} else {
|
||||
rootNode = make_shared<ModelNode>(nodeIndex++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
rootNode->setAbsoluteTransform(getModelMatrix());
|
||||
}
|
||||
|
||||
// Convert meshes to model nodes
|
||||
for (uint16_t i = 0; i < _numMeshes; ++i) {
|
||||
auto node = make_shared<ModelNode>(nodeIndex);
|
||||
node->setNodeNumber(nodeIndex);
|
||||
node->setName(_meshes[i]->header.name);
|
||||
node->setMesh(_meshes[i]->mesh);
|
||||
|
||||
// If skeleton is present configure the model node skin
|
||||
if (_skeleton) {
|
||||
auto skin = make_shared<ModelNode::Skin>();
|
||||
for (uint16_t j = 0; j < _meshes[i]->header.numUsedBones; ++j) {
|
||||
auto boneNode = _skeleton->findNodeByName(_meshes[i]->bones[j]->name);
|
||||
skin->nodeIdxByBoneIdx.insert(make_pair(j, boneNode->index()));
|
||||
}
|
||||
node->setSkin(move(skin));
|
||||
}
|
||||
|
||||
node->setAbsoluteTransform(rootNode->absoluteTransform());
|
||||
rootNode->addChild(move(node));
|
||||
++nodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
rootNode->computeLocalTransforms();
|
||||
|
||||
_model = make_shared<Model>(_resRef, Model::Classification::Character, move(rootNode), _animations);
|
||||
}
|
||||
|
||||
static string getSkeletonByModel(const string &modelResRef) {
|
||||
if (boost::starts_with(modelResRef, "rancor_rancor_")) return "rancor_skeleton";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static vector<string> getAnimationsBySkeleton(const string &skeletonResRef) {
|
||||
vector<string> result;
|
||||
if (skeletonResRef == "rancor_skeleton") {
|
||||
result.push_back("dl_roar");
|
||||
}
|
||||
return move(result);
|
||||
}
|
||||
|
||||
shared_ptr<Model> Gr2ModelLoader::loadModel(GameID gameId, const string &resRef) {
|
||||
shared_ptr<Model> skeletonModel;
|
||||
vector<shared_ptr<Animation>> animations;
|
||||
|
||||
string skeletonResRef(getSkeletonByModel(resRef));
|
||||
if (!skeletonResRef.empty()) {
|
||||
shared_ptr<ByteArray> skeletonData(Resources::instance().get(skeletonResRef, ResourceType::Gr2));
|
||||
if (skeletonData) {
|
||||
Gr2File skeleton(skeletonResRef, vector<shared_ptr<Animation>>());
|
||||
skeleton.load(wrap(skeletonData));
|
||||
skeletonModel = skeleton.model();
|
||||
}
|
||||
|
||||
vector<string> animationResRefs(getAnimationsBySkeleton(skeletonResRef));
|
||||
for (auto &animResRef : animationResRefs) {
|
||||
shared_ptr<ByteArray> jbaData(Resources::instance().get(animResRef, ResourceType::Jba));
|
||||
if (jbaData) {
|
||||
JbaFile jba(animResRef, skeletonModel);
|
||||
jba.load(wrap(jbaData));
|
||||
shared_ptr<Animation> animation(jba.animation());
|
||||
if (animation) {
|
||||
// DEBUG: currently we only add a single animation and rename it to "cpause1"
|
||||
animation->setName("cpause1");
|
||||
animations.push_back(move(animation));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ByteArray> geometryData(Resources::instance().get(resRef, ResourceType::Gr2));
|
||||
if (geometryData) {
|
||||
Gr2File geometry(resRef, move(animations), move(skeletonModel));
|
||||
geometry.load(wrap(geometryData));
|
||||
return geometry.model();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../render/model/model.h"
|
||||
#include "../../render/model/modelloader.h"
|
||||
#include "../../resource/format/binfile.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tor {
|
||||
|
||||
/**
|
||||
* Encapsulates loading GR2 model files, used by Star Wars: The Old Republic.
|
||||
*/
|
||||
class Gr2File : public resource::BinaryFile {
|
||||
public:
|
||||
enum class FileType {
|
||||
Geometry = 0,
|
||||
GeometryWithCollision = 1,
|
||||
Skeleton = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @param resRef ResRef of the model
|
||||
* @param animations list of animations to pass to the loaded model
|
||||
* @param skeleton the skeleton model to append to the loaded model
|
||||
*/
|
||||
Gr2File(
|
||||
std::string resRef,
|
||||
std::vector<std::shared_ptr<render::Animation>> animations,
|
||||
std::shared_ptr<render::Model> skeleton = nullptr);
|
||||
|
||||
std::shared_ptr<render::Model> model() const { return _model; }
|
||||
|
||||
private:
|
||||
struct MeshHeader {
|
||||
std::string name;
|
||||
uint16_t numPieces { 0 };
|
||||
uint16_t numUsedBones { 0 };
|
||||
uint16_t vertexMask { 0 };
|
||||
uint16_t vertexSize { 0 };
|
||||
uint32_t numVertices { 0 };
|
||||
uint32_t numIndices { 0 };
|
||||
uint32_t offsetVertices { 0 };
|
||||
uint32_t offsetPieces { 0 };
|
||||
uint32_t offsetIndices { 0 };
|
||||
uint32_t offsetBones { 0 };
|
||||
};
|
||||
|
||||
struct MeshPiece {
|
||||
uint32_t startFaceIdx { 0 };
|
||||
uint32_t numFaces { 0 };
|
||||
uint32_t materialIndex { 0 };
|
||||
uint32_t pieceIndex { 0 };
|
||||
};
|
||||
|
||||
struct MeshBone {
|
||||
std::string name;
|
||||
std::vector<float> bounds;
|
||||
};
|
||||
|
||||
struct Gr2Mesh {
|
||||
MeshHeader header;
|
||||
std::vector<std::shared_ptr<MeshPiece>> pieces;
|
||||
std::shared_ptr<render::ModelMesh> mesh;
|
||||
std::vector<std::shared_ptr<MeshBone>> bones;
|
||||
};
|
||||
|
||||
struct SkeletonBone {
|
||||
std::string name;
|
||||
uint32_t parentIndex { 0 };
|
||||
glm::mat4 rootToBone { 1.0f };
|
||||
};
|
||||
|
||||
struct Attachment {
|
||||
std::string name;
|
||||
std::string boneName;
|
||||
glm::mat4 transform { 1.0f };
|
||||
};
|
||||
|
||||
std::string _resRef;
|
||||
std::vector<std::shared_ptr<render::Animation>> _animations;
|
||||
std::shared_ptr<render::Model> _skeleton;
|
||||
|
||||
FileType _fileType { FileType::Geometry };
|
||||
uint16_t _numMeshes { 0 };
|
||||
uint16_t _numMaterials { 0 };
|
||||
uint16_t _numBones { 0 };
|
||||
uint16_t _numAttachments { 0 };
|
||||
uint32_t _offsetMeshHeader { 0 };
|
||||
uint32_t _offsetMaterialHeader { 0 };
|
||||
uint32_t _offsetBoneStructure { 0 };
|
||||
uint32_t _offsetAttachments { 0 };
|
||||
|
||||
std::vector<std::shared_ptr<Gr2Mesh>> _meshes;
|
||||
std::vector<std::shared_ptr<SkeletonBone>> _bones;
|
||||
std::vector<std::string> _materials;
|
||||
std::vector<std::shared_ptr<Attachment>> _attachments;
|
||||
std::shared_ptr<render::Model> _model;
|
||||
|
||||
void doLoad() override;
|
||||
|
||||
void loadMeshes();
|
||||
void loadMaterials();
|
||||
void loadSkeletonBones();
|
||||
void loadAttachments();
|
||||
void loadModel();
|
||||
|
||||
std::unique_ptr<Gr2Mesh> readMesh();
|
||||
std::unique_ptr<MeshPiece> readMeshPiece();
|
||||
std::unique_ptr<render::ModelMesh> readModelMesh(const Gr2Mesh &mesh);
|
||||
std::unique_ptr<MeshBone> readMeshBone();
|
||||
std::unique_ptr<SkeletonBone> readSkeletonBone();
|
||||
std::unique_ptr<Attachment> readAttachment();
|
||||
};
|
||||
|
||||
class Gr2ModelLoader : public render::IModelLoader {
|
||||
public:
|
||||
std::shared_ptr<render::Model> loadModel(resource::GameID gameId, const std::string &resRef) override;
|
||||
};
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
|
@ -1,324 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "jbafile.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "glm/gtc/type_ptr.hpp"
|
||||
|
||||
#include "../../common/log.h"
|
||||
#include "../../render/model/model.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::render;
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tor {
|
||||
|
||||
JbaFile::JbaFile(const string &resRef, shared_ptr<Model> skeleton) :
|
||||
BinaryFile(4, "\0\0\0\0"),
|
||||
_resRef(resRef),
|
||||
_skeleton(skeleton) {
|
||||
|
||||
if (!skeleton) {
|
||||
throw invalid_argument("skeleton must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
void JbaFile::doLoad() {
|
||||
// This is a reference implementation of https://github.com/seedhartha/reone/wiki/SWTOR-Research
|
||||
|
||||
loadHeader();
|
||||
loadPartHeaders();
|
||||
loadBoneData();
|
||||
loadPartData();
|
||||
loadKeyframes();
|
||||
loadBones();
|
||||
loadAnimation();
|
||||
}
|
||||
|
||||
void JbaFile::loadHeader() {
|
||||
_length = 10.0f * readFloat();
|
||||
_fps = readFloat();
|
||||
_numParts = readUint32();
|
||||
|
||||
ignore(8);
|
||||
|
||||
_numBones = readUint32();
|
||||
|
||||
ignore(12);
|
||||
|
||||
debug(boost::format("JBA: length=%.06f numParts=%d, numBones=%d") % _length % _numParts % _numBones);
|
||||
}
|
||||
|
||||
void JbaFile::loadPartHeaders() {
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
JbaPart part;
|
||||
part.keyframeIdx = readUint32();
|
||||
part.keyframesSize = readUint32();
|
||||
debug(boost::format("JBA: part %d header: %u %u") % i % part.keyframeIdx % part.keyframesSize);
|
||||
_parts.push_back(move(part));
|
||||
}
|
||||
ignore(4 * _numParts);
|
||||
}
|
||||
|
||||
static constexpr size_t alignAt80(size_t offset) {
|
||||
size_t result = 0;
|
||||
while (result < offset) {
|
||||
result += 0x80;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static string describeVector(const glm::vec3 &vec) {
|
||||
return str(boost::format("(%.06f, %.06f, %.06f)") % vec.x % vec.y % vec.z);
|
||||
}
|
||||
|
||||
void JbaFile::loadBoneData() {
|
||||
seek(alignAt80(tell()));
|
||||
vector<uint8_t> data(readArray<uint8_t>(48ll * _numBones));
|
||||
const float *dataPtr = reinterpret_cast<float *>(&data[0]);
|
||||
|
||||
for (uint32_t i = 0; i < _numBones; ++i) {
|
||||
JbaBone bone;
|
||||
bone.translationStride = glm::make_vec3(dataPtr + 0);
|
||||
bone.translationBase = glm::make_vec3(dataPtr + 3);
|
||||
bone.orientationStride = glm::make_vec3(dataPtr + 6);
|
||||
bone.orientationBase = glm::make_vec3(dataPtr + 9);
|
||||
|
||||
debug(boost::format("JBA: bone %d data: %s, %s, %s, %s")
|
||||
% i
|
||||
% describeVector(bone.translationStride) % describeVector(bone.translationBase)
|
||||
% describeVector(bone.orientationStride) % describeVector(bone.orientationBase), 2);
|
||||
|
||||
_bones.push_back(move(bone));
|
||||
|
||||
dataPtr += 12;
|
||||
}
|
||||
}
|
||||
|
||||
void JbaFile::loadPartData() {
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
uint32_t start = static_cast<uint32_t>(alignAt80(tell()));
|
||||
seek(start);
|
||||
|
||||
_parts[i].keyframes = readPartKeyframes();
|
||||
|
||||
seek(start + _parts[i].keyframesSize);
|
||||
}
|
||||
}
|
||||
|
||||
static glm::vec3 decompressPosition(uint32_t value, const glm::vec3 &base, const glm::vec3 &stride) {
|
||||
float x = static_cast<float>((value >> 21) & 0x7ff);
|
||||
float y = static_cast<float>((value >> 10) & 0x7ff);
|
||||
float z = static_cast<float>(value & 0x3ff);
|
||||
|
||||
return glm::vec3(base + stride * glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
static glm::quat decompressOrientation(const uint16_t *values, const glm::vec3 &base, const glm::vec3 &stride) {
|
||||
bool sign = (values[0] & 0x8000) != 0;
|
||||
|
||||
float x = static_cast<float>(values[0] & 0x7fff);
|
||||
float y = static_cast<float>(values[1] & 0xffff);
|
||||
float z = static_cast<float>(values[2] & 0xffff);
|
||||
|
||||
glm::vec3 axis(base + stride * glm::vec3(x, y, z));
|
||||
float w;
|
||||
|
||||
float dot = glm::dot(axis, axis);
|
||||
if (dot > 1.0f) {
|
||||
w = 0.0f;
|
||||
} else {
|
||||
w = glm::sqrt(1.0f - dot);
|
||||
if (sign) {
|
||||
w *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return glm::normalize(glm::quat(w, axis));
|
||||
}
|
||||
|
||||
static string describeQuaternion(const glm::quat &q) {
|
||||
return str(boost::format("(%.06f, %.06f, %.06f, %.06f)") % q.x % q.y % q.z % q.w);
|
||||
}
|
||||
|
||||
vector<vector<JbaFile::JbaKeyframe>> JbaFile::readPartKeyframes() {
|
||||
vector<vector<JbaKeyframe>> keyframes;
|
||||
|
||||
ignore(8); // number of bones again
|
||||
|
||||
// Determine the size of the keyframes section
|
||||
int dataSize = 0;
|
||||
vector<uint32_t> keyframeLayout(readArray<uint32_t>(4 * _numBones));
|
||||
for (uint32_t i = 0; i < 4 * _numBones; i += 4) {
|
||||
debug(boost::format("JBA: bone %d keyframe layout: %u %u %u %u") % (i / 4) % keyframeLayout[i + 0] % keyframeLayout[i + 1ll] % keyframeLayout[i + 2ll] % keyframeLayout[i + 3ll], 2);
|
||||
dataSize += 6 * keyframeLayout[i + 0] + 4 * keyframeLayout[i + 2ll];
|
||||
}
|
||||
|
||||
uint32_t pos = static_cast<uint32_t>(tell());
|
||||
debug(boost::format("JBA: part data: reading %d bytes from %X to %X") % dataSize % pos % (pos + dataSize - 1));
|
||||
|
||||
const uint32_t *keyframeLayoutPtr = &keyframeLayout[0];
|
||||
for (uint32_t i = 0; i < _numBones; ++i) {
|
||||
vector<JbaKeyframe> boneKeyframes;
|
||||
boneKeyframes.resize(keyframeLayoutPtr[0]);
|
||||
|
||||
// Decompress 48-bit orientation poses
|
||||
for (uint32_t j = 0; j < keyframeLayoutPtr[0]; ++j) {
|
||||
vector<uint16_t> values(readArray<uint16_t>(3));
|
||||
boneKeyframes[j].orientation = decompressOrientation(&values[0], _bones[i].orientationBase, _bones[i].orientationStride);
|
||||
debug(boost::format("JBA: bone %u: keyframe %u: orientation: %04X %04X %04X -> %s") % i % j % values[0] % values[1] % values[2] % describeQuaternion(boneKeyframes[j].orientation), 2);
|
||||
}
|
||||
// Decompress 32-bit translation poses, if any
|
||||
for (uint32_t j = 0; j < keyframeLayoutPtr[2]; ++j) {
|
||||
uint32_t value = readUint32();
|
||||
boneKeyframes[j].translation = decompressPosition(value, _bones[i].translationBase, _bones[i].translationStride);
|
||||
debug(boost::format("JBA: bone %u: keyframe %u: translation: %08X -> %s") % i % j % value % describeVector(boneKeyframes[j].translation), 2);
|
||||
}
|
||||
|
||||
keyframes.push_back(move(boneKeyframes));
|
||||
|
||||
keyframeLayoutPtr += 4;
|
||||
}
|
||||
|
||||
return move(keyframes);
|
||||
}
|
||||
|
||||
void JbaFile::loadKeyframes() {
|
||||
uint32_t pos = static_cast<uint32_t>(alignAt80(tell()));
|
||||
seek(pos);
|
||||
|
||||
ignore(8);
|
||||
|
||||
vector<float> valuesAt08(readArray<float>(12));
|
||||
_translationStride = glm::make_vec3(&valuesAt08[0]);
|
||||
_translationBase = glm::make_vec3(&valuesAt08[3]);
|
||||
_orientationStride = glm::make_vec3(&valuesAt08[6]);
|
||||
_orientationBase = glm::make_vec3(&valuesAt08[9]);
|
||||
debug(boost::format("JBA: translationStride=%s translationBase=%s orientationStride=%s orientationBase=%s") %
|
||||
describeVector(_translationStride) % describeVector(_translationBase) %
|
||||
describeVector(_orientationStride) % describeVector(_orientationBase));
|
||||
|
||||
_numKeyframes = readUint32();
|
||||
debug("JBA: numKeyframes=" + to_string(_numKeyframes));
|
||||
_keyframes.resize(_numKeyframes);
|
||||
|
||||
ignore(3 * sizeof(uint32_t));
|
||||
|
||||
vector<uint16_t> valuesAt48(readArray<uint16_t>(3 * _numKeyframes));
|
||||
const uint16_t *valuesAt48Ptr = &valuesAt48[0];
|
||||
for (uint32_t i = 0; i < _numKeyframes; ++i) {
|
||||
glm::quat orientation(decompressOrientation(valuesAt48Ptr, _orientationBase, _orientationStride));
|
||||
debug(boost::format("JBA: keyframe %d orientation: %04X %04X %04X -> %s") % i %
|
||||
valuesAt48Ptr[0] % valuesAt48Ptr[1] % valuesAt48Ptr[2] %
|
||||
describeQuaternion(orientation), 2);
|
||||
|
||||
_keyframes[i].orientation = move(orientation);
|
||||
valuesAt48Ptr += 3;
|
||||
}
|
||||
if (_numKeyframes % 2 != 0) {
|
||||
ignore(2);
|
||||
}
|
||||
|
||||
vector<uint32_t> keyframeValues(readArray<uint32_t>(_numKeyframes));
|
||||
for (uint32_t i = 0; i < _numKeyframes; ++i) {
|
||||
glm::vec3 translation(decompressPosition(keyframeValues[i], _translationBase, _translationStride));
|
||||
debug(boost::format("JBA: keyframe %d translation: %08X -> %s") % i % keyframeValues[i] % describeVector(translation), 2);
|
||||
_keyframes[i].translation = move(translation);
|
||||
}
|
||||
}
|
||||
|
||||
void JbaFile::loadBones() {
|
||||
uint32_t numBones = readUint32();
|
||||
|
||||
ignore(16);
|
||||
|
||||
for (uint32_t i = 0; i < numBones; ++i) {
|
||||
_bones[i].index = readUint32();
|
||||
}
|
||||
vector<uint32_t> nameOffsets(readArray<uint32_t>(numBones));
|
||||
|
||||
for (uint32_t i = 0; i < numBones; ++i) {
|
||||
_bones[i].name = _reader->getCString();
|
||||
debug(boost::format("JBA: bone %d: %s") % i % _bones[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
void JbaFile::loadAnimation() {
|
||||
if (_numParts == 0 || _parts[0].keyframes.empty()) return;
|
||||
|
||||
// Determine the total number of keyframes
|
||||
|
||||
int numKeyframes = 0;
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
numKeyframes += static_cast<int>(_parts[i].keyframes[0].size());
|
||||
}
|
||||
float step = _length / static_cast<float>(numKeyframes - 1);
|
||||
|
||||
|
||||
// Convert keyframes to model nodes
|
||||
|
||||
int index = 0;
|
||||
auto rootNode = make_shared<ModelNode>(index++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
vector<vector<JbaKeyframe>> partKeyframes(_parts[i].keyframes);
|
||||
|
||||
for (uint32_t j = 0; j < _numBones; ++j) {
|
||||
auto node = make_shared<ModelNode>(index);
|
||||
node->setNodeNumber(index);
|
||||
node->setName(_bones[j].name);
|
||||
|
||||
auto skeletonNode = _skeleton->findNodeByName(_bones[j].name);
|
||||
if (!skeletonNode) continue;
|
||||
|
||||
vector<JbaKeyframe> boneKeyframes(partKeyframes[j]);
|
||||
for (size_t k = 0; k < boneKeyframes.size(); ++k) {
|
||||
float time = k * step;
|
||||
|
||||
ModelNode::Keyframe position;
|
||||
position.time = time;
|
||||
position.translation = boneKeyframes[k].translation * _translationBase;
|
||||
node->addTranslationKeyframe(move(position));
|
||||
|
||||
ModelNode::Keyframe orientation;
|
||||
orientation.time = time;
|
||||
orientation.orientation = skeletonNode->orientation() * boneKeyframes[k].orientation;
|
||||
node->addOrientationKeyframe(move(orientation));
|
||||
}
|
||||
|
||||
rootNode->addChild(move(node));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create the animation
|
||||
|
||||
vector<Animation::Event> events;
|
||||
_animation = make_shared<Animation>(_resRef, _length, 0.5f * _length, move(events), move(rootNode));
|
||||
}
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "glm/gtc/quaternion.hpp"
|
||||
#include "glm/vec3.hpp"
|
||||
|
||||
#include "../../render/model/animation.h"
|
||||
#include "../../render/model/model.h"
|
||||
#include "../../resource/format/binfile.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tor {
|
||||
|
||||
class JbaFile : public resource::BinaryFile {
|
||||
public:
|
||||
JbaFile(const std::string &resRef, std::shared_ptr<render::Model> skeleton);
|
||||
|
||||
std::shared_ptr<render::Animation> animation() const { return _animation; }
|
||||
|
||||
private:
|
||||
struct JbaKeyframe {
|
||||
glm::vec3 translation { 0.0f };
|
||||
glm::quat orientation { 1.0f, 0.0f, 0.0, 0.0f };
|
||||
};
|
||||
|
||||
struct JbaPart {
|
||||
uint32_t keyframeIdx { 0 };
|
||||
uint32_t keyframesSize;
|
||||
std::vector<std::vector<JbaKeyframe>> keyframes;
|
||||
};
|
||||
|
||||
struct JbaBone {
|
||||
glm::vec3 translationStride { 0.0f };
|
||||
glm::vec3 translationBase { 0.0f };
|
||||
glm::vec3 orientationStride { 0.0f };
|
||||
glm::vec3 orientationBase { 0.0f };
|
||||
uint32_t index { 0 };
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::string _resRef;
|
||||
std::shared_ptr<render::Model> _skeleton;
|
||||
|
||||
float _length { 0.0f };
|
||||
float _fps { 0 };
|
||||
uint32_t _numParts { 0 };
|
||||
uint32_t _numKeyframes { 0 };
|
||||
uint32_t _numBones { 0 };
|
||||
|
||||
glm::vec3 _translationStride { 0.0f };
|
||||
glm::vec3 _translationBase { 0.0f };
|
||||
glm::vec3 _orientationStride { 0.0f };
|
||||
glm::vec3 _orientationBase { 0.0f };
|
||||
|
||||
std::vector<JbaPart> _parts;
|
||||
std::vector<JbaKeyframe> _keyframes;
|
||||
std::vector<JbaBone> _bones;
|
||||
std::shared_ptr<render::Animation> _animation;
|
||||
|
||||
void doLoad();
|
||||
|
||||
void loadHeader();
|
||||
void loadPartHeaders();
|
||||
void loadBoneData();
|
||||
void loadPartData();
|
||||
void loadKeyframes();
|
||||
void loadBones();
|
||||
void loadAnimation();
|
||||
|
||||
std::vector<std::vector<JbaKeyframe>> readPartKeyframes();
|
||||
};
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
|
@ -24,7 +24,6 @@
|
|||
#include "../audio/soundhandle.h"
|
||||
#include "../common/log.h"
|
||||
#include "../common/pathutil.h"
|
||||
#include "../experimental/tor/gr2file.h"
|
||||
#include "../render/featureutil.h"
|
||||
#include "../render/lip/lips.h"
|
||||
#include "../render/materials.h"
|
||||
|
@ -54,7 +53,6 @@ using namespace reone::render;
|
|||
using namespace reone::resource;
|
||||
using namespace reone::scene;
|
||||
using namespace reone::script;
|
||||
using namespace reone::tor;
|
||||
using namespace reone::video;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
@ -125,7 +123,6 @@ void Game::init() {
|
|||
|
||||
void Game::registerModelLoaders() {
|
||||
Models::instance().registerLoader(ResourceType::Mdl, make_shared<MdlModelLoader>());
|
||||
Models::instance().registerLoader(ResourceType::Gr2, make_shared<Gr2ModelLoader>());
|
||||
}
|
||||
|
||||
void Game::setCursorType(CursorType type) {
|
||||
|
|
|
@ -40,11 +40,6 @@ namespace reone {
|
|||
|
||||
namespace game {
|
||||
|
||||
// This is a hook to replace MDL models with GR2 models (SWTOR).
|
||||
static const unordered_map<string, string> g_gr2Models {
|
||||
//{ "c_rancors", "rancor_rancor_a01" }
|
||||
};
|
||||
|
||||
static const string g_headHookNode("headhook");
|
||||
static const string g_maskHookNode("gogglehook");
|
||||
|
||||
|
@ -58,18 +53,10 @@ shared_ptr<ModelSceneNode> CreatureModelBuilder::build() {
|
|||
string modelName(getBodyModelName());
|
||||
if (modelName.empty()) return nullptr;
|
||||
|
||||
bool gr2Model = false;
|
||||
auto maybeGr2Model = g_gr2Models.find(modelName);
|
||||
if (maybeGr2Model != g_gr2Models.end()) {
|
||||
modelName = maybeGr2Model->second;
|
||||
gr2Model = true;
|
||||
}
|
||||
|
||||
shared_ptr<Model> model(Models::instance().get(modelName, gr2Model ? ResourceType::Gr2 : ResourceType::Mdl));
|
||||
shared_ptr<Model> model(Models::instance().get(modelName, ResourceType::Mdl));
|
||||
if (!model) return nullptr;
|
||||
|
||||
auto modelSceneNode = make_unique<ModelSceneNode>(ModelSceneNode::Classification::Creature, model, &_creature->sceneGraph());
|
||||
if (gr2Model) return move(modelSceneNode);
|
||||
|
||||
// Body texture
|
||||
|
||||
|
|
|
@ -89,9 +89,6 @@ enum class ResourceType : uint16_t {
|
|||
Mdx = 3008,
|
||||
|
||||
Mp3 = 0x1000,
|
||||
Gr2 = 0x1001, // SWTOR model
|
||||
Mat = 0x1002, // SWTOR material
|
||||
Jba = 0x1003, // SWTOR animation
|
||||
|
||||
Invalid = 0xffff
|
||||
};
|
||||
|
|
|
@ -82,10 +82,7 @@ static unordered_map<ResourceType, string> g_extByType {
|
|||
{ ResourceType::Lip, "lip" },
|
||||
{ ResourceType::Tpc, "tpc" },
|
||||
{ ResourceType::Mdx, "mdx" },
|
||||
{ ResourceType::Mp3, "mp3" },
|
||||
{ ResourceType::Gr2, "gr2" },
|
||||
{ ResourceType::Mat, "mat" },
|
||||
{ ResourceType::Jba, "jba" }
|
||||
{ ResourceType::Mp3, "mp3" }
|
||||
};
|
||||
|
||||
static unordered_map<string, ResourceType> g_typeByExt;
|
||||
|
|
Loading…
Reference in a new issue