DKR Assets Tool v0.5.2 (Basic glTF support) (#610)

* Removed old unused files

* Added gltf support

* Reverted dkr_assets_tool_settings.json to previous

* Added animated model example

* document audio_vehicle.c (#588)

* wip

* wip

* wip

* wip

* document audio_vehicle.c

* update score once again

* unify function prefix

* small tweaks

* match func_80060910 and func_80060AC8 (#589)

* match func_80060AC8

* reworked func_80060C58

* match func_80060910

* non equivalent func_8006017C

* format

* match calc_env_mapping_for_object

* Homogenate batch flags and render flags and begin the painful task of documenting waves. (#590)

* part one of wave pain

* 2

* littel bit more

* m

* h

* l

* aagghh

* yeah

* :)

* bruh

* b

* m

* faith

* z

* b

* rename batch flags

* Update waves.c

* fix conflicts

* match func_8000B38C (#592)

* match func_8000B38C

* format code

* update score

* match calc_dynamic_lighting_for_object_1 (#594)

* match calc_dynamic_lighting_for_object_1

* format and score

* update score

* update score again

* Document some Boost data (#593)

* Run formatter

* Document some boost info

* decompile and document math_util.c (#595)

* Match func_8002F440 (#596)

* Add a WIP attempt of func_8004447C, and add some scratch links

* match func_8002F440 and run formatter

* Cleanup some comments

* Update score, fix warnings, and run formatter again

* Remove some bad comments

* match: func_8007FFEC (#599)

* feat: allow score script to generate treemap file for decomp progress (#598)

* Various touchups here and there, some renames in camera.c (#597)

* the beginning of the void

* update asset tool

* void :)

* there

* cam

* model

* more

* fix warns

* ok

* whoops missed a rename

* Add a few NON_EQUIVALENT functions from scratches to keep them up to date (#602)

* WIP func_800101AC

* WIP func_800452A0

* cleanup

* Formatter and func_8004CC20 near match

* func_8004CC20 better score and documentation

* func_80049794 WIP and compilable

* Major cleanup of func_80049794

* Formatter

* Update scratch

* Add func_80019808 from scratch

* Cleanup func_80019808

* func_80019808 score improvment

* Improve func_80049794 score (Thanks Dominik!)

* func_8001C6C4 WIP

* func_8001E93C WIP

* small documenting

* Eliminate a lot of Obj64 casts.

* fix: do not pass default to treemap arg (#604)

* Quick fix for score script (#606)

* WIP func_80060EA8

* Quick fix for score script

* name and document lights, CC modes and textures (#607)

* improve match for func_800BDC80 & match func_80060EA8, func_8002F2AC (#609)

* improve match for func_800BDC80 & match func_80060EA8

* match: func_8002F2AC

* match: func_800452A0 & func_80042D20 (#611)

* partial match: func_800452A0, improve from non equiv to non matching

* match: func_800452A0 & func_80042D20

* document: remaining funcs, add decomp.me links (#612)

* match func_8004447C (#615)

* Document camera and sprites (#614)

* Cleanup, and use constants where possible (#617)

* Update func_80060EA8 WIP

* formatter

* More func_80060EA8

* More func_80060EA8

* WIP func_80060EA8

* Minor cleanups and documenting

* minot cleanup

* Define some constant flags properly

* Misc documenting and cleanup

* formatter

* Fix the scratch link for vsprintf (It was DP's)

* Add the WIP version of vsprintf that's 95.82% matching.

* Fix score script (#619)

* Added gltf support

* Allow either commas or vertical pipes for splitting render flags

---------

Co-authored-by: Unnunu <serge_zorkin@mail.ru>
Co-authored-by: Fazana <52551480+FazanaJ@users.noreply.github.com>
Co-authored-by: Ryan Myers <ryan.p.myers@gmail.com>
Co-authored-by: Dominik Peters <github@d-peters.com>
This commit is contained in:
David Benepe
2025-06-12 10:29:08 -04:00
committed by GitHub
co-authored by Unnunu Fazana Ryan Myers Dominik Peters
parent bb9ed5ea5d
commit e30f1c535a
64 changed files with 8206 additions and 741 deletions
+1 -1
View File
@@ -410,7 +410,7 @@ endif
build_assets:
$(info Building Assets...)
@$(TOOLS_DIR)/dkr_assets_tool build -o $(ASSETS_OUTPUT) -dkrv $(REGION).$(VERSION) $(MODDED_ARG) >&2 || echo FAIL
@$(TOOLS_DIR)/dkr_assets_tool build -o $(ASSETS_OUTPUT) -dkrv $(REGION).$(VERSION) $(MODDED_ARG) >&2 || exit 1
###############################
+21 -21
View File
@@ -7,9 +7,9 @@ All versions are supported, and the US 1.0 version (SHA1 = 0cb115d8716dbbc2922fd
<!-- README_SCORE_SUMMARY_BEGIN -->
As of June 11, 2025, this is our current score:
&emsp;&emsp;&emsp;&emsp;Decomp progress: 90.62%
&emsp;&emsp;&emsp;&emsp;Decomp progress: 90.87%
&emsp;&emsp;&emsp;&emsp;Documentation progress: 59.08%
&emsp;&emsp;&emsp;&emsp;Documentation progress: 59.40%
<!-- README_SCORE_SUMMARY_END -->
---
@@ -119,28 +119,28 @@ s32 is_drumstick_unlocked(void) {
<!-- README_SCORE_BEGIN -->
As of June 11, 2025, this is our current score:
```
===================================================================
ADVENTURE ONE (ASM -> C Decompilation)
-------------- 90.62% Complete (91.40% NON_MATCHING) --------------
# Decompiled functions: 1912
# GLOBAL_ASM remaining: 32
====================================================================
ADVENTURE ONE (ASM -> C Decompilation)
-------------- 90.87% Complete (91.65% NON_MATCHING) ---------------
# Decompiled functions: 1913
# GLOBAL_ASM remaining: 37
# NON_MATCHING functions: 4
# NON_EQUIVALENT WIP functions: 28
--------------------------- Game Status ---------------------------
# NON_EQUIVALENT WIP functions: 30
--------------------------- Game Status ----------------------------
Balloons: 42/47, Keys: 4/4, Trophies: 4/5
T.T. Amulets: 4/4, Wizpig Amulets: 4/4
-------------------------------------------------------------------
T.T. Amulets: 4/4, Wizpig Amulets: 4/4
--------------------------------------------------------------------
We are racing in Star City. (Lap 3/3)
===================================================================
====================================================================
ADVENTURE TWO (Cleanup & Documentation)
------------------------- 59.08% Complete -------------------------
# Documented functions: 1207
# Undocumented remaining: 440
--------------------------- Game Status ---------------------------
Balloons: 28/47, Keys: 3/4, Trophies: 2/5
T.T. Amulets: 3/4, Wizpig Amulets: 2/4
-------------------------------------------------------------------
We are collecting silver coins in Pirate Lagoon. (8/8 silver coins)
===================================================================
------------------------- 59.40% Complete --------------------------
# Documented functions: 1208
# Undocumented remaining: 445
--------------------------- Game Status ----------------------------
Balloons: 29/47, Keys: 3/4, Trophies: 2/5
T.T. Amulets: 3/4, Wizpig Amulets: 2/4
--------------------------------------------------------------------
We are collecting silver coins in Treasure Caves. (1/8 silver coins)
====================================================================
```
<!-- README_SCORE_END -->
@@ -0,0 +1,12 @@
{
"files": {
"order": [
"ASSET_CUSTOM_ANIMATED_TESTCUBE"
],
"sections": {
"ASSET_CUSTOM_ANIMATED_TESTCUBE": {
"filename": "animatedBox.json"
}
}
}
}
@@ -0,0 +1,5 @@
{
"name": "Custom Animated Model Test",
"authors" : ["Example"],
"description": "A simple cube model with a custom animation."
}
@@ -0,0 +1,288 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.2.70",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"animations":[
{
"channels":[
{
"sampler":0,
"target":{
"node":0,
"path":"weights"
}
}
],
"name":"Key.001Action",
"samplers":[
{
"input":9,
"interpolation":"LINEAR",
"output":10
}
]
}
],
"materials":[
{
"doubleSided":true,
"name":"Material.001",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"extras":{
"targetNames":[
"Key 1",
"Key 2"
]
},
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2,
"COLOR_0":3
},
"indices":4,
"material":0,
"targets":[
{
"POSITION":5,
"NORMAL":6
},
{
"POSITION":7,
"NORMAL":8
}
]
}
],
"weights":[
0,
0
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"logo",
"uri":"logo.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":24,
"normalized":true,
"type":"VEC4"
},
{
"bufferView":4,
"componentType":5123,
"count":36,
"type":"SCALAR"
},
{
"bufferView":5,
"componentType":5126,
"count":24,
"max":[
0.5727667212486267,
0,
0.9693711996078491
],
"min":[
-1.1323010921478271,
0,
-0.820482611656189
],
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":7,
"componentType":5126,
"count":24,
"max":[
0.8063837289810181,
0,
1.0958211421966553
],
"min":[
-0.9761124849319458,
0,
-0.6933281421661377
],
"type":"VEC3"
},
{
"bufferView":8,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":9,
"componentType":5126,
"count":25,
"max":[
1
],
"min":[
0
],
"type":"SCALAR"
},
{
"bufferView":10,
"componentType":5126,
"count":50,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":576,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":768,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":960,
"target":34963
},
{
"buffer":0,
"byteLength":288,
"byteOffset":1032,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":1320,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":1608,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":1896,
"target":34962
},
{
"buffer":0,
"byteLength":100,
"byteOffset":2184
},
{
"buffer":0,
"byteLength":200,
"byteOffset":2284
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":2484,
"uri":"animatedBox.bin"
}
]
}
@@ -0,0 +1,8 @@
{
"model": "animatedBox.gltf",
"type": "ObjectModel",
"scale": 10.0,
"animation": {
"seconds-per-keyframe": 0.25
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

-3
View File
@@ -6490,12 +6490,10 @@ void func_80019808(s32 updateRate) {
}
}
}
if ((currentLevelHeader->race_type != RACETYPE_CHALLENGE_BATTLE && numFinishedRacers > 0) ||
((((numHumanRacers == 1 && numHumanRacersFinished == 1) ||
(numHumanRacers >= 2 && numHumanRacersFinished >= numHumanRacers)) ||
numFinishedRacers >= 3))) {
for (i = 0; i < gNumRacers; i++) {
if (currentLevelHeader->race_type == RACETYPE_CHALLENGE_BATTLE) {
sp5C[i] = 10 - racer[i]->bananas;
@@ -6542,7 +6540,6 @@ void func_80019808(s32 updateRate) {
} while (racerIndex != -1);
gSwapLeadPlayer = FALSE;
if (!is_in_tracks_mode() &&
(racer[0]->finishPosition == 1 ||
is_in_two_player_adventure() && racer[1]->finishPosition == 1) &&
+6
View File
@@ -1,5 +1,11 @@
# Make `OPT` -g for better debugging info.
OPT := -O2
# If -g option was set, then add some debugging flags for the stack tracer.
ifneq (,$(findstring -g,$(OPT)))
OPT += -rdynamic -ldl -lelf -ldwarf -DDEBUG -I/usr/include/libdwarf
endif
CC := gcc
CXX := g++
CFLAGS := -I . -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 $(OPT) -s
+1 -1
View File
@@ -8,7 +8,7 @@
"build-subpath" : "build/",
"data-subpath" : "data/",
"model-scale": 0.001,
"threads" : 0,
"threads" : 1,
"debug": {
"keep-uncompressed": true,
"write-assets-map": false
@@ -29,8 +29,9 @@ BuildInfo::BuildInfo(std::string buildId, std::string buildSectionId, const Json
_type = jsonFile.get_string("/type", "NoType");
}
BuildInfo::BuildInfo(std::string buildId, std::string buildSectionId, const std::vector<uint8_t> &outData, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext)
: out(outData), _dstPath(""), _localDirectory(dir), _jsonFile(std::nullopt), _infoContext(infoContext), _buildId(buildId), _buildSectionId(buildSectionId), _type("Binary"), _fileIndex(fileIndex) {
BuildInfo::BuildInfo(std::string buildId, std::string buildSectionId, std::string deferredFromBuildId, std::string deferredFromSectionId, const std::vector<uint8_t> &outData,
size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext) : out(outData), _dstPath(""), _localDirectory(dir), _jsonFile(std::nullopt), _infoContext(infoContext),
_buildId(buildId), _buildSectionId(buildSectionId), _deferredFromBuildId(deferredFromBuildId), _deferredFromSectionId(deferredFromSectionId), _type("Binary"), _fileIndex(fileIndex) {
_buildType = BUILD_TO_BINARY;
}
@@ -116,10 +117,22 @@ std::string BuildInfo::get_section_build_id() const {
return _buildSectionId;
}
std::string BuildInfo::get_deferred_from_build_id() const {
return _deferredFromBuildId;
}
std::string BuildInfo::get_deferred_from_section_build_id() const {
return _deferredFromSectionId;
}
size_t BuildInfo::get_file_index() const {
return _fileIndex;
}
bool BuildInfo::is_deferred() const {
return !_deferredFromSectionId.empty();
}
bool BuildInfo::is_complete() const {
return _complete;
}
@@ -27,7 +27,8 @@ public:
//BuildInfo(std::string buildId, JsonFile *src, const fs::path &dst, const fs::path &dir); // BUILD_TO_FILE, TODO
//BuildInfo(std::string buildId, JsonFile *src, const fs::path &dir); // BUILD_TO_BINARY
BuildInfo(std::string buildId, std::string buildSectionId, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext);
BuildInfo(std::string buildId, std::string buildSectionId, const std::vector<uint8_t> &outData, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext);
BuildInfo(std::string buildId, std::string buildSectionId, std::string deferredFromBuildId, std::string deferredFromSectionId, const std::vector<uint8_t> &outData,
size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext);
~BuildInfo();
std::vector<uint8_t> out;
@@ -56,8 +57,12 @@ public:
std::string get_type() const;
std::string get_build_id() const;
std::string get_section_build_id() const;
std::string get_deferred_from_build_id() const;
std::string get_deferred_from_section_build_id() const;
size_t get_file_index() const;
bool is_deferred() const; // Returns true if this asset was deferred from another section.
bool is_complete() const;
void done();
@@ -75,6 +80,8 @@ private:
std::string _buildId;
std::string _buildSectionId;
std::string _deferredFromBuildId;
std::string _deferredFromSectionId;
std::string _type;
size_t _fileIndex;
BuildInfoType _buildType;
@@ -46,7 +46,8 @@ void BuildInfoCollection::add_build_info(std::string sectionBuildId, std::string
_buildInfoMutex.unlock();
}
size_t BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext) {
size_t BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, std::string deferredFromBuildId, std::string deferredFromSectionId,
const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext) {
_buildInfoMutex.lock();
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
@@ -59,7 +60,7 @@ size_t BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId,
"(BuildInfoCollection::add_build_info) Max number of assets reached! Limit was ", MAX_NUMBER_OF_BUILD_INFOS);
size_t fileIndex = _buildInfoSections[sectionBuildId].size();
_buildInfos.emplace_back(buildId, sectionBuildId, out, fileIndex, dir, infoContext);
_buildInfos.emplace_back(buildId, sectionBuildId, deferredFromBuildId, deferredFromSectionId, out, fileIndex, dir, infoContext);
// Do not process deferred assets.
_buildInfos.back().done();
@@ -72,8 +73,8 @@ size_t BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId,
return fileIndex;
}
void BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, size_t fileIndex, const std::vector<uint8_t> &out,
const fs::path &dir, const BuildInfoContext &infoContext) {
void BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, std::string deferredFromBuildId, std::string deferredFromSectionId,
size_t fileIndex, const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext) {
_buildInfoMutex.lock();
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
@@ -89,7 +90,7 @@ void BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, st
DebugHelper::assert_(index < MAX_NUMBER_OF_BUILD_INFOS,
"(BuildInfoCollection::add_build_info) Max number of assets reached! Limit was ", MAX_NUMBER_OF_BUILD_INFOS);
_buildInfos.emplace_back(buildId, sectionBuildId, out, fileIndex, dir, infoContext);
_buildInfos.emplace_back(buildId, sectionBuildId, deferredFromBuildId, deferredFromSectionId, out, fileIndex, dir, infoContext);
// Do not process deferred assets.
_buildInfos.back().done();
@@ -19,12 +19,12 @@ public:
void add_build_info(std::string sectionBuildId, std::string buildId, const JsonFile &src, const fs::path &dir, const BuildInfoContext &infoContext);
// Adds deferred info to the end of the list. Returns the fileIndex of the added asset.
size_t add_deferred_build_info(std::string sectionBuildId, std::string buildId, const std::vector<uint8_t> &out,
const fs::path &dir, const BuildInfoContext &infoContext);
size_t add_deferred_build_info(std::string sectionBuildId, std::string buildId, std::string deferredFromBuildId, std::string deferredFromSectionId,
const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext);
// Adds deferred info to a specific file index.
void add_deferred_build_info(std::string sectionBuildId, std::string buildId, size_t fileIndex, const std::vector<uint8_t> &out,
const fs::path &dir, const BuildInfoContext &infoContext);
void add_deferred_build_info(std::string sectionBuildId, std::string buildId, std::string deferredFromBuildId, std::string deferredFromSectionId,
size_t fileIndex, const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext);
void run_builds(std::function<void(BuildInfo &info)> callbackFunction, const std::vector<std::string> &sectionsOrder);
@@ -121,8 +121,8 @@ void build_v77_beta_font(BuildInfo &info, const JsonFile &jsonFile) {
//outJpFontTableView.print(0, outJpFontTable.size());
// Add jp font table to build collection.
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", outJpFontTable,
info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", info.get_build_id(),
info.get_section_build_id(), outJpFontTable,info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
}
@@ -282,8 +282,8 @@ void build_v79_jp_fonts(BuildInfo &info, const JsonFile &jsonFile) {
}
// Add jp font table to build collection.
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", outJpFontTable,
info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", info.get_build_id(),
info.get_section_build_id(), outJpFontTable,info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
}
void BuildJPFonts::build(BuildInfo &info) {
@@ -89,8 +89,8 @@ void build_level_name(BuildInfo &info, const JsonFile &jsonFile) {
BuildInfoCollection &collection = info.get_collection();
std::string buildId = info.get_build_id() + deferInfo.idPostfix;
collection.add_deferred_build_info(levelNamesSectionBuildId, buildId, info.get_file_index(),
out, info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
collection.add_deferred_build_info(levelNamesSectionBuildId, buildId, info.get_build_id(), info.get_section_build_id(),
info.get_file_index(), out, info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
}
}
@@ -10,29 +10,42 @@
#include "fileTypes/vertex.hpp"
#include "fileTypes/textureInfo.hpp"
#include "libs/bytes_view.hpp"
#include "misc/globalSettings.h"
#include "builder/buildInfoCollection.h"
using namespace DkrAssetsTool;
void BuildModel::generate_object_model(BuildInfo &info) {
const JsonFile &modelJson = info.get_src_json_file();
float modelScale = modelJson.get_float("/scale", 1000.0f);
size_t numberOfTriangles = _total_number_of_triangles();
size_t numberOfVertices = _total_number_of_vertices();
size_t numberOfAnimatedVertices = _total_number_of_animated_vertices();
size_t numberOfBatches = _total_number_of_batches();
size_t numberOfBatchesToAllocate = _total_number_of_batches_to_allocate();
size_t numberOfTextures = _total_number_of_textures();
size_t numberOfAttachPoints = _attachPoints.size();
size_t sizeOfHeader = DataHelper::align16(sizeof(ObjectModel));
size_t sizeOfTextures = DataHelper::align16(sizeof(DkrTextureInfo) * numberOfTextures);
size_t sizeOfVertices = DataHelper::align16(sizeof(DkrVertex) * numberOfVertices);
size_t sizeOfVertices = DataHelper::align16(sizeof(DkrVertex) * (numberOfVertices + numberOfAttachPoints));
size_t sizeOfTriangles = DataHelper::align16(sizeof(DkrTriangle) * numberOfTriangles);
size_t sizeOfBatches = DataHelper::align16(sizeof(DkrBatch) * numberOfBatchesToAllocate);
size_t sizeOfAnimatedVertexIndices = _animations.empty() ? 0 : DataHelper::align16(sizeof(int16_t) * numberOfVertices);
size_t sizeOfAttachPoints = DataHelper::align16(sizeof(be_int16_t) * numberOfAttachPoints);
// Calculated the file size for the binary model file.
size_t calculatedFileSize = sizeOfHeader +
sizeOfTextures +
sizeOfVertices +
sizeOfTriangles +
sizeOfBatches;
sizeOfBatches +
sizeOfAnimatedVertexIndices +
sizeOfAttachPoints;
info.out.resize(calculatedFileSize);
@@ -42,27 +55,47 @@ void BuildModel::generate_object_model(BuildInfo &info) {
objectModelHeader->vertices = objectModelHeader->textures + sizeOfTextures;
objectModelHeader->triangles = objectModelHeader->vertices + sizeOfVertices;
objectModelHeader->batches = objectModelHeader->triangles + sizeOfTriangles;
objectModelHeader->numberOfTextures= (int16_t)numberOfTextures;
objectModelHeader->animatedVertexIndices = objectModelHeader->batches + sizeOfBatches;
objectModelHeader->attachPoints = objectModelHeader->animatedVertexIndices + sizeOfAnimatedVertexIndices;
objectModelHeader->numberOfTextures = (int16_t)numberOfTextures;
objectModelHeader->numberOfTriangles = (int16_t)numberOfTriangles;
objectModelHeader->numberOfVertices = (int16_t)numberOfVertices;
objectModelHeader->numberOfBatches = (int16_t)numberOfBatches;
objectModelHeader->numberOfAttachPoints = (int16_t)numberOfAttachPoints;
objectModelHeader->fileSize = (int16_t)calculatedFileSize;
objectModelHeader->numberOfAnimatedVertices = (int16_t)numberOfAnimatedVertices;
DkrTextureInfo *textures = reinterpret_cast<DkrTextureInfo *>(&info.out[objectModelHeader->textures]);
DkrVertex *vertices = reinterpret_cast<DkrVertex *>(&info.out[objectModelHeader->vertices]);
DkrTriangle *triangles = reinterpret_cast<DkrTriangle *>(&info.out[objectModelHeader->triangles]);
DkrBatch *batches = reinterpret_cast<DkrBatch *>(&info.out[objectModelHeader->batches]);
be_int16_t *animatedVertexIndices = reinterpret_cast<be_int16_t *>(&info.out[objectModelHeader->animatedVertexIndices]);
be_int16_t *attachPoints = reinterpret_cast<be_int16_t *>(&info.out[objectModelHeader->attachPoints]);
bool hasAnimatedTexture = false;
for(size_t materialIndex = 0; materialIndex < numberOfTextures; materialIndex++) {
BuildModelMaterial &material = _materials[materialIndex];
material.write_to(&textures[materialIndex], info);
hasAnimatedTexture = hasAnimatedTexture || material.is_texture_animated();
}
objectModelHeader->hasAnimatedTexture = (int)hasAnimatedTexture;
for(auto &block : _blocks) {
block.write_batches(_materialIds, _materials, batches, vertices, triangles);
block.write_batches(_materialIds, _materials, batches, vertices, triangles, modelScale);
}
// Does nothing with static models.
_generate_animations(info, objectModelHeader, animatedVertexIndices);
vertices = reinterpret_cast<DkrVertex *>(&info.out[objectModelHeader->vertices]);
for(size_t i = 0; i < numberOfAttachPoints; i++) {
attachPoints[i] = numberOfVertices + i;
DkrVertex *vertex = &vertices[numberOfVertices + i];
vertex->x = static_cast<int16_t>(_attachPoints[i].x * modelScale);
vertex->y = static_cast<int16_t>(_attachPoints[i].y * modelScale);
vertex->z = static_cast<int16_t>(_attachPoints[i].z * modelScale);
}
bool debugKeepUncompressed = GlobalSettings::get_value<bool>("/debug/keep-uncompressed", false);
@@ -105,7 +138,16 @@ void BuildModel::_add_material(std::string id, fs::path texturePath) {
return;
}
_materialIds[id] = _materials.size();
_materials.emplace_back(texturePath);
_materials.emplace_back(texturePath, id);
}
void BuildModel::_add_material(std::string id, uint8_t *imgData, size_t imgDataLength, std::string mimeType) {
if(imgData == nullptr || imgDataLength == 0) {
_materialIds[id] = -1;
return;
}
_materialIds[id] = _materials.size();
_materials.emplace_back(imgData, imgDataLength, mimeType, id);
}
void BuildModel::_set_material(std::string id) {
@@ -145,6 +187,24 @@ size_t BuildModel::_total_number_of_vertices() {
return count;
}
size_t BuildModel::_total_number_of_animated_vertices() {
if(_animations.empty()) {
return 0;
}
size_t count = 0;
for(auto &block : _blocks) {
block.process_vertices([&count](BuildModelVertex &vertex) {
if(vertex.is_animated()) {
count++;
}
});
}
return count;
}
size_t BuildModel::_total_number_of_textures() {
size_t count = 0;
@@ -176,3 +236,148 @@ size_t BuildModel::_total_number_of_batches_to_allocate() {
return count;
}
template<typename T, typename T2>
inline void process_keyframe(T *keyframe, size_t &animDataOffset, const std::vector<std::reference_wrapper<BuildModelVertex>> &animatedVertices, AnimWeights &weights,
std::optional<AnimWeights> previousWeights, float modelScale) {
size_t numberOfAnimatedVertices = animatedVertices.size();
Vec3f vertexDelta;
for(size_t i = 0; i < numberOfAnimatedVertices; i++) {
const BuildModelVertex &vertex = animatedVertices.at(i);
if(previousWeights.has_value()) {
vertexDelta = vertex.animated_position(weights) - vertex.animated_position(previousWeights.value());
} else {
vertexDelta = vertex.animated_position(weights) - vertex.position();
}
keyframe[i].offsetX = static_cast<T2>(vertexDelta.x * modelScale);
keyframe[i].offsetY = static_cast<T2>(vertexDelta.y * modelScale);
keyframe[i].offsetZ = static_cast<T2>(vertexDelta.z * modelScale);
}
animDataOffset += sizeof(T) * numberOfAnimatedVertices;
}
void BuildModel::_generate_animations(BuildInfo &info, ObjectModel *objectModelHeader, be_int16_t *outAnimatedIndicesPtr) {
if((_animations.empty()) || (objectModelHeader->numberOfAnimatedVertices < 1)) {
return;
}
std::vector<int16_t> animatedVertexIndices;
std::vector<std::reference_wrapper<BuildModelVertex>> animatedVertices;
animatedVertexIndices.reserve(objectModelHeader->numberOfVertices);
animatedVertices.reserve(objectModelHeader->numberOfVertices); // Should have no more than the number of vertices.
for(auto &block : _blocks) {
block.process_vertices([&animatedVertexIndices, &animatedVertices](BuildModelVertex &vertex) {
if(!vertex.is_animated()) {
animatedVertexIndices.emplace_back(-1);
return; // Don't include this vertex.
}
animatedVertexIndices.emplace_back(animatedVertices.size());
animatedVertices.emplace_back(vertex);
});
}
for(size_t i = 0; i < objectModelHeader->numberOfVertices; i++) {
outAnimatedIndicesPtr[i] = animatedVertexIndices[i];
}
std::optional<AnimWeights> weights = std::nullopt;
std::optional<AnimTranslations> translations = std::nullopt;
std::optional<AnimRotations> rotations = std::nullopt;
std::optional<AnimScales> scales = std::nullopt;
std::optional<AnimWeights> previousWeights = std::nullopt;
size_t numberOfAnimatedVertices = animatedVertices.size();
bool debugKeepUncompressed = GlobalSettings::get_value<bool>("/debug/keep-uncompressed", false);
BuildInfoCollection &collection = info.get_collection();
std::vector<size_t> animationIndices;
const JsonFile &modelJson = info.get_src_json_file();
float secondsPerKeyframe = modelJson.get_float("/animation/seconds-per-keyframe", 0.267f);
float modelScale = modelJson.get_float("/scale", 1000.0f);
size_t animIndex = 0;
for(BuildModelAnimation &anim : _animations) {
size_t numberOfKeyframes = std::floor(anim.get_max_time() / secondsPerKeyframe) + 1;
size_t animationDataSize = sizeof(ObjectModelAnimationHeader) +
(sizeof(ObjectModelAnimationKeyframeHeader) * numberOfKeyframes) + // Each keyframe has a header that is 12-bytes long. Mainly used for head tilting?
(sizeof(ObjectModelAnimationKeyframeVertexS16) * numberOfAnimatedVertices) + // First keyframe uses 16-bit values.
(sizeof(ObjectModelAnimationKeyframeVertexS8) * (numberOfAnimatedVertices - 1) * numberOfKeyframes); // Following keyframes use 8-bit values.
std::vector<uint8_t> animationData(animationDataSize);
BytesView animDataView(animationData);
size_t animDataOffset = 0;
ObjectModelAnimationHeader *animHeader = animDataView.data_cast<ObjectModelAnimationHeader>(animDataOffset);
animHeader->keyframeCount = numberOfKeyframes;
animDataOffset += sizeof(ObjectModelAnimationHeader);
bool skipAnimation = false;
for(size_t keyframe = 0; keyframe < numberOfKeyframes; keyframe++) {
//ObjectModelAnimationKeyframeHeader *keyframeHeader = animDataView.data_cast<ObjectModelAnimationKeyframeHeader>(animDataOffset);
animDataOffset += sizeof(ObjectModelAnimationKeyframeHeader);
float keyframeTime = keyframe * secondsPerKeyframe;
anim.get_inputs_for_time(keyframeTime, weights, translations, rotations, scales);
if(keyframe == 0) {
if(translations.has_value() || rotations.has_value() || scales.has_value()) {
DebugHelper::warn("Only morph target/shape key animation is supported at the moment.");
}
if(!weights.has_value()) {
skipAnimation = true;
break; // Only Morph Targets are supported at the moment.
}
ObjectModelAnimationKeyframeVertexS16 *firstKeyframe = animDataView.data_cast<ObjectModelAnimationKeyframeVertexS16>(animDataOffset);
process_keyframe<ObjectModelAnimationKeyframeVertexS16, int16_t>(firstKeyframe, animDataOffset, animatedVertices,
weights.value(), std::nullopt, modelScale);
} else {
ObjectModelAnimationKeyframeVertexS8 *followingKeyframe = animDataView.data_cast<ObjectModelAnimationKeyframeVertexS8>(animDataOffset);
process_keyframe<ObjectModelAnimationKeyframeVertexS8, int8_t>(followingKeyframe, animDataOffset, animatedVertices,
weights.value(), previousWeights, modelScale);
}
previousWeights = weights;
}
if(skipAnimation) {
continue;
}
while((animationData.size() % 16) != 0) {
animationData.emplace_back(0);
}
std::string buildId = info.get_build_id() + "_ANIM_" + std::to_string(animIndex);
if(debugKeepUncompressed) {
fs::path outUncompressedPath = GlobalSettings::get_decomp_path("build_subpath", "build/");
outUncompressedPath /= "debug/custom-animations";
outUncompressedPath /= buildId + ".bin";
FileHelper::write_binary_file(animationData, outUncompressedPath, true);
}
animationData = GzipHelper::compress_gzip(animationData);
size_t newAnimId = collection.add_deferred_build_info("ASSET_OBJECT_ANIMATIONS", buildId, info.get_build_id(), info.get_section_build_id(),
animationData, info.get_path_to_directory(), info.get_info_context());
animationIndices.emplace_back(newAnimId);
animIndex++;
}
}
@@ -8,9 +8,11 @@
#include "helpers/fileHelper.h"
#include "fileTypes/batch.hpp"
#include "fileTypes/objectModel.hpp"
#include "buildModelBlock.h"
#include "buildModelMaterial.h"
#include "buildModelAnimation.h"
namespace DkrAssetsTool {
@@ -26,21 +28,29 @@ protected:
std::vector<BuildModelMaterial> _materials;
std::map<std::string, int> _materialIds;
std::vector<BuildModelAnimation> _animations;
std::vector<Vec3f> _attachPoints;
void _add_new_triangle_to_current_block(const BuildModelVertex& vertA, const BuildModelVertex& vertB, const BuildModelVertex& vertC,
Vec2f uv0, Vec2f uv1, Vec2f uv2);
void _add_new_triangle_to_current_block(const BuildModelVertex& vertA, const BuildModelVertex& vertB, const BuildModelVertex& vertC);
void _new_block();
void _add_material(std::string id, fs::path texturePath="");
void _add_material(std::string id, uint8_t *imgData, size_t imgDataLength, std::string mimeType);
void _set_material(std::string id);
std::optional<std::reference_wrapper<BuildModelMaterial>> _try_get_material(std::string id);
size_t _total_number_of_triangles();
size_t _total_number_of_vertices();
size_t _total_number_of_animated_vertices();
size_t _total_number_of_textures();
size_t _total_number_of_batches();
size_t _total_number_of_batches_to_allocate();
void _generate_animations(BuildInfo &info, ObjectModel *objectModelHeader, be_int16_t *outAnimatedIndicesPtr);
};
}
@@ -0,0 +1,215 @@
#include "buildModelAnimation.h"
#include "helpers/dataHelper.h"
#include "helpers/debugHelper.h"
#include "helpers/gltf/gltfHelper.h"
#include <algorithm>
using namespace DkrAssetsTool;
/***************************************************************************************************************/
AnimWeights BuildModelAnimationInputs::get_weights_at_time(float time) {
if(time < 0) {
time = 0;
}
if(!weights.has_value()) {
return {};
}
std::map<float, AnimWeights> &inputWeights = weights.value();
if(inputWeights.find(time) != inputWeights.end()) {
// No need to interpolate!
return inputWeights.at(time);
}
auto upperBound = inputWeights.upper_bound(time);
auto lowerBound = std::prev(upperBound);
float lowerTime = lowerBound->first;
float upperTime = upperBound->first;
DebugHelper::info(time, " is between ", lowerTime, " and ", upperTime);
// Should handle the edgecases of time being outside timeline range.
if(lowerTime == upperTime) {
return upperBound->second;
}
AnimWeights &lowerWeights = lowerBound->second;
AnimWeights &upperWeights = upperBound->second;
// Percentage of interpolation.
float t = (time - lowerTime) / (upperTime - lowerTime);
AnimWeights out(lowerWeights.size());
for(size_t i = 0; i < lowerWeights.size(); i++) {
// Linear interpolation of weights.
out[i] = lowerWeights[i] + t * (upperWeights[i] - lowerWeights[i]);
}
return out;
}
AnimTranslations BuildModelAnimationInputs::get_translations_at_time(float time) {
return {};
}
AnimRotations BuildModelAnimationInputs::get_rotations_at_time(float time) {
return {};
}
AnimScales BuildModelAnimationInputs::get_scales_at_time(float time) {
return {};
}
/***************************************************************************************************************/
BuildModelAnimation::BuildModelAnimation() {
}
BuildModelAnimation::BuildModelAnimation(GltfFile &gltf, tinygltf::Animation &animation) {
for(size_t i = 0; i < animation.channels.size(); i++) {
tinygltf::AnimationChannel &channel = animation.channels[i];
if(channel.sampler < 0) {
continue;
}
tinygltf::AnimationSampler &sampler = animation.samplers[channel.sampler];
if(sampler.interpolation != "LINEAR") {
DebugHelper::warn(sampler.interpolation + " is currently not supported. Will use LINEAR instead.");
}
// I believe animation inputs are always floats? Couldn't find anything 100% definitive in the spec.
std::vector<float> inputTimes = GltfHelper::get_data_from_accessor<float>(gltf, sampler.input);
size_t numberOfInputs = inputTimes.size();
if(channel.target_path == "weights") {
// morph targets
auto tryGetWeights = GltfHelper::get_weights_from_accessor(gltf, sampler.output);
DebugHelper::assert_(tryGetWeights.has_value(),
"(BuildModelAnimation::BuildModelAnimation) Failed to get weights from gltf accessor ", sampler.output);
std::vector<float> &weights = tryGetWeights.value();
size_t numberOfWeights = weights.size() / numberOfInputs;
for(size_t j = 0; j < numberOfInputs; j++) {
add_weights_input(inputTimes[j], weights, j * numberOfWeights, numberOfWeights);
}
} else if(channel.target_path == "translation") {
DebugHelper::warn("(BuildModelAnimation::BuildModelAnimation) TODO: Support translation channel.path");
} else if(channel.target_path == "rotation") {
DebugHelper::warn("(BuildModelAnimation::BuildModelAnimation) TODO: Support rotation channel.path");
} else if(channel.target_path == "scale") {
DebugHelper::warn("(BuildModelAnimation::BuildModelAnimation) TODO: Support scale channel.path");
}
}
}
void BuildModelAnimation::add_weights_input(float time, std::vector<float> &values, std::optional<size_t> offset, std::optional<size_t> length) {
if(offset == std::nullopt) {
offset = 0;
}
if(length == std::nullopt) {
length = values.size() - offset.value();
}
if(_inputs.weights == std::nullopt) {
_inputs.weights = std::map<float, std::vector<float>>();
}
_inputs.weights.value()[time] = std::vector<float>(values.begin() + offset.value(), values.begin() + offset.value() + length.value());
if(time > _maxTime) {
_maxTime = time;
}
}
std::vector<float> BuildModelAnimation::get_timeline() {
std::vector<float> timeline;
int numberOfSections = 0;
if(_inputs.weights.has_value()) {
for(auto &pair : _inputs.weights.value()) {
timeline.emplace_back(pair.first);
}
numberOfSections++;
}
if(_inputs.translations.has_value()) {
for(auto &pair : _inputs.translations.value()) {
if(DataHelper::vector_has(timeline, pair.first)) {
continue;
}
timeline.emplace_back(pair.first);
}
numberOfSections++;
}
if(_inputs.rotations.has_value()) {
for(auto &pair : _inputs.rotations.value()) {
if(DataHelper::vector_has(timeline, pair.first)) {
continue;
}
timeline.emplace_back(pair.first);
}
numberOfSections++;
}
if(_inputs.scales.has_value()) {
for(auto &pair : _inputs.scales.value()) {
if(DataHelper::vector_has(timeline, pair.first)) {
continue;
}
timeline.emplace_back(pair.first);
}
numberOfSections++;
}
if(numberOfSections > 1) {
std::sort(timeline.begin(), timeline.end());
}
return timeline;
}
float BuildModelAnimation::get_max_time() const {
return _maxTime;
}
// Returns weights/translations/rotations/scales for a specific time input. Values will be set to std::nullopt if there is no data.
// Values will be interpolated if there is data, but the time input was not specified.
void BuildModelAnimation::get_inputs_for_time(float time, std::optional<AnimWeights> &outWeights, std::optional<AnimTranslations> &outTranslations,
std::optional<AnimRotations> &outRotations, std::optional<AnimScales> &outScales) {
// Set new data
if(_inputs.weights.has_value()) {
outWeights = _inputs.get_weights_at_time(time);
} else {
outWeights = std::nullopt;
}
if(_inputs.translations.has_value()) {
outTranslations = _inputs.get_translations_at_time(time);
} else {
outTranslations = std::nullopt;
}
if(_inputs.rotations.has_value()) {
outRotations = _inputs.get_rotations_at_time(time);
} else {
outRotations = std::nullopt;
}
if(_inputs.scales.has_value()) {
outScales = _inputs.get_scales_at_time(time);
} else {
outScales = std::nullopt;
}
}
bool BuildModelAnimation::only_has_morph_targets() const {
return _inputs.weights.has_value() && !_inputs.translations.has_value() &&
!_inputs.rotations.has_value() && !_inputs.scales.has_value();
}

Some files were not shown because too many files have changed in this diff Show More