Skip to content

Commit

Permalink
add GLTF import
Browse files Browse the repository at this point in the history
  • Loading branch information
Deweh committed Dec 1, 2023
1 parent 2df36bd commit 8d4da66
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 13 deletions.
16 changes: 15 additions & 1 deletion Plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ source_group(
)

# dependencies
find_package(simdjson CONFIG REQUIRED)
find_package(fastgltf CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_dependency_path(DKUtil include/DKUtil/Logger.hpp)
find_dependency_path(CommonLibSF CommonLibSF/include/SFSE/SFSE.h)
Expand All @@ -51,7 +53,17 @@ add_library(
${CMAKE_CURRENT_BINARY_DIR}/version.rc
.clang-format
vcpkg.json
"src/Animation/Interpolation.cpp" "src/Tasks/MainLoop.cpp" "src/Animation/Graph.cpp" "src/Animation/Transform.cpp" "src/Util/Math.cpp" "src/Animation/GraphManager.cpp" "src/Settings/Settings.cpp" "src/API/API_Internal.cpp" "src/Settings/SkeletonDescriptor.cpp" "src/Animation/Node.cpp")
"src/Animation/Interpolation.cpp"
"src/Tasks/MainLoop.cpp"
"src/Animation/Graph.cpp"
"src/Animation/Transform.cpp"
"src/Util/Math.cpp"
"src/Animation/GraphManager.cpp"
"src/Settings/Settings.cpp"
"src/API/API_Internal.cpp"
"src/Settings/SkeletonDescriptor.cpp"
"src/Animation/Node.cpp"
"src/Serialization/GLTFImport.cpp")

# copy to Starfield directory
set(STARFIELD_PATH "C:/Program Files (x86)/Steam/steamapps/common/Starfield/Data/SFSE/Plugins")
Expand All @@ -77,6 +89,8 @@ target_link_libraries(
DKUtil::DKUtil
CommonLibSF::CommonLibSF
spdlog::spdlog
simdjson::simdjson
fastgltf::fastgltf
)

