DKR Assets Tool v0.5.1, finished up .obj model support (#584)

* Assets Revision 0.5.1, finish up OBJ support

* Made wrap the default texture wrap mode. Clamping can be done through a .json file

* fixed out_of_range issue with getting multiple textures.

* Fixed issue with thwomp test model.

* Removed unused problematic code from fileHelper

* Why did I add an error here? That was dumb.

* Updated json lib to latest version to fix issue on mac.
This commit is contained in:
David Benepe
2025-05-22 10:59:51 -04:00
committed by GitHub
parent 97aaf39e64
commit ff76af0c79
32 changed files with 8224 additions and 8352 deletions
+1 -6
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 || echo FAIL
###############################
@@ -447,11 +447,6 @@ $(BUILD_DIR)/%.s.o: %.s | build_assets
$(call print,Assembling:,$<,$@)
$(V)$(AS) $(ASFLAGS) -MD $(BUILD_DIR)/$*.d -o $@ $<
# Specifically override the assets bin output location to match what splat output to the ld file.
$(BUILD_DIR)/asm/assets/assets.s.o: asm/assets/assets.s | build_assets
$(call print,Assembling Assets:,$<,$@)
$(V)$(AS) $(ASFLAGS) -o $(BUILD_DIR)/assets/assets.bin.o $<
# Specifically override the header file from what splat extracted to be replaced by what we have in the hasm folder
$(BUILD_DIR)/asm/header.s.o: src/hasm/header.s | build_assets
$(call print,Assembling Header:,$<,$@)
+11 -70
View File
@@ -6,69 +6,6 @@
using namespace DkrAssetsTool;
/**************************************************************************/
BuildInfoContext::BuildInfoContext(CContext &cContext, BuildStats &stats, BuildInfoCollection &collection)
: _cContext(cContext), _stats(stats), _collection(collection) {}
CContext &BuildInfoContext::get_c_context() const {
return _cContext;
}
BuildStats &BuildInfoContext::get_stats() const {
return _stats;
}
BuildInfoCollection &BuildInfoContext::get_collection() const {
return _collection;
}
void BuildInfoContext::init_obj_beh_to_entry_map() {
if(!_objBehaviorToEntry.empty()) {
return; // Don't redo this if the map is already filled!
}
// TODO: This was copy-pasted from extract/config.cpp; need to refactor to not require the config file.
fs::path pathToConfig = GlobalSettings::get_decomp_path("tools", "tools/") / "dkr_assets_tool_extract.json";
auto tryGetConfigFile = JsonHelper::get_file(pathToConfig);
DebugHelper::assert_(tryGetConfigFile.has_value(),
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find the config file at: ", pathToConfig);
JsonFile &configJson = tryGetConfigFile.value();
CContext &cContext = get_c_context();
CEnum *objBehaviors = cContext.get_enum("ObjectBehaviours");
DebugHelper::assert_(objBehaviors != nullptr,
"(BuildInfoContext::init_obj_beh_to_entry_map) The enum ObjectBehaviours could not be loaded!");
std::vector<std::string> defaultObjEntriesOrder;
configJson.get_array<std::string>("/misc/default-object-entries-order", defaultObjEntriesOrder);
for(int i = 0; i < 128; i++) {
std::string symbol;
DebugHelper::assert_(objBehaviors->get_symbol_of_value(i, symbol),
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not get a symbol for the value ", i, " in the ObjectBehaviors enum.");
CStruct *entryStruct = cContext.get_struct(defaultObjEntriesOrder[i]);
DebugHelper::assert_(entryStruct != nullptr,
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find struct \"", defaultObjEntriesOrder[i], "\"");
_objBehaviorToEntry[symbol] = defaultObjEntriesOrder[i];
}
}
std::string BuildInfoContext::get_object_entry_from_behavior(std::string objBehavior) const {
DebugHelper::assert_(_objBehaviorToEntry.find(objBehavior) != _objBehaviorToEntry.end(),
"(AssetExtractConfig::get_object_entry_from_behavior) ", objBehavior,
" was not in the _objBehaviorToEntry map!");
return _objBehaviorToEntry.at(objBehavior);
}
/**************************************************************************/
BuildInfo::BuildInfo() {
DebugHelper::error("Must not initalize empty build info.");
}
@@ -85,15 +22,15 @@ BuildInfo::BuildInfo(JsonFile *src, const fs::path &dir)
}
*/
BuildInfo::BuildInfo(std::string buildId, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext)
: _dstPath(""), _localDirectory(dir), _jsonFile(src), _infoContext(infoContext), _buildId(buildId), _fileIndex(fileIndex) {
BuildInfo::BuildInfo(std::string buildId, std::string buildSectionId, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext)
: _dstPath(""), _localDirectory(dir), _jsonFile(src), _infoContext(infoContext), _buildId(buildId), _buildSectionId(buildSectionId), _fileIndex(fileIndex) {
_buildType = BUILD_TO_BINARY;
const JsonFile &jsonFile = get_src_json_file();
_type = jsonFile.get_string("/type", "NoType");
}
BuildInfo::BuildInfo(std::string buildId, 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), _type("Binary"), _fileIndex(fileIndex) {
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) {
_buildType = BUILD_TO_BINARY;
}
@@ -145,11 +82,11 @@ CContext &BuildInfo::get_c_context() const {
return infoContext.get_c_context();
}
BuildStats &BuildInfo::get_stats() const {
BuildTextureCache &BuildInfo::get_texture_cache() const {
const BuildInfoContext &infoContext = _infoContext.value();
return infoContext.get_stats();
return infoContext.get_texture_cache();
}
BuildInfoCollection &BuildInfo::get_collection() const {
const BuildInfoContext &infoContext = _infoContext.value();
return infoContext.get_collection();
@@ -175,6 +112,10 @@ std::string BuildInfo::get_build_id() const {
return _buildId;
}
std::string BuildInfo::get_section_build_id() const {
return _buildSectionId;
}
size_t BuildInfo::get_file_index() const {
return _fileIndex;
}
+7 -28
View File
@@ -10,6 +10,8 @@
#include "helpers/jsonHelper.h"
#include "helpers/c/cContext.h"
#include "builder/buildInfoContext.h"
namespace DkrAssetsTool {
typedef enum BuildInfoType {
@@ -17,38 +19,17 @@ typedef enum BuildInfoType {
BUILD_TO_BINARY // When building all assets
} BuildInfoType;
class BuildStats;
class BuildInfoCollection;
class BuildInfoContext {
public:
BuildInfoContext(CContext &cContext, BuildStats &stats, BuildInfoCollection &collection);
CContext &get_c_context() const;
BuildStats &get_stats() const;
BuildInfoCollection &get_collection() const;
// Used in BuildObjectMap
void init_obj_beh_to_entry_map();
std::string get_object_entry_from_behavior(std::string objBehavior) const;
private:
std::reference_wrapper<CContext> _cContext;
std::reference_wrapper<BuildStats> _stats;
std::reference_wrapper<BuildInfoCollection> _collection;
std::unordered_map<std::string, std::string> _objBehaviorToEntry;
};
class BuildInfo {
public:
BuildInfo();
//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, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext); // BUILD_TO_BINARY
BuildInfo(std::string buildId, const std::vector<uint8_t> &outData, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext); // 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();
//JsonFile *srcFile; // Deprecated!
std::vector<uint8_t> out;
BuildInfoType get_build_type() const;
@@ -63,14 +44,10 @@ public:
fs::path get_dst_folder() const;
fs::path get_dst_filename() const;
//void set_c_context(CContext &cContext);
//void set_stats(BuildStats &stats);
//void set_collection(BuildInfoCollection &collection);
fs::path get_path_to_directory() const;
CContext &get_c_context() const;
BuildStats &get_stats() const;
BuildTextureCache &get_texture_cache() const;
BuildInfoCollection &get_collection() const;
const JsonFile &get_src_json_file() const;
@@ -78,6 +55,7 @@ public:
std::string get_type() const;
std::string get_build_id() const;
std::string get_section_build_id() const;
size_t get_file_index() const;
bool is_complete() const;
@@ -96,6 +74,7 @@ private:
std::optional<std::reference_wrapper<const BuildInfoContext>> _infoContext;
std::string _buildId;
std::string _buildSectionId;
std::string _type;
size_t _fileIndex;
BuildInfoType _buildType;
@@ -38,7 +38,7 @@ void BuildInfoCollection::add_build_info(std::string sectionBuildId, std::string
"(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, src, fileIndex, dir, infoContext);
_buildInfos.emplace_back(buildId, sectionBuildId, src, fileIndex, dir, infoContext);
// Add the index to the section.
_buildInfoSections[sectionBuildId].emplace_back(index);
@@ -59,7 +59,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, out, fileIndex, dir, infoContext);
_buildInfos.emplace_back(buildId, sectionBuildId, out, fileIndex, dir, infoContext);
// Do not process deferred assets.
_buildInfos.back().done();
@@ -89,7 +89,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, out, fileIndex, dir, infoContext);
_buildInfos.emplace_back(buildId, sectionBuildId, out, fileIndex, dir, infoContext);
// Do not process deferred assets.
_buildInfos.back().done();
@@ -100,7 +100,7 @@ void BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, st
_buildInfoMutex.unlock();
}
void BuildInfoCollection::run_builds(std::function<void(BuildInfo &)> callbackFunction) {
void BuildInfoCollection::run_builds(std::function<void(BuildInfo &)> callbackFunction, const std::vector<std::string> &sectionsOrder) {
size_t threadCount = GlobalSettings::get_max_thread_count();
bool multithreaded = threadCount != 1;
DebugHelper::info_verbose("Using ", threadCount, " thread", (multithreaded ? "s" : ""));
@@ -108,8 +108,12 @@ void BuildInfoCollection::run_builds(std::function<void(BuildInfo &)> callbackFu
if(multithreaded) {
// Multi-threaded (Better for performance)
ThreadPool pool(threadCount);
for(auto &pair : _buildInfoSections) {
for(int buildInfoIndex : pair.second) {
for(const std::string &sectionBuildId : sectionsOrder) {
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
// Skip empty sections.
continue;
}
for(int buildInfoIndex : _buildInfoSections[sectionBuildId]) {
BuildInfo &info = _buildInfos[buildInfoIndex];
pool.enqueue([&info, &callbackFunction] {
if(!info.is_complete()) {
@@ -121,8 +125,12 @@ void BuildInfoCollection::run_builds(std::function<void(BuildInfo &)> callbackFu
}
} else {
// Single-threaded (Better for debugging)
for(auto &pair : _buildInfoSections) {
for(int buildInfoIndex : pair.second) {
for(const std::string &sectionBuildId : sectionsOrder) {
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
// Skip empty sections.
continue;
}
for(int buildInfoIndex : _buildInfoSections[sectionBuildId]) {
BuildInfo &info = _buildInfos[buildInfoIndex];
if(!info.is_complete()) {
callbackFunction(info);
@@ -26,7 +26,7 @@ public:
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 run_builds(std::function<void(BuildInfo &info)> callbackFunction);
void run_builds(std::function<void(BuildInfo &info)> callbackFunction, const std::vector<std::string> &sectionsOrder);
void get_infos_for_section(std::string sectionBuildId, std::function<void(BuildInfo &info)> callbackFunction);
@@ -49,8 +49,6 @@ private:
// Note: Should only ever be added to! Do NOT erase any entries during the building process!
std::vector<BuildInfo> _buildInfos;
std::vector<std::string> _sectionOrder;
// <key: Section build id, value: Array of indices into _buildInfos for files associated with the section>
std::unordered_map<std::string, std::vector<int>> _buildInfoSections;
@@ -0,0 +1,66 @@
#include "buildInfo.h"
#include "misc/globalSettings.h"
#include "helpers/c/cContext.h"
#include "helpers/jsonHelper.h"
using namespace DkrAssetsTool;
BuildInfoContext::BuildInfoContext(CContext &cContext, BuildTextureCache &textureCache, BuildInfoCollection &collection)
: _cContext(cContext), _textureCache(textureCache), _collection(collection) {}
CContext &BuildInfoContext::get_c_context() const {
return _cContext;
}
BuildTextureCache &BuildInfoContext::get_texture_cache() const {
return _textureCache;
}
BuildInfoCollection &BuildInfoContext::get_collection() const {
return _collection;
}
void BuildInfoContext::init_obj_beh_to_entry_map() {
if(!_objBehaviorToEntry.empty()) {
return; // Don't redo this if the map is already filled!
}
// TODO: This was copy-pasted from extract/config.cpp; need to refactor to not require the config file.
fs::path pathToConfig = GlobalSettings::get_decomp_path("tools", "tools/") / "dkr_assets_tool_extract.json";
auto tryGetConfigFile = JsonHelper::get_file(pathToConfig);
DebugHelper::assert_(tryGetConfigFile.has_value(),
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find the config file at: ", pathToConfig);
JsonFile &configJson = tryGetConfigFile.value();
CContext &cContext = get_c_context();
CEnum *objBehaviors = cContext.get_enum("ObjectBehaviours");
DebugHelper::assert_(objBehaviors != nullptr,
"(BuildInfoContext::init_obj_beh_to_entry_map) The enum ObjectBehaviours could not be loaded!");
std::vector<std::string> defaultObjEntriesOrder;
configJson.get_array<std::string>("/misc/default-object-entries-order", defaultObjEntriesOrder);
for(int i = 0; i < 128; i++) {
std::string symbol;
DebugHelper::assert_(objBehaviors->get_symbol_of_value(i, symbol),
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not get a symbol for the value ", i, " in the ObjectBehaviors enum.");
CStruct *entryStruct = cContext.get_struct(defaultObjEntriesOrder[i]);
DebugHelper::assert_(entryStruct != nullptr,
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find struct \"", defaultObjEntriesOrder[i], "\"");
_objBehaviorToEntry[symbol] = defaultObjEntriesOrder[i];
}
}
std::string BuildInfoContext::get_object_entry_from_behavior(std::string objBehavior) const {
DebugHelper::assert_(_objBehaviorToEntry.find(objBehavior) != _objBehaviorToEntry.end(),
"(AssetExtractConfig::get_object_entry_from_behavior) ", objBehavior,
" was not in the _objBehaviorToEntry map!");
return _objBehaviorToEntry.at(objBehavior);
}
@@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
#include <mutex>
#include "helpers/fileHelper.h"
#include "helpers/jsonHelper.h"
#include "helpers/c/cContext.h"
#include "builder/textureCache.h"
namespace DkrAssetsTool {
class BuildInfoCollection;
class BuildInfoContext {
public:
BuildInfoContext(CContext &cContext, BuildTextureCache &textureCache, BuildInfoCollection &collection);
CContext &get_c_context() const;
BuildTextureCache &get_texture_cache() const;
BuildInfoCollection &get_collection() const;
// Used in BuildObjectMap
void init_obj_beh_to_entry_map();
std::string get_object_entry_from_behavior(std::string objBehavior) const;
private:
std::reference_wrapper<CContext> _cContext;
std::reference_wrapper<BuildTextureCache> _textureCache;
std::reference_wrapper<BuildInfoCollection> _collection;
std::unordered_map<std::string, std::string> _objBehaviorToEntry;
};
}
@@ -53,12 +53,16 @@ void BuildModel::generate_object_model(BuildInfo &info) {
DkrTriangle *triangles = reinterpret_cast<DkrTriangle *>(&info.out[objectModelHeader->triangles]);
DkrBatch *batches = reinterpret_cast<DkrBatch *>(&info.out[objectModelHeader->batches]);
bool hasAnimatedTexture = false;
for(size_t materialIndex = 0; materialIndex < numberOfTextures; materialIndex++) {
_materials[materialIndex].write_to(&textures[materialIndex], info);
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, batches, vertices, triangles);
block.write_batches(_materialIds, _materials, batches, vertices, triangles);
}
bool debugKeepUncompressed = GlobalSettings::get_value<bool>("/debug/keep-uncompressed", false);
@@ -46,23 +46,39 @@ size_t BuildModelBatch::number_of_vertices() {
return _vertices.size();
}
void BuildModelBatch::write_batch(const std::map<std::string, int> &materialIds, DkrBatch* outBatch, DkrVertex*& outVertices, DkrTriangle*& outTriangles) {
void BuildModelBatch::write_batch(const std::map<std::string, int> &materialIds, std::vector<BuildModelMaterial> &materials,
DkrBatch* outBatch, DkrVertex*& outVertices, DkrTriangle*& outTriangles) {
uint32_t flags = 0x00000003;
bool materialDoubleSided = false;
if(_materialId.has_value()) {
outBatch->textureIndex = materialIds.at(_materialId.value());
BuildModelMaterial &material = materials[outBatch->textureIndex];
if(material.is_texture_animated()) {
flags |= 0x10000; // BATCH_FLAGS_TEXTURE_ANIM
}
materialDoubleSided = material.is_texture_double_sided();
} else {
outBatch->textureIndex = 0xFF; // No texture
}
//outBatch->lightSource = 0; // 0xFF = vertex colors, 0 = Use generated normals.
outBatch->flags = 0x00000003; // TODO
outBatch->flags = flags;
bool useVertexColors = false;
for(auto &vertex : _vertices) {
vertex.write_to(outVertices);
outVertices++; // Next out vertex.
if(!vertex.is_color_white()) {
useVertexColors = true;
}
}
outBatch->lightSource = useVertexColors ? 0xFF : 0x00;
uint8_t triFlags = materialDoubleSided ? 0x40 : 0x00;
for(auto &tri : _triangles) {
outTriangles->flags = 0x40; // double-sided (debug)
outTriangles->flags = triFlags;
outTriangles->vi0 = tri.a;
outTriangles->vi1 = tri.b;
outTriangles->vi2 = tri.c;
@@ -7,6 +7,7 @@
#include "helpers/mathHelper.h"
#include "buildModelVertex.h"
#include "buildModelMaterial.h"
#include "fileTypes/vertex.hpp"
#include "fileTypes/triangle.hpp"
@@ -35,7 +36,8 @@ public:
size_t number_of_triangles();
size_t number_of_vertices();
void write_batch(const std::map<std::string, int> &materialIds, DkrBatch* outBatch, DkrVertex*& outVertices, DkrTriangle*& outTriangles);
void write_batch(const std::map<std::string, int> &materialIds, std::vector<BuildModelMaterial> &materials,
DkrBatch* outBatch, DkrVertex*& outVertices, DkrTriangle*& outTriangles);
private:
std::vector<BuildModelTriangle> _triangles;
@@ -61,11 +61,12 @@ size_t BuildModelBlock::number_of_batches_to_allocate() {
return _batches.size() + 1; // Must have an extra entry to calculate the sizes for the last batch.
}
void BuildModelBlock::write_batches(const std::map<std::string, int> &materialIds, DkrBatch*& outBatches, DkrVertex*& outVertices, DkrTriangle*& outTriangles) {
void BuildModelBlock::write_batches(const std::map<std::string, int> &materialIds, std::vector<BuildModelMaterial> &materials,
DkrBatch*& outBatches, DkrVertex*& outVertices, DkrTriangle*& outTriangles) {
size_t vertexCount = 0;
size_t triCount = 0;
for(auto &batch : _batches) {
batch.write_batch(materialIds, outBatches, outVertices, outTriangles);
batch.write_batch(materialIds, materials, outBatches, outVertices, outTriangles);
outBatches->verticesOffset = vertexCount;
outBatches->trianglesOffset = triCount;
outBatches->unk1 = (int8_t)batch.number_of_vertices();
@@ -5,6 +5,7 @@
#include <map>
#include "buildModelVertex.h"
#include "buildModelMaterial.h"
#include "buildModelBatch.h"
#include "fileTypes/vertex.hpp"
@@ -25,7 +26,8 @@ public:
size_t number_of_batches();
size_t number_of_batches_to_allocate();
void write_batches(const std::map<std::string, int> &materialIds, DkrBatch*& outBatches, DkrVertex*& outVertices, DkrTriangle*& outTriangles);
void write_batches(const std::map<std::string, int> &materialIds, std::vector<BuildModelMaterial> &materials,
DkrBatch*& outBatches, DkrVertex*& outVertices, DkrTriangle*& outTriangles);
private:
std::vector<BuildModelBatch> _batches;
@@ -36,6 +36,26 @@ void BuildModelMaterial::write_to(DkrTextureInfo *outTexInfo, BuildInfo &info) {
bool BuildModelMaterial::has_texture() const {
return _texturePath.has_value();
}
bool BuildModelMaterial::is_texture_animated() {
if(!_texturePath.has_value()) {
return false;
}
if(!_isTextureAnimated.has_value()) {
_isTextureAnimated = ImageHelper::guess_if_texture_is_animated(_texturePath.value());
}
return _isTextureAnimated.value();
}
bool BuildModelMaterial::is_texture_double_sided() {
if(!_texturePath.has_value()) {
return false;
}
if(!_isTextureDoubleSided.has_value()) {
_isTextureDoubleSided = ImageHelper::guess_if_texture_double_sided(_texturePath.value());
}
return _isTextureDoubleSided.value();
}
void BuildModelMaterial::get_texture_width_and_height(int &outWidth, int &outHeight) {
if(!_texturePath.has_value()) {
@@ -19,11 +19,15 @@ public:
std::optional<fs::path> get_texture_path();
bool has_texture() const;
bool is_texture_animated();
bool is_texture_double_sided();
void get_texture_width_and_height(int &outWidth, int &outHeight);
private:
std::optional<fs::path> _texturePath;
std::optional<bool> _isTextureAnimated = std::nullopt;
std::optional<bool> _isTextureDoubleSided = std::nullopt;
};
}
@@ -34,6 +34,10 @@ Vec4f BuildModelVertex::color() const {
return _color;
}
bool BuildModelVertex::is_color_white() const {
return _color == COLOR_WHITE;
}
void BuildModelVertex::write_to(DkrVertex *outVertex) {
// Write position
outVertex->x = static_cast<int16_t>(_position.x * 10);
@@ -26,6 +26,8 @@ public:
Vec3f position() const;
Vec4f color() const;
bool is_color_white() const;
void write_to(DkrVertex *outVertex);
private:
@@ -1,10 +1,88 @@
#include "objModel.h"
#include <sstream>
#include "helpers/fileHelper.h"
#include "helpers/dataHelper.h"
#include "helpers/debugHelper.h"
#include "libs/TriangulatePolygon.hpp"
using namespace DkrAssetsTool;
typedef struct ObjFaceComponent {
int vertexIndex = -1;
int uvIndex = -1;
int normalIndex = -1; // Not used in DKR, since normals are generated automatically.
} ObjFaceComponent;
typedef struct ObjTriangle {
ObjFaceComponent a;
ObjFaceComponent b;
ObjFaceComponent c;
ObjTriangle(ObjFaceComponent newA, ObjFaceComponent newB, ObjFaceComponent newC)
: a(newA), b(newB), c(newC) {}
} ObjTriangle;
ObjFaceComponent parse_face_component(std::string component) {
std::vector<std::string> faceComponents;
StringHelper::split(component, '/', faceComponents);
ObjFaceComponent out;
out.vertexIndex = std::stoi(faceComponents[0]) - 1;
if((faceComponents.size() > 1) && (!faceComponents[1].empty())) {
out.uvIndex = std::stoi(faceComponents[1]) - 1;
}
if((faceComponents.size() > 2) && (!faceComponents[2].empty())) {
out.normalIndex = std::stoi(faceComponents[2]) - 1;
}
return out;
}
std::vector<ObjTriangle> triangulate_polygon(const std::vector<std::string> &components, const std::vector<BuildModelVertex> &vertices) {
size_t numberOfOutTriangles = components.size() - 3;
std::vector<ObjTriangle> out;
if(numberOfOutTriangles == 1) {
// No need to triangulate.
out.push_back({
parse_face_component(components[1]),
parse_face_component(components[2]),
parse_face_component(components[3])
});
} else {
std::vector<ObjFaceComponent> faceComponents(components.size() - 1);
std::vector<triangulate::Point> polygonPoints;
for(size_t i = 1; i < components.size(); i++) {
faceComponents[i - 1] = parse_face_component(components[i]);
const BuildModelVertex &vertex = vertices.at(faceComponents[i - 1].vertexIndex);
Vec3f vertexPos = vertex.position();
polygonPoints.emplace_back(vertexPos.x, vertexPos.y, vertexPos.z);
}
const auto triangles = triangulate::triangulate(polygonPoints);
for(auto &tri : triangles) {
int index0 = DataHelper::vector_index_of(polygonPoints, tri.p0);
int index1 = DataHelper::vector_index_of(polygonPoints, tri.p1);
int index2 = DataHelper::vector_index_of(polygonPoints, tri.p2);
DebugHelper::assert_(index0 >= 0, "(triangulate_polygon) Invalid point 0");
DebugHelper::assert_(index1 >= 0, "(triangulate_polygon) Invalid point 1");
DebugHelper::assert_(index2 >= 0, "(triangulate_polygon) Invalid point 2");
out.emplace_back(faceComponents[index0], faceComponents[index1], faceComponents[index2]);
}
}
return out;
}
ObjBuildModel::ObjBuildModel(fs::path filepath) {
std::string objText = FileHelper::read_text_file(filepath);
@@ -16,8 +94,6 @@ ObjBuildModel::ObjBuildModel(fs::path filepath) {
std::string currentMaterial = "";
size_t faceIndex = 0;
for(std::string &line : lines) {
if(line[0] == '#' || line.empty()) {
// Skip this line if it is empty or a comment.
@@ -30,15 +106,18 @@ ObjBuildModel::ObjBuildModel(fs::path filepath) {
double x = std::stod(components[1]);
double y = std::stod(components[2]);
double z = std::stod(components[3]);
// Scale the model according that is specified in settings.
//settings.adjust_from_model_scale(x);
//settings.adjust_from_model_scale(y);
// settings.adjust_from_model_scale(z);
// Add the new vertex
vertices.emplace_back(Vec3f(x, y, z));
continue;
if(components.size() < 6) {
// Add the new vertex
vertices.emplace_back(Vec3f(x, y, z));
continue;
}
double r = std::stod(components[4]);
double g = std::stod(components[5]);
double b = std::stod(components[6]);
vertices.emplace_back(Vec3f(x, y, z), Vec4f(r, g, b, 1.0f));
continue;
}
if(components[0] == "vn") {
// User-generated normals are not used in DKR.
@@ -52,47 +131,24 @@ ObjBuildModel::ObjBuildModel(fs::path filepath) {
continue;
}
if(components[0] == "f") {
std::vector<std::string> faceComponentsA;
std::vector<std::string> faceComponentsB;
std::vector<std::string> faceComponentsC;
std::vector<ObjTriangle> tris = triangulate_polygon(components, vertices);
// split the different indicies for a face.
StringHelper::split(components[1], '/', faceComponentsA);
StringHelper::split(components[2], '/', faceComponentsB);
StringHelper::split(components[3], '/', faceComponentsC);
// parse index for vertex.
int positionAIndex = std::stoi(faceComponentsA[0]) - 1;
int positionBIndex = std::stoi(faceComponentsB[0]) - 1;
int positionCIndex = std::stoi(faceComponentsC[0]) - 1;
// Make sure the indicies are in the correct range.
DebugHelper::assert_((positionAIndex >= 0) && (positionAIndex < vertices.size()),
"(ObjBuildModel::ObjBuildModel) Face ", faceIndex, " first index is out of range!");
DebugHelper::assert_((positionBIndex >= 0) && (positionBIndex < vertices.size()),
"(ObjBuildModel::ObjBuildModel) Face ", faceIndex, " second index is out of range!");
DebugHelper::assert_((positionCIndex >= 0) && (positionCIndex < vertices.size()),
"(ObjBuildModel::ObjBuildModel) Face ", faceIndex, " third index is out of range!");
if(faceComponentsA.size() == 1 || faceComponentsA[1].empty()) {
DebugHelper::info("No UVs!");
// Add the new triangle with no UV coordinates.
_add_new_triangle_to_current_block(vertices[positionAIndex], vertices[positionBIndex], vertices[positionCIndex]);
} else {
int uvAIndex = std::stoi(faceComponentsA[1]) - 1;
int uvBIndex = std::stoi(faceComponentsB[1]) - 1;
int uvCIndex = std::stoi(faceComponentsC[1]) - 1;
// Adjust the uv coordinates to the N64 format.
Vec2f uv0 = _get_adjusted_uv(uvs[uvAIndex], currentMaterial);
Vec2f uv1 = _get_adjusted_uv(uvs[uvBIndex], currentMaterial);
Vec2f uv2 = _get_adjusted_uv(uvs[uvCIndex], currentMaterial);
// Add the new triangle with UV coordinates.
_add_new_triangle_to_current_block(vertices[positionAIndex], vertices[positionBIndex], vertices[positionCIndex], uv0, uv1, uv2);
for(ObjTriangle &tri : tris) {
BuildModelVertex &vertexA = vertices[tri.a.vertexIndex];
BuildModelVertex &vertexB = vertices[tri.b.vertexIndex];
BuildModelVertex &vertexC = vertices[tri.c.vertexIndex];
if(currentMaterial.empty() || (tri.a.uvIndex == -1)) {
// Add triangle without UV coordinates.
_add_new_triangle_to_current_block(vertexA, vertexB, vertexC);
} else {
Vec2f uv0 = _get_adjusted_uv(uvs[tri.a.uvIndex], currentMaterial);
Vec2f uv1 = _get_adjusted_uv(uvs[tri.b.uvIndex], currentMaterial);
Vec2f uv2 = _get_adjusted_uv(uvs[tri.c.uvIndex], currentMaterial);
// Add the new triangle with UV coordinates.
_add_new_triangle_to_current_block(vertexA, vertexB, vertexC, uv0, uv1, uv2);
}
}
faceIndex++;
continue;
}
if(components[0] == "usemtl") {
@@ -113,8 +169,6 @@ ObjBuildModel::ObjBuildModel(fs::path filepath) {
continue;
}
}
DebugHelper::info("Done!");
}
Vec2f ObjBuildModel::_get_adjusted_uv(Vec2f uv, std::string materialId) {
@@ -2,6 +2,7 @@
#include <cmath> // for std::round
#include "helpers/assetsHelper.h"
#include "helpers/dataHelper.h"
#include "helpers/imageHelper.h"
#include "helpers/gzipHelper.h"
@@ -32,7 +33,9 @@ size_t calculate_out_size(BuildInfo &info, const JsonFile &jsonFile) {
}
void BuildTexture::build(BuildInfo &info) {
BuildTextureCache &textureCache = info.get_texture_cache();
const JsonFile &jsonFile = info.get_src_json_file();
bool is3dTexture = info.get_section_build_id() == "ASSET_TEXTURES_3D";
info.out.resize(calculate_out_size(info, jsonFile));
@@ -124,6 +127,12 @@ void BuildTexture::build(BuildInfo &info) {
}
}
if(is3dTexture) {
// Calculate sha1 of texture data, then add it to the texture cache. Need this so that we can reuse existing textures.
std::string textureSha1 = DataHelper::make_sha1_hash_of_bytes(BytesView(info.out));
textureCache.add_texture_to_sha1_cache(textureSha1, info.get_build_id());
}
bool debugKeepUncompressed = GlobalSettings::get_value<bool>("/debug/keep-uncompressed", false);
if(debugKeepUncompressed) {
@@ -154,44 +163,105 @@ void BuildTexture::build(BuildInfo &info) {
}
size_t BuildTexture::build_deferred(BuildInfo &baseInfo, fs::path pathToImage) {
std::string format = "RGBA16";
std::string renderMode = "OPAQUE";
std::string format, renderMode;
ImageHelper::guess_texture_format_and_render_mode(pathToImage, format, renderMode);
N64Image img(pathToImage, format);
std::vector<fs::path> texturePaths = ImageHelper::get_multiple_textures_from_one(pathToImage);
int imgWidth = img.get_width();
int imgHeight = img.get_height();
DebugHelper::assert_(texturePaths.size() > 0,
"(BuildTexture::build_deferred) ImageHelper::get_multiple_textures_from_one() failed for ", pathToImage.filename());
std::vector<uint8_t> out(sizeof(TextureHeader) + ImageHelper::image_size(imgWidth, imgHeight, format));
size_t totalSize = 0;
TextureHeader *header = reinterpret_cast<TextureHeader *>(&out[0]);
uint8_t *textureData = &out[sizeof(TextureHeader)];
for(auto &path : texturePaths) {
int width, height;
ImageHelper::get_width_and_height(path, width, height);
totalSize += DataHelper::align16(sizeof(TextureHeader) + ImageHelper::image_size(width, height, format));
}
uint8_t formatVal = DataHelper::vector_index_of<std::string>(TEXTURE_FORMAT_INT_TO_STRING, format);
uint8_t renderModeVal = DataHelper::vector_index_of<std::string>(TEXTURE_RENDER_MODES, renderMode);
DebugHelper::assert_(totalSize > 0,
"(BuildTexture::build_deferred) Could not calculate texture size for ", pathToImage.filename());
header->width = imgWidth;
header->height = imgHeight;
header->format = (renderModeVal << 4) | formatVal;
header->numberOfInstances = 1;
header->numOfTextures = 1;
header->textureSize = DataHelper::align16(out.size());
int frameAdvanceDelay = 0;
be_int16_t flags = (
((int)1 << 10) |
((int)1 << 6) |
((int)1 << 7)
);
if(texturePaths.size() > 1) {
float animTexTiming = ImageHelper::guess_animated_texture_timing(pathToImage);
frameAdvanceDelay = std::round(animTexTiming * TEXTURE_FPS);
}
header->flags = flags;
std::string wrapS, wrapT;
ImageHelper::guess_texture_wrap_mode(pathToImage, wrapS, wrapT);
bool wrapSClamped = wrapS == "clamp";
bool wrapTClamped = wrapT == "clamp";
img.flip_vertically();
img.interlace();
img.copy_to(textureData);
std::vector<uint8_t> out(totalSize);
size_t offset = 0;
for(auto &path : texturePaths) {
N64Image img(path, format);
int imgWidth = img.get_width();
int imgHeight = img.get_height();
TextureHeader *header = reinterpret_cast<TextureHeader *>(&out[offset]);
offset += sizeof(TextureHeader);
uint8_t *textureData = &out[offset];
uint8_t formatVal = DataHelper::vector_index_of<std::string>(TEXTURE_FORMAT_INT_TO_STRING, format);
uint8_t renderModeVal = DataHelper::vector_index_of<std::string>(TEXTURE_RENDER_MODES, renderMode);
header->width = imgWidth;
header->height = imgHeight;
header->format = (renderModeVal << 4) | formatVal;
header->numberOfInstances = 1;
header->numOfTextures = texturePaths.size();
header->textureSize = DataHelper::align16(img.size() + sizeof(TextureHeader));
if(header->numOfTextures > 1) {
header->frameAdvanceDelay = frameAdvanceDelay;
}
int16_t flags = 0;
if(wrapSClamped) {
flags |= (1 << 6);
}
if(wrapTClamped) {
flags |= (1 << 7);
}
img.flip_vertically();
if(!DataHelper::is_power_of_two(imgWidth)) {
flags |= (1 << 10);
img.interlace();
}
header->flags = flags;
img.copy_to(textureData);
offset += img.size();
}
// Need to check the texture cache to see if the texture already exists or not.
// Avoiding duplicate textures saves a lot of space in the ROM file.
BuildTextureCache &textureCache = baseInfo.get_texture_cache();
std::string textureSha1 = DataHelper::make_sha1_hash_of_bytes(BytesView(out));
std::optional<std::string> foundTexBuildId = textureCache.get_build_id_of_texture_hash(textureSha1);
if(foundTexBuildId.has_value()) {
DebugHelper::info_verbose("Found existing texture for ", pathToImage.filename(), ". Will use ID \"", foundTexBuildId.value(), "\".");
return AssetsHelper::get_asset_index("ASSET_TEXTURES_3D", foundTexBuildId.value());
}
// Add the new texture to the cache.
std::string buildId = baseInfo.get_build_id() + "_" + pathToImage.stem().generic_string();
DebugHelper::info_verbose("Created new texture: ", pathToImage.filename(), ", ID: ", buildId);
textureCache.add_texture_to_sha1_cache(textureSha1, buildId);
BuildInfoCollection &collection = baseInfo.get_collection();
std::string buildId = baseInfo.get_build_id() + "_" + pathToImage.stem().generic_string();
StringHelper::make_uppercase(buildId);
bool debugKeepUncompressed = GlobalSettings::get_value<bool>("/debug/keep-uncompressed", false);
@@ -25,8 +25,9 @@
#include "builder/buildInfo.h"
#include "builder/buildInfoCollection.h"
#include "builder/buildInfoContext.h"
#include "builder/buildAssetTable.h"
#include "builder/stats.h"
#include "builder/textureCache.h"
#include "buildTypes/buildAudio.h"
#include "buildTypes/buildBinary.h"
@@ -204,11 +205,11 @@ void AssetBuilder::build_all(const fs::path &dstPath) {
size_t numberOfAssetSections = sectionIds.size();
CContext cContext;
BuildStats stats;
BuildTextureCache textureCache;
BuildInfoCollection collection;
// BuildInfoContext holds info that is shared between all assets.
BuildInfoContext infoContext(cContext, stats, collection);
BuildInfoContext infoContext(cContext, textureCache, collection);
fs::path includeFolder = GlobalSettings::get_decomp_path("include_subpath", "include/");
CEnumsHelper::load_enums_from_file(cContext, includeFolder / "enums.h");
@@ -254,7 +255,7 @@ void AssetBuilder::build_all(const fs::path &dstPath) {
collection.run_builds([](BuildInfo &info) {
std::string type = info.get_type();
builderMap[type](info);
});
}, sectionIds);
// Second loop to create the tables for the asset sections.
for(size_t sectionIndex = 0; sectionIndex < numberOfAssetSections; sectionIndex++) {
@@ -1 +0,0 @@
#include "stats.h"

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