# compiler def
Expand Down
3 changes: 3 additions & 0 deletions Plugin/src/Animation/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ namespace Animation
{
Node::~Node() {}

GameNode::GameNode(RE::NiAVObject* n) :
n(n) {}

Transform GameNode::GetLocal()
{
return n->local;
Expand Down
3 changes: 3 additions & 0 deletions Plugin/src/Animation/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace Animation
public:
RE::NiAVObject* n;

GameNode(RE::NiAVObject* n);

virtual Transform GetLocal();
virtual Transform GetWorld();
virtual void SetLocal(const Transform& t);
Expand All @@ -27,6 +29,7 @@ namespace Animation

class NullNode : public Node
{
public:
virtual Transform GetLocal();
virtual Transform GetWorld();
virtual void SetLocal(const Transform& t);
Expand Down
5 changes: 4 additions & 1 deletion Plugin/src/PCH.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,7 @@ DLLEXPORT constinit auto SFSEPlugin_Version = []() noexcept {
};
}();

#include "RE/RE.h"
#include "RE/RE.h"
//Other dependencies
#include "fastgltf/parser.hpp"
#include "fastgltf/types.hpp"
145 changes: 145 additions & 0 deletions Plugin/src/Serialization/GLTFImport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "GLTFImport.h"

namespace Serialization
{
std::unique_ptr<Animation::LinearClipGenerator> GLTFImport::CreateClipGenerator(const fastgltf::Asset* asset, const fastgltf::Animation* anim, const Settings::SkeletonDescriptor* skeleton)
{
//Create a map of GLTF node indexes -> skeleton indexes
std::vector<size_t> skeletonIdxs;
skeletonIdxs.reserve(asset->nodes.size());
auto skeletonMap = skeleton->GetNodeIndexMap();

for (const auto& n : asset->nodes) {
if (auto iter = skeletonMap.find(n.name); iter != skeletonMap.end()) {
skeletonIdxs.push_back(iter->second);
} else {
skeletonIdxs.push_back(UINT64_MAX);
}
}

//Create the clip generator
std::unique_ptr<Animation::LinearClipGenerator> result = std::make_unique<Animation::LinearClipGenerator>();
result->duration = 0.001f;
result->SetSize(skeletonMap.size());

//Process GLTF data
std::vector<float> times;
for (auto& c : anim->channels) {
times.clear();
auto idx = skeletonIdxs[c.nodeIndex];
if (idx == UINT64_MAX)
continue;

auto& rTl = result->rotation[idx];
auto& pTl = result->position[idx];

if (c.samplerIndex > anim->samplers.size())
continue;

auto& sampler = anim->samplers[c.samplerIndex];
if (sampler.inputAccessor > asset->accessors.size() || sampler.outputAccessor > asset->accessors.size())
continue;

auto& timeAccessor = asset->accessors[sampler.inputAccessor];
auto& dataAccessor = asset->accessors[sampler.outputAccessor];

size_t elementSize = 0;
switch (c.path) {
case fastgltf::AnimationPath::Rotation:
if (dataAccessor.type != fastgltf::AccessorType::Vec4)
continue;
elementSize = 16;
break;
case fastgltf::AnimationPath::Translation:
if (dataAccessor.type != fastgltf::AccessorType::Vec3)
continue;
elementSize = 12;
break;
default:
continue;
}

if (timeAccessor.count != dataAccessor.count ||
dataAccessor.componentType != fastgltf::ComponentType::Float ||
timeAccessor.componentType != fastgltf::ComponentType::Float ||
timeAccessor.type != fastgltf::AccessorType::Scalar ||
!timeAccessor.bufferViewIndex.has_value() ||
!dataAccessor.bufferViewIndex.has_value())
continue;

auto& timeBV = asset->bufferViews[timeAccessor.bufferViewIndex.value()];
auto& dataBV = asset->bufferViews[dataAccessor.bufferViewIndex.value()];

size_t timeByteOffset = timeAccessor.byteOffset + timeBV.byteOffset;
size_t dataByteOffset = dataAccessor.byteOffset + dataBV.byteOffset;
size_t timeByteCount = timeAccessor.count * 4;
size_t dataByteCount = dataAccessor.count * elementSize;

auto& timeBuffer = asset->buffers[timeBV.bufferIndex];
auto& dataBuffer = asset->buffers[dataBV.bufferIndex];

if (timeByteOffset + (timeByteCount) > timeBuffer.byteLength ||
dataByteOffset + (dataByteCount) > dataBuffer.byteLength ||
!std::holds_alternative<fastgltf::sources::Vector>(timeBuffer.data) ||
!std::holds_alternative<fastgltf::sources::Vector>(dataBuffer.data))
continue;

auto& tBufData = std::get<fastgltf::sources::Vector>(timeBuffer.data);
auto& dBufData = std::get<fastgltf::sources::Vector>(dataBuffer.data);
for (size_t i = 0; i < timeAccessor.count; i++) {
float t;
std::memcpy(&t, &tBufData.bytes[timeByteOffset + (i * 4)], 4);
if (t > result->duration)
result->duration = t;

size_t off = dataByteOffset + (i * elementSize);
RE::NiQuaternion q;
RE::NiPoint3 p;
switch (c.path) {
case fastgltf::AnimationPath::Rotation:
//NiQuaternions are stored as WXYZ, but GLTF stores rotations as XYZW, so we have to copy XYZ and W separately.
std::memcpy(&q.x, &dBufData.bytes[off], 12);
off += 12;
std::memcpy(&q.w, &dBufData.bytes[off], 4);
rTl.keys.emplace(t, q);
break;
case fastgltf::AnimationPath::Translation:
std::memcpy(&p.x, &dBufData.bytes[off], elementSize);
pTl.keys.emplace(t, p);
break;
}
}
}

return result;
}

std::unique_ptr<fastgltf::Asset> GLTFImport::LoadGLTF(const std::filesystem::path& fileName)
{
fastgltf::GltfDataBuffer data;
if (!data.loadFromFile(fileName))
return nullptr;

fastgltf::Parser parser;
auto gltfOptions =
fastgltf::Options::LoadGLBBuffers;

auto type = fastgltf::determineGltfFileType(&data);
std::unique_ptr<fastgltf::glTF> gltf;
if (type == fastgltf::GltfType::glTF) {
gltf = parser.loadGLTF(&data, fileName.parent_path(), gltfOptions);
} else if (type == fastgltf::GltfType::GLB) {
gltf = parser.loadBinaryGLTF(&data, fileName.parent_path(), gltfOptions);
} else {
return nullptr;
}

if (!gltf)
return nullptr;

if (gltf->parse(fastgltf::Category::OnlyAnimations | fastgltf::Category::Nodes) != fastgltf::Error::None || gltf->validate() != fastgltf::Error::None)
return nullptr;

return gltf->getParsedAsset();
}
}
13 changes: 13 additions & 0 deletions Plugin/src/Serialization/GLTFImport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#include "Animation/Generator.h"
#include "Settings/SkeletonDescriptor.h"

namespace Serialization
{
class GLTFImport
{
public:
static std::unique_ptr<Animation::LinearClipGenerator> CreateClipGenerator(const fastgltf::Asset* asset, const fastgltf::Animation* anim, const Settings::SkeletonDescriptor* skeleton);
static std::unique_ptr<fastgltf::Asset> LoadGLTF(const std::filesystem::path& fileName);
};
}
9 changes: 9 additions & 0 deletions Plugin/src/Settings/SkeletonDescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ namespace Settings
}
return std::nullopt;
}

std::map<std::string, size_t> SkeletonDescriptor::GetNodeIndexMap() const
{
std::map<std::string, size_t> result;
for (size_t i = 0; i < nodeNames.size(); i++) {
result[nodeNames[i]] = i;
}
return result;
}
}
1 change: 1 addition & 0 deletions Plugin/src/Settings/SkeletonDescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ namespace Settings
std::vector<std::string> nodeNames;

std::optional<size_t> GetNodeIndex(const std::string& name) const;
std::map<std::string, size_t> GetNodeIndexMap() const;
};
}
24 changes: 15 additions & 9 deletions Plugin/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "Settings/Settings.h"
#include "Animation/GraphManager.h"
#include "Util/Math.h"
#include "Serialization/GLTFImport.h"
#define NDEBUG

namespace
Expand Down Expand Up @@ -60,15 +61,20 @@ namespace
[[maybe_unused]] std::uint32_t* opcodeOffsetPtr) {
static bool isOpen = false;

if (!isOpen) {
auto gen = std::make_unique<Animation::LinearClipGenerator>();
auto human = Settings::GetSkeleton("HumanRace");
gen->SetSize(human->nodeNames.size());
gen->duration = 1.0f;
size_t lcalf = human->GetNodeIndex("L_Calf").value();
gen->rotation[lcalf].keys.emplace(0.0f, RE::NiQuaternion(55.0f * Util::DEGREE_TO_RADIAN, { 0, 1, 0 }));
gen->rotation[lcalf].keys.emplace(0.5f, RE::NiQuaternion(-55.0f * Util::DEGREE_TO_RADIAN, { 0, 1, 0 }));
gen->rotation[lcalf].keys.emplace(1.0f, RE::NiQuaternion(55.0f * Util::DEGREE_TO_RADIAN, { 0, 1, 0 }));
auto log = RE::ConsoleLog::GetSingleton();
` if (!isOpen) {
auto asset = Serialization::GLTFImport::LoadGLTF("Data/NAF/test.gltf");
if (!asset) {
log->Print("Failed to load GLTF.");
return true;
}
auto humanSkeleton = Settings::GetSkeleton("HumanRace");
auto gen = Serialization::GLTFImport::CreateClipGenerator(asset.get(), &asset->animations[0], humanSkeleton.get());
if (!gen) {
log->Print("Failed to create ClipGenerator.");
return true;
}

for (auto& tl : gen->rotation) {
tl.Init();
}
Expand Down
6 changes: 4 additions & 2 deletions Plugin/vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"name": "nativeanimationframeworksf",
"version-string": "1.0.0",
"description": "sfse plugin for starfield",
"builtin-baseline": "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1",
"builtin-baseline": "9edb1b8e590cc086563301d735cae4b6e732d2d2",
"homepage": "https://github.com/Deweh/NativeAnimationFrameworkSF",
"dependencies": [
"spdlog",
"nlohmann-json",
"simpleini",
"tomlplusplus",
"xbyak"
"xbyak",
"simdjson",
"fastgltf"
]
}

0 comments on commit 8d4da66

Please sign in to comment.