From aae112d37e6b1439a02654c7fd851cbc0a7a2c2c Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 7 Jul 2025 15:53:45 +0800 Subject: [PATCH 001/123] Added the ability to dump and load technique sets. note: techniques should be dumped in seperate files from technique sets, but compiling them all into the techset works as well but is harder to read. --- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 3 +- .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 183 ++++++++++++++++++ .../T6/TechniqueSet/LoaderTechniqueSetT6.h | 13 ++ .../AssetDumpers/AssetDumperTechniqueSet.cpp | 116 +++++++++-- 4 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp create mode 100644 src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 37afdf895..7dff3c69d 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -40,6 +40,7 @@ #include "Weapon/RawLoaderWeaponT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" +#include "TechniqueSet/LoaderTechniqueSetT6.h" #include #include @@ -400,7 +401,7 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(CreateXModelLoader(memory, searchPath, zone)); collection.AddAssetCreator(CreateMaterialLoader(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(CreateTechniqueSetLoader(memory, searchPath)); collection.AddAssetCreator(CreateImageLoader(memory, searchPath)); collection.AddAssetCreator(CreateSoundBankLoader(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp new file mode 100644 index 000000000..f9e793f5a --- /dev/null +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -0,0 +1,183 @@ +#include "Game/T6/T6.h" +#include "LoaderTechniqueSetT6.h" + +#include +#include +#include + +using namespace T6; + +namespace +{ + class TechniqueSetLoader final : public AssetCreator + { + public: + TechniqueSetLoader(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto fileName = std::format("techniquesets/{}.json", assetName); + const auto file = m_search_path.Open(assetName); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + MaterialTechniqueSet* techniqueSet = new MaterialTechniqueSet; + memset(techniqueSet, 0, sizeof(MaterialTechniqueSet)); + + const auto js = nlohmann::json::parse(*file.m_stream.get()); + + std::string techSetName = js["name"]; + techniqueSet->name = _strdup(techSetName.c_str()); + techniqueSet->worldVertFormat = (unsigned char)js["worldVertFormat"]; + + for (int i = 0; i < 36; i++) + { + nlohmann::json techniqueJs = js["techniques"][i]; + + if (techniqueJs.size() == 0) + { + techniqueSet->techniques[i] = NULL; + } + else + { + MaterialTechnique* technique = new MaterialTechnique; + techniqueSet->techniques[i] = technique; + + std::string techName = techniqueJs["name"]; + technique->name = _strdup(techName.c_str()); + technique->flags = techniqueJs["flags"]; + technique->passCount = techniqueJs["passCount"]; + _ASSERT(technique->passCount == 1); + + for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + { + MaterialPass* currPass = &technique->passArray[passIndex]; + nlohmann::json passJs = techniqueJs["passArray"][passIndex]; + + currPass->perPrimArgCount = (unsigned char)passJs["perPrimArgCount"]; + currPass->perObjArgCount = (unsigned char)passJs["perObjArgCount"]; + currPass->stableArgCount = (unsigned char)passJs["stableArgCount"]; + currPass->customSamplerFlags = (unsigned char)passJs["customSamplerFlags"]; + currPass->precompiledIndex = (unsigned char)passJs["precompiledIndex"]; + currPass->materialType = (unsigned char)passJs["materialType"]; + + int argCount = currPass->perPrimArgCount + currPass->perObjArgCount + currPass->stableArgCount; + if (argCount == 0) + { + currPass->args = NULL; + } + else + { + currPass->args = new MaterialShaderArgument[argCount]; + for (int i = 0; i < argCount; i++) + { + MaterialShaderArgument* currArg = &currPass->args[i]; + auto currArgJs = passJs["args"][i]; + + currArg->type = currArgJs["type"]; + currArg->location.offset = currArgJs["location"]; + currArg->size = currArgJs["size"]; + currArg->buffer = currArgJs["buffer"]; + if (currArg->type == MTL_ARG_LITERAL_VERTEX_CONST || currArg->type == MTL_ARG_LITERAL_PIXEL_CONST) + { + float* consts = new float[4]; + consts[0] = currArgJs["u"]["const0"]; + consts[1] = currArgJs["u"]["const1"]; + consts[2] = currArgJs["u"]["const2"]; + consts[3] = currArgJs["u"]["const3"]; + currArg->u.literalConst = (float(*)[4])consts; + } + else + { + currArg->u.nameHash = currArgJs["u"]["value"]; + } + } + } + + if (passJs["vertexDecl"].size() == 0) + { + currPass->vertexDecl = NULL; + } + else + { + currPass->vertexDecl = new MaterialVertexDeclaration; + currPass->vertexDecl->streamCount = (unsigned char)passJs["vertexDecl"]["streamCount"]; + currPass->vertexDecl->hasOptionalSource = passJs["vertexDecl"]["hasOptionalSource"]; + currPass->vertexDecl->isLoaded = passJs["vertexDecl"]["isLoaded"]; + for (int i = 0; i < 16; i++) + { + currPass->vertexDecl->routing.data[i].source = (unsigned char)passJs["vertexDecl"]["routing"][i]["source"]; + currPass->vertexDecl->routing.data[i].dest = (unsigned char)passJs["vertexDecl"]["routing"][i]["dest"]; + } + } + + if (passJs["pixelShader"].size() == 0) + { + currPass->pixelShader = NULL; + } + else + { + currPass->pixelShader = new MaterialPixelShader; + + std::string pixelName = passJs["pixelShader"]["name"]; + currPass->pixelShader->name = _strdup(pixelName.c_str()); + currPass->pixelShader->prog.ps = NULL; + + const auto psFileName = std::format("techniques/shader_bin/ps_{}", pixelName); + const auto psFile = m_search_path.Open(psFileName); + if (!psFile.IsOpen()) + { + printf("ERROR: Cant find pixel shader %s\n", psFileName.c_str()); + return AssetCreationResult::Failure(); + } + currPass->pixelShader->prog.loadDef.programSize = psFile.m_length; + currPass->pixelShader->prog.loadDef.program = new char[psFile.m_length]; + psFile.m_stream->read(currPass->pixelShader->prog.loadDef.program, psFile.m_length); + } + + if (passJs["vertexShader"].size() == 0) + { + currPass->vertexShader = NULL; + } + else + { + currPass->vertexShader = new MaterialVertexShader; + + std::string vertexName = passJs["vertexShader"]["name"]; + currPass->vertexShader->name = _strdup(vertexName.c_str()); + currPass->vertexShader->prog.vs = NULL; + + const auto vsFileName = std::format("techniques/shader_bin/vs_{}", vertexName); + const auto vsFile = m_search_path.Open(vsFileName); + if (!vsFile.IsOpen()) + { + printf("ERROR: Cant find vertex shader %s\n", vsFileName.c_str()); + return AssetCreationResult::Failure(); + } + currPass->vertexShader->prog.loadDef.programSize = vsFile.m_length; + currPass->vertexShader->prog.loadDef.program = new char[vsFile.m_length]; + vsFile.m_stream->read(currPass->vertexShader->prog.loadDef.program, vsFile.m_length); + } + } + } + } + return AssetCreationResult::Success(context.AddAsset(assetName, techniqueSet)); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace T6 +{ + std::unique_ptr> CreateTechniqueSetLoader(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h new file mode 100644 index 000000000..78081a86e --- /dev/null +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace T6 +{ + std::unique_ptr> CreateTechniqueSetLoader(MemoryManager& memory, ISearchPath& searchPath); +} // namespace T6 diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperTechniqueSet.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperTechniqueSet.cpp index e5b0b1428..9ff914a8b 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperTechniqueSet.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperTechniqueSet.cpp @@ -1,5 +1,6 @@ #include "AssetDumperTechniqueSet.h" +#include #include #include @@ -58,7 +59,7 @@ bool AssetDumperTechniqueSet::ShouldDump(XAssetInfo* asset void AssetDumperTechniqueSet::DumpPixelShader(const AssetDumpingContext& context, const MaterialPixelShader* pixelShader) { std::ostringstream ss; - ss << "shader_bin/ps_" << pixelShader->name << ".cso"; + ss << "techniquesets/shader_bin/ps_" << pixelShader->name; const auto shaderFile = context.OpenAssetFile(ss.str()); @@ -71,7 +72,7 @@ void AssetDumperTechniqueSet::DumpPixelShader(const AssetDumpingContext& context void AssetDumperTechniqueSet::DumpVertexShader(const AssetDumpingContext& context, const MaterialVertexShader* vertexShader) { std::ostringstream ss; - ss << "shader_bin/vs_" << vertexShader->name << ".cso"; + ss << "techniquesets/shader_bin/vs_" << vertexShader->name; const auto shaderFile = context.OpenAssetFile(ss.str()); @@ -86,21 +87,110 @@ void AssetDumperTechniqueSet::DumpAsset(AssetDumpingContext& context, XAssetInfo const auto* techniqueSet = asset->Asset(); auto* shaderState = context.GetZoneAssetDumperState(); + const auto assetFile = context.OpenAssetFile(std::format("techniquesets/{}.json", techniqueSet->name)); + if (!assetFile) + return; + + nlohmann::json js; + + js["name"] = techniqueSet->name; + js["worldVertFormat"] = techniqueSet->worldVertFormat; + + js["techniques"] = nlohmann::json::array(); for (const auto* technique : techniqueSet->techniques) { - if (!technique || !shaderState->ShouldDumpTechnique(technique)) - continue; + nlohmann::json techniqueJs = nlohmann::json::object(); - for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + if (technique != NULL) { - const auto* pixelShader = technique->passArray[passIndex].pixelShader; - - if (pixelShader && shaderState->ShouldDumpPixelShader(pixelShader)) - DumpPixelShader(context, pixelShader); - - const auto* vertexShader = technique->passArray[passIndex].vertexShader; - if (vertexShader && shaderState->ShouldDumpVertexShader(vertexShader)) - DumpVertexShader(context, vertexShader); + techniqueJs["name"] = technique->name; + techniqueJs["flags"] = technique->flags; + techniqueJs["passCount"] = technique->passCount; + + _ASSERT(technique->passCount == 1); + + techniqueJs["passArray"] = nlohmann::json::array(); + for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + { + const MaterialPass* currPass = &technique->passArray[passIndex]; + nlohmann::json passJs = nlohmann::json::object(); + + passJs["perPrimArgCount"] = currPass->perPrimArgCount; + passJs["perObjArgCount"] = currPass->perObjArgCount; + passJs["stableArgCount"] = currPass->stableArgCount; + passJs["customSamplerFlags"] = currPass->customSamplerFlags; + passJs["precompiledIndex"] = currPass->precompiledIndex; + passJs["materialType"] = currPass->materialType; + + nlohmann::json vertDeclJs = nlohmann::json::object(); + if (currPass->vertexDecl != NULL) + { + vertDeclJs["streamCount"] = currPass->vertexDecl->streamCount; + vertDeclJs["hasOptionalSource"] = currPass->vertexDecl->hasOptionalSource; + vertDeclJs["isLoaded"] = currPass->vertexDecl->isLoaded; + for (int i = 0; i < 16; i++) + { + vertDeclJs["routing"][i]["source"] = currPass->vertexDecl->routing.data[i].source; + vertDeclJs["routing"][i]["dest"] = currPass->vertexDecl->routing.data[i].dest; + + _ASSERT(currPass->vertexDecl->routing.decl[i] == NULL); + } + } + passJs["vertexDecl"] = vertDeclJs; + + passJs["args"] = nlohmann::json::array(); + if (currPass->args != NULL) + { + for (int i = 0; i < currPass->perPrimArgCount + currPass->perObjArgCount + currPass->stableArgCount; i++) + { + nlohmann::json argsJs = nlohmann::json::object(); + MaterialShaderArgument* currArg = &currPass->args[i]; + + argsJs["type"] = currArg->type; + argsJs["location"] = currArg->location.offset; + argsJs["size"] = currArg->size; + argsJs["buffer"] = currArg->buffer; + if (currArg->type == MTL_ARG_LITERAL_VERTEX_CONST || currArg->type == MTL_ARG_LITERAL_PIXEL_CONST) + { + argsJs["u"]["const0"] = currArg->u.literalConst[0]; + argsJs["u"]["const1"] = currArg->u.literalConst[1]; + argsJs["u"]["const2"] = currArg->u.literalConst[2]; + argsJs["u"]["const3"] = currArg->u.literalConst[3]; + } + else + { + argsJs["u"]["value"] = currArg->u.nameHash; + } + + passJs["args"].push_back(argsJs); + } + } + + nlohmann::json pixelJs = nlohmann::json::object(); + if (currPass->pixelShader != NULL) + { + pixelJs["name"] = currPass->pixelShader->name; + if (shaderState->ShouldDumpPixelShader(currPass->pixelShader)) + DumpPixelShader(context, currPass->pixelShader); + } + passJs["pixelShader"] = pixelJs; + + nlohmann::json vertexJs = nlohmann::json::object(); + if (currPass->vertexShader != NULL) + { + vertexJs["name"] = currPass->vertexShader->name; + if (shaderState->ShouldDumpVertexShader(currPass->vertexShader)) + DumpVertexShader(context, currPass->vertexShader); + } + passJs["vertexShader"] = vertexJs; + + techniqueJs["passArray"].push_back(passJs); + } } + + js["techniques"].push_back(techniqueJs); } + + std::string jsonString = js.dump(4); + assetFile->write(jsonString.c_str(), jsonString.size()); } From c045d5473914e5049dbc77913fa2136501daa8bc Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 7 Jul 2025 23:04:08 +0800 Subject: [PATCH 002/123] Initial commit of BSP compilation --- src/Common/Game/T6/T6.h | 46 + src/Common/Game/T6/T6_Assets.h | 22 +- .../BinarySpacePartitionTreePreCalc.h | 232 +++ .../Game/T6/CustomMap/CustomMapConsts.h | 20 + .../Game/T6/CustomMap/CustomMapLinker.cpp | 1646 +++++++++++++++++ .../Game/T6/CustomMap/CustomMapLinker.h | 14 + .../Game/T6/CustomMap/LoaderCustomMapT6.cpp | 57 + .../Game/T6/CustomMap/LoaderCustomMapT6.h | 13 + src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h | 1160 ++++++++++++ .../Game/T6/CustomMap/ProjectCreator.cpp | 105 ++ .../Game/T6/CustomMap/ProjectCreator.h | 13 + src/ObjLoading/Game/T6/CustomMap/Util.h | 73 + src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 5 + .../Game/T6/AssetNameResolverT6.cpp | 2 + 14 files changed, 3406 insertions(+), 2 deletions(-) create mode 100644 src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp create mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp create mode 100644 src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp create mode 100644 src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/Util.h diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index 72cd851be..252c39e9f 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -159,6 +159,51 @@ namespace T6 AUFT_NUM_FIELD_TYPES, }; + + struct customMapVertex + { + vec3_t pos; + float binormalSign; + float color[4]; + float texCoord[2]; + vec3_t normal; + vec3_t tangent; + unsigned int packedLmapCoord; + }; + + struct worldSurface + { + char flags; + char lightmapIndex; + std::string materialName; + char primaryLightIndex; + char reflectionProbeIndex; + + int triCount; + int firstVertexIndex; + int firstIndex_Index; + }; + + struct customMapGfx + { + int vertexCount; + customMapVertex* vertices; + + int indexCount; + uint16_t* indices; + + int surfaceCount; + worldSurface* surfaces; + }; + + struct customMapInfo + { + std::string name; + std::string bspName; + + customMapGfx gfxInfo; + }; + using AssetPhysPreset = Asset; using AssetPhysConstraints = Asset; using AssetDestructibleDef = Asset; @@ -208,6 +253,7 @@ namespace T6 using AssetFootstepTable = Asset; using AssetFootstepFxTable = Asset; using AssetZBarrier = Asset; + using AssetCustomMap = Asset; } // namespace T6 DEFINE_ASSET_NAME_ACCESSOR(T6::AssetPhysPreset, name); diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 62f3f300e..7e6953011 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -304,6 +304,8 @@ namespace T6 ASSET_TYPE_REPORT = 0x3E, ASSET_TYPE_DEPEND = 0x3F, ASSET_TYPE_FULL_COUNT = 0x40, + + ASSET_TYPE_CUSTOM_MAP = 0x41 }; enum XFileBlock @@ -1244,13 +1246,13 @@ namespace T6 struct GfxWorldVertexData0 { - byte128* data; + byte128* data; // GfxPackedWorldVertex void /*ID3D11Buffer*/* vb; }; struct GfxWorldVertexData1 { - byte128* data; + byte128* data; // GfxPackedWorldVertex void /*ID3D11Buffer*/* vb; }; @@ -5718,6 +5720,11 @@ namespace T6 unsigned int packed; }; + union PackedLmapCoords + { + unsigned int packed; + }; + struct type_align(16) GfxPackedVertex { vec3_t xyz; @@ -5728,6 +5735,17 @@ namespace T6 PackedUnitVec tangent; }; + struct GfxPackedWorldVertex + { + vec3_t xyz; + float binormalSign; + GfxColor color; + PackedTexCoords texCoord; + PackedUnitVec normal; + PackedUnitVec tangent; + PackedLmapCoords lmapCoord; + }; + struct XRigidVertList { uint16_t boneOffset; diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h new file mode 100644 index 000000000..97c260225 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h @@ -0,0 +1,232 @@ +#pragma once + +/* +Heavily modified version of https://github.com/sudeshnapal12/Space-Partitioning-Algorithms BSP implementation +Credit to sudeshnapal12 + + +Precalculated, evenly sized BSPs are much more efficient and smaller compared to dynamically creating them +*/ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_AABB_SIZE 512 // maximum size an AABB tree can be before it becomes a node + +enum PlaneAxis +{ + AXIS_X, + AXIS_Y, + AXIS_Z +}; + +class Object +{ +public: + double low[3]; + double high[3]; + + // custom data + int partitionIndex; // index of the partition the object is based on (custom) + + Object(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int partition_Index) + { + low[0] = min_x; + low[1] = min_y, low[2] = min_z; + high[0] = max_x; + high[1] = max_y; + high[2] = max_z; + partitionIndex = partition_Index; + } +}; + +union u_BSPNode; +class BSPTree; + +class BSPLeaf +{ +private: + std::vector objectList; + +public: + BSPLeaf() + { + objectList = std::vector(); + } + + ~BSPLeaf() + { + objectList.clear(); + } + + void addToList(Object* object) + { + objectList.push_back(object); + } + + int getObjectCount() + { + return objectList.size(); + } + + Object* getObject(int index) + { + return objectList.at(index); + } +}; + +enum objectPlaneSide +{ + SIDE_FRONT, + SIDE_BACK, + SIDE_INTERSECTS +}; + +class BSPNode +{ +public: + BSPTree* front; + BSPTree* back; + + PlaneAxis axis; // axis that the split plane is on + double distance; // distance from the origin (0, 0, 0) to the plane + + BSPNode(BSPTree* _front, BSPTree* _back, PlaneAxis _axis, double _distance) + { + front = _front; + back = _back; + axis = _axis; + distance = _distance; + } + + objectPlaneSide objectIsInfront(Object* object) + { + double minCoord, maxCoord; + + // Select the relevant coordinate based on the plane's axis + switch (axis) + { + case AXIS_X: + minCoord = object->low[0]; + maxCoord = object->high[0]; + break; + case AXIS_Y: + minCoord = object->low[1]; + maxCoord = object->high[1]; + break; + case AXIS_Z: + minCoord = object->low[2]; + maxCoord = object->high[2]; + break; + default: + _ASSERT(false); // this should never be executed + } + + // Compare with the plane's distance + if (maxCoord < distance) + { + return SIDE_BACK; // Object is entirely on the negative side + } + else if (minCoord > distance) + { + return SIDE_FRONT; // Object is entirely on the positive side + } + else + { + return SIDE_INTERSECTS; + } + } +}; + +union u_BSPNode +{ + BSPLeaf* leaf; + BSPNode* node; +}; + +class BSPTree +{ +public: + bool isLeaf; + u_BSPNode u; + + int level; // level in the BSP tree + double low[3]; // mins + double high[3]; // maxs + + BSPTree(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int _level) + { + low[0] = min_x; + low[1] = min_y, low[2] = min_z; + high[0] = max_x; + high[1] = max_y; + high[2] = max_z; + level = _level; + splitTree(); + } + + // For simplicity, only split across the X and Z axis. + // It is unlikely that there are many layers to a map, and is instead mostly flat + void splitTree() + { + BSPTree* front; + BSPTree* back; + double halfLength; + + if (high[0] - low[0] > MAX_AABB_SIZE) + { + // split along the x axis + halfLength = (low[0] + high[0]) * 0.5f; + front = new BSPTree(halfLength, low[1], low[2], high[0], high[1], high[2], level + 1); + back = new BSPTree(low[0], low[1], low[2], halfLength, high[1], high[2], level + 1); + + isLeaf = false; + u.node = new BSPNode(front, back, AXIS_X, halfLength); + } + else if (high[2] - low[2] > MAX_AABB_SIZE) + { + // split along the z axis + halfLength = (low[2] + high[2]) * 0.5f; + front = new BSPTree(low[0], low[1], halfLength, high[0], high[1], high[2], level + 1); + back = new BSPTree(low[0], low[1], low[2], high[0], high[1], halfLength, level + 1); + + isLeaf = false; + u.node = new BSPNode(front, back, AXIS_Z, halfLength); + } + else + { + isLeaf = true; + u.leaf = new BSPLeaf(); + } + } + + void addObject(Object* object) + { + if (isLeaf) + { + u.leaf->addToList(object); + } + else + { + objectPlaneSide side = u.node->objectIsInfront(object); + if (side == SIDE_FRONT) + { + u.node->front->addObject(object); + } + else if (side == SIDE_BACK) + { + u.node->back->addObject(object); + } + else // intersects + { + u.node->front->addObject(object); + u.node->back->addObject(object); + } + } + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h new file mode 100644 index 000000000..a8b1d3e02 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -0,0 +1,20 @@ +#include + +#define DYN_ENT_COUNT 0 + +// the clipMap->cmodels[0].leaf.terrainContents takes precendence over leaf and material terrain contents + +// material flags determine the features of the surface +// unsure which flag type changes what right now +// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast +// 1 results in: normal surface features, grenades work, +#define MATERIAL_SURFACE_FLAGS 1 +#define MATERIAL_CONTENT_FLAGS 1 + +// terrain flags: does not change the type of terrain or what features they have +// from testing, as long at it isn't 0 things will work correctly +#define LEAF_TERRAIN_CONTENTS 1 // match all flags +#define WORLD_TERRAIN_CONTENTS 1 // match all flags + +// const std::string defaultMaterialName = "wpc/intro_wall_plaster_light_blue_01"; +const std::string defaultMaterialName = "wpc/dig_plastic_blue"; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp new file mode 100644 index 000000000..798438e7f --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp @@ -0,0 +1,1646 @@ +#include "CustomMapLinker.h" + +#include "Utils/Pack.h" +#include "Util.h" +#include "CustomMapConsts.h" +#include "BinarySpacePartitionTreePreCalc.h" + +#include +#include +using json = nlohmann::json; + +class CustomMapLinker +{ + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + AssetCreationContext& m_context; + + bool hasLinkFailed; + + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone), + m_context(context) + { + hasLinkFailed = false; + } + + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxInfo.vertexCount; + customMapVertex* worldVertices = projInfo->gfxInfo.vertices; + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + customMapVertex* WorldVertex = &worldVertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); + + GfxVertex->binormalSign = WorldVertex->binormalSign; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); + + GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxInfo.indexCount; + _ASSERT(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = new uint16_t[indexCount]; + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; + } + + return true; + } + + struct s_sortedSurf + { + int surfaceIndex; + int vertexCount; + }; + + bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) + { + return surf0.vertexCount > surf1.vertexCount; + } + + void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); + if (!overwriteResult) + return; + + unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxInfo.surfaces[i]; + + currSurface->lightmapIndex = objSurface->lightmapIndex; + currSurface->primaryLightIndex = objSurface->primaryLightIndex; + currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; + currSurface->flags = objSurface->flags; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->firstIndex_Index; + + currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + auto* assetInfo = m_context.LoadDependency(objSurface->materialName); + if (assetInfo == NULL) + { + printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); + assetInfo = m_context.LoadDependency(defaultMaterialName); + _ASSERT(assetInfo != NULL); + } + currSurface->material = assetInfo->Asset(); + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // sort all vertexes by vertex count + // unsure if this does anything + s_sortedSurf* sortedSurfs = new s_sortedSurf[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + sortedSurfs[i].surfaceIndex = i; + sortedSurfs[i].vertexCount = gfxWorld->dpvs.surfaces[i].tris.triCount * 3; + } + std::sort(sortedSurfs, &sortedSurfs[surfaceCount], compareSurfaces); + + gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = sortedSurfs[i].surfaceIndex; + } + + gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // all visdata is alligned by 128 + gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; + memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); + + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + } + +#define SMODEL_FLAG_NO_SHADOW 1 +#define SMODEL_FLAG_IS_LIT 2 + + void overwriteMapSModels(GfxWorld* gfxWorld) + { + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + + gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + /* + for (unsigned int i = 0; i < modelCount; i++) + { + auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + json currModelJs = js["models"][i]; + + // TODO: load custom xmodels + std::string modelName = currModelJs["model"]; + auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); + if (xModelAsset == NULL) + { + printf("Custom model (%s) not supported!\n", modelName.c_str()); + hasLinkFailed = true; + return; + } + currModel->model = (XModel*)xModelAsset->m_ptr; + + currModel->placement.origin.x = currModelJs["origin"]["x"]; + currModel->placement.origin.y = currModelJs["origin"]["y"]; + currModel->placement.origin.z = currModelJs["origin"]["z"]; + currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); + currModel->placement.axis[0].x = currModelJs["forward"]["x"]; + currModel->placement.axis[0].y = currModelJs["forward"]["y"]; + currModel->placement.axis[0].z = currModelJs["forward"]["z"]; + currModel->placement.axis[1].x = currModelJs["right"]["x"]; + currModel->placement.axis[1].y = currModelJs["right"]["y"]; + currModel->placement.axis[1].z = currModelJs["right"]["z"]; + currModel->placement.axis[2].x = currModelJs["up"]["x"]; + currModel->placement.axis[2].y = currModelJs["up"]["y"]; + currModel->placement.axis[2].z = currModelJs["up"]["z"]; + currModel->placement.scale = currModelJs["scale"]; + + // mins and maxs are calculated in world space not local space + // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame + currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + + currModel->cullDist = currModelJs["cullDist"]; + currModel->flags = currModelJs["flags"]; + currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; + currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; + + // unknown use / unused + currModel->smid = i; + memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + currModel->invScaleSq = 0.0f; + currModel->lightingHandle = 0; + currModel->colorsIndex = 0; + currModel->visibility = 0; + + // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // TODO: calculate lighting and store it here + currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + } + */ + + // all visdata is alligned by 128 + gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); + + gfxWorld->dpvs.usageCount = 0; + } + + void cleanGfxWorld(GfxWorld* gfxWorld) + { + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = new GfxLight; + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 + + gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; + for (int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + } + + gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; + for (int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; + memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + void overwriteLightGrid(GfxWorld* gfxWorld) + { + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 0; + gfxWorld->lightGrid.maxs[1] = 0; + gfxWorld->lightGrid.maxs[2] = 0; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 + gfxWorld->lightGrid.offset = 0.0f; + + // if rowDataStart's first value is -1, it will not look up any lightmap data + gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; + *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); + + gfxWorld->lightGrid.rawRowDataSize = 0; + gfxWorld->lightGrid.rawRowData = NULL; + + gfxWorld->lightGrid.colorCount = 0; + gfxWorld->lightGrid.colors = NULL; + + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + + gfxWorld->lightGrid.entryCount = 0; + gfxWorld->lightGrid.entries = NULL; + + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = new GfxCell[cellCount]; + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].reflectionProbeCount = 0; + gfxWorld->cells[0].reflectionProbes = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void overwriteModels(GfxWorld* gfxWorld) + { + gfxWorld->modelCount = 1; + gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; + + // first model is the world model, the world json doesn't include it so its added here + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + // for (int i = 1; i < gfxWorld->modelCount; i++) + //{ + // auto currEntModel = &gfxWorld->models[i]; + // json currEntModelJs = js["entityModels"][i - 1]; + // + // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; + // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; + // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; + // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; + // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; + // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; + // + // currEntModel->surfaceCount = 0; + // currEntModel->startSurfIndex = (unsigned int)(-1); + // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); + // } + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = 0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + void updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + } + + void overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + } + + void overwriteSkyBox(GfxWorld* gfxWorld) + { + const char* skyBoxName = "skybox_mp_dig"; + gfxWorld->skyBoxModel = skyBoxName; + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + printf("WARN: Unable to find the skybox model %s\n", skyBoxName); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void updateDynEntData(GfxWorld* gfxWorld) + { + gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + void updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); + hasLinkFailed = true; + return; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + } + + void createGfxWorld(customMapInfo* projInfo) + { + GfxWorld* gfxWorld = new GfxWorld; + gfxWorld->baseName = _strdup(projInfo->name.c_str()); + gfxWorld->name = _strdup(projInfo->bspName.c_str()); + + // Default values taken from mp_dig + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + overwriteMapSurfaces(projInfo, gfxWorld); + + overwriteMapSModels(gfxWorld); + + overwriteLightmapData(gfxWorld); + + overwriteSkyBox(gfxWorld); + + updateReflectionProbeData(gfxWorld); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + updateOutdoors(gfxWorld); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + m_context.AddAsset(gfxWorld->name, gfxWorld); + } + + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + out_origin->x = temp.x * 0.5f; + out_origin->y = temp.y * 0.5f; + out_origin->z = temp.z * 0.5f; + + // Half-size is half the difference: (max - min) / 2 + temp.x = maxs->x - mins->x; + temp.y = maxs->y - mins->y; + temp.z = maxs->z - mins->z; + out_halfSize->x = temp.x * 0.5f; + out_halfSize->y = temp.y * 0.5f; + out_halfSize->z = temp.z * 0.5f; + } + + bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) + { + + unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); + + return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; + } + + float distBetweenPoints(vec3_t p1, vec3_t p2) + { + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); + } + + + + void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->u.leaf->getObjectCount() > 0) + { + *numAABBTrees += node->u.leaf->getObjectCount() + 1; + + if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->u.leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + } + } + + vec3_t normalX = {1.0f, 0.0f, 0.0f}; + vec3_t normalY = {0.0f, 1.0f, 0.0f}; + vec3_t normalZ = {0.0f, 0.0f, 1.0f}; + + int currPlaneCount = 0; + int currNodeCount = 0; + int currLeafCount = 0; + int currAABBCount = 0; + + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + { + _ASSERT(node->isLeaf); + + int objectCount = node->u.leaf->getObjectCount(); + int firstAABBIndex = currAABBCount; + currAABBCount += objectCount + 1; + + // calculate root AABB node mins and maxs + // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs + // so we have to recompute every min and max, not hard just tedious + int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; + auto firstPartition = &clipMap->partitions[firstPartitionIndex]; + uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t aabbMins; + vec3_t aabbMaxs; + aabbMins.x = firstVert->x; + aabbMins.y = firstVert->y; + aabbMins.z = firstVert->z; + aabbMaxs.x = firstVert->x; + aabbMaxs.y = firstVert->y; + aabbMaxs.z = firstVert->z; + for (int i = 0; i < objectCount; i++) + { + int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + auto currPartition = &clipMap->partitions[currPartitionIndex]; + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = clipMap->verts[vertIndex]; + CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + // create root AABB node + CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; + aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); + rootAABB->materialIndex = 0; + rootAABB->childCount = objectCount; + rootAABB->u.firstChildIndex = firstAABBIndex + 1; + + // populate child AABB nodes + for (int i = 0; i < objectCount; i++) + { + CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; + int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + + currAabbTree->materialIndex = 0; + currAabbTree->childCount = 0; + currAabbTree->u.partitionIndex = currPartitionIndex; + + // calculate partition origin and half size + CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; + uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; + vec3_t* firstVertex = &clipMap->verts[firstUind]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVertex->x; + mins.y = firstVertex->y; + mins.z = firstVertex->z; + maxs.x = firstVertex->x; + maxs.y = firstVertex->y; + maxs.z = firstVertex->z; + for (int i = 1; i < aabbPartition->nuinds; i++) + { + uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; + vec3_t* currVertex = &clipMap->verts[currUind]; + + CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + } + + aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + + } + + return firstAABBIndex; + } + + // returns the index corresponding to the BSPTree* node parsed + int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + { + new cplane_s; + new cNode_t; + new cLeaf_s; + new CollisionAabbTree; + + if (node->isLeaf) + { + int currLeafIndex = currLeafCount; + currLeafCount++; + cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + + currLeaf->cluster = 0; + currLeaf->brushContents = 0; // no brushes used so contents is 0 + currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + + // unused when leafBrushNode == 0 + currLeaf->mins.x = 0.0f; + currLeaf->mins.y = 0.0f; + currLeaf->mins.z = 0.0f; + currLeaf->maxs.x = 0.0f; + currLeaf->maxs.y = 0.0f; + currLeaf->maxs.z = 0.0f; + currLeaf->leafBrushNode = 0; + + if (node->u.leaf->getObjectCount() > 0) + { + currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); + currLeaf->collAabbCount = 1; + } + else + { + currLeaf->firstCollAabbIndex = 0; + currLeaf->collAabbCount = 0; + } + + return -1 - currLeafIndex; + } + else + { + cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; + currPlaneCount++; + + if (node->u.node->axis == AXIS_X) + { + // X is unchanged when going from OGL x -> BO2 x + currPlane->normal = normalX; + + // converting OGL -> BO2 X coords doesn't change the x coords at all, so + // the dist stays the same + currPlane->dist = node->u.node->distance; + } + else + { + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // convert the z normal to the y normal, but don't negate it. Negative normals don't do + // what is expected when the game uses them + _ASSERT(node->u.node->axis == AXIS_Z); + currPlane->normal = normalY; + + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // just negate it here as it is just the distance from the orgin along the axis + currPlane->dist = -node->u.node->distance; + } + + bool foundType = false; + if (currPlane->normal.x == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 0; + } + else if (currPlane->normal.y == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 1; + } + else if (currPlane->normal.z == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 2; + } + else + _ASSERT(foundType); + + currPlane->signbits = 0; + if (currPlane->normal.x < 0.0f) + currPlane->signbits |= 1; + if (currPlane->normal.y < 0.0f) + currPlane->signbits |= 2; + if (currPlane->normal.z < 0.0f) + currPlane->signbits |= 4; + + currPlane->pad[0] = 0; + currPlane->pad[1] = 0; + + int currNodeIndex = currNodeCount; + currNodeCount++; + cNode_t* currNode = &clipMap->nodes[currNodeIndex]; + + currNode->plane = currPlane; + // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): + // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, + // the normal vector flips and objects behind the plane are now in front, and vise versa + // so the back node now represents the front, and the front node represents the back. + // Do the OGL -> Bo2 coord change on paper and it will make sense + if (currPlane->type == 1) + { + currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); + currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); + } + + return currNodeIndex; + } + } + + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + int numPlanes = 0; + int numNodes = 0; + int numLeafs = 0; + int numAABBTrees = 0; + int maxObjsPerLeaf = 0; + + traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); + + printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); + + clipMap->info.planeCount = numPlanes; + clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; + clipMap->numNodes = numNodes; + clipMap->nodes = new cNode_t[clipMap->numNodes]; + // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. + // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. + clipMap->aabbTreeCount = numAABBTrees; + clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; + memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); + currLeafCount = 1; + + populateBSPTree_r(clipMap, tree); + + _ASSERT(clipMap->info.planeCount == currPlaneCount); + _ASSERT(clipMap->numNodes == currNodeCount); + _ASSERT(clipMap->numLeafs == currLeafCount); + _ASSERT(clipMap->aabbTreeCount == currAABBCount); + } + + void createClipMap(customMapInfo* projInfo) + { + clipMap_t* clipMap = new clipMap_t; + + clipMap->name = _strdup(projInfo->bspName.c_str()); + + std::string mapEntsName = projInfo->bspName.c_str(); + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + _ASSERT(mapEntsAsset != NULL); + clipMap->mapEnts = mapEntsAsset->Asset(); + + clipMap->pInfo = NULL; + + clipMap->box_model.mins.x = 0.0f; + clipMap->box_model.mins.y = 0.0f; + clipMap->box_model.mins.z = 0.0f; + clipMap->box_model.maxs.x = 0.0f; + clipMap->box_model.maxs.y = 0.0f; + clipMap->box_model.maxs.z = 0.0f; + clipMap->box_model.radius = 0.0f; + clipMap->box_model.info = NULL; + clipMap->box_model.leaf.mins.x = 3.4028235e38; + clipMap->box_model.leaf.mins.y = 3.4028235e38; + clipMap->box_model.leaf.mins.z = 3.4028235e38; + clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive + clipMap->box_model.leaf.maxs.y = -3.4028235e38; + clipMap->box_model.leaf.maxs.z = -3.4028235e38; + clipMap->box_model.leaf.brushContents = -1; + clipMap->box_model.leaf.terrainContents = 0; + clipMap->box_model.leaf.cluster = 0; + clipMap->box_model.leaf.collAabbCount = 0; + clipMap->box_model.leaf.firstCollAabbIndex = 0; + clipMap->box_model.leaf.leafBrushNode = 0; + + clipMap->box_brush = new cbrush_t; + clipMap->box_brush->axial_sflags[0][0] = -1; + clipMap->box_brush->axial_sflags[0][1] = -1; + clipMap->box_brush->axial_sflags[0][2] = -1; + clipMap->box_brush->axial_sflags[1][0] = -1; + clipMap->box_brush->axial_sflags[1][1] = -1; + clipMap->box_brush->axial_sflags[1][2] = -1; + clipMap->box_brush->axial_cflags[0][0] = -1; + clipMap->box_brush->axial_cflags[0][1] = -1; + clipMap->box_brush->axial_cflags[0][2] = -1; + clipMap->box_brush->axial_cflags[1][0] = -1; + clipMap->box_brush->axial_cflags[1][1] = -1; + clipMap->box_brush->axial_cflags[1][2] = -1; + clipMap->box_brush->contents = -1; + clipMap->box_brush->mins.x = 0.0f; + clipMap->box_brush->mins.y = 0.0f; + clipMap->box_brush->mins.z = 0.0f; + clipMap->box_brush->maxs.x = 0.0f; + clipMap->box_brush->maxs.y = 0.0f; + clipMap->box_brush->maxs.z = 0.0f; + clipMap->box_brush->numsides = 0; + clipMap->box_brush->numverts = 0; + clipMap->box_brush->sides = NULL; + clipMap->box_brush->verts = NULL; + + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = new char[clipMap->clusterBytes]; + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + + clipMap->isInUse = true; + clipMap->checksum = 0; + + clipMap->num_constraints = 0; + clipMap->constraints = NULL; + clipMap->max_ropes = 32; + clipMap->ropes = new rope_t[clipMap->max_ropes]; + memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); + + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = NULL; + clipMap->info.leafbrushNodesCount = 0; + clipMap->info.leafbrushNodes = NULL; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = NULL; + clipMap->info.numBrushVerts = 0; + clipMap->info.brushVerts = NULL; + clipMap->info.numBrushes = NULL; + clipMap->info.brushes = NULL; + clipMap->info.brushBounds = NULL; + clipMap->info.brushContents = NULL; + + clipMap->originalDynEntCount = DYN_ENT_COUNT; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; + clipMap->dynEntCount[1] = 0; + clipMap->dynEntCount[2] = 0; + clipMap->dynEntCount[3] = 0; + + // assume that there are 0 dyn ents from here on + clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; + clipMap->dynEntClientList[1] = NULL; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = NULL; + clipMap->dynEntServerList[1] = NULL; + + clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; + clipMap->dynEntCollList[1] = NULL; + clipMap->dynEntCollList[2] = NULL; + clipMap->dynEntCollList[3] = NULL; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; + clipMap->dynEntPoseList[1] = NULL; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; + clipMap->dynEntDefList[1] = NULL; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + for (int i = 0; i < clipMap->dynEntCount[0]; i++) + { + DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; + currDef->physConstraints[0] = 0x1FF; + currDef->physConstraints[1] = 0x1FF; + currDef->physConstraints[2] = 0x1FF; + currDef->physConstraints[3] = 0x1FF; + } + + clipMap->numSubModels = 1; + clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = NULL; + + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; + clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; + clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; + clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; + clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; + clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; + clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + + clipMap->numStaticModels = 0; + clipMap->staticModelList = NULL; + + clipMap->info.numMaterials = 1; + clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; + clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); + clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; + + int collisionVertexCount = projInfo->gfxInfo.vertexCount; + vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; + for (int i = 0; i < collisionVertexCount; i++) + { + convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = convertedCollisionVerts; + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + const int maxVerts = UINT16_MAX - 1; + if (collisionVertexCount > maxVerts) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); + hasLinkFailed = true; + return; + } + + clipMap->triCount = projInfo->gfxInfo.indexCount / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri + int triIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) + { + clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; + clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; + clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; + triIndex++; + } + + int partitionCount = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int subdividedSize = triCount / 16; // integer division used + partitionCount += subdividedSize; + + // add another partition if the triangle count doesn't add up to a multiple of 16 + if (triCount % 16 != 0) + partitionCount++; + } + clipMap->partitionCount = partitionCount; + clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; + + int partitionIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; + int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; + int firstTri = firstIndex / 3; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. + // NEED TO FIX + // fixup the tri indices. + // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] + // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] + // this loop fixes the index buffer by adding the firstVertex to each index + for (int k = 0; k < triCount; k++) + { + clipMap->triIndices[k + firstTri][0] += firstVertex; + clipMap->triIndices[k + firstTri][1] += firstVertex; + clipMap->triIndices[k + firstTri][2] += firstVertex; + } + + while (triCount > 0) + { + if (triCount > 16) + clipMap->partitions[partitionIndex].triCount = 16; + else + clipMap->partitions[partitionIndex].triCount = triCount; + + clipMap->partitions[partitionIndex].firstTri = firstTri; + + triCount -= 16; + firstIndex += 16 * 3; + partitionIndex++; + } + } + + int uindCount = 0; + clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + int uniqueVertBufferCount = 0; + uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (int m = 0; m < uniqueVertBufferCount; m++) + { + if (uniqueVertBuffer[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + { + uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; + uniqueVertBufferCount++; + } + } + } + + _ASSERT(uniqueVertBufferCount <= 3 * 16); + + currPartition->fuind = uindCount; + currPartition->nuinds = uniqueVertBufferCount; + memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); + uindCount += uniqueVertBufferCount; + } + _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); + clipMap->info.nuinds = uindCount; + + // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility + // might do weird stuff on walls + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); + + // BSP creation must go last as it depends on unids, tris and verts already being populated + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, + // and then convert them back when it is being parsed into the clipmap. Requires some hacky + // logic, check populateBSPTree_r and addAABBTreeFromLeaf + + vec3_t* firstVert = &clipMap->verts[0]; + vec3_t clipMins; + vec3_t clipMaxs; + clipMins.x = firstVert->x; + clipMins.y = firstVert->y; + clipMins.z = firstVert->z; + clipMaxs.x = firstVert->x; + clipMaxs.y = firstVert->y; + clipMaxs.z = firstVert->z; + clipMins = CMUtil::convertFromBO2Coords(clipMins); + clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); + for (int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVert->x; + mins.y = firstVert->y; + mins.z = firstVert->z; + maxs.x = firstVert->x; + maxs.y = firstVert->y; + maxs.z = firstVert->z; + mins = CMUtil::convertFromBO2Coords(mins); + maxs = CMUtil::convertFromBO2Coords(maxs); + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObject(currObject); + } + + populateBSPTree(clipMap, tree); + + m_context.AddAsset(clipMap->name, clipMap); + } + + void createComWorld(customMapInfo* projInfo) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = new ComWorld; + comWorld->name = _strdup(projInfo->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; + + ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; + memset(defaultLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + // Below are default values taken from mp_dig + sunLight->diffuseColor.r = 1.0f; + sunLight->diffuseColor.g = 1.0f; + sunLight->diffuseColor.b = 1.0f; + sunLight->diffuseColor.a = 0.0f; + sunLight->dir.x = -0.2410777360200882; + sunLight->dir.y = -0.8407384753227234; + sunLight->dir.z = 0.48480960726737976; + + m_context.AddAsset(comWorld->name, comWorld); + } + + void createMapEnts(customMapInfo* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + const auto file = m_search_path.Open("custom_map/entities.json"); + if (!file.IsOpen()) + { + printf("WARN: can't find entity json!\n"); + return; + } + + json entJs = json::parse(*file.m_stream); + + std::string entityString; + int entityCount = entJs["entityCount"]; + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entJs["entities"][i]; + + if (i == 0) + { + std::string className = currEntity["classname"]; + if (className.compare("worldspawn") != 0) + { + printf("ERROR: first entity in the map entity string must be the worldspawn class!"); + hasLinkFailed = true; + return; + } + } + + entityString.append("{\n"); + + for (auto& element : currEntity.items()) + { + std::string key = element.key(); + std::string value = element.value(); + entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); + } + + entityString.append("}\n"); + } + entityString.pop_back(); // remove newline character from the end of the string + + mapEnts->entityString = _strdup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + m_context.AddAsset(mapEnts->name, mapEnts); + } + + void createGameWorldMp(customMapInfo* projInfo) + { + GameWorldMp* gameWorldMp = new GameWorldMp; + + gameWorldMp->name = _strdup(projInfo->bspName.c_str()); + + gameWorldMp->path.nodeCount = 0; + gameWorldMp->path.originalNodeCount = 0; + gameWorldMp->path.visBytes = 0; + gameWorldMp->path.smoothBytes = 0; + gameWorldMp->path.nodeTreeCount = 0; + + int nodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = new pathnode_t[nodeCount]; + gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = NULL; + gameWorldMp->path.smoothCache = NULL; + gameWorldMp->path.nodeTree = NULL; + + m_context.AddAsset(gameWorldMp->name, gameWorldMp); + } + + void createSkinnedVerts(customMapInfo* projInfo) + { + SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; + skinnedVerts->name = "skinnedverts"; + skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; + + m_context.AddAsset("skinnedverts", skinnedVerts); + } + + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return NULL; + + FootstepTableDef* footstepTable = new FootstepTableDef; + footstepTable->name = _strdup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) + { + if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } + + addEmptyFootstepTableAsset("default_1st_person"); + addEmptyFootstepTableAsset("default_3rd_person"); + addEmptyFootstepTableAsset("default_1st_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_loud"); + addEmptyFootstepTableAsset("default_ai"); + } + + bool linkCustomMap(customMapInfo* projInfo) + { + _ASSERT(projInfo != NULL); + + createGfxWorld(projInfo); + createComWorld(projInfo); + createMapEnts(projInfo); + createGameWorldMp(projInfo); + createSkinnedVerts(projInfo); + createClipMap(projInfo); // must go last (requires gfx and mapents asset) + + checkAndAddDefaultRequiredAssets(projInfo); + + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + return true; + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h new file mode 100644 index 000000000..6dcefb44b --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -0,0 +1,14 @@ +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "Asset/IAssetCreator.h" +using namespace T6; + +#include "OBJ_Loader.h" + +class CustomMapLinker +{ +public: + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); + bool linkCustomMap(customMapInfo* projInfo); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp new file mode 100644 index 000000000..284cb1b3e --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -0,0 +1,57 @@ +#include "LoaderCustomMapT6.h" +#include "ProjectCreator.h" +#include "CustomMapLinker.h" + +#include "Game/T6/T6.h" + +#include + +using namespace T6; + +namespace +{ + class CustomMapLoader final : public AssetCreator + { + public: + CustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + auto mapFile = m_search_path.Open("custom_map/map.obj"); + if (!mapFile.IsOpen()) + return AssetCreationResult::NoAction(); + + // create map info from the obj file + customMapInfo* mapInfo = CustomMapInfo::createCustomMapInfo(m_zone.m_name, m_search_path); + if (mapInfo == NULL) + return AssetCreationResult::Failure(); + + // linker will add all the assets needed + CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone); + bool result = linker->linkCustomMap(mapInfo); + + if (result) + return AssetCreationResult::NoAction(); + else + return AssetCreationResult::Failure(); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + }; +} // namespace + +namespace T6 +{ + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + { + return std::make_unique(memory, searchPath, zone); + } +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h new file mode 100644 index 000000000..13862ba61 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace T6 +{ + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath); +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h new file mode 100644 index 000000000..d73226ee4 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h @@ -0,0 +1,1160 @@ +// OBJ_Loader.h - A Single Header OBJ Model Loader + +#pragma once + +// Iostream - STD I/O Library +#include + +// Vector - STD Vector/Array Library +#include + +// String - STD String Library +#include + +// fStream - STD File I/O Library +#include + +// Math.h - STD math Library +#include + +// Print progress to console while loading (large models) +// #define OBJL_CONSOLE_OUTPUT + +// Namespace: OBJL +// +// Description: The namespace that holds eveyrthing that +// is needed and used for the OBJ Model Loader +namespace objl +{ + // Structure: Vector2 + // + // Description: A 2D Vector that Holds Positional Data + struct Vector2 + { + // Default Constructor + Vector2() + { + X = 0.0f; + Y = 0.0f; + } + + // Variable Set Constructor + Vector2(float X_, float Y_) + { + X = X_; + Y = Y_; + } + + // Bool Equals Operator Overload + bool operator==(const Vector2& other) const + { + return (this->X == other.X && this->Y == other.Y); + } + + // Bool Not Equals Operator Overload + bool operator!=(const Vector2& other) const + { + return !(this->X == other.X && this->Y == other.Y); + } + + // Addition Operator Overload + Vector2 operator+(const Vector2& right) const + { + return Vector2(this->X + right.X, this->Y + right.Y); + } + + // Subtraction Operator Overload + Vector2 operator-(const Vector2& right) const + { + return Vector2(this->X - right.X, this->Y - right.Y); + } + + // Float Multiplication Operator Overload + Vector2 operator*(const float& other) const + { + return Vector2(this->X * other, this->Y * other); + } + + // Positional Variables + float X; + float Y; + }; + + // Structure: Vector3 + // + // Description: A 3D Vector that Holds Positional Data + struct Vector3 + { + // Default Constructor + Vector3() + { + X = 0.0f; + Y = 0.0f; + Z = 0.0f; + } + + // Variable Set Constructor + Vector3(float X_, float Y_, float Z_) + { + X = X_; + Y = Y_; + Z = Z_; + } + + // Bool Equals Operator Overload + bool operator==(const Vector3& other) const + { + return (this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + + // Bool Not Equals Operator Overload + bool operator!=(const Vector3& other) const + { + return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + + // Addition Operator Overload + Vector3 operator+(const Vector3& right) const + { + return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z); + } + + // Subtraction Operator Overload + Vector3 operator-(const Vector3& right) const + { + return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z); + } + + // Float Multiplication Operator Overload + Vector3 operator*(const float& other) const + { + return Vector3(this->X * other, this->Y * other, this->Z * other); + } + + // Float Division Operator Overload + Vector3 operator/(const float& other) const + { + return Vector3(this->X / other, this->Y / other, this->Z / other); + } + + // Positional Variables + float X; + float Y; + float Z; + }; + + // Structure: Vertex + // + // Description: Model Vertex object that holds + // a Position, Normal, and Texture Coordinate + struct Vertex + { + // Position Vector + Vector3 Position; + + // Normal Vector + Vector3 Normal; + + // Texture Coordinate Vector + Vector2 TextureCoordinate; + }; + + struct Material + { + Material() + { + name; + Ns = 0.0f; + Ni = 0.0f; + d = 0.0f; + illum = 0; + } + + // Material Name + std::string name; + // Ambient Color + Vector3 Ka; + // Diffuse Color + Vector3 Kd; + // Specular Color + Vector3 Ks; + // Specular Exponent + float Ns; + // Optical Density + float Ni; + // Dissolve + float d; + // Illumination + int illum; + // Ambient Texture Map + std::string map_Ka; + // Diffuse Texture Map + std::string map_Kd; + // Specular Texture Map + std::string map_Ks; + // Specular Hightlight Map + std::string map_Ns; + // Alpha Texture Map + std::string map_d; + // Bump Map + std::string map_bump; + }; + + // Structure: Mesh + // + // Description: A Simple Mesh Object that holds + // a name, a vertex list, and an index list + struct Mesh + { + // Default Constructor + Mesh() {} + + // Variable Set Constructor + Mesh(std::vector& _Vertices, std::vector& _Indices) + { + Vertices = _Vertices; + Indices = _Indices; + } + + // Mesh Name + std::string MeshName; + // Vertex List + std::vector Vertices; + // Index List + std::vector Indices; + + // Material + Material MeshMaterial; + }; + + // Namespace: Math + // + // Description: The namespace that holds all of the math + // functions need for OBJL + namespace math + { + // Vector3 Cross Product + Vector3 CrossV3(const Vector3 a, const Vector3 b) + { + return Vector3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); + } + + // Vector3 Magnitude Calculation + float MagnitudeV3(const Vector3 in) + { + return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2))); + } + + // Vector3 DotProduct + float DotV3(const Vector3 a, const Vector3 b) + { + return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); + } + + // Angle between 2 Vector3 Objects + float AngleBetweenV3(const Vector3 a, const Vector3 b) + { + float angle = DotV3(a, b); + angle /= (MagnitudeV3(a) * MagnitudeV3(b)); + return angle = acosf(angle); + } + + // Projection Calculation of a onto b + Vector3 ProjV3(const Vector3 a, const Vector3 b) + { + Vector3 bn = b / MagnitudeV3(b); + return bn * DotV3(a, bn); + } + } // namespace math + + // Namespace: Algorithm + // + // Description: The namespace that holds all of the + // Algorithms needed for OBJL + namespace algorithm + { + // Vector3 Multiplication Opertor Overload + Vector3 operator*(const float& left, const Vector3& right) + { + return Vector3(right.X * left, right.Y * left, right.Z * left); + } + + // A test to see if P1 is on the same side as P2 of a line segment ab + bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) + { + Vector3 cp1 = math::CrossV3(b - a, p1 - a); + Vector3 cp2 = math::CrossV3(b - a, p2 - a); + + if (math::DotV3(cp1, cp2) >= 0) + return true; + else + return false; + } + + // Generate a cross produect normal for a triangle + Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3) + { + Vector3 u = t2 - t1; + Vector3 v = t3 - t1; + + Vector3 normal = math::CrossV3(u, v); + + return normal; + } + + // Check to see if a Vector3 Point is within a 3 Vector3 Triangle + bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3) + { + // Test to see if it is within an infinite prism that the triangle outlines. + bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && SameSide(point, tri2, tri1, tri3) && SameSide(point, tri3, tri1, tri2); + + // If it isn't it will never be on the triangle + if (!within_tri_prisim) + return false; + + // Calulate Triangle's Normal + Vector3 n = GenTriNormal(tri1, tri2, tri3); + + // Project the point onto this normal + Vector3 proj = math::ProjV3(point, n); + + // If the distance from the triangle to the point is 0 + // it lies on the triangle + if (math::MagnitudeV3(proj) == 0) + return true; + else + return false; + } + + // Split a String into a string array at a given token + inline void split(const std::string& in, std::vector& out, std::string token) + { + out.clear(); + + std::string temp; + + for (int i = 0; i < int(in.size()); i++) + { + std::string test = in.substr(i, token.size()); + + if (test == token) + { + if (!temp.empty()) + { + out.push_back(temp); + temp.clear(); + i += (int)token.size() - 1; + } + else + { + out.push_back(""); + } + } + else if (i + token.size() >= in.size()) + { + temp += in.substr(i, token.size()); + out.push_back(temp); + break; + } + else + { + temp += in[i]; + } + } + } + + // Get tail of string after first token and possibly following spaces + inline std::string tail(const std::string& in) + { + size_t token_start = in.find_first_not_of(" \t"); + size_t space_start = in.find_first_of(" \t", token_start); + size_t tail_start = in.find_first_not_of(" \t", space_start); + size_t tail_end = in.find_last_not_of(" \t"); + if (tail_start != std::string::npos && tail_end != std::string::npos) + { + return in.substr(tail_start, tail_end - tail_start + 1); + } + else if (tail_start != std::string::npos) + { + return in.substr(tail_start); + } + return ""; + } + + // Get first token of string + inline std::string firstToken(const std::string& in) + { + if (!in.empty()) + { + size_t token_start = in.find_first_not_of(" \t"); + size_t token_end = in.find_first_of(" \t", token_start); + if (token_start != std::string::npos && token_end != std::string::npos) + { + return in.substr(token_start, token_end - token_start); + } + else if (token_start != std::string::npos) + { + return in.substr(token_start); + } + } + return ""; + } + + // Get element at given index position + template inline const T& getElement(const std::vector& elements, std::string& index) + { + int idx = std::stoi(index); + if (idx < 0) + idx = int(elements.size()) + idx; + else + idx--; + return elements[idx]; + } + } // namespace algorithm + + // Class: Loader + // + // Description: The OBJ Model Loader + class Loader + { + public: + // Default Constructor + Loader() {} + + ~Loader() + { + LoadedMeshes.clear(); + } + + // Load a file into the loader + // + // If file is loaded return true + // + // If the file is unable to be found + // or unable to be loaded return false + bool LoadFile(std::string Path) + { + // If the file is not an .obj file return false + if (Path.substr(Path.size() - 4, 4) != ".obj") + return false; + + std::ifstream file(Path); + + if (!file.is_open()) + return false; + + LoadedMeshes.clear(); + LoadedVertices.clear(); + LoadedIndices.clear(); + + std::vector Positions; + std::vector TCoords; + std::vector Normals; + + std::vector Vertices; + std::vector Indices; + + std::vector MeshMatNames; + + bool listening = false; + std::string meshname; + + Mesh tempMesh; + +#ifdef OBJL_CONSOLE_OUTPUT + const unsigned int outputEveryNth = 1000; + unsigned int outputIndicator = outputEveryNth; +#endif + + std::string curline; + while (std::getline(file, curline)) + { +#ifdef OBJL_CONSOLE_OUTPUT + if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) + { + if (!meshname.empty()) + { + std::cout << "\r- " << meshname << "\t| vertices > " << Positions.size() << "\t| texcoords > " << TCoords.size() << "\t| normals > " + << Normals.size() << "\t| triangles > " << (Vertices.size() / 3) + << (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() : ""); + } + } +#endif + + // Generate a Mesh Object or Prepare for an object to be created + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || curline[0] == 'g') + { + if (!listening) + { + listening = true; + + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") + { + meshname = algorithm::tail(curline); + } + else + { + meshname = "unnamed"; + } + } + else + { + // Generate the mesh to put into the array + + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + meshname.clear(); + + meshname = algorithm::tail(curline); + } + else + { + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") + { + meshname = algorithm::tail(curline); + } + else + { + meshname = "unnamed"; + } + } + } +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; + outputIndicator = 0; +#endif + } + // Generate a Vertex Position + if (algorithm::firstToken(curline) == "v") + { + std::vector spos; + Vector3 vpos; + algorithm::split(algorithm::tail(curline), spos, " "); + + vpos.X = std::stof(spos[0]); + vpos.Y = std::stof(spos[1]); + vpos.Z = std::stof(spos[2]); + + Positions.push_back(vpos); + } + // Generate a Vertex Texture Coordinate + if (algorithm::firstToken(curline) == "vt") + { + std::vector stex; + Vector2 vtex; + algorithm::split(algorithm::tail(curline), stex, " "); + + vtex.X = std::stof(stex[0]); + vtex.Y = std::stof(stex[1]); + + TCoords.push_back(vtex); + } + // Generate a Vertex Normal; + if (algorithm::firstToken(curline) == "vn") + { + std::vector snor; + Vector3 vnor; + algorithm::split(algorithm::tail(curline), snor, " "); + + vnor.X = std::stof(snor[0]); + vnor.Y = std::stof(snor[1]); + vnor.Z = std::stof(snor[2]); + + Normals.push_back(vnor); + } + // Generate a Face (vertices & indices) + if (algorithm::firstToken(curline) == "f") + { + // Generate the vertices + std::vector vVerts; + GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline); + + // Add Vertices + for (int i = 0; i < int(vVerts.size()); i++) + { + Vertices.push_back(vVerts[i]); + + LoadedVertices.push_back(vVerts[i]); + } + + std::vector iIndices; + + VertexTriangluation(iIndices, vVerts); + + // Add Indices + for (int i = 0; i < int(iIndices.size()); i++) + { + unsigned int indnum = (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i]; + Indices.push_back(indnum); + + indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i]; + LoadedIndices.push_back(indnum); + } + } + // Get Mesh Material Name + if (algorithm::firstToken(curline) == "usemtl") + { + MeshMatNames.push_back(algorithm::tail(curline)); + + // Create new Mesh, if Material changes within a group + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + int i = 2; + while (1) + { + tempMesh.MeshName = meshname + "_" + std::to_string(i); + + for (auto& m : LoadedMeshes) + if (m.MeshName == tempMesh.MeshName) + continue; + break; + } + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + } + +#ifdef OBJL_CONSOLE_OUTPUT + outputIndicator = 0; +#endif + } + // Load Materials + if (algorithm::firstToken(curline) == "mtllib") + { + // Generate LoadedMaterial + + // Generate a path to the material file + std::vector temp; + algorithm::split(Path, temp, "\\"); // update: use windows file seperators + + std::string pathtomat = ""; + + if (temp.size() != 1) + { + for (int i = 0; i < temp.size() - 1; i++) + { + pathtomat += temp[i] + "/"; + } + } + + pathtomat += algorithm::tail(curline); + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl << "- find materials in: " << pathtomat << std::endl; +#endif + + // Load Materials + LoadMaterials(pathtomat); + } + } + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; +#endif + + // Deal with last mesh + + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + } + + file.close(); + + // Set Materials for each Mesh + for (int i = 0; i < MeshMatNames.size(); i++) + { + std::string matname = MeshMatNames[i]; + + // Find corresponding material name in loaded materials + // when found copy material variables into mesh material + for (int j = 0; j < LoadedMaterials.size(); j++) + { + if (LoadedMaterials[j].name == matname) + { + LoadedMeshes[i].MeshMaterial = LoadedMaterials[j]; + break; + } + } + } + + if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty()) + { + return false; + } + else + { + return true; + } + } + + // Loaded Mesh Objects + std::vector LoadedMeshes; + // Loaded Vertex Objects + std::vector LoadedVertices; + // Loaded Index Positions + std::vector LoadedIndices; + // Loaded Material Objects + std::vector LoadedMaterials; + + private: + // Generate vertices from a list of positions, + // tcoords, normals and a face line + void GenVerticesFromRawOBJ(std::vector& oVerts, + const std::vector& iPositions, + const std::vector& iTCoords, + const std::vector& iNormals, + std::string icurline) + { + std::vector sface, svert; + Vertex vVert; + algorithm::split(algorithm::tail(icurline), sface, " "); + + bool noNormal = false; + + // For every given vertex do this + for (int i = 0; i < int(sface.size()); i++) + { + // See What type the vertex is. + int vtype; + + algorithm::split(sface[i], svert, "/"); + + // Check for just position - v1 + if (svert.size() == 1) + { + // Only position + vtype = 1; + } + + // Check for position & texture - v1/vt1 + if (svert.size() == 2) + { + // Position & Texture + vtype = 2; + } + + // Check for Position, Texture and Normal - v1/vt1/vn1 + // or if Position and Normal - v1//vn1 + if (svert.size() == 3) + { + if (svert[1] != "") + { + // Position, Texture, and Normal + vtype = 4; + } + else + { + // Position & Normal + vtype = 3; + } + } + + // Calculate and store the vertex + switch (vtype) + { + case 1: // P + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 2: // P/T + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 3: // P//N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + case 4: // P/T/N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + default: + { + break; + } + } + } + + // take care of missing normals + // these may not be truly acurate but it is the + // best they get for not compiling a mesh with normals + if (noNormal) + { + Vector3 A = oVerts[0].Position - oVerts[1].Position; + Vector3 B = oVerts[2].Position - oVerts[1].Position; + + Vector3 normal = math::CrossV3(A, B); + + for (int i = 0; i < int(oVerts.size()); i++) + { + oVerts[i].Normal = normal; + } + } + } + + // Triangulate a list of vertices into a face by printing + // inducies corresponding with triangles within it + void VertexTriangluation(std::vector& oIndices, const std::vector& iVerts) + { + // If there are 2 or less verts, + // no triangle can be created, + // so exit + if (iVerts.size() < 3) + { + return; + } + // If it is a triangle no need to calculate it + if (iVerts.size() == 3) + { + oIndices.push_back(0); + oIndices.push_back(1); + oIndices.push_back(2); + return; + } + + // Create a list of vertices + std::vector tVerts = iVerts; + + while (true) + { + // For every vertex + for (int i = 0; i < int(tVerts.size()); i++) + { + // pPrev = the previous vertex in the list + Vertex pPrev; + if (i == 0) + { + pPrev = tVerts[tVerts.size() - 1]; + } + else + { + pPrev = tVerts[i - 1]; + } + + // pCur = the current vertex; + Vertex pCur = tVerts[i]; + + // pNext = the next vertex in the list + Vertex pNext; + if (i == tVerts.size() - 1) + { + pNext = tVerts[0]; + } + else + { + pNext = tVerts[i + 1]; + } + + // Check to see if there are only 3 verts left + // if so this is the last triangle + if (tVerts.size() == 3) + { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(tVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + if (tVerts.size() == 4) + { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + Vector3 tempVec; + for (int j = 0; j < int(tVerts.size()); j++) + { + if (tVerts[j].Position != pCur.Position && tVerts[j].Position != pPrev.Position && tVerts[j].Position != pNext.Position) + { + tempVec = tVerts[j].Position; + break; + } + } + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + if (iVerts[j].Position == tempVec) + oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + + // If Vertex is not an interior vertex + float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (180 / 3.14159265359); + if (angle <= 0 && angle >= 180) + continue; + + // If any vertices are within this triangle + bool inTri = false; + for (int j = 0; j < int(iVerts.size()); j++) + { + if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, pNext.Position) && iVerts[j].Position != pPrev.Position + && iVerts[j].Position != pCur.Position && iVerts[j].Position != pNext.Position) + { + inTri = true; + break; + } + } + if (inTri) + continue; + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + // Delete pCur from the list + for (int j = 0; j < int(tVerts.size()); j++) + { + if (tVerts[j].Position == pCur.Position) + { + tVerts.erase(tVerts.begin() + j); + break; + } + } + + // reset i to the start + // -1 since loop will add 1 to it + i = -1; + } + + // if no triangles were created + if (oIndices.size() == 0) + break; + + // if no more vertices + if (tVerts.size() == 0) + break; + } + } + + // Load Materials from .mtl file + bool LoadMaterials(std::string path) + { + // If the file is not a material file return false + if (path.substr(path.size() - 4, path.size()) != ".mtl") + return false; + + std::ifstream file(path); + + // If the file is not found return false + if (!file.is_open()) + return false; + + Material tempMaterial; + + bool listening = false; + + // Go through each line looking for material variables + std::string curline; + while (std::getline(file, curline)) + { + // new material and material name + if (algorithm::firstToken(curline) == "newmtl") + { + if (!listening) + { + listening = true; + + if (curline.size() > 7) + { + tempMaterial.name = algorithm::tail(curline); + } + else + { + tempMaterial.name = "none"; + } + } + else + { + // Generate the material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Clear Loaded Material + tempMaterial = Material(); + + if (curline.size() > 7) + { + tempMaterial.name = algorithm::tail(curline); + } + else + { + tempMaterial.name = "none"; + } + } + } + // Ambient Color + if (algorithm::firstToken(curline) == "Ka") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Ka.X = std::stof(temp[0]); + tempMaterial.Ka.Y = std::stof(temp[1]); + tempMaterial.Ka.Z = std::stof(temp[2]); + } + // Diffuse Color + if (algorithm::firstToken(curline) == "Kd") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Kd.X = std::stof(temp[0]); + tempMaterial.Kd.Y = std::stof(temp[1]); + tempMaterial.Kd.Z = std::stof(temp[2]); + } + // Specular Color + if (algorithm::firstToken(curline) == "Ks") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Ks.X = std::stof(temp[0]); + tempMaterial.Ks.Y = std::stof(temp[1]); + tempMaterial.Ks.Z = std::stof(temp[2]); + } + // Specular Exponent + if (algorithm::firstToken(curline) == "Ns") + { + tempMaterial.Ns = std::stof(algorithm::tail(curline)); + } + // Optical Density + if (algorithm::firstToken(curline) == "Ni") + { + tempMaterial.Ni = std::stof(algorithm::tail(curline)); + } + // Dissolve + if (algorithm::firstToken(curline) == "d") + { + tempMaterial.d = std::stof(algorithm::tail(curline)); + } + // Illumination + if (algorithm::firstToken(curline) == "illum") + { + tempMaterial.illum = std::stoi(algorithm::tail(curline)); + } + // Ambient Texture Map + if (algorithm::firstToken(curline) == "map_Ka") + { + tempMaterial.map_Ka = algorithm::tail(curline); + } + // Diffuse Texture Map + if (algorithm::firstToken(curline) == "map_Kd") + { + tempMaterial.map_Kd = algorithm::tail(curline); + } + // Specular Texture Map + if (algorithm::firstToken(curline) == "map_Ks") + { + tempMaterial.map_Ks = algorithm::tail(curline); + } + // Specular Hightlight Map + if (algorithm::firstToken(curline) == "map_Ns") + { + tempMaterial.map_Ns = algorithm::tail(curline); + } + // Alpha Texture Map + if (algorithm::firstToken(curline) == "map_d") + { + tempMaterial.map_d = algorithm::tail(curline); + } + // Bump Map + if (algorithm::firstToken(curline) == "map_Bump" || algorithm::firstToken(curline) == "map_bump" || algorithm::firstToken(curline) == "bump") + { + tempMaterial.map_bump = algorithm::tail(curline); + } + } + + // Deal with last material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Test to see if anything was loaded + // If not return false + if (LoadedMaterials.empty()) + return false; + // If so return true + else + return true; + } + }; +} // namespace objl diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp new file mode 100644 index 000000000..77d5d53a5 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -0,0 +1,105 @@ +#include "ProjectCreator.h" + +class CustomMapLoader +{ + void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo) + { + int meshVertexCount = OBJMesh->Vertices.size(); + int meshIndexCount = OBJMesh->Indices.size(); + int surfVertexStart = projInfo->gfxInfo.vertexCount; + int surfIndexStart = projInfo->gfxInfo.indexCount; + + projInfo->gfxInfo.vertexCount += meshVertexCount; + projInfo->gfxInfo.indexCount += meshIndexCount; + + projInfo->gfxInfo.vertices = (customMapVertex*)realloc(projInfo->gfxInfo.vertices, sizeof(customMapVertex) * projInfo->gfxInfo.vertexCount); + projInfo->gfxInfo.indices = (uint16_t*)realloc(projInfo->gfxInfo.indices, sizeof(uint16_t) * projInfo->gfxInfo.indexCount); + + customMapVertex* surfVertices = &projInfo->gfxInfo.vertices[surfVertexStart]; + uint16_t* surfIndices = &projInfo->gfxInfo.indices[surfIndexStart]; + + meshSurface->firstIndex_Index = surfIndexStart; + meshSurface->firstVertexIndex = surfVertexStart; + meshSurface->triCount = meshIndexCount / 3; + _ASSERT(meshIndexCount % 3 == 0); + + for (int i = 0; i < meshIndexCount; i++) + { + _ASSERT(OBJMesh->Indices[i] < UINT16_MAX); + surfIndices[i] = OBJMesh->Indices[i]; + } + + for (int i = 0; i < meshVertexCount; i++) + { + objl::Vertex* meshVertex = &OBJMesh->Vertices[i]; + customMapVertex* surfVertex = &surfVertices[i]; + + surfVertex->pos.x = meshVertex->Position.X; + surfVertex->pos.y = meshVertex->Position.Y; + surfVertex->pos.z = meshVertex->Position.Z; + + surfVertex->color[0] = OBJMesh->MeshMaterial.Kd.X; + surfVertex->color[1] = OBJMesh->MeshMaterial.Kd.Y; + surfVertex->color[2] = OBJMesh->MeshMaterial.Kd.Z; + surfVertex->color[3] = 1.0f; + + surfVertex->texCoord[0] = meshVertex->TextureCoordinate.X; + surfVertex->texCoord[1] = meshVertex->TextureCoordinate.Y; + + surfVertex->normal.x = meshVertex->Normal.X; + surfVertex->normal.y = meshVertex->Normal.Y; + surfVertex->normal.z = meshVertex->Normal.Z; + + // TODO: fix tangents, seems to work for now though + surfVertex->tangent.x = 1.0f; + surfVertex->tangent.y = 0.0f; + surfVertex->tangent.z = 0.0f; + + surfVertex->packedLmapCoord = 0; + + surfVertex->binormalSign = 0.0f; + } + } + + customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) + { + std::string objFilePath = searchPath.GetPath() + "custom_map/world.obj"; + + objl::Loader OBJloader; + bool isLoaded = OBJloader.LoadFile(objFilePath); + if (!isLoaded) + { + printf("OBJLoader: unable to load obj file %s\n", objFilePath.c_str()); + return NULL; + } + + customMapInfo* projInfo = new customMapInfo; + + projInfo->name = projectName; + projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + + projInfo->gfxInfo.surfaceCount = OBJloader.LoadedMeshes.size(); + projInfo->gfxInfo.surfaces = new worldSurface[projInfo->gfxInfo.surfaceCount]; + + projInfo->gfxInfo.vertexCount = 0; + projInfo->gfxInfo.indexCount = 0; + projInfo->gfxInfo.vertices = (customMapVertex*)malloc(1); + projInfo->gfxInfo.indices = (uint16_t*)malloc(1); + + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + objl::Mesh* currMesh = &OBJloader.LoadedMeshes[i]; + worldSurface* currSurface = &projInfo->gfxInfo.surfaces[i]; + + currSurface->materialName = currMesh->MeshMaterial.name; + currSurface->reflectionProbeIndex = 0; + currSurface->primaryLightIndex = 0; + currSurface->lightmapIndex = 0; + currSurface->flags = 0; + + parseMesh(currMesh, currSurface, projInfo); + } + + return projInfo; + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h new file mode 100644 index 000000000..8c6a54088 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h @@ -0,0 +1,13 @@ +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "Game/T6/T6.h" +using namespace T6; + +#include "OBJ_Loader.h" + +class CustomMapInfo +{ +public: + static void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo); + static customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h new file mode 100644 index 000000000..30e71cbf7 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -0,0 +1,73 @@ +#include "Game/T6/T6.h" +using namespace T6; + +class CMUtil +{ +public: + // BO2 uses a different coordinate system, so this converts it back from OpenGLs default + static vec3_t convertToBO2Coords(vec3_t OGL_coordinate) + { + vec3_t result; + result.x = OGL_coordinate.x; + result.y = -OGL_coordinate.z; + result.z = OGL_coordinate.y; + return result; + } + + // BO2 uses a weird coordinate system, so this converts it to OpenGLs default + static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate) + { + vec3_t result; + result.x = bo2_coordinate.x; + result.y = bo2_coordinate.z; + result.z = -bo2_coordinate.y; + return result; + } + + static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs) + { + if (currmins->x > newmins->x) + currmins->x = newmins->x; + + if (newmaxs->x > currmaxs->x) + currmaxs->x = newmaxs->x; + + if (currmins->y > newmins->y) + currmins->y = newmins->y; + + if (newmaxs->y > currmaxs->y) + currmaxs->y = newmaxs->y; + + if (currmins->z > newmins->z) + currmins->z = newmins->z; + + if (newmaxs->z > currmaxs->z) + currmaxs->z = newmaxs->z; + } + + static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs) + { + if (currmins->x > point->x) + currmins->x = point->x; + + if (point->x > currmaxs->x) + currmaxs->x = point->x; + + if (currmins->y > point->y) + currmins->y = point->y; + + if (point->y > currmaxs->y) + currmaxs->y = point->y; + + if (currmins->z > point->z) + currmins->z = point->z; + + if (point->z > currmaxs->z) + currmaxs->z = point->z; + } + + static int allignBy128(int size) + { + return ((size + 127) & 0xFFFFFF80); + } +}; diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 37afdf895..5c16d7196 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -40,6 +40,7 @@ #include "Weapon/RawLoaderWeaponT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" +#include "CustomMap/LoaderCustomMapT6.h" #include #include @@ -334,6 +335,7 @@ namespace T6 collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); + // custom maps have no default } void ConfigureGlobalAssetPoolsLoaders(AssetCreatorCollection& collection, Zone& zone) @@ -386,6 +388,7 @@ namespace T6 collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); + collection.AddAssetCreator(std::make_unique>(zone)); } void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) @@ -446,6 +449,8 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(CreateRawZBarrierLoader(memory, searchPath, zone)); collection.AddAssetCreator(CreateGdtZBarrierLoader(memory, searchPath, gdt, zone)); + + collection.AddAssetCreator(CreateCustomMapLoader(memory, searchPath)); } } // namespace diff --git a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp index 765172d66..46c0a3faa 100644 --- a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp +++ b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp @@ -9,6 +9,8 @@ AssetNameResolver::AssetNameResolver() { for (auto assetType = 0; assetType < ASSET_TYPE_COUNT; assetType++) AddAssetTypeName(assetType, *GameAssetPoolT6::AssetTypeNameByType(assetType)); + + AddAssetTypeName(ASSET_TYPE_CUSTOM_MAP, "custom_map"); } GameId AssetNameResolver::GetGameId() const From 54114a96ae94b14afe25885c2f40ddada80cc855 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 7 Jul 2025 23:54:05 +0800 Subject: [PATCH 003/123] WIP fix, loading is currently not working correctly --- .../Game/T6/CustomMap/CustomMapConsts.h | 3 + .../Game/T6/CustomMap/CustomMapLinker.cpp | 3294 +++++++++-------- .../Game/T6/CustomMap/CustomMapLinker.h | 1669 ++++++++- .../Game/T6/CustomMap/LoaderCustomMapT6.cpp | 6 +- .../Game/T6/CustomMap/LoaderCustomMapT6.h | 2 +- .../Game/T6/CustomMap/ProjectCreator.cpp | 154 +- .../Game/T6/CustomMap/ProjectCreator.h | 5 +- src/ObjLoading/Game/T6/CustomMap/Util.h | 3 + src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 4 +- 9 files changed, 3400 insertions(+), 1740 deletions(-) diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index a8b1d3e02..ccda4ba6e 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -1,3 +1,6 @@ +#pragma once + + #include #define DYN_ENT_COUNT 0 diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp index 798438e7f..e865f6ba7 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp @@ -1,1646 +1,1648 @@ -#include "CustomMapLinker.h" - -#include "Utils/Pack.h" -#include "Util.h" -#include "CustomMapConsts.h" -#include "BinarySpacePartitionTreePreCalc.h" - -#include -#include -using json = nlohmann::json; - -class CustomMapLinker -{ - MemoryManager& m_memory; - ISearchPath& m_search_path; - Zone& m_zone; - AssetCreationContext& m_context; - - bool hasLinkFailed; - - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_zone(zone), - m_context(context) - { - hasLinkFailed = false; - } - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) - { - int vertexCount = projInfo->gfxInfo.vertexCount; - customMapVertex* worldVertices = projInfo->gfxInfo.vertices; - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); - GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { - customMapVertex* WorldVertex = &worldVertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); - - GfxVertex->binormalSign = WorldVertex->binormalSign; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); - - GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; - } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; - - // we don't use vd1 but still needs to be initialised - // the data type varies and 0x20 is enough for all types - gfxWorld->draw.vertexDataSize1 = 0x20; - gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - - int indexCount = projInfo->gfxInfo.indexCount; - _ASSERT(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; - gfxWorld->draw.indices = new uint16_t[indexCount]; - for (int i = 0; i < indexCount; i += 3) - { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; - } - - return true; - } - - struct s_sortedSurf - { - int surfaceIndex; - int vertexCount; - }; - - bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) - { - return surf0.vertexCount > surf1.vertexCount; - } - - void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) - { - bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); - if (!overwriteResult) - return; - - unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; - gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (int i = 0; i < surfaceCount; i++) - { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxInfo.surfaces[i]; - - currSurface->lightmapIndex = objSurface->lightmapIndex; - currSurface->primaryLightIndex = objSurface->primaryLightIndex; - currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; - currSurface->flags = objSurface->flags; - - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->firstIndex_Index; - - currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; - - auto* assetInfo = m_context.LoadDependency(objSurface->materialName); - if (assetInfo == NULL) - { - printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); - assetInfo = m_context.LoadDependency(defaultMaterialName); - _ASSERT(assetInfo != NULL); - } - currSurface->material = assetInfo->Asset(); - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) - { - CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); - } - - // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; - } - - // sort all vertexes by vertex count - // unsure if this does anything - s_sortedSurf* sortedSurfs = new s_sortedSurf[surfaceCount]; - for (int i = 0; i < surfaceCount; i++) - { - sortedSurfs[i].surfaceIndex = i; - sortedSurfs[i].vertexCount = gfxWorld->dpvs.surfaces[i].tris.triCount * 3; - } - std::sort(sortedSurfs, &sortedSurfs[surfaceCount], compareSurfaces); - - gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = sortedSurfs[i].surfaceIndex; - } - - gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); - - // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; - memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); - - gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; - } - -#define SMODEL_FLAG_NO_SHADOW 1 -#define SMODEL_FLAG_IS_LIT 2 - - void overwriteMapSModels(GfxWorld* gfxWorld) - { - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - /* - for (unsigned int i = 0; i < modelCount; i++) - { - auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - json currModelJs = js["models"][i]; - - // TODO: load custom xmodels - std::string modelName = currModelJs["model"]; - auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); - if (xModelAsset == NULL) - { - printf("Custom model (%s) not supported!\n", modelName.c_str()); - hasLinkFailed = true; - return; - } - currModel->model = (XModel*)xModelAsset->m_ptr; - - currModel->placement.origin.x = currModelJs["origin"]["x"]; - currModel->placement.origin.y = currModelJs["origin"]["y"]; - currModel->placement.origin.z = currModelJs["origin"]["z"]; - currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); - currModel->placement.axis[0].x = currModelJs["forward"]["x"]; - currModel->placement.axis[0].y = currModelJs["forward"]["y"]; - currModel->placement.axis[0].z = currModelJs["forward"]["z"]; - currModel->placement.axis[1].x = currModelJs["right"]["x"]; - currModel->placement.axis[1].y = currModelJs["right"]["y"]; - currModel->placement.axis[1].z = currModelJs["right"]["z"]; - currModel->placement.axis[2].x = currModelJs["up"]["x"]; - currModel->placement.axis[2].y = currModelJs["up"]["y"]; - currModel->placement.axis[2].z = currModelJs["up"]["z"]; - currModel->placement.scale = currModelJs["scale"]; - - // mins and maxs are calculated in world space not local space - // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame - currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - - currModel->cullDist = currModelJs["cullDist"]; - currModel->flags = currModelJs["flags"]; - currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; - currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; - - // unknown use / unused - currModel->smid = i; - memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - currModel->invScaleSq = 0.0f; - currModel->lightingHandle = 0; - currModel->colorsIndex = 0; - currModel->visibility = 0; - - // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // TODO: calculate lighting and store it here - currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - } - */ - - // all visdata is alligned by 128 - gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; - for (unsigned int i = 0; i < modelCount; i++) - { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) - gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; - } - memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); - - gfxWorld->dpvs.usageCount = 0; - } - - void cleanGfxWorld(GfxWorld* gfxWorld) - { - gfxWorld->checksum = 0; - - // Remove Coronas - gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; - - // Remove exposure volumes - gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; - gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; - - // Remove hero lights - gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; - gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; - - // remove LUT data - gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; - gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; - - // remove occluders - gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; - - // remove Siege Skins - gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; - - // remove outdoor bounds - gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; - - // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; - - // remove shadow maps - gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; - gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; - - // remove stream info - gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; - gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; - - // remove sun data - memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); - gfxWorld->sun.hasValidData = false; - - // Remove Water - gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; - - // Remove Fog - gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; - gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; - gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; - gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; - - // materialMemory is unused - gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; - - // sunLight is overwritten by the game, just needs to be a valid pointer - gfxWorld->sunLight = new GfxLight; - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); - } - - void overwriteGfxLights(GfxWorld* gfxWorld) - { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 - - gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; - } - - gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; - } - - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; - if (lightEntShadowVisSize != 0) - { - gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); - } - else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } - } - - void overwriteLightGrid(GfxWorld* gfxWorld) - { - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 0; - gfxWorld->lightGrid.maxs[1] = 0; - gfxWorld->lightGrid.maxs[2] = 0; - - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 - gfxWorld->lightGrid.offset = 0.0f; - - // if rowDataStart's first value is -1, it will not look up any lightmap data - gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; - *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); - - gfxWorld->lightGrid.rawRowDataSize = 0; - gfxWorld->lightGrid.rawRowData = NULL; - - gfxWorld->lightGrid.colorCount = 0; - gfxWorld->lightGrid.colors = NULL; - - gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; - - gfxWorld->lightGrid.entryCount = 0; - gfxWorld->lightGrid.entries = NULL; - - gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; - } - - void updateGfxCells(GfxWorld* gfxWorld) - { - // Cells are basically data used to determine what can be seen and what cant be seen - // Right now custom maps have no optimisation so there is only 1 cell - int cellCount = 1; - - gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; - - int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); - gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); - - gfxWorld->cells = new GfxCell[cellCount]; - gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].reflectionProbeCount = 0; - gfxWorld->cells[0].reflectionProbes = NULL; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; - - // AABB trees are used to detect what should be rendered and what shouldn't - // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn - gfxWorld->cells[0].aabbTreeCount = 1; - gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; - gfxWorld->cells[0].aabbTree[0].childCount = 0; - gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; - gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) - { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; - } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node - // Nodes mnode_t.cellIndex indexes gfxWorld->cells - // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes - gfxWorld->nodeCount = 1; - gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; - gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 - gfxWorld->dpvsPlanes.planes = NULL; - } - - void updateWorldBounds(GfxWorld* gfxWorld) - { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - - for (int i = 0; i < gfxWorld->surfaceCount; i++) - { - CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); - } - } - - void overwriteModels(GfxWorld* gfxWorld) - { - gfxWorld->modelCount = 1; - gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - - // first model is the world model, the world json doesn't include it so its added here - gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - - // for (int i = 1; i < gfxWorld->modelCount; i++) - //{ - // auto currEntModel = &gfxWorld->models[i]; - // json currEntModelJs = js["entityModels"][i - 1]; - // - // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; - // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; - // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; - // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; - // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; - // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; - // - // currEntModel->surfaceCount = 0; - // currEntModel->startSurfIndex = (unsigned int)(-1); - // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); - // } - } - - void updateSunData(GfxWorld* gfxWorld) - { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = 0.001; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; - - gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; - gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; - gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; - gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; - gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; - gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; - gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; - gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; - gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; - gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; - gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; - gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; - gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; - gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; - gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; - gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; - } - - void updateReflectionProbeData(GfxWorld* gfxWorld) - { - gfxWorld->draw.reflectionProbeCount = 1; - - gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; - - // default values taken from mp_dig - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; - - gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - - std::string probeImageName = "*reflection_probe0"; - auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) - { - printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); - } - - void overwriteLightmapData(GfxWorld* gfxWorld) - { - gfxWorld->draw.lightmapCount = 1; - - gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - - std::string secondaryTexture = "*lightmap0_secondary"; - auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) - { - printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL - gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); - } - - void overwriteSkyBox(GfxWorld* gfxWorld) - { - const char* skyBoxName = "skybox_mp_dig"; - gfxWorld->skyBoxModel = skyBoxName; - - if (m_context.LoadDependency(skyBoxName) == NULL) - { - printf("WARN: Unable to find the skybox model %s\n", skyBoxName); - } - - // default skybox values from mp_dig - gfxWorld->skyDynIntensity.angle0 = 0.0f; - gfxWorld->skyDynIntensity.angle1 = 0.0f; - gfxWorld->skyDynIntensity.factor0 = 1.0f; - gfxWorld->skyDynIntensity.factor1 = 1.0f; - } - - void updateDynEntData(GfxWorld* gfxWorld) - { - gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; - gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 - gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; - gfxWorld->dpvsDyn.usageCount = 0; - - int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; - gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); - - int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; - gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); - gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); - - gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); - } - - void updateOutdoors(GfxWorld* gfxWorld) - { - float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); - float xScale = -(xRecip * gfxWorld->mins.x); - - float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); - float yScale = -(yRecip * gfxWorld->mins.y); - - float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); - float zScale = -(zRecip * gfxWorld->mins.z); - - memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); - - gfxWorld->outdoorLookupMatrix[0].x = xRecip; - gfxWorld->outdoorLookupMatrix[1].y = yRecip; - gfxWorld->outdoorLookupMatrix[2].z = zRecip; - gfxWorld->outdoorLookupMatrix[3].x = xScale; - gfxWorld->outdoorLookupMatrix[3].y = yScale; - gfxWorld->outdoorLookupMatrix[3].z = zScale; - gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - - std::string outdoorImageName = std::string("$outdoor"); - auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) - { - printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); - hasLinkFailed = true; - return; - } - gfxWorld->outdoorImage = outdoorImageAsset->Asset(); - } - - void createGfxWorld(customMapInfo* projInfo) - { - GfxWorld* gfxWorld = new GfxWorld; - gfxWorld->baseName = _strdup(projInfo->name.c_str()); - gfxWorld->name = _strdup(projInfo->bspName.c_str()); - - // Default values taken from mp_dig - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; - - cleanGfxWorld(gfxWorld); - - overwriteMapSurfaces(projInfo, gfxWorld); - - overwriteMapSModels(gfxWorld); - - overwriteLightmapData(gfxWorld); - - overwriteSkyBox(gfxWorld); - - updateReflectionProbeData(gfxWorld); - - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); - - updateOutdoors(gfxWorld); - - // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); - - overwriteLightGrid(gfxWorld); - - overwriteGfxLights(gfxWorld); - - overwriteModels(gfxWorld); - - updateSunData(gfxWorld); - - updateDynEntData(gfxWorld); - - m_context.AddAsset(gfxWorld->name, gfxWorld); - } - - void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) - { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - out_origin->x = temp.x * 0.5f; - out_origin->y = temp.y * 0.5f; - out_origin->z = temp.z * 0.5f; - - // Half-size is half the difference: (max - min) / 2 - temp.x = maxs->x - mins->x; - temp.y = maxs->y - mins->y; - temp.z = maxs->z - mins->z; - out_halfSize->x = temp.x * 0.5f; - out_halfSize->y = temp.y * 0.5f; - out_halfSize->z = temp.z * 0.5f; - } - - bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) - { - - unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); - - return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; - } - - float distBetweenPoints(vec3_t p1, vec3_t p2) - { - float x = p2.x - p1.x; - float y = p2.y - p1.y; - float z = p2.z - p1.z; - return sqrtf((x * x) + (y * y) + (z * z)); - } - - - - void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) - { - if (node->isLeaf) - { - (*numLeafs)++; - // there won't be an AABB tree when objectList is empty - if (node->u.leaf->getObjectCount() > 0) - { - *numAABBTrees += node->u.leaf->getObjectCount() + 1; - - if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->u.leaf->getObjectCount(); - } - } - else - { - (*numPlanes)++; - (*numNodes)++; - traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - } - } - - vec3_t normalX = {1.0f, 0.0f, 0.0f}; - vec3_t normalY = {0.0f, 1.0f, 0.0f}; - vec3_t normalZ = {0.0f, 0.0f, 1.0f}; - - int currPlaneCount = 0; - int currNodeCount = 0; - int currLeafCount = 0; - int currAABBCount = 0; - - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) - { - _ASSERT(node->isLeaf); - - int objectCount = node->u.leaf->getObjectCount(); - int firstAABBIndex = currAABBCount; - currAABBCount += objectCount + 1; - - // calculate root AABB node mins and maxs - // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs - // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; - auto firstPartition = &clipMap->partitions[firstPartitionIndex]; - uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t aabbMins; - vec3_t aabbMaxs; - aabbMins.x = firstVert->x; - aabbMins.y = firstVert->y; - aabbMins.z = firstVert->z; - aabbMaxs.x = firstVert->x; - aabbMaxs.y = firstVert->y; - aabbMaxs.z = firstVert->z; - for (int i = 0; i < objectCount; i++) - { - int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; - auto currPartition = &clipMap->partitions[currPartitionIndex]; - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = clipMap->verts[vertIndex]; - CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); - } - } - } - // create root AABB node - CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; - aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); - rootAABB->materialIndex = 0; - rootAABB->childCount = objectCount; - rootAABB->u.firstChildIndex = firstAABBIndex + 1; - - // populate child AABB nodes - for (int i = 0; i < objectCount; i++) - { - CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; - - currAabbTree->materialIndex = 0; - currAabbTree->childCount = 0; - currAabbTree->u.partitionIndex = currPartitionIndex; - - // calculate partition origin and half size - CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; - uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; - vec3_t* firstVertex = &clipMap->verts[firstUind]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVertex->x; - mins.y = firstVertex->y; - mins.z = firstVertex->z; - maxs.x = firstVertex->x; - maxs.y = firstVertex->y; - maxs.z = firstVertex->z; - for (int i = 1; i < aabbPartition->nuinds; i++) - { - uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; - vec3_t* currVertex = &clipMap->verts[currUind]; - - CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); - } - - aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); - - } - - return firstAABBIndex; - } - - // returns the index corresponding to the BSPTree* node parsed - int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) - { - new cplane_s; - new cNode_t; - new cLeaf_s; - new CollisionAabbTree; - - if (node->isLeaf) - { - int currLeafIndex = currLeafCount; - currLeafCount++; - cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; - - currLeaf->cluster = 0; - currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence - - // unused when leafBrushNode == 0 - currLeaf->mins.x = 0.0f; - currLeaf->mins.y = 0.0f; - currLeaf->mins.z = 0.0f; - currLeaf->maxs.x = 0.0f; - currLeaf->maxs.y = 0.0f; - currLeaf->maxs.z = 0.0f; - currLeaf->leafBrushNode = 0; - - if (node->u.leaf->getObjectCount() > 0) - { - currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); - currLeaf->collAabbCount = 1; - } - else - { - currLeaf->firstCollAabbIndex = 0; - currLeaf->collAabbCount = 0; - } - - return -1 - currLeafIndex; - } - else - { - cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; - currPlaneCount++; - - if (node->u.node->axis == AXIS_X) - { - // X is unchanged when going from OGL x -> BO2 x - currPlane->normal = normalX; - - // converting OGL -> BO2 X coords doesn't change the x coords at all, so - // the dist stays the same - currPlane->dist = node->u.node->distance; - } - else - { - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // convert the z normal to the y normal, but don't negate it. Negative normals don't do - // what is expected when the game uses them - _ASSERT(node->u.node->axis == AXIS_Z); - currPlane->normal = normalY; - - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = -node->u.node->distance; - } - - bool foundType = false; - if (currPlane->normal.x == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 0; - } - else if (currPlane->normal.y == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 1; - } - else if (currPlane->normal.z == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 2; - } - else - _ASSERT(foundType); - - currPlane->signbits = 0; - if (currPlane->normal.x < 0.0f) - currPlane->signbits |= 1; - if (currPlane->normal.y < 0.0f) - currPlane->signbits |= 2; - if (currPlane->normal.z < 0.0f) - currPlane->signbits |= 4; - - currPlane->pad[0] = 0; - currPlane->pad[1] = 0; - - int currNodeIndex = currNodeCount; - currNodeCount++; - cNode_t* currNode = &clipMap->nodes[currNodeIndex]; - - currNode->plane = currPlane; - // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): - // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, - // the normal vector flips and objects behind the plane are now in front, and vise versa - // so the back node now represents the front, and the front node represents the back. - // Do the OGL -> Bo2 coord change on paper and it will make sense - if (currPlane->type == 1) - { - currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); - currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); - } - else - { - currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); - currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); - } - - return currNodeIndex; - } - } - - void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) - { - int numPlanes = 0; - int numNodes = 0; - int numLeafs = 0; - int numAABBTrees = 0; - int maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; - clipMap->numNodes = numNodes; - clipMap->nodes = new cNode_t[clipMap->numNodes]; - // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. - // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. - clipMap->aabbTreeCount = numAABBTrees; - clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; - memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); - currLeafCount = 1; - - populateBSPTree_r(clipMap, tree); - - _ASSERT(clipMap->info.planeCount == currPlaneCount); - _ASSERT(clipMap->numNodes == currNodeCount); - _ASSERT(clipMap->numLeafs == currLeafCount); - _ASSERT(clipMap->aabbTreeCount == currAABBCount); - } - - void createClipMap(customMapInfo* projInfo) - { - clipMap_t* clipMap = new clipMap_t; - - clipMap->name = _strdup(projInfo->bspName.c_str()); - - std::string mapEntsName = projInfo->bspName.c_str(); - auto mapEntsAsset = m_context.LoadDependency(mapEntsName); - _ASSERT(mapEntsAsset != NULL); - clipMap->mapEnts = mapEntsAsset->Asset(); - - clipMap->pInfo = NULL; - - clipMap->box_model.mins.x = 0.0f; - clipMap->box_model.mins.y = 0.0f; - clipMap->box_model.mins.z = 0.0f; - clipMap->box_model.maxs.x = 0.0f; - clipMap->box_model.maxs.y = 0.0f; - clipMap->box_model.maxs.z = 0.0f; - clipMap->box_model.radius = 0.0f; - clipMap->box_model.info = NULL; - clipMap->box_model.leaf.mins.x = 3.4028235e38; - clipMap->box_model.leaf.mins.y = 3.4028235e38; - clipMap->box_model.leaf.mins.z = 3.4028235e38; - clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive - clipMap->box_model.leaf.maxs.y = -3.4028235e38; - clipMap->box_model.leaf.maxs.z = -3.4028235e38; - clipMap->box_model.leaf.brushContents = -1; - clipMap->box_model.leaf.terrainContents = 0; - clipMap->box_model.leaf.cluster = 0; - clipMap->box_model.leaf.collAabbCount = 0; - clipMap->box_model.leaf.firstCollAabbIndex = 0; - clipMap->box_model.leaf.leafBrushNode = 0; - - clipMap->box_brush = new cbrush_t; - clipMap->box_brush->axial_sflags[0][0] = -1; - clipMap->box_brush->axial_sflags[0][1] = -1; - clipMap->box_brush->axial_sflags[0][2] = -1; - clipMap->box_brush->axial_sflags[1][0] = -1; - clipMap->box_brush->axial_sflags[1][1] = -1; - clipMap->box_brush->axial_sflags[1][2] = -1; - clipMap->box_brush->axial_cflags[0][0] = -1; - clipMap->box_brush->axial_cflags[0][1] = -1; - clipMap->box_brush->axial_cflags[0][2] = -1; - clipMap->box_brush->axial_cflags[1][0] = -1; - clipMap->box_brush->axial_cflags[1][1] = -1; - clipMap->box_brush->axial_cflags[1][2] = -1; - clipMap->box_brush->contents = -1; - clipMap->box_brush->mins.x = 0.0f; - clipMap->box_brush->mins.y = 0.0f; - clipMap->box_brush->mins.z = 0.0f; - clipMap->box_brush->maxs.x = 0.0f; - clipMap->box_brush->maxs.y = 0.0f; - clipMap->box_brush->maxs.z = 0.0f; - clipMap->box_brush->numsides = 0; - clipMap->box_brush->numverts = 0; - clipMap->box_brush->sides = NULL; - clipMap->box_brush->verts = NULL; - - clipMap->numClusters = 1; - clipMap->vised = 0; - clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; - clipMap->visibility = new char[clipMap->clusterBytes]; - memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); - - clipMap->isInUse = true; - clipMap->checksum = 0; - - clipMap->num_constraints = 0; - clipMap->constraints = NULL; - clipMap->max_ropes = 32; - clipMap->ropes = new rope_t[clipMap->max_ropes]; - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); - - clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = NULL; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = NULL; - clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = NULL; - clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = NULL; - clipMap->info.numBrushes = NULL; - clipMap->info.brushes = NULL; - clipMap->info.brushBounds = NULL; - clipMap->info.brushContents = NULL; - - clipMap->originalDynEntCount = DYN_ENT_COUNT; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; - clipMap->dynEntCount[1] = 0; - clipMap->dynEntCount[2] = 0; - clipMap->dynEntCount[3] = 0; - - // assume that there are 0 dyn ents from here on - clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; - clipMap->dynEntClientList[1] = NULL; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); - - clipMap->dynEntServerList[0] = NULL; - clipMap->dynEntServerList[1] = NULL; - - clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; - clipMap->dynEntCollList[1] = NULL; - clipMap->dynEntCollList[2] = NULL; - clipMap->dynEntCollList[3] = NULL; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); - - clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; - clipMap->dynEntPoseList[1] = NULL; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); - - clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; - clipMap->dynEntDefList[1] = NULL; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - for (int i = 0; i < clipMap->dynEntCount[0]; i++) - { - DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; - currDef->physConstraints[0] = 0x1FF; - currDef->physConstraints[1] = 0x1FF; - currDef->physConstraints[2] = 0x1FF; - currDef->physConstraints[3] = 0x1FF; - } - - clipMap->numSubModels = 1; - clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = NULL; - - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; - clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; - clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; - clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; - clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; - clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; - clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - - clipMap->numStaticModels = 0; - clipMap->staticModelList = NULL; - - clipMap->info.numMaterials = 1; - clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); - clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; - - int collisionVertexCount = projInfo->gfxInfo.vertexCount; - vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; - for (int i = 0; i < collisionVertexCount; i++) - { - convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = convertedCollisionVerts; - - // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - const int maxVerts = UINT16_MAX - 1; - if (collisionVertexCount > maxVerts) - { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); - hasLinkFailed = true; - return; - } - - clipMap->triCount = projInfo->gfxInfo.indexCount / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri - int triIndex = 0; - for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) - { - clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; - clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; - clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; - triIndex++; - } - - int partitionCount = 0; - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) - { - int triCount = projInfo->gfxInfo.surfaces[i].triCount; - int subdividedSize = triCount / 16; // integer division used - partitionCount += subdividedSize; - - // add another partition if the triangle count doesn't add up to a multiple of 16 - if (triCount % 16 != 0) - partitionCount++; - } - clipMap->partitionCount = partitionCount; - clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; - - int partitionIndex = 0; - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) - { - int triCount = projInfo->gfxInfo.surfaces[i].triCount; - int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; - int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; - int firstTri = firstIndex / 3; - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. - // NEED TO FIX - // fixup the tri indices. - // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] - // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] - // this loop fixes the index buffer by adding the firstVertex to each index - for (int k = 0; k < triCount; k++) - { - clipMap->triIndices[k + firstTri][0] += firstVertex; - clipMap->triIndices[k + firstTri][1] += firstVertex; - clipMap->triIndices[k + firstTri][2] += firstVertex; - } - - while (triCount > 0) - { - if (triCount > 16) - clipMap->partitions[partitionIndex].triCount = 16; - else - clipMap->partitions[partitionIndex].triCount = triCount; - - clipMap->partitions[partitionIndex].firstTri = firstTri; - - triCount -= 16; - firstIndex += 16 * 3; - partitionIndex++; - } - } - - int uindCount = 0; - clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - int uniqueVertBufferCount = 0; - uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (int m = 0; m < uniqueVertBufferCount; m++) - { - if (uniqueVertBuffer[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - { - uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; - uniqueVertBufferCount++; - } - } - } - - _ASSERT(uniqueVertBufferCount <= 3 * 16); - - currPartition->fuind = uindCount; - currPartition->nuinds = uniqueVertBufferCount; - memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); - uindCount += uniqueVertBufferCount; - } - _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); - clipMap->info.nuinds = uindCount; - - // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls - int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); - - // BSP creation must go last as it depends on unids, tris and verts already being populated - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, - // and then convert them back when it is being parsed into the clipmap. Requires some hacky - // logic, check populateBSPTree_r and addAABBTreeFromLeaf - - vec3_t* firstVert = &clipMap->verts[0]; - vec3_t clipMins; - vec3_t clipMaxs; - clipMins.x = firstVert->x; - clipMins.y = firstVert->y; - clipMins.z = firstVert->z; - clipMaxs.x = firstVert->x; - clipMaxs.y = firstVert->y; - clipMaxs.z = firstVert->z; - clipMins = CMUtil::convertFromBO2Coords(clipMins); - clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); - for (int i = 1; i < clipMap->vertCount; i++) - { - vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); - CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); - } - - BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVert->x; - mins.y = firstVert->y; - mins.z = firstVert->z; - maxs.x = firstVert->x; - maxs.y = firstVert->y; - maxs.z = firstVert->z; - mins = CMUtil::convertFromBO2Coords(mins); - maxs = CMUtil::convertFromBO2Coords(maxs); - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); - } - } - - Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - - tree->addObject(currObject); - } - - populateBSPTree(clipMap, tree); - - m_context.AddAsset(clipMap->name, clipMap); - } - - void createComWorld(customMapInfo* projInfo) - { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset - ComWorld* comWorld = new ComWorld; - comWorld->name = _strdup(projInfo->bspName.c_str()); - comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; - comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; - - ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; - memset(defaultLight, 0, sizeof(ComPrimaryLight)); - - ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - memset(sunLight, 0, sizeof(ComPrimaryLight)); - sunLight->type = 1; - // Below are default values taken from mp_dig - sunLight->diffuseColor.r = 1.0f; - sunLight->diffuseColor.g = 1.0f; - sunLight->diffuseColor.b = 1.0f; - sunLight->diffuseColor.a = 0.0f; - sunLight->dir.x = -0.2410777360200882; - sunLight->dir.y = -0.8407384753227234; - sunLight->dir.z = 0.48480960726737976; - - m_context.AddAsset(comWorld->name, comWorld); - } - - void createMapEnts(customMapInfo* projInfo) - { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - const auto file = m_search_path.Open("custom_map/entities.json"); - if (!file.IsOpen()) - { - printf("WARN: can't find entity json!\n"); - return; - } - - json entJs = json::parse(*file.m_stream); - - std::string entityString; - int entityCount = entJs["entityCount"]; - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entJs["entities"][i]; - - if (i == 0) - { - std::string className = currEntity["classname"]; - if (className.compare("worldspawn") != 0) - { - printf("ERROR: first entity in the map entity string must be the worldspawn class!"); - hasLinkFailed = true; - return; - } - } - - entityString.append("{\n"); - - for (auto& element : currEntity.items()) - { - std::string key = element.key(); - std::string value = element.value(); - entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); - } - - entityString.append("}\n"); - } - entityString.pop_back(); // remove newline character from the end of the string - - mapEnts->entityString = _strdup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - m_context.AddAsset(mapEnts->name, mapEnts); - } - - void createGameWorldMp(customMapInfo* projInfo) - { - GameWorldMp* gameWorldMp = new GameWorldMp; - - gameWorldMp->name = _strdup(projInfo->bspName.c_str()); - - gameWorldMp->path.nodeCount = 0; - gameWorldMp->path.originalNodeCount = 0; - gameWorldMp->path.visBytes = 0; - gameWorldMp->path.smoothBytes = 0; - gameWorldMp->path.nodeTreeCount = 0; - - int nodeCount = gameWorldMp->path.nodeCount + 128; - gameWorldMp->path.nodes = new pathnode_t[nodeCount]; - gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - - gameWorldMp->path.pathVis = NULL; - gameWorldMp->path.smoothCache = NULL; - gameWorldMp->path.nodeTree = NULL; - - m_context.AddAsset(gameWorldMp->name, gameWorldMp); - } - - void createSkinnedVerts(customMapInfo* projInfo) - { - SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; - skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; - - m_context.AddAsset("skinnedverts", skinnedVerts); - } - - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) - { - if (assetName.length() == 0) - return NULL; - - FootstepTableDef* footstepTable = new FootstepTableDef; - footstepTable->name = _strdup(assetName.c_str()); - memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); - - m_context.AddAsset(assetName, footstepTable); - - return footstepTable; - } - - void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) - { - if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } - - addEmptyFootstepTableAsset("default_1st_person"); - addEmptyFootstepTableAsset("default_3rd_person"); - addEmptyFootstepTableAsset("default_1st_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_loud"); - addEmptyFootstepTableAsset("default_ai"); - } - - bool linkCustomMap(customMapInfo* projInfo) - { - _ASSERT(projInfo != NULL); - - createGfxWorld(projInfo); - createComWorld(projInfo); - createMapEnts(projInfo); - createGameWorldMp(projInfo); - createSkinnedVerts(projInfo); - createClipMap(projInfo); // must go last (requires gfx and mapents asset) - - checkAndAddDefaultRequiredAssets(projInfo); - - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - return true; - } -}; +//#include "CustomMapLinker.h" +// +//#include "Utils/Pack.h" +//#include "Util.h" +//#include "CustomMapConsts.h" +//#include "BinarySpacePartitionTreePreCalc.h" +// +//#include +//#include +//using json = nlohmann::json; +// +//class CustomMapLinker +//{ +//public: +// CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) +// : m_memory(memory), +// m_search_path(searchPath), +// m_zone(zone), +// m_context(context) +// { +// hasLinkFailed = false; +// } +// +// bool linkCustomMap(customMapInfo* projInfo) +// { +// _ASSERT(projInfo != NULL); +// +// createGfxWorld(projInfo); +// createComWorld(projInfo); +// createMapEnts(projInfo); +// createGameWorldMp(projInfo); +// createSkinnedVerts(projInfo); +// createClipMap(projInfo); // must go last (requires gfx and mapents asset) +// +// checkAndAddDefaultRequiredAssets(projInfo); +// +// if (hasLinkFailed) +// { +// printf("Custom Map link has failed.\n"); +// return false; +// } +// +// return true; +// } +// +//private: +// MemoryManager& m_memory; +// ISearchPath& m_search_path; +// Zone& m_zone; +// AssetCreationContext& m_context; +// +// bool hasLinkFailed; +// +// // TODO vd1: +// // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour +// // could fix by removing sub textures or figure out how they are created and redo that +// // its not an important issue though +// bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) +// { +// int vertexCount = projInfo->gfxInfo.vertexCount; +// customMapVertex* worldVertices = projInfo->gfxInfo.vertices; +// +// gfxWorld->draw.vertexCount = vertexCount; +// gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); +// GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; +// for (int i = 0; i < vertexCount; i++) +// { +// customMapVertex* WorldVertex = &worldVertices[i]; +// GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; +// +// GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); +// +// GfxVertex->binormalSign = WorldVertex->binormalSign; +// +// GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); +// +// GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); +// +// GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); +// +// GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); +// +// GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; +// } +// gfxWorld->draw.vd0.data = (char*)vertexBuffer; +// +// // we don't use vd1 but still needs to be initialised +// // the data type varies and 0x20 is enough for all types +// gfxWorld->draw.vertexDataSize1 = 0x20; +// gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; +// memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); +// +// int indexCount = projInfo->gfxInfo.indexCount; +// _ASSERT(indexCount % 3 == 0); +// gfxWorld->draw.indexCount = indexCount; +// gfxWorld->draw.indices = new uint16_t[indexCount]; +// for (int i = 0; i < indexCount; i += 3) +// { +// // the editor orders their vertices opposite to bo2, so its converted here +// gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; +// gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; +// gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; +// } +// +// return true; +// } +// +// struct s_sortedSurf +// { +// int surfaceIndex; +// int vertexCount; +// }; +// +// bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) +// { +// return surf0.vertexCount > surf1.vertexCount; +// } +// +// void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) +// { +// bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); +// if (!overwriteResult) +// return; +// +// unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; +// gfxWorld->surfaceCount = surfaceCount; +// gfxWorld->dpvs.staticSurfaceCount = surfaceCount; +// gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; +// for (int i = 0; i < surfaceCount; i++) +// { +// auto currSurface = &gfxWorld->dpvs.surfaces[i]; +// auto objSurface = &projInfo->gfxInfo.surfaces[i]; +// +// currSurface->lightmapIndex = objSurface->lightmapIndex; +// currSurface->primaryLightIndex = objSurface->primaryLightIndex; +// currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; +// currSurface->flags = objSurface->flags; +// +// currSurface->tris.triCount = objSurface->triCount; +// currSurface->tris.baseIndex = objSurface->firstIndex_Index; +// +// currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); +// currSurface->tris.vertexDataOffset1 = 0; +// +// auto* assetInfo = m_context.LoadDependency(objSurface->materialName); +// if (assetInfo == NULL) +// { +// printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); +// assetInfo = m_context.LoadDependency(defaultMaterialName); +// _ASSERT(assetInfo != NULL); +// } +// currSurface->material = assetInfo->Asset(); +// +// GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; +// currSurface->bounds[0].x = firstVert[0].xyz.x; +// currSurface->bounds[0].y = firstVert[0].xyz.y; +// currSurface->bounds[0].z = firstVert[0].xyz.z; +// currSurface->bounds[1].x = firstVert[0].xyz.x; +// currSurface->bounds[1].y = firstVert[0].xyz.y; +// currSurface->bounds[1].z = firstVert[0].xyz.z; +// for (int k = 0; k < currSurface->tris.triCount * 3; k++) +// { +// CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); +// } +// +// // unused values +// currSurface->tris.mins.x = 0.0f; +// currSurface->tris.mins.y = 0.0f; +// currSurface->tris.mins.z = 0.0f; +// currSurface->tris.maxs.x = 0.0f; +// currSurface->tris.maxs.y = 0.0f; +// currSurface->tris.maxs.z = 0.0f; +// currSurface->tris.himipRadiusInvSq = 0.0f; +// currSurface->tris.vertexCount = 0; +// currSurface->tris.firstVertex = 0; +// } +// +// // sort all vertexes by vertex count +// // unsure if this does anything +// s_sortedSurf* sortedSurfs = new s_sortedSurf[surfaceCount]; +// for (int i = 0; i < surfaceCount; i++) +// { +// sortedSurfs[i].surfaceIndex = i; +// sortedSurfs[i].vertexCount = gfxWorld->dpvs.surfaces[i].tris.triCount * 3; +// } +// std::sort(sortedSurfs, &sortedSurfs[surfaceCount], compareSurfaces); +// +// gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; +// for (int i = 0; i < surfaceCount; i++) +// { +// gfxWorld->dpvs.sortedSurfIndex[i] = sortedSurfs[i].surfaceIndex; +// } +// +// gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; +// memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); +// +// // all visdata is alligned by 128 +// gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); +// gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; +// gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; +// gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; +// gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; +// gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; +// gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; +// memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); +// memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); +// memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); +// memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); +// memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); +// memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); +// +// gfxWorld->dpvs.litSurfsBegin = 0; +// gfxWorld->dpvs.litSurfsEnd = surfaceCount; +// gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; +// gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; +// gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; +// gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; +// gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; +// gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; +// } +// +//#define SMODEL_FLAG_NO_SHADOW 1 +//#define SMODEL_FLAG_IS_LIT 2 +// +// void overwriteMapSModels(GfxWorld* gfxWorld) +// { +// unsigned int modelCount = 0; +// gfxWorld->dpvs.smodelCount = modelCount; +// +// gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; +// gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; +// /* +// for (unsigned int i = 0; i < modelCount; i++) +// { +// auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; +// auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; +// json currModelJs = js["models"][i]; +// +// // TODO: load custom xmodels +// std::string modelName = currModelJs["model"]; +// auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); +// if (xModelAsset == NULL) +// { +// printf("Custom model (%s) not supported!\n", modelName.c_str()); +// hasLinkFailed = true; +// return; +// } +// currModel->model = (XModel*)xModelAsset->m_ptr; +// +// currModel->placement.origin.x = currModelJs["origin"]["x"]; +// currModel->placement.origin.y = currModelJs["origin"]["y"]; +// currModel->placement.origin.z = currModelJs["origin"]["z"]; +// currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); +// currModel->placement.axis[0].x = currModelJs["forward"]["x"]; +// currModel->placement.axis[0].y = currModelJs["forward"]["y"]; +// currModel->placement.axis[0].z = currModelJs["forward"]["z"]; +// currModel->placement.axis[1].x = currModelJs["right"]["x"]; +// currModel->placement.axis[1].y = currModelJs["right"]["y"]; +// currModel->placement.axis[1].z = currModelJs["right"]["z"]; +// currModel->placement.axis[2].x = currModelJs["up"]["x"]; +// currModel->placement.axis[2].y = currModelJs["up"]["y"]; +// currModel->placement.axis[2].z = currModelJs["up"]["z"]; +// currModel->placement.scale = currModelJs["scale"]; +// +// // mins and maxs are calculated in world space not local space +// // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame +// currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; +// currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; +// currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; +// currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; +// currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; +// currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; +// +// currModel->cullDist = currModelJs["cullDist"]; +// currModel->flags = currModelJs["flags"]; +// currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; +// currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; +// +// // unknown use / unused +// currModel->smid = i; +// memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); +// currModel->invScaleSq = 0.0f; +// currModel->lightingHandle = 0; +// currModel->colorsIndex = 0; +// currModel->visibility = 0; +// +// // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow +// // TODO: calculate lighting and store it here +// currModel->lmapVertexInfo[0].numLmapVertexColors = 0; +// currModel->lmapVertexInfo[0].lmapVertexColors = NULL; +// currModel->lmapVertexInfo[1].numLmapVertexColors = 0; +// currModel->lmapVertexInfo[1].lmapVertexColors = NULL; +// currModel->lmapVertexInfo[2].numLmapVertexColors = 0; +// currModel->lmapVertexInfo[2].lmapVertexColors = NULL; +// currModel->lmapVertexInfo[3].numLmapVertexColors = 0; +// currModel->lmapVertexInfo[3].lmapVertexColors = NULL; +// } +// */ +// +// // all visdata is alligned by 128 +// gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); +// gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; +// gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; +// gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; +// gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; +// gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; +// for (unsigned int i = 0; i < modelCount; i++) +// { +// if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) +// gfxWorld->dpvs.smodelCastsShadow[i] = 1; +// else +// gfxWorld->dpvs.smodelCastsShadow[i] = 0; +// } +// memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); +// memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); +// memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); +// memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); +// +// gfxWorld->dpvs.usageCount = 0; +// } +// +// void cleanGfxWorld(GfxWorld* gfxWorld) +// { +// gfxWorld->checksum = 0; +// +// // Remove Coronas +// gfxWorld->coronaCount = 0; +// gfxWorld->coronas = NULL; +// +// // Remove exposure volumes +// gfxWorld->exposureVolumeCount = 0; +// gfxWorld->exposureVolumes = NULL; +// gfxWorld->exposureVolumePlaneCount = 0; +// gfxWorld->exposureVolumePlanes = NULL; +// +// // Remove hero lights +// gfxWorld->heroLightCount = 0; +// gfxWorld->heroLights = NULL; +// gfxWorld->heroLightTreeCount = 0; +// gfxWorld->heroLightTree = NULL; +// +// // remove LUT data +// gfxWorld->lutVolumeCount = 0; +// gfxWorld->lutVolumes = NULL; +// gfxWorld->lutVolumePlaneCount = 0; +// gfxWorld->lutVolumePlanes = NULL; +// +// // remove occluders +// gfxWorld->numOccluders = 0; +// gfxWorld->occluders = NULL; +// +// // remove Siege Skins +// gfxWorld->numSiegeSkinInsts = 0; +// gfxWorld->siegeSkinInsts = NULL; +// +// // remove outdoor bounds +// gfxWorld->numOutdoorBounds = 0; +// gfxWorld->outdoorBounds = NULL; +// +// // remove materials +// gfxWorld->ropeMaterial = NULL; +// gfxWorld->lutMaterial = NULL; +// gfxWorld->waterMaterial = NULL; +// gfxWorld->coronaMaterial = NULL; +// +// // remove shadow maps +// gfxWorld->shadowMapVolumeCount = 0; +// gfxWorld->shadowMapVolumes = NULL; +// gfxWorld->shadowMapVolumePlaneCount = 0; +// gfxWorld->shadowMapVolumePlanes = NULL; +// +// // remove stream info +// gfxWorld->streamInfo.aabbTreeCount = 0; +// gfxWorld->streamInfo.aabbTrees = NULL; +// gfxWorld->streamInfo.leafRefCount = 0; +// gfxWorld->streamInfo.leafRefs = NULL; +// +// // remove sun data +// memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); +// gfxWorld->sun.hasValidData = false; +// +// // Remove Water +// gfxWorld->waterDirection = 0.0f; +// gfxWorld->waterBuffers[0].buffer = NULL; +// gfxWorld->waterBuffers[0].bufferSize = NULL; +// gfxWorld->waterBuffers[1].buffer = NULL; +// gfxWorld->waterBuffers[1].bufferSize = NULL; +// +// // Remove Fog +// gfxWorld->worldFogModifierVolumeCount = 0; +// gfxWorld->worldFogModifierVolumes = NULL; +// gfxWorld->worldFogModifierVolumePlaneCount = 0; +// gfxWorld->worldFogModifierVolumePlanes = NULL; +// gfxWorld->worldFogVolumeCount = 0; +// gfxWorld->worldFogVolumes = NULL; +// gfxWorld->worldFogVolumePlaneCount = 0; +// gfxWorld->worldFogVolumePlanes = NULL; +// +// // materialMemory is unused +// gfxWorld->materialMemoryCount = 0; +// gfxWorld->materialMemory = NULL; +// +// // sunLight is overwritten by the game, just needs to be a valid pointer +// gfxWorld->sunLight = new GfxLight; +// memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); +// } +// +// void overwriteGfxLights(GfxWorld* gfxWorld) +// { +// // there must be 2 or more lights, first is the default light and second is the sun +// gfxWorld->primaryLightCount = 2; +// gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 +// +// gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; +// for (int i = 0; i < gfxWorld->primaryLightCount; i++) +// { +// gfxWorld->shadowGeom[i].smodelCount = 0; +// gfxWorld->shadowGeom[i].surfaceCount = 0; +// gfxWorld->shadowGeom[i].smodelIndex = NULL; +// gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; +// } +// +// gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; +// for (int i = 0; i < gfxWorld->primaryLightCount; i++) +// { +// gfxWorld->lightRegion[i].hullCount = 0; +// gfxWorld->lightRegion[i].hulls = NULL; +// } +// +// int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; +// if (lightEntShadowVisSize != 0) +// { +// gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; +// memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); +// } +// else +// { +// gfxWorld->primaryLightEntityShadowVis = NULL; +// } +// } +// +// void overwriteLightGrid(GfxWorld* gfxWorld) +// { +// gfxWorld->lightGrid.mins[0] = 0; +// gfxWorld->lightGrid.mins[1] = 0; +// gfxWorld->lightGrid.mins[2] = 0; +// gfxWorld->lightGrid.maxs[0] = 0; +// gfxWorld->lightGrid.maxs[1] = 0; +// gfxWorld->lightGrid.maxs[2] = 0; +// +// gfxWorld->lightGrid.rowAxis = 0; // default value +// gfxWorld->lightGrid.colAxis = 1; // default value +// gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 +// gfxWorld->lightGrid.offset = 0.0f; +// +// // if rowDataStart's first value is -1, it will not look up any lightmap data +// gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; +// *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); +// +// gfxWorld->lightGrid.rawRowDataSize = 0; +// gfxWorld->lightGrid.rawRowData = NULL; +// +// gfxWorld->lightGrid.colorCount = 0; +// gfxWorld->lightGrid.colors = NULL; +// +// gfxWorld->lightGrid.coeffCount = 0; +// gfxWorld->lightGrid.coeffs = NULL; +// +// gfxWorld->lightGrid.entryCount = 0; +// gfxWorld->lightGrid.entries = NULL; +// +// gfxWorld->lightGrid.skyGridVolumeCount = 0; +// gfxWorld->lightGrid.skyGridVolumes = NULL; +// } +// +// void updateGfxCells(GfxWorld* gfxWorld) +// { +// // Cells are basically data used to determine what can be seen and what cant be seen +// // Right now custom maps have no optimisation so there is only 1 cell +// int cellCount = 1; +// +// gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; +// +// int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); +// gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; +// memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); +// +// gfxWorld->cells = new GfxCell[cellCount]; +// gfxWorld->cells[0].portalCount = 0; +// gfxWorld->cells[0].portals = NULL; +// gfxWorld->cells[0].reflectionProbeCount = 0; +// gfxWorld->cells[0].reflectionProbes = NULL; +// gfxWorld->cells[0].mins.x = gfxWorld->mins.x; +// gfxWorld->cells[0].mins.y = gfxWorld->mins.y; +// gfxWorld->cells[0].mins.z = gfxWorld->mins.z; +// gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; +// gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; +// gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; +// +// // AABB trees are used to detect what should be rendered and what shouldn't +// // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn +// gfxWorld->cells[0].aabbTreeCount = 1; +// gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; +// gfxWorld->cells[0].aabbTree[0].childCount = 0; +// gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; +// gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; +// gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; +// gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; +// gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; +// for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) +// { +// gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; +// } +// gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; +// gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; +// gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; +// gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; +// gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; +// gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; +// +// gfxWorld->dpvsPlanes.cellCount = cellCount; +// +// int sceneEntCellBitsCount = cellCount * 512; +// gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; +// memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); +// +// // nodes have the struct mnode_t, and there must be at least 1 node +// // Nodes mnode_t.cellIndex indexes gfxWorld->cells +// // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes +// gfxWorld->nodeCount = 1; +// gfxWorld->planeCount = 0; +// gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; +// gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 +// gfxWorld->dpvsPlanes.planes = NULL; +// } +// +// void updateWorldBounds(GfxWorld* gfxWorld) +// { +// gfxWorld->mins.x = 0.0f; +// gfxWorld->mins.y = 0.0f; +// gfxWorld->mins.z = 0.0f; +// gfxWorld->maxs.x = 0.0f; +// gfxWorld->maxs.y = 0.0f; +// gfxWorld->maxs.z = 0.0f; +// +// for (int i = 0; i < gfxWorld->surfaceCount; i++) +// { +// CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); +// } +// } +// +// void overwriteModels(GfxWorld* gfxWorld) +// { +// gfxWorld->modelCount = 1; +// gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; +// +// // first model is the world model, the world json doesn't include it so its added here +// gfxWorld->models[0].startSurfIndex = 0; +// gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; +// gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; +// gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; +// gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; +// gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; +// gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; +// gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; +// memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); +// +// // for (int i = 1; i < gfxWorld->modelCount; i++) +// //{ +// // auto currEntModel = &gfxWorld->models[i]; +// // json currEntModelJs = js["entityModels"][i - 1]; +// // +// // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; +// // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; +// // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; +// // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; +// // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; +// // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; +// // +// // currEntModel->surfaceCount = 0; +// // currEntModel->startSurfIndex = (unsigned int)(-1); +// // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); +// // } +// } +// +// void updateSunData(GfxWorld* gfxWorld) +// { +// // default values taken from mp_dig +// gfxWorld->sunParse.fogTransitionTime = 0.001; +// gfxWorld->sunParse.name[0] = 0x00; +// +// gfxWorld->sunParse.initWorldSun->control = 0; +// gfxWorld->sunParse.initWorldSun->exposure = 2.5f; +// gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; +// gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; +// gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; +// gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; +// gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; +// gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; +// gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; +// gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; +// gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; +// gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; +// gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; +// gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; +// gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; +// gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; +// gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; +// gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; +// gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; +// gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; +// gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; +// +// gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; +// gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; +// gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; +// gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; +// gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; +// gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; +// gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; +// gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; +// gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; +// gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; +// gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; +// gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; +// gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; +// gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; +// gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; +// gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; +// } +// +// void updateReflectionProbeData(GfxWorld* gfxWorld) +// { +// gfxWorld->draw.reflectionProbeCount = 1; +// +// gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; +// memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); +// +// gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; +// +// // default values taken from mp_dig +// gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; +// +// gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; +// gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; +// gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; +// +// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; +// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; +// +// gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; +// gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; +// +// std::string probeImageName = "*reflection_probe0"; +// auto probeImageAsset = m_context.LoadDependency(probeImageName); +// if (probeImageAsset == NULL) +// { +// printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); +// hasLinkFailed = true; +// return; +// } +// gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); +// } +// +// void overwriteLightmapData(GfxWorld* gfxWorld) +// { +// gfxWorld->draw.lightmapCount = 1; +// +// gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; +// gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; +// gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; +// +// // always set to 0 +// memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); +// memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); +// +// std::string secondaryTexture = "*lightmap0_secondary"; +// auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); +// if (secondaryTextureAsset == NULL) +// { +// printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); +// hasLinkFailed = true; +// return; +// } +// gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL +// gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); +// } +// +// void overwriteSkyBox(GfxWorld* gfxWorld) +// { +// const char* skyBoxName = "skybox_mp_dig"; +// gfxWorld->skyBoxModel = skyBoxName; +// +// if (m_context.LoadDependency(skyBoxName) == NULL) +// { +// printf("WARN: Unable to find the skybox model %s\n", skyBoxName); +// } +// +// // default skybox values from mp_dig +// gfxWorld->skyDynIntensity.angle0 = 0.0f; +// gfxWorld->skyDynIntensity.angle1 = 0.0f; +// gfxWorld->skyDynIntensity.factor0 = 1.0f; +// gfxWorld->skyDynIntensity.factor1 = 1.0f; +// } +// +// void updateDynEntData(GfxWorld* gfxWorld) +// { +// gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; +// gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; +// gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 +// gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; +// gfxWorld->dpvsDyn.usageCount = 0; +// +// int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; +// gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; +// gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; +// memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); +// +// int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; +// gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; +// gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; +// gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; +// gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; +// gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; +// gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; +// memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); +// memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); +// memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); +// +// int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); +// gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; +// gfxWorld->primaryLightDynEntShadowVis[1] = NULL; +// memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); +// +// gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; +// gfxWorld->sceneDynBrush = NULL; +// memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); +// } +// +// void updateOutdoors(GfxWorld* gfxWorld) +// { +// float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); +// float xScale = -(xRecip * gfxWorld->mins.x); +// +// float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); +// float yScale = -(yRecip * gfxWorld->mins.y); +// +// float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); +// float zScale = -(zRecip * gfxWorld->mins.z); +// +// memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); +// +// gfxWorld->outdoorLookupMatrix[0].x = xRecip; +// gfxWorld->outdoorLookupMatrix[1].y = yRecip; +// gfxWorld->outdoorLookupMatrix[2].z = zRecip; +// gfxWorld->outdoorLookupMatrix[3].x = xScale; +// gfxWorld->outdoorLookupMatrix[3].y = yScale; +// gfxWorld->outdoorLookupMatrix[3].z = zScale; +// gfxWorld->outdoorLookupMatrix[3].w = 1.0f; +// +// std::string outdoorImageName = std::string("$outdoor"); +// auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); +// if (outdoorImageAsset == NULL) +// { +// printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); +// hasLinkFailed = true; +// return; +// } +// gfxWorld->outdoorImage = outdoorImageAsset->Asset(); +// } +// +// void createGfxWorld(customMapInfo* projInfo) +// { +// GfxWorld* gfxWorld = new GfxWorld; +// gfxWorld->baseName = _strdup(projInfo->name.c_str()); +// gfxWorld->name = _strdup(projInfo->bspName.c_str()); +// +// // Default values taken from mp_dig +// gfxWorld->lightingFlags = 0; +// gfxWorld->lightingQuality = 4096; +// +// cleanGfxWorld(gfxWorld); +// +// overwriteMapSurfaces(projInfo, gfxWorld); +// +// overwriteMapSModels(gfxWorld); +// +// overwriteLightmapData(gfxWorld); +// +// overwriteSkyBox(gfxWorld); +// +// updateReflectionProbeData(gfxWorld); +// +// // world bounds are based on surface mins/maxs +// // Other update functions depend on the bounds being set first +// updateWorldBounds(gfxWorld); +// +// updateOutdoors(gfxWorld); +// +// // gfx cells depend on surface/smodel count +// updateGfxCells(gfxWorld); +// +// overwriteLightGrid(gfxWorld); +// +// overwriteGfxLights(gfxWorld); +// +// overwriteModels(gfxWorld); +// +// updateSunData(gfxWorld); +// +// updateDynEntData(gfxWorld); +// +// m_context.AddAsset(gfxWorld->name, gfxWorld); +// } +// +// void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) +// { +// // Origin is the midpoint: (min + max) / 2 +// vec3_t temp; +// temp.x = mins->x + maxs->x; +// temp.y = mins->y + maxs->y; +// temp.z = mins->z + maxs->z; +// out_origin->x = temp.x * 0.5f; +// out_origin->y = temp.y * 0.5f; +// out_origin->z = temp.z * 0.5f; +// +// // Half-size is half the difference: (max - min) / 2 +// temp.x = maxs->x - mins->x; +// temp.y = maxs->y - mins->y; +// temp.z = maxs->z - mins->z; +// out_halfSize->x = temp.x * 0.5f; +// out_halfSize->y = temp.y * 0.5f; +// out_halfSize->z = temp.z * 0.5f; +// } +// +// bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) +// { +// +// unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); +// +// return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; +// } +// +// float distBetweenPoints(vec3_t p1, vec3_t p2) +// { +// float x = p2.x - p1.x; +// float y = p2.y - p1.y; +// float z = p2.z - p1.z; +// return sqrtf((x * x) + (y * y) + (z * z)); +// } +// +// +// +// void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) +// { +// if (node->isLeaf) +// { +// (*numLeafs)++; +// // there won't be an AABB tree when objectList is empty +// if (node->u.leaf->getObjectCount() > 0) +// { +// *numAABBTrees += node->u.leaf->getObjectCount() + 1; +// +// if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) +// *maxObjsPerLeaf = node->u.leaf->getObjectCount(); +// } +// } +// else +// { +// (*numPlanes)++; +// (*numNodes)++; +// traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); +// traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); +// } +// } +// +// vec3_t normalX = {1.0f, 0.0f, 0.0f}; +// vec3_t normalY = {0.0f, 1.0f, 0.0f}; +// vec3_t normalZ = {0.0f, 0.0f, 1.0f}; +// +// int currPlaneCount = 0; +// int currNodeCount = 0; +// int currLeafCount = 0; +// int currAABBCount = 0; +// +// int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) +// { +// _ASSERT(node->isLeaf); +// +// int objectCount = node->u.leaf->getObjectCount(); +// int firstAABBIndex = currAABBCount; +// currAABBCount += objectCount + 1; +// +// // calculate root AABB node mins and maxs +// // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs +// // so we have to recompute every min and max, not hard just tedious +// int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; +// auto firstPartition = &clipMap->partitions[firstPartitionIndex]; +// uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; +// vec3_t* firstVert = &clipMap->verts[firstTri[0]]; +// vec3_t aabbMins; +// vec3_t aabbMaxs; +// aabbMins.x = firstVert->x; +// aabbMins.y = firstVert->y; +// aabbMins.z = firstVert->z; +// aabbMaxs.x = firstVert->x; +// aabbMaxs.y = firstVert->y; +// aabbMaxs.z = firstVert->z; +// for (int i = 0; i < objectCount; i++) +// { +// int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; +// auto currPartition = &clipMap->partitions[currPartitionIndex]; +// +// for (int k = 0; k < currPartition->triCount; k++) +// { +// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; +// for (int l = 0; l < 3; l++) +// { +// uint16_t vertIndex = tri[l]; +// vec3_t vertCoord = clipMap->verts[vertIndex]; +// CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); +// } +// } +// } +// // create root AABB node +// CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; +// aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); +// rootAABB->materialIndex = 0; +// rootAABB->childCount = objectCount; +// rootAABB->u.firstChildIndex = firstAABBIndex + 1; +// +// // populate child AABB nodes +// for (int i = 0; i < objectCount; i++) +// { +// CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; +// int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; +// +// currAabbTree->materialIndex = 0; +// currAabbTree->childCount = 0; +// currAabbTree->u.partitionIndex = currPartitionIndex; +// +// // calculate partition origin and half size +// CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; +// uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; +// vec3_t* firstVertex = &clipMap->verts[firstUind]; +// vec3_t mins; +// vec3_t maxs; +// mins.x = firstVertex->x; +// mins.y = firstVertex->y; +// mins.z = firstVertex->z; +// maxs.x = firstVertex->x; +// maxs.y = firstVertex->y; +// maxs.z = firstVertex->z; +// for (int i = 1; i < aabbPartition->nuinds; i++) +// { +// uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; +// vec3_t* currVertex = &clipMap->verts[currUind]; +// +// CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); +// } +// +// aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); +// +// } +// +// return firstAABBIndex; +// } +// +// // returns the index corresponding to the BSPTree* node parsed +// int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) +// { +// new cplane_s; +// new cNode_t; +// new cLeaf_s; +// new CollisionAabbTree; +// +// if (node->isLeaf) +// { +// int currLeafIndex = currLeafCount; +// currLeafCount++; +// cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; +// +// currLeaf->cluster = 0; +// currLeaf->brushContents = 0; // no brushes used so contents is 0 +// currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence +// +// // unused when leafBrushNode == 0 +// currLeaf->mins.x = 0.0f; +// currLeaf->mins.y = 0.0f; +// currLeaf->mins.z = 0.0f; +// currLeaf->maxs.x = 0.0f; +// currLeaf->maxs.y = 0.0f; +// currLeaf->maxs.z = 0.0f; +// currLeaf->leafBrushNode = 0; +// +// if (node->u.leaf->getObjectCount() > 0) +// { +// currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); +// currLeaf->collAabbCount = 1; +// } +// else +// { +// currLeaf->firstCollAabbIndex = 0; +// currLeaf->collAabbCount = 0; +// } +// +// return -1 - currLeafIndex; +// } +// else +// { +// cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; +// currPlaneCount++; +// +// if (node->u.node->axis == AXIS_X) +// { +// // X is unchanged when going from OGL x -> BO2 x +// currPlane->normal = normalX; +// +// // converting OGL -> BO2 X coords doesn't change the x coords at all, so +// // the dist stays the same +// currPlane->dist = node->u.node->distance; +// } +// else +// { +// // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. +// // convert the z normal to the y normal, but don't negate it. Negative normals don't do +// // what is expected when the game uses them +// _ASSERT(node->u.node->axis == AXIS_Z); +// currPlane->normal = normalY; +// +// // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. +// // just negate it here as it is just the distance from the orgin along the axis +// currPlane->dist = -node->u.node->distance; +// } +// +// bool foundType = false; +// if (currPlane->normal.x == 1.0f) +// { +// _ASSERT(!foundType); +// foundType = true; +// currPlane->type = 0; +// } +// else if (currPlane->normal.y == 1.0f) +// { +// _ASSERT(!foundType); +// foundType = true; +// currPlane->type = 1; +// } +// else if (currPlane->normal.z == 1.0f) +// { +// _ASSERT(!foundType); +// foundType = true; +// currPlane->type = 2; +// } +// else +// _ASSERT(foundType); +// +// currPlane->signbits = 0; +// if (currPlane->normal.x < 0.0f) +// currPlane->signbits |= 1; +// if (currPlane->normal.y < 0.0f) +// currPlane->signbits |= 2; +// if (currPlane->normal.z < 0.0f) +// currPlane->signbits |= 4; +// +// currPlane->pad[0] = 0; +// currPlane->pad[1] = 0; +// +// int currNodeIndex = currNodeCount; +// currNodeCount++; +// cNode_t* currNode = &clipMap->nodes[currNodeIndex]; +// +// currNode->plane = currPlane; +// // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): +// // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, +// // the normal vector flips and objects behind the plane are now in front, and vise versa +// // so the back node now represents the front, and the front node represents the back. +// // Do the OGL -> Bo2 coord change on paper and it will make sense +// if (currPlane->type == 1) +// { +// currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); +// currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); +// } +// else +// { +// currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); +// currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); +// } +// +// return currNodeIndex; +// } +// } +// +// void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) +// { +// int numPlanes = 0; +// int numNodes = 0; +// int numLeafs = 0; +// int numAABBTrees = 0; +// int maxObjsPerLeaf = 0; +// +// traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); +// +// printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); +// +// clipMap->info.planeCount = numPlanes; +// clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; +// clipMap->numNodes = numNodes; +// clipMap->nodes = new cNode_t[clipMap->numNodes]; +// // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. +// // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. +// clipMap->aabbTreeCount = numAABBTrees; +// clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; +// +// currPlaneCount = 0; +// currNodeCount = 0; +// currAABBCount = 0; +// +// // first leaf is always empty +// clipMap->numLeafs = numLeafs + 1; +// clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; +// memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); +// currLeafCount = 1; +// +// populateBSPTree_r(clipMap, tree); +// +// _ASSERT(clipMap->info.planeCount == currPlaneCount); +// _ASSERT(clipMap->numNodes == currNodeCount); +// _ASSERT(clipMap->numLeafs == currLeafCount); +// _ASSERT(clipMap->aabbTreeCount == currAABBCount); +// } +// +// void createClipMap(customMapInfo* projInfo) +// { +// clipMap_t* clipMap = new clipMap_t; +// +// clipMap->name = _strdup(projInfo->bspName.c_str()); +// +// std::string mapEntsName = projInfo->bspName.c_str(); +// auto mapEntsAsset = m_context.LoadDependency(mapEntsName); +// _ASSERT(mapEntsAsset != NULL); +// clipMap->mapEnts = mapEntsAsset->Asset(); +// +// clipMap->pInfo = NULL; +// +// clipMap->box_model.mins.x = 0.0f; +// clipMap->box_model.mins.y = 0.0f; +// clipMap->box_model.mins.z = 0.0f; +// clipMap->box_model.maxs.x = 0.0f; +// clipMap->box_model.maxs.y = 0.0f; +// clipMap->box_model.maxs.z = 0.0f; +// clipMap->box_model.radius = 0.0f; +// clipMap->box_model.info = NULL; +// clipMap->box_model.leaf.mins.x = 3.4028235e38; +// clipMap->box_model.leaf.mins.y = 3.4028235e38; +// clipMap->box_model.leaf.mins.z = 3.4028235e38; +// clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive +// clipMap->box_model.leaf.maxs.y = -3.4028235e38; +// clipMap->box_model.leaf.maxs.z = -3.4028235e38; +// clipMap->box_model.leaf.brushContents = -1; +// clipMap->box_model.leaf.terrainContents = 0; +// clipMap->box_model.leaf.cluster = 0; +// clipMap->box_model.leaf.collAabbCount = 0; +// clipMap->box_model.leaf.firstCollAabbIndex = 0; +// clipMap->box_model.leaf.leafBrushNode = 0; +// +// clipMap->box_brush = new cbrush_t; +// clipMap->box_brush->axial_sflags[0][0] = -1; +// clipMap->box_brush->axial_sflags[0][1] = -1; +// clipMap->box_brush->axial_sflags[0][2] = -1; +// clipMap->box_brush->axial_sflags[1][0] = -1; +// clipMap->box_brush->axial_sflags[1][1] = -1; +// clipMap->box_brush->axial_sflags[1][2] = -1; +// clipMap->box_brush->axial_cflags[0][0] = -1; +// clipMap->box_brush->axial_cflags[0][1] = -1; +// clipMap->box_brush->axial_cflags[0][2] = -1; +// clipMap->box_brush->axial_cflags[1][0] = -1; +// clipMap->box_brush->axial_cflags[1][1] = -1; +// clipMap->box_brush->axial_cflags[1][2] = -1; +// clipMap->box_brush->contents = -1; +// clipMap->box_brush->mins.x = 0.0f; +// clipMap->box_brush->mins.y = 0.0f; +// clipMap->box_brush->mins.z = 0.0f; +// clipMap->box_brush->maxs.x = 0.0f; +// clipMap->box_brush->maxs.y = 0.0f; +// clipMap->box_brush->maxs.z = 0.0f; +// clipMap->box_brush->numsides = 0; +// clipMap->box_brush->numverts = 0; +// clipMap->box_brush->sides = NULL; +// clipMap->box_brush->verts = NULL; +// +// clipMap->numClusters = 1; +// clipMap->vised = 0; +// clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; +// clipMap->visibility = new char[clipMap->clusterBytes]; +// memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); +// +// clipMap->isInUse = true; +// clipMap->checksum = 0; +// +// clipMap->num_constraints = 0; +// clipMap->constraints = NULL; +// clipMap->max_ropes = 32; +// clipMap->ropes = new rope_t[clipMap->max_ropes]; +// memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); +// +// clipMap->info.numBrushSides = 0; +// clipMap->info.brushsides = NULL; +// clipMap->info.leafbrushNodesCount = 0; +// clipMap->info.leafbrushNodes = NULL; +// clipMap->info.numLeafBrushes = 0; +// clipMap->info.leafbrushes = NULL; +// clipMap->info.numBrushVerts = 0; +// clipMap->info.brushVerts = NULL; +// clipMap->info.numBrushes = NULL; +// clipMap->info.brushes = NULL; +// clipMap->info.brushBounds = NULL; +// clipMap->info.brushContents = NULL; +// +// clipMap->originalDynEntCount = DYN_ENT_COUNT; +// clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; +// clipMap->dynEntCount[1] = 0; +// clipMap->dynEntCount[2] = 0; +// clipMap->dynEntCount[3] = 0; +// +// // assume that there are 0 dyn ents from here on +// clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; +// clipMap->dynEntClientList[1] = NULL; +// memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); +// +// clipMap->dynEntServerList[0] = NULL; +// clipMap->dynEntServerList[1] = NULL; +// +// clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; +// clipMap->dynEntCollList[1] = NULL; +// clipMap->dynEntCollList[2] = NULL; +// clipMap->dynEntCollList[3] = NULL; +// memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); +// +// clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; +// clipMap->dynEntPoseList[1] = NULL; +// memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); +// +// clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; +// clipMap->dynEntDefList[1] = NULL; +// memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); +// for (int i = 0; i < clipMap->dynEntCount[0]; i++) +// { +// DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; +// currDef->physConstraints[0] = 0x1FF; +// currDef->physConstraints[1] = 0x1FF; +// currDef->physConstraints[2] = 0x1FF; +// currDef->physConstraints[3] = 0x1FF; +// } +// +// clipMap->numSubModels = 1; +// clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; +// clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; +// clipMap->cmodels[0].leaf.collAabbCount = 0; +// clipMap->cmodels[0].leaf.brushContents = 0; +// clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; +// clipMap->cmodels[0].leaf.mins.x = 0.0f; +// clipMap->cmodels[0].leaf.mins.y = 0.0f; +// clipMap->cmodels[0].leaf.mins.z = 0.0f; +// clipMap->cmodels[0].leaf.maxs.x = 0.0f; +// clipMap->cmodels[0].leaf.maxs.y = 0.0f; +// clipMap->cmodels[0].leaf.maxs.z = 0.0f; +// clipMap->cmodels[0].leaf.leafBrushNode = 0; +// clipMap->cmodels[0].leaf.cluster = 0; +// clipMap->cmodels[0].info = NULL; +// +// auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); +// _ASSERT(gfxWorldAsset != NULL); +// GfxWorld* gfxWorld = gfxWorldAsset->Asset(); +// +// clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; +// clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; +// clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; +// clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; +// clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; +// clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; +// clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; +// +// clipMap->numStaticModels = 0; +// clipMap->staticModelList = NULL; +// +// clipMap->info.numMaterials = 1; +// clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; +// clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); +// clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; +// clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; +// +// int collisionVertexCount = projInfo->gfxInfo.vertexCount; +// vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; +// for (int i = 0; i < collisionVertexCount; i++) +// { +// convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); +// } +// clipMap->vertCount = collisionVertexCount; +// clipMap->verts = convertedCollisionVerts; +// +// // due to tris using uint16_t as the type for indexing the vert array, +// // any vertex count over the uint16_t max means the vertices above it can't be indexed +// const int maxVerts = UINT16_MAX - 1; +// if (collisionVertexCount > maxVerts) +// { +// printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); +// hasLinkFailed = true; +// return; +// } +// +// clipMap->triCount = projInfo->gfxInfo.indexCount / 3; +// clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri +// int triIndex = 0; +// for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) +// { +// clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; +// clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; +// clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; +// triIndex++; +// } +// +// int partitionCount = 0; +// for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) +// { +// int triCount = projInfo->gfxInfo.surfaces[i].triCount; +// int subdividedSize = triCount / 16; // integer division used +// partitionCount += subdividedSize; +// +// // add another partition if the triangle count doesn't add up to a multiple of 16 +// if (triCount % 16 != 0) +// partitionCount++; +// } +// clipMap->partitionCount = partitionCount; +// clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; +// +// int partitionIndex = 0; +// for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) +// { +// int triCount = projInfo->gfxInfo.surfaces[i].triCount; +// int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; +// int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; +// int firstTri = firstIndex / 3; +// +// //////////////////////////////////////////////////////////////////////////////////////////////////// +// // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. +// // NEED TO FIX +// // fixup the tri indices. +// // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] +// // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] +// // this loop fixes the index buffer by adding the firstVertex to each index +// for (int k = 0; k < triCount; k++) +// { +// clipMap->triIndices[k + firstTri][0] += firstVertex; +// clipMap->triIndices[k + firstTri][1] += firstVertex; +// clipMap->triIndices[k + firstTri][2] += firstVertex; +// } +// +// while (triCount > 0) +// { +// if (triCount > 16) +// clipMap->partitions[partitionIndex].triCount = 16; +// else +// clipMap->partitions[partitionIndex].triCount = triCount; +// +// clipMap->partitions[partitionIndex].firstTri = firstTri; +// +// triCount -= 16; +// firstIndex += 16 * 3; +// partitionIndex++; +// } +// } +// +// int uindCount = 0; +// clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition +// for (int i = 0; i < clipMap->partitionCount; i++) +// { +// auto currPartition = &clipMap->partitions[i]; +// +// int uniqueVertBufferCount = 0; +// uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris +// +// for (int k = 0; k < currPartition->triCount; k++) +// { +// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; +// for (int l = 0; l < 3; l++) +// { +// bool isVertexIndexUnique = true; +// uint16_t vertIndex = tri[l]; +// +// for (int m = 0; m < uniqueVertBufferCount; m++) +// { +// if (uniqueVertBuffer[m] == vertIndex) +// { +// isVertexIndexUnique = false; +// break; +// } +// } +// +// if (isVertexIndexUnique) +// { +// uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; +// uniqueVertBufferCount++; +// } +// } +// } +// +// _ASSERT(uniqueVertBufferCount <= 3 * 16); +// +// currPartition->fuind = uindCount; +// currPartition->nuinds = uniqueVertBufferCount; +// memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); +// uindCount += uniqueVertBufferCount; +// } +// _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); +// clipMap->info.nuinds = uindCount; +// +// // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility +// // might do weird stuff on walls +// int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; +// clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; +// memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); +// +// // BSP creation must go last as it depends on unids, tris and verts already being populated +// // HACK: +// // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. +// // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, +// // and then convert them back when it is being parsed into the clipmap. Requires some hacky +// // logic, check populateBSPTree_r and addAABBTreeFromLeaf +// +// vec3_t* firstVert = &clipMap->verts[0]; +// vec3_t clipMins; +// vec3_t clipMaxs; +// clipMins.x = firstVert->x; +// clipMins.y = firstVert->y; +// clipMins.z = firstVert->z; +// clipMaxs.x = firstVert->x; +// clipMaxs.y = firstVert->y; +// clipMaxs.z = firstVert->z; +// clipMins = CMUtil::convertFromBO2Coords(clipMins); +// clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); +// for (int i = 1; i < clipMap->vertCount; i++) +// { +// vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); +// CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); +// } +// +// BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); +// for (int i = 0; i < clipMap->partitionCount; i++) +// { +// auto currPartition = &clipMap->partitions[i]; +// +// uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; +// vec3_t* firstVert = &clipMap->verts[firstTri[0]]; +// vec3_t mins; +// vec3_t maxs; +// mins.x = firstVert->x; +// mins.y = firstVert->y; +// mins.z = firstVert->z; +// maxs.x = firstVert->x; +// maxs.y = firstVert->y; +// maxs.z = firstVert->z; +// mins = CMUtil::convertFromBO2Coords(mins); +// maxs = CMUtil::convertFromBO2Coords(maxs); +// for (int k = 0; k < currPartition->triCount; k++) +// { +// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; +// for (int l = 0; l < 3; l++) +// { +// uint16_t vertIndex = tri[l]; +// vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); +// CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); +// } +// } +// +// Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); +// +// tree->addObject(currObject); +// } +// +// populateBSPTree(clipMap, tree); +// +// m_context.AddAsset(clipMap->name, clipMap); +// } +// +// void createComWorld(customMapInfo* projInfo) +// { +// // all lights that aren't the sunlight or default light need their own GfxLightDef asset +// ComWorld* comWorld = new ComWorld; +// comWorld->name = _strdup(projInfo->bspName.c_str()); +// comWorld->isInUse = 1; +// comWorld->primaryLightCount = 2; +// comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; +// +// ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; +// memset(defaultLight, 0, sizeof(ComPrimaryLight)); +// +// ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; +// memset(sunLight, 0, sizeof(ComPrimaryLight)); +// sunLight->type = 1; +// // Below are default values taken from mp_dig +// sunLight->diffuseColor.r = 1.0f; +// sunLight->diffuseColor.g = 1.0f; +// sunLight->diffuseColor.b = 1.0f; +// sunLight->diffuseColor.a = 0.0f; +// sunLight->dir.x = -0.2410777360200882; +// sunLight->dir.y = -0.8407384753227234; +// sunLight->dir.z = 0.48480960726737976; +// +// m_context.AddAsset(comWorld->name, comWorld); +// } +// +// void createMapEnts(customMapInfo* projInfo) +// { +// MapEnts* mapEnts = new MapEnts; +// +// mapEnts->name = _strdup(projInfo->bspName.c_str()); +// +// // don't need these +// mapEnts->trigger.count = 0; +// mapEnts->trigger.models = NULL; +// mapEnts->trigger.hullCount = 0; +// mapEnts->trigger.hulls = NULL; +// mapEnts->trigger.slabCount = 0; +// mapEnts->trigger.slabs = NULL; +// +// const auto file = m_search_path.Open("custom_map/entities.json"); +// if (!file.IsOpen()) +// { +// printf("WARN: can't find entity json!\n"); +// return; +// } +// +// json entJs = json::parse(*file.m_stream); +// +// std::string entityString; +// int entityCount = entJs["entityCount"]; +// for (int i = 0; i < entityCount; i++) +// { +// auto currEntity = entJs["entities"][i]; +// +// if (i == 0) +// { +// std::string className = currEntity["classname"]; +// if (className.compare("worldspawn") != 0) +// { +// printf("ERROR: first entity in the map entity string must be the worldspawn class!"); +// hasLinkFailed = true; +// return; +// } +// } +// +// entityString.append("{\n"); +// +// for (auto& element : currEntity.items()) +// { +// std::string key = element.key(); +// std::string value = element.value(); +// entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); +// } +// +// entityString.append("}\n"); +// } +// entityString.pop_back(); // remove newline character from the end of the string +// +// mapEnts->entityString = _strdup(entityString.c_str()); +// mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character +// +// m_context.AddAsset(mapEnts->name, mapEnts); +// } +// +// void createGameWorldMp(customMapInfo* projInfo) +// { +// GameWorldMp* gameWorldMp = new GameWorldMp; +// +// gameWorldMp->name = _strdup(projInfo->bspName.c_str()); +// +// gameWorldMp->path.nodeCount = 0; +// gameWorldMp->path.originalNodeCount = 0; +// gameWorldMp->path.visBytes = 0; +// gameWorldMp->path.smoothBytes = 0; +// gameWorldMp->path.nodeTreeCount = 0; +// +// int nodeCount = gameWorldMp->path.nodeCount + 128; +// gameWorldMp->path.nodes = new pathnode_t[nodeCount]; +// gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; +// memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); +// memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); +// +// gameWorldMp->path.pathVis = NULL; +// gameWorldMp->path.smoothCache = NULL; +// gameWorldMp->path.nodeTree = NULL; +// +// m_context.AddAsset(gameWorldMp->name, gameWorldMp); +// } +// +// void createSkinnedVerts(customMapInfo* projInfo) +// { +// SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; +// skinnedVerts->name = "skinnedverts"; +// skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; +// +// m_context.AddAsset("skinnedverts", skinnedVerts); +// } +// +// FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) +// { +// if (assetName.length() == 0) +// return NULL; +// +// FootstepTableDef* footstepTable = new FootstepTableDef; +// footstepTable->name = _strdup(assetName.c_str()); +// memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); +// +// m_context.AddAsset(assetName, footstepTable); +// +// return footstepTable; +// } +// +// void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) +// { +// if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } +// if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } +// +// addEmptyFootstepTableAsset("default_1st_person"); +// addEmptyFootstepTableAsset("default_3rd_person"); +// addEmptyFootstepTableAsset("default_1st_person_quiet"); +// addEmptyFootstepTableAsset("default_3rd_person_quiet"); +// addEmptyFootstepTableAsset("default_3rd_person_loud"); +// addEmptyFootstepTableAsset("default_ai"); +// } +//}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 6dcefb44b..e66c1caa9 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,14 +1,1667 @@ -#include "Game/T6/T6.h" -#include "SearchPath/ISearchPath.h" -#include "Utils/MemoryManager.h" -#include "Asset/IAssetCreator.h" -using namespace T6; +#pragma once -#include "OBJ_Loader.h" +#include "BinarySpacePartitionTreePreCalc.h" +#include "CustomMapConsts.h" +#include "Util.h" +#include "Utils/Pack.h" + +#include +using json = nlohmann::json; class CustomMapLinker { public: - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); - bool linkCustomMap(customMapInfo* projInfo); + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone), + m_context(context) + { + hasLinkFailed = false; + } + + bool linkCustomMap(customMapInfo* projInfo) + { + _ASSERT(projInfo != NULL); + + createGfxWorld(projInfo); + createComWorld(projInfo); + createMapEnts(projInfo); + createGameWorldMp(projInfo); + createSkinnedVerts(projInfo); + createClipMap(projInfo); // must go last (requires gfx and mapents asset) + + checkAndAddDefaultRequiredAssets(projInfo); + + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + return true; + } + +private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + AssetCreationContext& m_context; + + bool hasLinkFailed; + + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxInfo.vertexCount; + customMapVertex* worldVertices = projInfo->gfxInfo.vertices; + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + customMapVertex* WorldVertex = &worldVertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); + + GfxVertex->binormalSign = WorldVertex->binormalSign; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); + + GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxInfo.indexCount; + _ASSERT(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = new uint16_t[indexCount]; + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; + } + + return true; + } + + struct s_sortedSurf + { + int surfaceIndex; + int vertexCount; + }; + + bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) + { + return surf0.vertexCount > surf1.vertexCount; + } + + void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); + if (!overwriteResult) + return; + + unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxInfo.surfaces[i]; + + currSurface->lightmapIndex = objSurface->lightmapIndex; + currSurface->primaryLightIndex = objSurface->primaryLightIndex; + currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; + currSurface->flags = objSurface->flags; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->firstIndex_Index; + + currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + auto* assetInfo = m_context.LoadDependency(objSurface->materialName); + if (assetInfo == NULL) + { + printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); + assetInfo = m_context.LoadDependency(defaultMaterialName); + _ASSERT(assetInfo != NULL); + } + currSurface->material = assetInfo->Asset(); + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // doesn't seem to matter what order the sorted surfs go in + gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = i; + } + + gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // all visdata is alligned by 128 + gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; + memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); + + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + } + +#define SMODEL_FLAG_NO_SHADOW 1 +#define SMODEL_FLAG_IS_LIT 2 + + void overwriteMapSModels(GfxWorld* gfxWorld) + { + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + + gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + /* + for (unsigned int i = 0; i < modelCount; i++) + { + auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + json currModelJs = js["models"][i]; + + // TODO: load custom xmodels + std::string modelName = currModelJs["model"]; + auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); + if (xModelAsset == NULL) + { + printf("Custom model (%s) not supported!\n", modelName.c_str()); + hasLinkFailed = true; + return; + } + currModel->model = (XModel*)xModelAsset->m_ptr; + + currModel->placement.origin.x = currModelJs["origin"]["x"]; + currModel->placement.origin.y = currModelJs["origin"]["y"]; + currModel->placement.origin.z = currModelJs["origin"]["z"]; + currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); + currModel->placement.axis[0].x = currModelJs["forward"]["x"]; + currModel->placement.axis[0].y = currModelJs["forward"]["y"]; + currModel->placement.axis[0].z = currModelJs["forward"]["z"]; + currModel->placement.axis[1].x = currModelJs["right"]["x"]; + currModel->placement.axis[1].y = currModelJs["right"]["y"]; + currModel->placement.axis[1].z = currModelJs["right"]["z"]; + currModel->placement.axis[2].x = currModelJs["up"]["x"]; + currModel->placement.axis[2].y = currModelJs["up"]["y"]; + currModel->placement.axis[2].z = currModelJs["up"]["z"]; + currModel->placement.scale = currModelJs["scale"]; + + // mins and maxs are calculated in world space not local space + // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame + currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + + currModel->cullDist = currModelJs["cullDist"]; + currModel->flags = currModelJs["flags"]; + currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; + currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; + + // unknown use / unused + currModel->smid = i; + memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + currModel->invScaleSq = 0.0f; + currModel->lightingHandle = 0; + currModel->colorsIndex = 0; + currModel->visibility = 0; + + // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // TODO: calculate lighting and store it here + currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + } + */ + + // all visdata is alligned by 128 + gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); + + gfxWorld->dpvs.usageCount = 0; + } + + void cleanGfxWorld(GfxWorld* gfxWorld) + { + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = new GfxLight; + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 + + gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; + for (int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + } + + gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; + for (int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; + memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + void overwriteLightGrid(GfxWorld* gfxWorld) + { + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 0; + gfxWorld->lightGrid.maxs[1] = 0; + gfxWorld->lightGrid.maxs[2] = 0; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 + gfxWorld->lightGrid.offset = 0.0f; + + // if rowDataStart's first value is -1, it will not look up any lightmap data + gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; + *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); + + gfxWorld->lightGrid.rawRowDataSize = 0; + gfxWorld->lightGrid.rawRowData = NULL; + + gfxWorld->lightGrid.colorCount = 0; + gfxWorld->lightGrid.colors = NULL; + + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + + gfxWorld->lightGrid.entryCount = 0; + gfxWorld->lightGrid.entries = NULL; + + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = new GfxCell[cellCount]; + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].reflectionProbeCount = 0; + gfxWorld->cells[0].reflectionProbes = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void overwriteModels(GfxWorld* gfxWorld) + { + gfxWorld->modelCount = 1; + gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; + + // first model is the world model, the world json doesn't include it so its added here + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + // for (int i = 1; i < gfxWorld->modelCount; i++) + //{ + // auto currEntModel = &gfxWorld->models[i]; + // json currEntModelJs = js["entityModels"][i - 1]; + // + // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; + // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; + // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; + // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; + // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; + // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; + // + // currEntModel->surfaceCount = 0; + // currEntModel->startSurfIndex = (unsigned int)(-1); + // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); + // } + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = 0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + void updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + } + + void overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + } + + void overwriteSkyBox(GfxWorld* gfxWorld) + { + const char* skyBoxName = "skybox_mp_dig"; + gfxWorld->skyBoxModel = skyBoxName; + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + printf("WARN: Unable to find the skybox model %s\n", skyBoxName); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void updateDynEntData(GfxWorld* gfxWorld) + { + gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + void updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); + hasLinkFailed = true; + return; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + } + + void createGfxWorld(customMapInfo* projInfo) + { + GfxWorld* gfxWorld = new GfxWorld; + gfxWorld->baseName = _strdup(projInfo->name.c_str()); + gfxWorld->name = _strdup(projInfo->bspName.c_str()); + + // Default values taken from mp_dig + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + overwriteMapSurfaces(projInfo, gfxWorld); + + overwriteMapSModels(gfxWorld); + + overwriteLightmapData(gfxWorld); + + overwriteSkyBox(gfxWorld); + + updateReflectionProbeData(gfxWorld); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + updateOutdoors(gfxWorld); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + m_context.AddAsset(gfxWorld->name, gfxWorld); + } + + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + out_origin->x = temp.x * 0.5f; + out_origin->y = temp.y * 0.5f; + out_origin->z = temp.z * 0.5f; + + // Half-size is half the difference: (max - min) / 2 + temp.x = maxs->x - mins->x; + temp.y = maxs->y - mins->y; + temp.z = maxs->z - mins->z; + out_halfSize->x = temp.x * 0.5f; + out_halfSize->y = temp.y * 0.5f; + out_halfSize->z = temp.z * 0.5f; + } + + bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) + { + + unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); + + return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; + } + + float distBetweenPoints(vec3_t p1, vec3_t p2) + { + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); + } + + void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->u.leaf->getObjectCount() > 0) + { + *numAABBTrees += node->u.leaf->getObjectCount() + 1; + + if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->u.leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + } + } + + vec3_t normalX = {1.0f, 0.0f, 0.0f}; + vec3_t normalY = {0.0f, 1.0f, 0.0f}; + vec3_t normalZ = {0.0f, 0.0f, 1.0f}; + + int currPlaneCount = 0; + int currNodeCount = 0; + int currLeafCount = 0; + int currAABBCount = 0; + + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + { + _ASSERT(node->isLeaf); + + int objectCount = node->u.leaf->getObjectCount(); + int firstAABBIndex = currAABBCount; + currAABBCount += objectCount + 1; + + // calculate root AABB node mins and maxs + // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs + // so we have to recompute every min and max, not hard just tedious + int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; + auto firstPartition = &clipMap->partitions[firstPartitionIndex]; + uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t aabbMins; + vec3_t aabbMaxs; + aabbMins.x = firstVert->x; + aabbMins.y = firstVert->y; + aabbMins.z = firstVert->z; + aabbMaxs.x = firstVert->x; + aabbMaxs.y = firstVert->y; + aabbMaxs.z = firstVert->z; + for (int i = 0; i < objectCount; i++) + { + int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + auto currPartition = &clipMap->partitions[currPartitionIndex]; + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = clipMap->verts[vertIndex]; + CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + // create root AABB node + CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; + aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); + rootAABB->materialIndex = 0; + rootAABB->childCount = objectCount; + rootAABB->u.firstChildIndex = firstAABBIndex + 1; + + // populate child AABB nodes + for (int i = 0; i < objectCount; i++) + { + CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; + int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + + currAabbTree->materialIndex = 0; + currAabbTree->childCount = 0; + currAabbTree->u.partitionIndex = currPartitionIndex; + + // calculate partition origin and half size + CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; + uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; + vec3_t* firstVertex = &clipMap->verts[firstUind]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVertex->x; + mins.y = firstVertex->y; + mins.z = firstVertex->z; + maxs.x = firstVertex->x; + maxs.y = firstVertex->y; + maxs.z = firstVertex->z; + for (int i = 1; i < aabbPartition->nuinds; i++) + { + uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; + vec3_t* currVertex = &clipMap->verts[currUind]; + + CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + } + + aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + } + + return firstAABBIndex; + } + + // returns the index corresponding to the BSPTree* node parsed + int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + { + new cplane_s; + new cNode_t; + new cLeaf_s; + new CollisionAabbTree; + + if (node->isLeaf) + { + int currLeafIndex = currLeafCount; + currLeafCount++; + cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + + currLeaf->cluster = 0; + currLeaf->brushContents = 0; // no brushes used so contents is 0 + currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + + // unused when leafBrushNode == 0 + currLeaf->mins.x = 0.0f; + currLeaf->mins.y = 0.0f; + currLeaf->mins.z = 0.0f; + currLeaf->maxs.x = 0.0f; + currLeaf->maxs.y = 0.0f; + currLeaf->maxs.z = 0.0f; + currLeaf->leafBrushNode = 0; + + if (node->u.leaf->getObjectCount() > 0) + { + currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); + currLeaf->collAabbCount = 1; + } + else + { + currLeaf->firstCollAabbIndex = 0; + currLeaf->collAabbCount = 0; + } + + return -1 - currLeafIndex; + } + else + { + cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; + currPlaneCount++; + + if (node->u.node->axis == AXIS_X) + { + // X is unchanged when going from OGL x -> BO2 x + currPlane->normal = normalX; + + // converting OGL -> BO2 X coords doesn't change the x coords at all, so + // the dist stays the same + currPlane->dist = node->u.node->distance; + } + else + { + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // convert the z normal to the y normal, but don't negate it. Negative normals don't do + // what is expected when the game uses them + _ASSERT(node->u.node->axis == AXIS_Z); + currPlane->normal = normalY; + + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // just negate it here as it is just the distance from the orgin along the axis + currPlane->dist = -node->u.node->distance; + } + + bool foundType = false; + if (currPlane->normal.x == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 0; + } + else if (currPlane->normal.y == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 1; + } + else if (currPlane->normal.z == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 2; + } + else + _ASSERT(foundType); + + currPlane->signbits = 0; + if (currPlane->normal.x < 0.0f) + currPlane->signbits |= 1; + if (currPlane->normal.y < 0.0f) + currPlane->signbits |= 2; + if (currPlane->normal.z < 0.0f) + currPlane->signbits |= 4; + + currPlane->pad[0] = 0; + currPlane->pad[1] = 0; + + int currNodeIndex = currNodeCount; + currNodeCount++; + cNode_t* currNode = &clipMap->nodes[currNodeIndex]; + + currNode->plane = currPlane; + // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): + // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, + // the normal vector flips and objects behind the plane are now in front, and vise versa + // so the back node now represents the front, and the front node represents the back. + // Do the OGL -> Bo2 coord change on paper and it will make sense + if (currPlane->type == 1) + { + currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); + currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); + } + + return currNodeIndex; + } + } + + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + int numPlanes = 0; + int numNodes = 0; + int numLeafs = 0; + int numAABBTrees = 0; + int maxObjsPerLeaf = 0; + + traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); + + printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); + + clipMap->info.planeCount = numPlanes; + clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; + clipMap->numNodes = numNodes; + clipMap->nodes = new cNode_t[clipMap->numNodes]; + // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. + // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. + clipMap->aabbTreeCount = numAABBTrees; + clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; + memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); + currLeafCount = 1; + + populateBSPTree_r(clipMap, tree); + + _ASSERT(clipMap->info.planeCount == currPlaneCount); + _ASSERT(clipMap->numNodes == currNodeCount); + _ASSERT(clipMap->numLeafs == currLeafCount); + _ASSERT(clipMap->aabbTreeCount == currAABBCount); + } + + void createClipMap(customMapInfo* projInfo) + { + clipMap_t* clipMap = new clipMap_t; + + clipMap->name = _strdup(projInfo->bspName.c_str()); + + std::string mapEntsName = projInfo->bspName.c_str(); + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + _ASSERT(mapEntsAsset != NULL); + clipMap->mapEnts = mapEntsAsset->Asset(); + + clipMap->pInfo = NULL; + + clipMap->box_model.mins.x = 0.0f; + clipMap->box_model.mins.y = 0.0f; + clipMap->box_model.mins.z = 0.0f; + clipMap->box_model.maxs.x = 0.0f; + clipMap->box_model.maxs.y = 0.0f; + clipMap->box_model.maxs.z = 0.0f; + clipMap->box_model.radius = 0.0f; + clipMap->box_model.info = NULL; + clipMap->box_model.leaf.mins.x = 3.4028235e38; + clipMap->box_model.leaf.mins.y = 3.4028235e38; + clipMap->box_model.leaf.mins.z = 3.4028235e38; + clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive + clipMap->box_model.leaf.maxs.y = -3.4028235e38; + clipMap->box_model.leaf.maxs.z = -3.4028235e38; + clipMap->box_model.leaf.brushContents = -1; + clipMap->box_model.leaf.terrainContents = 0; + clipMap->box_model.leaf.cluster = 0; + clipMap->box_model.leaf.collAabbCount = 0; + clipMap->box_model.leaf.firstCollAabbIndex = 0; + clipMap->box_model.leaf.leafBrushNode = 0; + + clipMap->box_brush = new cbrush_t; + clipMap->box_brush->axial_sflags[0][0] = -1; + clipMap->box_brush->axial_sflags[0][1] = -1; + clipMap->box_brush->axial_sflags[0][2] = -1; + clipMap->box_brush->axial_sflags[1][0] = -1; + clipMap->box_brush->axial_sflags[1][1] = -1; + clipMap->box_brush->axial_sflags[1][2] = -1; + clipMap->box_brush->axial_cflags[0][0] = -1; + clipMap->box_brush->axial_cflags[0][1] = -1; + clipMap->box_brush->axial_cflags[0][2] = -1; + clipMap->box_brush->axial_cflags[1][0] = -1; + clipMap->box_brush->axial_cflags[1][1] = -1; + clipMap->box_brush->axial_cflags[1][2] = -1; + clipMap->box_brush->contents = -1; + clipMap->box_brush->mins.x = 0.0f; + clipMap->box_brush->mins.y = 0.0f; + clipMap->box_brush->mins.z = 0.0f; + clipMap->box_brush->maxs.x = 0.0f; + clipMap->box_brush->maxs.y = 0.0f; + clipMap->box_brush->maxs.z = 0.0f; + clipMap->box_brush->numsides = 0; + clipMap->box_brush->numverts = 0; + clipMap->box_brush->sides = NULL; + clipMap->box_brush->verts = NULL; + + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = new char[clipMap->clusterBytes]; + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + + clipMap->isInUse = true; + clipMap->checksum = 0; + + clipMap->num_constraints = 0; + clipMap->constraints = NULL; + clipMap->max_ropes = 32; + clipMap->ropes = new rope_t[clipMap->max_ropes]; + memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); + + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = NULL; + clipMap->info.leafbrushNodesCount = 0; + clipMap->info.leafbrushNodes = NULL; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = NULL; + clipMap->info.numBrushVerts = 0; + clipMap->info.brushVerts = NULL; + clipMap->info.numBrushes = NULL; + clipMap->info.brushes = NULL; + clipMap->info.brushBounds = NULL; + clipMap->info.brushContents = NULL; + + clipMap->originalDynEntCount = DYN_ENT_COUNT; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; + clipMap->dynEntCount[1] = 0; + clipMap->dynEntCount[2] = 0; + clipMap->dynEntCount[3] = 0; + + // assume that there are 0 dyn ents from here on + clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; + clipMap->dynEntClientList[1] = NULL; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = NULL; + clipMap->dynEntServerList[1] = NULL; + + clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; + clipMap->dynEntCollList[1] = NULL; + clipMap->dynEntCollList[2] = NULL; + clipMap->dynEntCollList[3] = NULL; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; + clipMap->dynEntPoseList[1] = NULL; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; + clipMap->dynEntDefList[1] = NULL; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + for (int i = 0; i < clipMap->dynEntCount[0]; i++) + { + DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; + currDef->physConstraints[0] = 0x1FF; + currDef->physConstraints[1] = 0x1FF; + currDef->physConstraints[2] = 0x1FF; + currDef->physConstraints[3] = 0x1FF; + } + + clipMap->numSubModels = 1; + clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = NULL; + + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; + clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; + clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; + clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; + clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; + clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; + clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + + clipMap->numStaticModels = 0; + clipMap->staticModelList = NULL; + + clipMap->info.numMaterials = 1; + clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; + clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); + clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; + + int collisionVertexCount = projInfo->gfxInfo.vertexCount; + vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; + for (int i = 0; i < collisionVertexCount; i++) + { + convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = convertedCollisionVerts; + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + const int maxVerts = UINT16_MAX - 1; + if (collisionVertexCount > maxVerts) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); + hasLinkFailed = true; + return; + } + + clipMap->triCount = projInfo->gfxInfo.indexCount / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri + int triIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) + { + clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; + clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; + clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; + triIndex++; + } + + int partitionCount = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int subdividedSize = triCount / 16; // integer division used + partitionCount += subdividedSize; + + // add another partition if the triangle count doesn't add up to a multiple of 16 + if (triCount % 16 != 0) + partitionCount++; + } + clipMap->partitionCount = partitionCount; + clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; + + int partitionIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; + int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; + int firstTri = firstIndex / 3; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. + // NEED TO FIX + // fixup the tri indices. + // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] + // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] + // this loop fixes the index buffer by adding the firstVertex to each index + for (int k = 0; k < triCount; k++) + { + clipMap->triIndices[k + firstTri][0] += firstVertex; + clipMap->triIndices[k + firstTri][1] += firstVertex; + clipMap->triIndices[k + firstTri][2] += firstVertex; + } + + while (triCount > 0) + { + if (triCount > 16) + clipMap->partitions[partitionIndex].triCount = 16; + else + clipMap->partitions[partitionIndex].triCount = triCount; + + clipMap->partitions[partitionIndex].firstTri = firstTri; + + triCount -= 16; + firstIndex += 16 * 3; + partitionIndex++; + } + } + + int uindCount = 0; + clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + int uniqueVertBufferCount = 0; + uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (int m = 0; m < uniqueVertBufferCount; m++) + { + if (uniqueVertBuffer[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + { + uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; + uniqueVertBufferCount++; + } + } + } + + _ASSERT(uniqueVertBufferCount <= 3 * 16); + + currPartition->fuind = uindCount; + currPartition->nuinds = uniqueVertBufferCount; + memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); + uindCount += uniqueVertBufferCount; + } + _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); + clipMap->info.nuinds = uindCount; + + // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility + // might do weird stuff on walls + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); + + // BSP creation must go last as it depends on unids, tris and verts already being populated + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, + // and then convert them back when it is being parsed into the clipmap. Requires some hacky + // logic, check populateBSPTree_r and addAABBTreeFromLeaf + + vec3_t* firstVert = &clipMap->verts[0]; + vec3_t clipMins; + vec3_t clipMaxs; + clipMins.x = firstVert->x; + clipMins.y = firstVert->y; + clipMins.z = firstVert->z; + clipMaxs.x = firstVert->x; + clipMaxs.y = firstVert->y; + clipMaxs.z = firstVert->z; + clipMins = CMUtil::convertFromBO2Coords(clipMins); + clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); + for (int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVert->x; + mins.y = firstVert->y; + mins.z = firstVert->z; + maxs.x = firstVert->x; + maxs.y = firstVert->y; + maxs.z = firstVert->z; + mins = CMUtil::convertFromBO2Coords(mins); + maxs = CMUtil::convertFromBO2Coords(maxs); + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObject(currObject); + } + + populateBSPTree(clipMap, tree); + + m_context.AddAsset(clipMap->name, clipMap); + } + + void createComWorld(customMapInfo* projInfo) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = new ComWorld; + comWorld->name = _strdup(projInfo->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; + + ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; + memset(defaultLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + // Below are default values taken from mp_dig + sunLight->diffuseColor.r = 1.0f; + sunLight->diffuseColor.g = 1.0f; + sunLight->diffuseColor.b = 1.0f; + sunLight->diffuseColor.a = 0.0f; + sunLight->dir.x = -0.2410777360200882; + sunLight->dir.y = -0.8407384753227234; + sunLight->dir.z = 0.48480960726737976; + + m_context.AddAsset(comWorld->name, comWorld); + } + + void createMapEnts(customMapInfo* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + const auto file = m_search_path.Open("custom_map/entities.json"); + if (!file.IsOpen()) + { + printf("WARN: can't find entity json!\n"); + return; + } + + json entJs = json::parse(*file.m_stream); + + std::string entityString; + int entityCount = entJs["entityCount"]; + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entJs["entities"][i]; + + if (i == 0) + { + std::string className = currEntity["classname"]; + if (className.compare("worldspawn") != 0) + { + printf("ERROR: first entity in the map entity string must be the worldspawn class!"); + hasLinkFailed = true; + return; + } + } + + entityString.append("{\n"); + + for (auto& element : currEntity.items()) + { + std::string key = element.key(); + std::string value = element.value(); + entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); + } + + entityString.append("}\n"); + } + entityString.pop_back(); // remove newline character from the end of the string + + mapEnts->entityString = _strdup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + m_context.AddAsset(mapEnts->name, mapEnts); + } + + void createGameWorldMp(customMapInfo* projInfo) + { + GameWorldMp* gameWorldMp = new GameWorldMp; + + gameWorldMp->name = _strdup(projInfo->bspName.c_str()); + + gameWorldMp->path.nodeCount = 0; + gameWorldMp->path.originalNodeCount = 0; + gameWorldMp->path.visBytes = 0; + gameWorldMp->path.smoothBytes = 0; + gameWorldMp->path.nodeTreeCount = 0; + + int nodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = new pathnode_t[nodeCount]; + gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = NULL; + gameWorldMp->path.smoothCache = NULL; + gameWorldMp->path.nodeTree = NULL; + + m_context.AddAsset(gameWorldMp->name, gameWorldMp); + } + + void createSkinnedVerts(customMapInfo* projInfo) + { + SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; + skinnedVerts->name = "skinnedverts"; + skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; + + m_context.AddAsset("skinnedverts", skinnedVerts); + } + + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return NULL; + + FootstepTableDef* footstepTable = new FootstepTableDef; + footstepTable->name = _strdup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) + { + if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) + { + hasLinkFailed = true; + return; + } + + addEmptyFootstepTableAsset("default_1st_person"); + addEmptyFootstepTableAsset("default_3rd_person"); + addEmptyFootstepTableAsset("default_1st_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_loud"); + addEmptyFootstepTableAsset("default_ai"); + } }; diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp index 284cb1b3e..42c41951b 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -10,7 +10,7 @@ using namespace T6; namespace { - class CustomMapLoader final : public AssetCreator + class CustomMapLoader final : public AssetCreator { public: CustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) @@ -32,7 +32,7 @@ namespace return AssetCreationResult::Failure(); // linker will add all the assets needed - CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone); + CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); bool result = linker->linkCustomMap(mapInfo); if (result) @@ -50,7 +50,7 @@ namespace namespace T6 { - std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) { return std::make_unique(memory, searchPath, zone); } diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h index 13862ba61..78a388fce 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h @@ -9,5 +9,5 @@ namespace T6 { - std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath); + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); } // namespace T6 diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp index 77d5d53a5..b08ca254d 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -1,105 +1,105 @@ #include "ProjectCreator.h" -class CustomMapLoader +#include "OBJ_Loader.h" + +void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo) { - void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo) - { - int meshVertexCount = OBJMesh->Vertices.size(); - int meshIndexCount = OBJMesh->Indices.size(); - int surfVertexStart = projInfo->gfxInfo.vertexCount; - int surfIndexStart = projInfo->gfxInfo.indexCount; + int meshVertexCount = OBJMesh->Vertices.size(); + int meshIndexCount = OBJMesh->Indices.size(); + int surfVertexStart = projInfo->gfxInfo.vertexCount; + int surfIndexStart = projInfo->gfxInfo.indexCount; - projInfo->gfxInfo.vertexCount += meshVertexCount; - projInfo->gfxInfo.indexCount += meshIndexCount; + projInfo->gfxInfo.vertexCount += meshVertexCount; + projInfo->gfxInfo.indexCount += meshIndexCount; - projInfo->gfxInfo.vertices = (customMapVertex*)realloc(projInfo->gfxInfo.vertices, sizeof(customMapVertex) * projInfo->gfxInfo.vertexCount); - projInfo->gfxInfo.indices = (uint16_t*)realloc(projInfo->gfxInfo.indices, sizeof(uint16_t) * projInfo->gfxInfo.indexCount); + projInfo->gfxInfo.vertices = (customMapVertex*)realloc(projInfo->gfxInfo.vertices, sizeof(customMapVertex) * projInfo->gfxInfo.vertexCount); + projInfo->gfxInfo.indices = (uint16_t*)realloc(projInfo->gfxInfo.indices, sizeof(uint16_t) * projInfo->gfxInfo.indexCount); - customMapVertex* surfVertices = &projInfo->gfxInfo.vertices[surfVertexStart]; - uint16_t* surfIndices = &projInfo->gfxInfo.indices[surfIndexStart]; + customMapVertex* surfVertices = &projInfo->gfxInfo.vertices[surfVertexStart]; + uint16_t* surfIndices = &projInfo->gfxInfo.indices[surfIndexStart]; - meshSurface->firstIndex_Index = surfIndexStart; - meshSurface->firstVertexIndex = surfVertexStart; - meshSurface->triCount = meshIndexCount / 3; - _ASSERT(meshIndexCount % 3 == 0); + meshSurface->firstIndex_Index = surfIndexStart; + meshSurface->firstVertexIndex = surfVertexStart; + meshSurface->triCount = meshIndexCount / 3; + _ASSERT(meshIndexCount % 3 == 0); - for (int i = 0; i < meshIndexCount; i++) - { - _ASSERT(OBJMesh->Indices[i] < UINT16_MAX); - surfIndices[i] = OBJMesh->Indices[i]; - } + for (int i = 0; i < meshIndexCount; i++) + { + _ASSERT(OBJMesh->Indices[i] < UINT16_MAX); + surfIndices[i] = OBJMesh->Indices[i]; + } - for (int i = 0; i < meshVertexCount; i++) - { - objl::Vertex* meshVertex = &OBJMesh->Vertices[i]; - customMapVertex* surfVertex = &surfVertices[i]; + for (int i = 0; i < meshVertexCount; i++) + { + objl::Vertex* meshVertex = &OBJMesh->Vertices[i]; + customMapVertex* surfVertex = &surfVertices[i]; - surfVertex->pos.x = meshVertex->Position.X; - surfVertex->pos.y = meshVertex->Position.Y; - surfVertex->pos.z = meshVertex->Position.Z; + surfVertex->pos.x = meshVertex->Position.X; + surfVertex->pos.y = meshVertex->Position.Y; + surfVertex->pos.z = meshVertex->Position.Z; - surfVertex->color[0] = OBJMesh->MeshMaterial.Kd.X; - surfVertex->color[1] = OBJMesh->MeshMaterial.Kd.Y; - surfVertex->color[2] = OBJMesh->MeshMaterial.Kd.Z; - surfVertex->color[3] = 1.0f; + surfVertex->color[0] = OBJMesh->MeshMaterial.Kd.X; + surfVertex->color[1] = OBJMesh->MeshMaterial.Kd.Y; + surfVertex->color[2] = OBJMesh->MeshMaterial.Kd.Z; + surfVertex->color[3] = 1.0f; - surfVertex->texCoord[0] = meshVertex->TextureCoordinate.X; - surfVertex->texCoord[1] = meshVertex->TextureCoordinate.Y; + surfVertex->texCoord[0] = meshVertex->TextureCoordinate.X; + surfVertex->texCoord[1] = meshVertex->TextureCoordinate.Y; - surfVertex->normal.x = meshVertex->Normal.X; - surfVertex->normal.y = meshVertex->Normal.Y; - surfVertex->normal.z = meshVertex->Normal.Z; + surfVertex->normal.x = meshVertex->Normal.X; + surfVertex->normal.y = meshVertex->Normal.Y; + surfVertex->normal.z = meshVertex->Normal.Z; - // TODO: fix tangents, seems to work for now though - surfVertex->tangent.x = 1.0f; - surfVertex->tangent.y = 0.0f; - surfVertex->tangent.z = 0.0f; + // TODO: fix tangents, seems to work for now though + surfVertex->tangent.x = 1.0f; + surfVertex->tangent.y = 0.0f; + surfVertex->tangent.z = 0.0f; - surfVertex->packedLmapCoord = 0; + surfVertex->packedLmapCoord = 0; - surfVertex->binormalSign = 0.0f; - } + surfVertex->binormalSign = 0.0f; } +} - customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +{ + std::string objFilePath = searchPath.GetPath() + "custom_map/world.obj"; + + objl::Loader OBJloader; + bool isLoaded = OBJloader.LoadFile(objFilePath); + if (!isLoaded) { - std::string objFilePath = searchPath.GetPath() + "custom_map/world.obj"; + printf("OBJLoader: unable to load obj file %s\n", objFilePath.c_str()); + return NULL; + } - objl::Loader OBJloader; - bool isLoaded = OBJloader.LoadFile(objFilePath); - if (!isLoaded) - { - printf("OBJLoader: unable to load obj file %s\n", objFilePath.c_str()); - return NULL; - } + customMapInfo* projInfo = new customMapInfo; - customMapInfo* projInfo = new customMapInfo; + projInfo->name = projectName; + projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; - projInfo->name = projectName; - projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + projInfo->gfxInfo.surfaceCount = OBJloader.LoadedMeshes.size(); + projInfo->gfxInfo.surfaces = new worldSurface[projInfo->gfxInfo.surfaceCount]; - projInfo->gfxInfo.surfaceCount = OBJloader.LoadedMeshes.size(); - projInfo->gfxInfo.surfaces = new worldSurface[projInfo->gfxInfo.surfaceCount]; + projInfo->gfxInfo.vertexCount = 0; + projInfo->gfxInfo.indexCount = 0; + projInfo->gfxInfo.vertices = (customMapVertex*)malloc(1); + projInfo->gfxInfo.indices = (uint16_t*)malloc(1); - projInfo->gfxInfo.vertexCount = 0; - projInfo->gfxInfo.indexCount = 0; - projInfo->gfxInfo.vertices = (customMapVertex*)malloc(1); - projInfo->gfxInfo.indices = (uint16_t*)malloc(1); + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + objl::Mesh* currMesh = &OBJloader.LoadedMeshes[i]; + worldSurface* currSurface = &projInfo->gfxInfo.surfaces[i]; - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) - { - objl::Mesh* currMesh = &OBJloader.LoadedMeshes[i]; - worldSurface* currSurface = &projInfo->gfxInfo.surfaces[i]; + currSurface->materialName = currMesh->MeshMaterial.name; + currSurface->reflectionProbeIndex = 0; + currSurface->primaryLightIndex = 0; + currSurface->lightmapIndex = 0; + currSurface->flags = 0; - currSurface->materialName = currMesh->MeshMaterial.name; - currSurface->reflectionProbeIndex = 0; - currSurface->primaryLightIndex = 0; - currSurface->lightmapIndex = 0; - currSurface->flags = 0; + parseMesh(currMesh, currSurface, projInfo); + } - parseMesh(currMesh, currSurface, projInfo); - } + return projInfo; +} - return projInfo; - } -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h index 8c6a54088..a75d041ac 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h @@ -1,13 +1,12 @@ +#pragma once + #include "SearchPath/ISearchPath.h" #include "Utils/MemoryManager.h" #include "Game/T6/T6.h" using namespace T6; -#include "OBJ_Loader.h" - class CustomMapInfo { public: - static void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo); static customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath); }; diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h index 30e71cbf7..216fa4bfd 100644 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -1,3 +1,6 @@ +#pragma once + + #include "Game/T6/T6.h" using namespace T6; diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 496dbd448..e1bcc7f1e 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -389,7 +389,7 @@ namespace T6 collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); - collection.AddAssetCreator(std::make_unique>(zone)); + //collection.AddAssetCreator(std::make_unique>(zone)); } void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) @@ -451,7 +451,7 @@ namespace T6 collection.AddAssetCreator(CreateRawZBarrierLoader(memory, searchPath, zone)); collection.AddAssetCreator(CreateGdtZBarrierLoader(memory, searchPath, gdt, zone)); - collection.AddAssetCreator(CreateCustomMapLoader(memory, searchPath)); + collection.AddAssetCreator(CreateCustomMapLoader(memory, searchPath, zone)); } } // namespace From 0a520b442f5a19d78b42effb1e6dfceca486f11d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 7 Jul 2025 23:54:41 +0800 Subject: [PATCH 004/123] Fix: technqiues weren't opened with the proper file path. --- src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index f9e793f5a..162c5fee0 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -21,7 +21,7 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { const auto fileName = std::format("techniquesets/{}.json", assetName); - const auto file = m_search_path.Open(assetName); + const auto file = m_search_path.Open(fileName); if (!file.IsOpen()) return AssetCreationResult::NoAction(); From ca4db1eec0bec1bf43bb007270bf908fe6345def Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 7 Jul 2025 23:58:59 +0800 Subject: [PATCH 005/123] fix: more incorrect paths --- src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index 162c5fee0..c92928011 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -127,7 +127,7 @@ namespace currPass->pixelShader->name = _strdup(pixelName.c_str()); currPass->pixelShader->prog.ps = NULL; - const auto psFileName = std::format("techniques/shader_bin/ps_{}", pixelName); + const auto psFileName = std::format("techniquesets/shader_bin/ps_{}", pixelName); const auto psFile = m_search_path.Open(psFileName); if (!psFile.IsOpen()) { @@ -151,7 +151,7 @@ namespace currPass->vertexShader->name = _strdup(vertexName.c_str()); currPass->vertexShader->prog.vs = NULL; - const auto vsFileName = std::format("techniques/shader_bin/vs_{}", vertexName); + const auto vsFileName = std::format("techniquesets/shader_bin/vs_{}", vertexName); const auto vsFile = m_search_path.Open(vsFileName); if (!vsFile.IsOpen()) { From a825bf965b12923455077732f9bf26516dde4636 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Thu, 28 Aug 2025 23:34:06 +0800 Subject: [PATCH 006/123] all known bugs fixed and loaded maps work correctly now. --- src/Common/Game/T6/T6.h | 60 +- .../BinarySpacePartitionTreePreCalc.h | 2 +- .../Game/T6/CustomMap/CustomMapConsts.h | 35 +- .../Game/T6/CustomMap/CustomMapLinker.h | 504 +- .../Game/T6/CustomMap/LoaderCustomMapT6.cpp | 17 +- src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h | 41 +- .../Game/T6/CustomMap/ProjectCreator.cpp | 440 +- .../Game/T6/CustomMap/TriangleSort.h | 99 + src/ObjLoading/Game/T6/CustomMap/Util.h | 47 + src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c | 33093 ++++++++++++++++ src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h | 6062 +++ .../Game/T6/Image/LoaderImageT6.cpp | 54 +- .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 8 +- .../Game/T6/ZoneWriterFactoryT6.cpp | 11 +- .../Writing/Steps/StepWriteZoneRSA.cpp | 35 + .../Writing/Steps/StepWriteZoneRSA.h | 13 + 16 files changed, 40177 insertions(+), 344 deletions(-) create mode 100644 src/ObjLoading/Game/T6/CustomMap/TriangleSort.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c create mode 100644 src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h create mode 100644 src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp create mode 100644 src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index 252c39e9f..d7413f40e 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -158,7 +158,6 @@ namespace T6 AUFT_NUM_FIELD_TYPES, }; - struct customMapVertex { @@ -171,13 +170,27 @@ namespace T6 unsigned int packedLmapCoord; }; + enum CM_MATERIAL_TYPE + { + NO_COLOUR_OR_TEXTURE, + CM_MATERIAL_COLOUR, + CM_MATERIAL_TEXTURE + }; + + struct customMapMaterial + { + CM_MATERIAL_TYPE materialType; + const char* materialName; + }; + struct worldSurface { char flags; - char lightmapIndex; - std::string materialName; - char primaryLightIndex; - char reflectionProbeIndex; + customMapMaterial material; + + //char lightmapIndex; + //char primaryLightIndex; + //char reflectionProbeIndex; int triCount; int firstVertexIndex; @@ -196,12 +209,49 @@ namespace T6 worldSurface* surfaces; }; + struct customMapColVertex + { + vec3_t pos; + }; + + struct collisionSurface + { + int triCount; + int firstVertexIndex; + int firstIndex_Index; + }; + + struct customMapCol + { + int vertexCount; + customMapColVertex* vertices; + + int indexCount; + uint16_t* indices; + + int surfaceCount; + collisionSurface* surfaces; + }; + + struct customMapModel + { + std::string name; + + vec3_t origin; + vec3_t rotation; // euler rotation in degrees + float scale; + }; + struct customMapInfo { std::string name; std::string bspName; customMapGfx gfxInfo; + customMapGfx colInfo; + + size_t modelCount; + customMapModel* models; }; using AssetPhysPreset = Asset; diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h index 97c260225..3c02217c2 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h @@ -16,7 +16,7 @@ Precalculated, evenly sized BSPs are much more efficient and smaller compared to #include #include -#define MAX_AABB_SIZE 512 // maximum size an AABB tree can be before it becomes a node +#include "CustomMapConsts.h" enum PlaneAxis { diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index ccda4ba6e..5c2a1310e 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -10,14 +10,37 @@ // material flags determine the features of the surface // unsure which flag type changes what right now // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast -// 1 results in: normal surface features, grenades work, +// 1 results in: normal surface features, grenades work, seems normal #define MATERIAL_SURFACE_FLAGS 1 #define MATERIAL_CONTENT_FLAGS 1 -// terrain flags: does not change the type of terrain or what features they have +// terrain/world flags: does not change the type of terrain or what features they have // from testing, as long at it isn't 0 things will work correctly -#define LEAF_TERRAIN_CONTENTS 1 // match all flags -#define WORLD_TERRAIN_CONTENTS 1 // match all flags +#define LEAF_TERRAIN_CONTENTS 1 +#define WORLD_TERRAIN_CONTENTS 1 -// const std::string defaultMaterialName = "wpc/intro_wall_plaster_light_blue_01"; -const std::string defaultMaterialName = "wpc/dig_plastic_blue"; +const std::string missingMaterialName = "custom/missing_material"; +const std::string colourOnlyMaterialName = "custom/colour_only_material"; +const std::string templateMaterialName = "custom/material_template"; + +#define DEFAULT_SURFACE_LIGHT 1 +#define DEFAULT_SURFACE_LIGHTMAP 0 +#define DEFAULT_SURFACE_REFLECTION_PROBE 0 + +#define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf + +// do not change +#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of verts a map can have + +// do not change +#define DEFAULT_LIGHT_INDEX 0 +#define SUN_LIGHT_INDEX 1 + +// do not change +#define SMODEL_FLAG_NO_SHADOW 1 +#define SMODEL_FLAG_IS_LIT 2 + +#define DEFAULT_SMODEL_CULL_DIST 10000.0f +#define DEFAULT_SMODEL_FLAGS SMODEL_FLAG_NO_SHADOW +#define DEFAULT_SMODEL_LIGHT 1 +#define DEFAULT_SMODEL_REFLECTION_PROBE 0 diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index e66c1caa9..9e43dcf76 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,9 +1,11 @@ #pragma once #include "BinarySpacePartitionTreePreCalc.h" +#include "TriangleSort.h" #include "CustomMapConsts.h" #include "Util.h" #include "Utils/Pack.h" +#include #include using json = nlohmann::json; @@ -30,9 +32,9 @@ class CustomMapLinker createGameWorldMp(projInfo); createSkinnedVerts(projInfo); createClipMap(projInfo); // must go last (requires gfx and mapents asset) - + checkAndAddDefaultRequiredAssets(projInfo); - + if (hasLinkFailed) { printf("Custom Map link has failed.\n"); @@ -68,6 +70,7 @@ class CustomMapLinker GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); + //GfxVertex->xyz = WorldVertex->pos; GfxVertex->binormalSign = WorldVertex->binormalSign; @@ -76,8 +79,10 @@ class CustomMapLinker GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); + //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); + //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; } @@ -125,14 +130,14 @@ class CustomMapLinker gfxWorld->surfaceCount = surfaceCount; gfxWorld->dpvs.staticSurfaceCount = surfaceCount; gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (int i = 0; i < surfaceCount; i++) + for (unsigned int i = 0; i < surfaceCount; i++) { auto currSurface = &gfxWorld->dpvs.surfaces[i]; auto objSurface = &projInfo->gfxInfo.surfaces[i]; - currSurface->lightmapIndex = objSurface->lightmapIndex; - currSurface->primaryLightIndex = objSurface->primaryLightIndex; - currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; + currSurface->primaryLightIndex = DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = DEFAULT_SURFACE_REFLECTION_PROBE; currSurface->flags = objSurface->flags; currSurface->tris.triCount = objSurface->triCount; @@ -141,15 +146,34 @@ class CustomMapLinker currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); currSurface->tris.vertexDataOffset1 = 0; - auto* assetInfo = m_context.LoadDependency(objSurface->materialName); + std::string surfMaterialName; + switch (objSurface->material.materialType) + { + case CM_MATERIAL_TEXTURE: + surfMaterialName = objSurface->material.materialName; + break; + + case CM_MATERIAL_COLOUR: + case NO_COLOUR_OR_TEXTURE: + surfMaterialName = colourOnlyMaterialName; + break; + + default: + _ASSERT(false); + } + auto* assetInfo = m_context.LoadDependency(surfMaterialName); if (assetInfo == NULL) { - printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); - assetInfo = m_context.LoadDependency(defaultMaterialName); - _ASSERT(assetInfo != NULL); + assetInfo = m_context.LoadDependency(missingMaterialName); + if (assetInfo == NULL) + { + printf("Error: unable to find the default texture!\n"); + hasLinkFailed = true; + return; + } } currSurface->material = assetInfo->Asset(); - + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; currSurface->bounds[0].x = firstVert[0].xyz.x; currSurface->bounds[0].y = firstVert[0].xyz.y; @@ -159,7 +183,8 @@ class CustomMapLinker currSurface->bounds[1].z = firstVert[0].xyz.z; for (int k = 0; k < currSurface->tris.triCount * 3; k++) { - CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; + CMUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); } // unused values @@ -176,7 +201,7 @@ class CustomMapLinker // doesn't seem to matter what order the sorted surfs go in gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (int i = 0; i < surfaceCount; i++) + for (unsigned int i = 0; i < surfaceCount; i++) { gfxWorld->dpvs.sortedSurfIndex[i] = i; } @@ -209,51 +234,38 @@ class CustomMapLinker gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; } -#define SMODEL_FLAG_NO_SHADOW 1 -#define SMODEL_FLAG_IS_LIT 2 - - void overwriteMapSModels(GfxWorld* gfxWorld) + void overwriteMapSModels(customMapInfo* projInfo, GfxWorld* gfxWorld) { - unsigned int modelCount = 0; + unsigned int modelCount = projInfo->modelCount; gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - /* + for (unsigned int i = 0; i < modelCount; i++) { auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - json currModelJs = js["models"][i]; + customMapModel* inModel = &projInfo->models[i]; - // TODO: load custom xmodels - std::string modelName = currModelJs["model"]; - auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); + auto xModelAsset = m_context.LoadDependency(inModel->name); if (xModelAsset == NULL) { - printf("Custom model (%s) not supported!\n", modelName.c_str()); - hasLinkFailed = true; - return; + printf("XModel %s not found!\n", inModel->name.c_str()); + currModel->model = NULL; } - currModel->model = (XModel*)xModelAsset->m_ptr; + else + currModel->model = (XModel*)xModelAsset->Asset(); - currModel->placement.origin.x = currModelJs["origin"]["x"]; - currModel->placement.origin.y = currModelJs["origin"]["y"]; - currModel->placement.origin.z = currModelJs["origin"]["z"]; + currModel->placement.origin.x = inModel->origin.x; + currModel->placement.origin.y = inModel->origin.y; + currModel->placement.origin.z = inModel->origin.z; currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); - currModel->placement.axis[0].x = currModelJs["forward"]["x"]; - currModel->placement.axis[0].y = currModelJs["forward"]["y"]; - currModel->placement.axis[0].z = currModelJs["forward"]["z"]; - currModel->placement.axis[1].x = currModelJs["right"]["x"]; - currModel->placement.axis[1].y = currModelJs["right"]["y"]; - currModel->placement.axis[1].z = currModelJs["right"]["z"]; - currModel->placement.axis[2].x = currModelJs["up"]["x"]; - currModel->placement.axis[2].y = currModelJs["up"]["y"]; - currModel->placement.axis[2].z = currModelJs["up"]["z"]; - currModel->placement.scale = currModelJs["scale"]; + currModel->placement.scale = inModel->scale; + + CMUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); // mins and maxs are calculated in world space not local space - // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame + // TODO: this does not account for model rotation or scale currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; @@ -261,10 +273,10 @@ class CustomMapLinker currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - currModel->cullDist = currModelJs["cullDist"]; - currModel->flags = currModelJs["flags"]; - currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; - currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; + currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + currModel->flags = DEFAULT_SMODEL_FLAGS; + currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; // unknown use / unused currModel->smid = i; @@ -285,7 +297,7 @@ class CustomMapLinker currModel->lmapVertexInfo[3].numLmapVertexColors = 0; currModel->lmapVertexInfo[3].lmapVertexColors = NULL; } - */ + // all visdata is alligned by 128 gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); @@ -399,10 +411,10 @@ class CustomMapLinker { // there must be 2 or more lights, first is the default light and second is the sun gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 + gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX; // the sun is always index 1 gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (int i = 0; i < gfxWorld->primaryLightCount; i++) + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) { gfxWorld->shadowGeom[i].smodelCount = 0; gfxWorld->shadowGeom[i].surfaceCount = 0; @@ -411,7 +423,7 @@ class CustomMapLinker } gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (int i = 0; i < gfxWorld->primaryLightCount; i++) + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) { gfxWorld->lightRegion[i].hullCount = 0; gfxWorld->lightRegion[i].hulls = NULL; @@ -440,7 +452,7 @@ class CustomMapLinker gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 + gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; // the sun is always index 1 gfxWorld->lightGrid.offset = 0.0f; // if rowDataStart's first value is -1, it will not look up any lightmap data @@ -518,9 +530,11 @@ class CustomMapLinker // Nodes mnode_t.cellIndex indexes gfxWorld->cells // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes gfxWorld->nodeCount = 1; - gfxWorld->planeCount = 0; gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + + // planes are overwritten by the clipmap loading code ingame + gfxWorld->planeCount = 0; gfxWorld->dpvsPlanes.planes = NULL; } @@ -541,10 +555,13 @@ class CustomMapLinker void overwriteModels(GfxWorld* gfxWorld) { + // these models are the collision for the entities defined in the mapents asset + // used for triggers and stuff + gfxWorld->modelCount = 1; gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - // first model is the world model, the world json doesn't include it so its added here + // first model is always the world model gfxWorld->models[0].startSurfIndex = 0; gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; @@ -576,7 +593,7 @@ class CustomMapLinker void updateSunData(GfxWorld* gfxWorld) { // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = 0.001; + gfxWorld->sunParse.fogTransitionTime = (float)0.001; gfxWorld->sunParse.name[0] = 0x00; gfxWorld->sunParse.initWorldSun->control = 0; @@ -783,7 +800,7 @@ class CustomMapLinker overwriteMapSurfaces(projInfo, gfxWorld); - overwriteMapSModels(gfxWorld); + overwriteMapSModels(projInfo, gfxWorld); overwriteLightmapData(gfxWorld); @@ -813,6 +830,49 @@ class CustomMapLinker m_context.AddAsset(gfxWorld->name, gfxWorld); } + void addXModelsToCollision(customMapInfo* projInfo, clipMap_t* clipMap) + { + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; + clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; + + for (unsigned int i = 0; i < clipMap->numStaticModels; i++) + { + GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; + GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; + cStaticModel_s* currModel = &clipMap->staticModelList[i]; + + memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); + currModel->xmodel = gfxModelDrawInst->model; + currModel->contents = gfxModelDrawInst->model->contents; + currModel->origin.x = gfxModelDrawInst->placement.origin.x; + currModel->origin.y = gfxModelDrawInst->placement.origin.y; + currModel->origin.z = gfxModelDrawInst->placement.origin.z; + + // TODO: this does not account for model rotation or scale + currModel->absmin.x = gfxModelInst->mins.x; + currModel->absmin.y = gfxModelInst->mins.y; + currModel->absmin.z = gfxModelInst->mins.z; + currModel->absmax.x = gfxModelInst->maxs.x; + currModel->absmax.y = gfxModelInst->maxs.y; + currModel->absmax.z = gfxModelInst->maxs.z; + + CMUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); + currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; + currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; + currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; + currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; + currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; + currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; + currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; + currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; + currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; + } + } + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) { // Origin is the midpoint: (min + max) / 2 @@ -841,14 +901,6 @@ class CustomMapLinker return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; } - float distBetweenPoints(vec3_t p1, vec3_t p2) - { - float x = p2.x - p1.x; - float y = p2.y - p1.y; - float z = p2.z - p1.z; - return sqrtf((x * x) + (y * y) + (z * z)); - } - void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) { if (node->isLeaf) @@ -920,7 +972,6 @@ class CustomMapLinker } } } - // create root AABB node CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); rootAABB->materialIndex = 0; @@ -1015,7 +1066,7 @@ class CustomMapLinker // converting OGL -> BO2 X coords doesn't change the x coords at all, so // the dist stays the same - currPlane->dist = node->u.node->distance; + currPlane->dist = (float)node->u.node->distance; } else { @@ -1027,7 +1078,7 @@ class CustomMapLinker // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = -node->u.node->distance; + currPlane->dist = (float)(-node->u.node->distance); } bool foundType = false; @@ -1127,6 +1178,118 @@ class CustomMapLinker _ASSERT(clipMap->aabbTreeCount == currAABBCount); } + void createPartitions(customMapInfo* projInfo, clipMap_t* clipMap) + { + int collisionVertexCount = projInfo->colInfo.vertexCount; + std::vector collisionVertVec; + for (int i = 0; i < collisionVertexCount; i++) + { + collisionVertVec.push_back(CMUtil::convertToBO2Coords(projInfo->colInfo.vertices[i].pos)); + //collisionVertVec.push_back(projInfo->colInfo.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = new vec3_t[collisionVertexCount]; + memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + if (collisionVertexCount > MAX_COL_VERTS) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, MAX_COL_VERTS); + hasLinkFailed = true; + return; + } + + std::vector triIndexVec; + for (int i = 0; i < projInfo->colInfo.surfaceCount; i++) + { + worldSurface* currSurface = &projInfo->colInfo.surfaces[i]; + int triCount = currSurface->triCount; + + for (int k = 0; k < triCount * 3; k += 3) + { + int firstIndex_Index = currSurface->firstIndex_Index; + int firstVertexIndex = currSurface->firstVertexIndex; + + // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire + // clipmap verts buffer, so this code updates the indexes to follow that. + int triIndex0 = projInfo->colInfo.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = projInfo->colInfo.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = projInfo->colInfo.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + + // triangle index ordering is opposite to blenders, so its converted here + triIndexVec.push_back(triIndex2); + triIndexVec.push_back(triIndex1); + triIndexVec.push_back(triIndex0); + } + } + _ASSERT(triIndexVec.size() % 3 == 0); + clipMap->triCount = triIndexVec.size() / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[triIndexVec.size()]); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + std::vector partitionVec; + for (int i = 0; i < projInfo->colInfo.surfaceCount; i++) + { + int triCount = projInfo->colInfo.surfaces[i].triCount; + int firstTriIndex = projInfo->colInfo.surfaces[i].firstIndex_Index / 3; + for (int k = 0; k < triCount; k++) + { + CollisionPartition newPartition; + newPartition.nuinds = 0; // initialised later + newPartition.fuind = 0; // initialised later + newPartition.triCount = 1; + newPartition.firstTri = firstTriIndex; + firstTriIndex += 1; + + partitionVec.push_back(newPartition); + } + } + clipMap->partitionCount = partitionVec.size(); + clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; + memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); + + int totalUindCount = 0; + std::vector uindVec; + for (int i = 0; i < clipMap->partitionCount; i++) + { + CollisionPartition* currPartition = &clipMap->partitions[i]; + std::vector uniqueVertVec; + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (size_t m = 0; m < uniqueVertVec.size(); m++) + { + if (uniqueVertVec[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + uniqueVertVec.push_back(vertIndex); + } + } + + currPartition->fuind = totalUindCount; + currPartition->nuinds = (int)uniqueVertVec.size(); + uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); + totalUindCount += currPartition->nuinds; + } + clipMap->info.nuinds = totalUindCount; + clipMap->info.uinds = new uint16_t[totalUindCount]; + memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); + } + void createClipMap(customMapInfo* projInfo) { clipMap_t* clipMap = new clipMap_t; @@ -1148,12 +1311,21 @@ class CustomMapLinker clipMap->box_model.maxs.z = 0.0f; clipMap->box_model.radius = 0.0f; clipMap->box_model.info = NULL; - clipMap->box_model.leaf.mins.x = 3.4028235e38; - clipMap->box_model.leaf.mins.y = 3.4028235e38; - clipMap->box_model.leaf.mins.z = 3.4028235e38; - clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive - clipMap->box_model.leaf.maxs.y = -3.4028235e38; - clipMap->box_model.leaf.maxs.z = -3.4028235e38; + + // for some reason the maxs are negative, and mins are positive + // float box_mins = 3.4028235e38; + // float box_maxs = -3.4028235e38; + // hack: the floats above can't be converted to 32 bit floats, and the game requires them to be exact + // so we use the hex representation and set it using int pointers + unsigned int box_mins = 0x7F7FFFFF; + unsigned int box_maxs = 0xFF7FFFFF; + *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; + *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; + *((unsigned int*)&clipMap->box_model.leaf.maxs.z) = box_maxs; + clipMap->box_model.leaf.brushContents = -1; clipMap->box_model.leaf.terrainContents = 0; clipMap->box_model.leaf.cluster = 0; @@ -1250,6 +1422,11 @@ class CustomMapLinker currDef->physConstraints[3] = 0x1FF; } + // cmodels is the collision for mapents + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + _ASSERT(gfxWorld->modelCount == 1); clipMap->numSubModels = 1; clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; @@ -1265,166 +1442,38 @@ class CustomMapLinker clipMap->cmodels[0].leaf.leafBrushNode = 0; clipMap->cmodels[0].leaf.cluster = 0; clipMap->cmodels[0].info = NULL; - - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; - clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + clipMap->cmodels[0].radius = CMUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - clipMap->numStaticModels = 0; - clipMap->staticModelList = NULL; + + addXModelsToCollision(projInfo, clipMap); clipMap->info.numMaterials = 1; clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); + clipMap->info.materials[0].name = _strdup(missingMaterialName.c_str()); clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; - int collisionVertexCount = projInfo->gfxInfo.vertexCount; - vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; - for (int i = 0; i < collisionVertexCount; i++) - { - convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = convertedCollisionVerts; - - // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - const int maxVerts = UINT16_MAX - 1; - if (collisionVertexCount > maxVerts) - { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); - hasLinkFailed = true; - return; - } - - clipMap->triCount = projInfo->gfxInfo.indexCount / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri - int triIndex = 0; - for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) - { - clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; - clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; - clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; - triIndex++; - } - - int partitionCount = 0; - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) - { - int triCount = projInfo->gfxInfo.surfaces[i].triCount; - int subdividedSize = triCount / 16; // integer division used - partitionCount += subdividedSize; - - // add another partition if the triangle count doesn't add up to a multiple of 16 - if (triCount % 16 != 0) - partitionCount++; - } - clipMap->partitionCount = partitionCount; - clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; - - int partitionIndex = 0; - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) - { - int triCount = projInfo->gfxInfo.surfaces[i].triCount; - int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; - int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; - int firstTri = firstIndex / 3; - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. - // NEED TO FIX - // fixup the tri indices. - // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] - // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] - // this loop fixes the index buffer by adding the firstVertex to each index - for (int k = 0; k < triCount; k++) - { - clipMap->triIndices[k + firstTri][0] += firstVertex; - clipMap->triIndices[k + firstTri][1] += firstVertex; - clipMap->triIndices[k + firstTri][2] += firstVertex; - } - - while (triCount > 0) - { - if (triCount > 16) - clipMap->partitions[partitionIndex].triCount = 16; - else - clipMap->partitions[partitionIndex].triCount = triCount; - - clipMap->partitions[partitionIndex].firstTri = firstTri; - - triCount -= 16; - firstIndex += 16 * 3; - partitionIndex++; - } - } - - int uindCount = 0; - clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - int uniqueVertBufferCount = 0; - uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (int m = 0; m < uniqueVertBufferCount; m++) - { - if (uniqueVertBuffer[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - { - uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; - uniqueVertBufferCount++; - } - } - } - - _ASSERT(uniqueVertBufferCount <= 3 * 16); - - currPartition->fuind = uindCount; - currPartition->nuinds = uniqueVertBufferCount; - memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); - uindCount += uniqueVertBufferCount; - } - _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); - clipMap->info.nuinds = uindCount; - // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls + // might do weird stuff on walls, but from testing doesnt seem to do much int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); + memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - // BSP creation must go last as it depends on unids, tris and verts already being populated + // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated // HACK: // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, // and then convert them back when it is being parsed into the clipmap. Requires some hacky // logic, check populateBSPTree_r and addAABBTreeFromLeaf + createPartitions(projInfo, clipMap); + vec3_t* firstVert = &clipMap->verts[0]; vec3_t clipMins; vec3_t clipMaxs; @@ -1436,13 +1485,16 @@ class CustomMapLinker clipMaxs.z = firstVert->z; clipMins = CMUtil::convertFromBO2Coords(clipMins); clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); - for (int i = 1; i < clipMap->vertCount; i++) + for (unsigned int i = 1; i < clipMap->vertCount; i++) { vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); } BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + + _ASSERT(!tree->isLeaf); + for (int i = 0; i < clipMap->partitionCount; i++) { auto currPartition = &clipMap->partitions[i]; @@ -1489,20 +1541,20 @@ class CustomMapLinker comWorld->primaryLightCount = 2; comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; + // default light is always empty ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; memset(defaultLight, 0, sizeof(ComPrimaryLight)); ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; memset(sunLight, 0, sizeof(ComPrimaryLight)); sunLight->type = 1; - // Below are default values taken from mp_dig - sunLight->diffuseColor.r = 1.0f; - sunLight->diffuseColor.g = 1.0f; - sunLight->diffuseColor.b = 1.0f; - sunLight->diffuseColor.a = 0.0f; - sunLight->dir.x = -0.2410777360200882; - sunLight->dir.y = -0.8407384753227234; - sunLight->dir.z = 0.48480960726737976; + sunLight->diffuseColor.r = 0.75f; + sunLight->diffuseColor.g = 0.75f; + sunLight->diffuseColor.b = 0.75f; + sunLight->diffuseColor.a = 1.0f; + sunLight->dir.x = 0.0f; + sunLight->dir.y = 0.0f; + sunLight->dir.z = 0.0f; m_context.AddAsset(comWorld->name, comWorld); } @@ -1616,42 +1668,42 @@ class CustomMapLinker void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) { - if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/mod.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/mod_amb.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/mod_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/createfx/mod_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/mod.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/mod_amb.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/mod_fx.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/createfx/mod_fx.csc") == NULL) { hasLinkFailed = true; return; diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp index 42c41951b..f780448cc 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -22,21 +22,32 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { - auto mapFile = m_search_path.Open("custom_map/map.obj"); - if (!mapFile.IsOpen()) + auto mapGfxFile = m_search_path.Open("custom_map/map_gfx.fbx"); + if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); + printf("Loading map data...\n"); + // create map info from the obj file customMapInfo* mapInfo = CustomMapInfo::createCustomMapInfo(m_zone.m_name, m_search_path); if (mapInfo == NULL) return AssetCreationResult::Failure(); + + printf("Creating map from data...\n"); // linker will add all the assets needed CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); bool result = linker->linkCustomMap(mapInfo); + //auto gfxWorldAsset = context.LoadDependency("default_1st_person"); + //return AssetCreationResult::Success(gfxWorldAsset); + if (result) - return AssetCreationResult::NoAction(); + { + auto gfxWorldAsset = context.LoadDependency(mapInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + return AssetCreationResult::Success(gfxWorldAsset); + } else return AssetCreationResult::Failure(); } diff --git a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h index d73226ee4..8ee1a62da 100644 --- a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h +++ b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h @@ -1,4 +1,5 @@ // OBJ_Loader.h - A Single Header OBJ Model Loader +// modified to work better with OpenAssetTools #pragma once @@ -17,6 +18,8 @@ // Math.h - STD math Library #include +#include "SearchPath/ISearchPath.h" + // Print progress to console while loading (large models) // #define OBJL_CONSOLE_OUTPUT @@ -432,15 +435,11 @@ namespace objl // // If the file is unable to be found // or unable to be loaded return false - bool LoadFile(std::string Path) + bool LoadFile(ISearchPath& searchPath, std::string fileName) { - // If the file is not an .obj file return false - if (Path.substr(Path.size() - 4, 4) != ".obj") - return false; - - std::ifstream file(Path); + auto file = searchPath.Open(fileName); - if (!file.is_open()) + if (!file.IsOpen()) return false; LoadedMeshes.clear(); @@ -467,7 +466,7 @@ namespace objl #endif std::string curline; - while (std::getline(file, curline)) + while (std::getline(*file.m_stream, curline)) { #ifdef OBJL_CONSOLE_OUTPUT if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) @@ -642,13 +641,13 @@ namespace objl // Generate a path to the material file std::vector temp; - algorithm::split(Path, temp, "\\"); // update: use windows file seperators + algorithm::split(fileName, temp, "/"); // update: use windows file seperators std::string pathtomat = ""; if (temp.size() != 1) { - for (int i = 0; i < temp.size() - 1; i++) + for (size_t i = 0; i < temp.size() - 1; i++) { pathtomat += temp[i] + "/"; } @@ -661,7 +660,7 @@ namespace objl #endif // Load Materials - LoadMaterials(pathtomat); + LoadMaterials(searchPath, pathtomat); } } @@ -681,16 +680,14 @@ namespace objl LoadedMeshes.push_back(tempMesh); } - file.close(); - // Set Materials for each Mesh - for (int i = 0; i < MeshMatNames.size(); i++) + for (size_t i = 0; i < MeshMatNames.size(); i++) { std::string matname = MeshMatNames[i]; // Find corresponding material name in loaded materials // when found copy material variables into mesh material - for (int j = 0; j < LoadedMaterials.size(); j++) + for (size_t j = 0; j < LoadedMaterials.size(); j++) { if (LoadedMaterials[j].name == matname) { @@ -941,7 +938,7 @@ namespace objl } // If Vertex is not an interior vertex - float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (180 / 3.14159265359); + float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (float)(180 / 3.14159265359); if (angle <= 0 && angle >= 180) continue; @@ -996,16 +993,12 @@ namespace objl } // Load Materials from .mtl file - bool LoadMaterials(std::string path) + bool LoadMaterials(ISearchPath& searchPath, std::string fileName) { - // If the file is not a material file return false - if (path.substr(path.size() - 4, path.size()) != ".mtl") - return false; - - std::ifstream file(path); + auto file = searchPath.Open(fileName); // If the file is not found return false - if (!file.is_open()) + if (!file.IsOpen()) return false; Material tempMaterial; @@ -1014,7 +1007,7 @@ namespace objl // Go through each line looking for material variables std::string curline; - while (std::getline(file, curline)) + while (std::getline(*file.m_stream, curline)) { // new material and material name if (algorithm::firstToken(curline) == "newmtl") diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp index b08ca254d..7682fdef9 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -1,105 +1,413 @@ #include "ProjectCreator.h" -#include "OBJ_Loader.h" +#include "fbx/ufbx.h" -void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo) +std::vector vertexVec; +std::vector indexVec; +std::vector surfaceVec; +std::vector modelVec; +bool hasTangentSpace = true; + +bool loadFBXMesh(ufbx_node* node) { - int meshVertexCount = OBJMesh->Vertices.size(); - int meshIndexCount = OBJMesh->Indices.size(); - int surfVertexStart = projInfo->gfxInfo.vertexCount; - int surfIndexStart = projInfo->gfxInfo.indexCount; + if (node->attrib_type != UFBX_ELEMENT_MESH) + { + printf("ignoring non-mesh node \"%s\"\n", node->name.data); + return false; + } + ufbx_mesh* mesh = node->mesh; - projInfo->gfxInfo.vertexCount += meshVertexCount; - projInfo->gfxInfo.indexCount += meshIndexCount; + if (mesh->instances.count != 1) + printf("node %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); + + if (mesh->num_triangles == 0) + { + printf("ignoring mesh %s: triangle count is 0.\n", node->name.data); + return false; + } - projInfo->gfxInfo.vertices = (customMapVertex*)realloc(projInfo->gfxInfo.vertices, sizeof(customMapVertex) * projInfo->gfxInfo.vertexCount); - projInfo->gfxInfo.indices = (uint16_t*)realloc(projInfo->gfxInfo.indices, sizeof(uint16_t) * projInfo->gfxInfo.indexCount); + if (mesh->num_indices % 3 != 0) + { + printf("ignoring mesh %s: it is not triangulated.\n", node->name.data); + return false; + } + + for (size_t k = 0; k < mesh->num_indices; k++) + { + if (mesh->vertex_indices[k] > UINT16_MAX) + { + printf("ignoring mesh %s, it has more than %i indices.\n", node->name.data, UINT16_MAX); + return false; + } + } - customMapVertex* surfVertices = &projInfo->gfxInfo.vertices[surfVertexStart]; - uint16_t* surfIndices = &projInfo->gfxInfo.indices[surfIndexStart]; + _ASSERT(mesh->vertex_position.unique_per_vertex == true); + _ASSERT(mesh->vertex_color.unique_per_vertex == false); + _ASSERT(mesh->vertex_uv.unique_per_vertex == false); + _ASSERT(mesh->vertex_normal.unique_per_vertex == false); + _ASSERT(mesh->vertex_tangent.unique_per_vertex == false); - meshSurface->firstIndex_Index = surfIndexStart; - meshSurface->firstVertexIndex = surfVertexStart; - meshSurface->triCount = meshIndexCount / 3; - _ASSERT(meshIndexCount % 3 == 0); + if (mesh->vertex_tangent.exists == false) + hasTangentSpace = false; - for (int i = 0; i < meshIndexCount; i++) + // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back + ufbx_transform origTransform = node->local_transform; + origTransform.translation.x /= 100.0f; + origTransform.translation.y /= 100.0f; + origTransform.translation.z /= 100.0f; + origTransform.scale.x /= 100.0f; + origTransform.scale.y /= 100.0f; + origTransform.scale.z /= 100.0f; + ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); + + CM_MATERIAL_TYPE meshMaterialType; + if (mesh->materials.count == 0) + { + if (!mesh->vertex_color.exists) + { + printf("mesh with no colour/texture data: %s\n", node->name.data); + meshMaterialType = NO_COLOUR_OR_TEXTURE; + } + else + { + printf("colour only mesh %s\n", node->name.data); + meshMaterialType = CM_MATERIAL_COLOUR; + } + } + else { - _ASSERT(OBJMesh->Indices[i] < UINT16_MAX); - surfIndices[i] = OBJMesh->Indices[i]; + meshMaterialType = CM_MATERIAL_TEXTURE; } - for (int i = 0; i < meshVertexCount; i++) + + // FBX loading code modified from https://ufbx.github.io/elements/meshes/ + // Seems like I have to use this exact way of loading, otherwise some values become incorrect + for (int i = 0; i < mesh->material_parts.count; i++) { - objl::Vertex* meshVertex = &OBJMesh->Vertices[i]; - customMapVertex* surfVertex = &surfVertices[i]; + ufbx_mesh_part* meshPart = &mesh->material_parts.data[i]; + + if (meshPart->num_faces == 0) + continue; + + worldSurface surface; + surface.flags = 0; + surface.triCount = meshPart->num_triangles; + surface.firstVertexIndex = vertexVec.size(); + surface.firstIndex_Index = indexVec.size(); + + surface.material.materialType = meshMaterialType; + switch (meshMaterialType) + { + case CM_MATERIAL_TEXTURE: + surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); + break; + + case CM_MATERIAL_COLOUR: + case NO_COLOUR_OR_TEXTURE: + surface.material.materialName = ""; + break; + + default: + _ASSERT(false); + } + + size_t num_triangles = meshPart->num_triangles; + customMapVertex* vertices = (customMapVertex*)calloc(num_triangles * 3, sizeof(customMapVertex)); + size_t num_vertices = 0; - surfVertex->pos.x = meshVertex->Position.X; - surfVertex->pos.y = meshVertex->Position.Y; - surfVertex->pos.z = meshVertex->Position.Z; + // Reserve space for the maximum triangle indices. + size_t num_tri_indices = mesh->max_face_triangles * 3; + uint32_t* tri_indices = (uint32_t*)calloc(num_tri_indices, sizeof(uint32_t)); - surfVertex->color[0] = OBJMesh->MeshMaterial.Kd.X; - surfVertex->color[1] = OBJMesh->MeshMaterial.Kd.Y; - surfVertex->color[2] = OBJMesh->MeshMaterial.Kd.Z; - surfVertex->color[3] = 1.0f; + _ASSERT(meshPart->num_triangles == meshPart->num_faces); + for (size_t face_ix = 0; face_ix < meshPart->num_faces; face_ix++) + { + ufbx_face face = mesh->faces.data[meshPart->face_indices.data[face_ix]]; - surfVertex->texCoord[0] = meshVertex->TextureCoordinate.X; - surfVertex->texCoord[1] = meshVertex->TextureCoordinate.Y; + // Triangulate the face into `tri_indices[]`. + uint32_t num_tris = ufbx_triangulate_face(tri_indices, num_tri_indices, mesh, face); - surfVertex->normal.x = meshVertex->Normal.X; - surfVertex->normal.y = meshVertex->Normal.Y; - surfVertex->normal.z = meshVertex->Normal.Z; + // Iterate over each triangle corner contiguously. + for (size_t q = 0; q < num_tris * 3; q++) + { + uint32_t index = tri_indices[q]; - // TODO: fix tangents, seems to work for now though - surfVertex->tangent.x = 1.0f; - surfVertex->tangent.y = 0.0f; - surfVertex->tangent.z = 0.0f; + customMapVertex* vertex = &vertices[num_vertices++]; - surfVertex->packedLmapCoord = 0; + ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); + vertex->pos.x = transformedPos.x; + vertex->pos.y = transformedPos.y; + vertex->pos.z = transformedPos.z; - surfVertex->binormalSign = 0.0f; + // textured and missing materials are set to white + switch (meshMaterialType) + { + case CM_MATERIAL_TEXTURE: + case NO_COLOUR_OR_TEXTURE: + vertex->color[0] = 1.0f; + vertex->color[1] = 1.0f; + vertex->color[2] = 1.0f; + vertex->color[3] = 1.0f; + break; + + case CM_MATERIAL_COLOUR: + vertex->color[0] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).x; + vertex->color[1] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).y; + vertex->color[2] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).z; + vertex->color[3] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).w; + break; + + default: + _ASSERT(false); + } + + + // 1.0f - uv.v: + // https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange + vertex->texCoord[0] = (float)(ufbx_get_vertex_vec2(&mesh->vertex_uv, index).x); + vertex->texCoord[1] = (float)(1.0f - ufbx_get_vertex_vec2(&mesh->vertex_uv, index).y); + + vertex->normal.x = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x; + vertex->normal.y = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y; + vertex->normal.z = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z; + + if (mesh->vertex_tangent.exists) + { + vertex->tangent.x = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x; + vertex->tangent.y = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y; + vertex->tangent.z = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z; + } + else + { + vertex->tangent.x = 0.0f; + vertex->tangent.y = 0.0f; + vertex->tangent.z = 0.0f; + } + + vertex->packedLmapCoord = 0; + + // possibly bitangent, unsure what the sign part means though + vertex->binormalSign = 0.0f; + } + } + + _ASSERT(num_vertices == num_triangles * 3); + + // Generate the index buffer. + ufbx_vertex_stream streams[1] = { + {vertices, num_vertices, sizeof(customMapVertex)}, + }; + size_t num_indices = num_triangles * 3; + uint32_t* indices = (uint32_t*)calloc(num_indices, sizeof(uint32_t)); + + // This call will deduplicate vertices, modifying the arrays passed in `streams[]`, + // indices are written in `indices[]` and the number of unique vertices is returned. + num_vertices = ufbx_generate_indices(streams, 1, indices, num_indices, NULL, NULL); + _ASSERT(num_vertices != 0); + + vertexVec.insert(vertexVec.end(), &vertices[0], &vertices[num_vertices]); + + size_t currIndexVecSize = indexVec.size(); + indexVec.resize(indexVec.size() + num_indices); + for (size_t m = 0; m < num_indices; m++) + { + indexVec[currIndexVecSize + m] = (uint16_t)indices[m]; + } + + surfaceVec.push_back(surface); } + + return true; } -customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +bool loadFBXModel(ufbx_node* node) { - std::string objFilePath = searchPath.GetPath() + "custom_map/world.obj"; + customMapModel model; + + model.name = node->name.data; + + ufbx_transform origTransform = node->local_transform; + origTransform.translation.x /= 100.0f; + origTransform.translation.y /= 100.0f; + origTransform.translation.z /= 100.0f; + origTransform.scale.x /= 100.0f; + origTransform.scale.y /= 100.0f; + origTransform.scale.z /= 100.0f; + ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - objl::Loader OBJloader; - bool isLoaded = OBJloader.LoadFile(objFilePath); - if (!isLoaded) + model.origin.x = node->local_transform.translation.x / 100.0f; + model.origin.y = node->local_transform.translation.y / 100.0f; + model.origin.z = node->local_transform.translation.z / 100.0f; + model.rotation.x = node->euler_rotation.x; + model.rotation.y = node->euler_rotation.y; + model.rotation.z = node->euler_rotation.z; + model.scale = node->local_transform.scale.x / 100.0f; + + if (model.scale == 0.0f) { - printf("OBJLoader: unable to load obj file %s\n", objFilePath.c_str()); - return NULL; + printf("WARN: Ignoring model %s: has a scale of 0!\n", node->name.data); + return false; } - customMapInfo* projInfo = new customMapInfo; + if (node->local_transform.scale.x != node->local_transform.scale.y || node->local_transform.scale.x != node->local_transform.scale.z) + printf("WARNING: model %s uses non-uniform scaling! Only the X axis will be used for the scale value.\n", node->name.data); + modelVec.push_back(model); - projInfo->name = projectName; - projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + return true; +} + +void parseGFXData(ufbx_scene* scene, customMapInfo* projInfo) +{ + vertexVec.clear(); + indexVec.clear(); + surfaceVec.clear(); + modelVec.clear(); + hasTangentSpace = true; + + for (size_t i = 0; i < scene->nodes.count; i++) + { + ufbx_node* node = scene->nodes.data[i]; + + switch (node->attrib_type) + { + case UFBX_ELEMENT_MESH: + loadFBXMesh(node); + break; + case UFBX_ELEMENT_EMPTY: + // loadFBXModel(node); + break; + default: + printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + break; + } + } + projInfo->gfxInfo.surfaceCount = surfaceVec.size(); + projInfo->gfxInfo.vertexCount = vertexVec.size(); + projInfo->gfxInfo.indexCount = indexVec.size(); + projInfo->modelCount = modelVec.size(); + projInfo->gfxInfo.surfaces = new worldSurface[surfaceVec.size()]; + projInfo->gfxInfo.vertices = new customMapVertex[vertexVec.size()]; + projInfo->gfxInfo.indices = new uint16_t[indexVec.size()]; + projInfo->models = new customMapModel[modelVec.size()]; + memcpy(projInfo->gfxInfo.surfaces, &surfaceVec[0], surfaceVec.size() * sizeof(worldSurface)); + memcpy(projInfo->gfxInfo.vertices, &vertexVec[0], vertexVec.size() * sizeof(customMapVertex)); + memcpy(projInfo->gfxInfo.indices, &indexVec[0], indexVec.size() * sizeof(uint16_t)); + // memcpy(projInfo->models, &modelVec[0], modelVec.size() * sizeof(customMapModel)); + + if (hasTangentSpace == false) + printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); +} + +void parseCollisionData(ufbx_scene* scene, customMapInfo* projInfo) +{ + // hack: cbf changing the code for collision data, so just load collision dada as if it was gfx data + + vertexVec.clear(); + indexVec.clear(); + surfaceVec.clear(); + modelVec.clear(); + hasTangentSpace = true; + + for (size_t i = 0; i < scene->nodes.count; i++) + { + ufbx_node* node = scene->nodes.data[i]; + + switch (node->attrib_type) + { + case UFBX_ELEMENT_MESH: + loadFBXMesh(node); + break; + case UFBX_ELEMENT_EMPTY: + // loadFBXModel(node); + break; + default: + printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + break; + } + } + projInfo->colInfo.surfaceCount = surfaceVec.size(); + projInfo->colInfo.vertexCount = vertexVec.size(); + projInfo->colInfo.indexCount = indexVec.size(); + projInfo->modelCount = modelVec.size(); + projInfo->colInfo.surfaces = new worldSurface[surfaceVec.size()]; + projInfo->colInfo.vertices = new customMapVertex[vertexVec.size()]; + projInfo->colInfo.indices = new uint16_t[indexVec.size()]; + projInfo->models = new customMapModel[modelVec.size()]; + memcpy(projInfo->colInfo.surfaces, &surfaceVec[0], surfaceVec.size() * sizeof(worldSurface)); + memcpy(projInfo->colInfo.vertices, &vertexVec[0], vertexVec.size() * sizeof(customMapVertex)); + memcpy(projInfo->colInfo.indices, &indexVec[0], indexVec.size() * sizeof(uint16_t)); + // memcpy(projInfo->models, &modelVec[0], modelVec.size() * sizeof(customMapModel)); + + if (hasTangentSpace == false) + printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); +} - projInfo->gfxInfo.surfaceCount = OBJloader.LoadedMeshes.size(); - projInfo->gfxInfo.surfaces = new worldSurface[projInfo->gfxInfo.surfaceCount]; +customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +{ + ufbx_scene* gfxScene; + ufbx_scene* colScene; + + std::string gfxFbxPath = "custom_map/map_gfx.fbx"; + auto gfxFile = searchPath.Open(gfxFbxPath); + if (!gfxFile.IsOpen()) + { + printf("Failed to open map gfx fbx file: %s\n", gfxFbxPath.c_str()); + return NULL; + } - projInfo->gfxInfo.vertexCount = 0; - projInfo->gfxInfo.indexCount = 0; - projInfo->gfxInfo.vertices = (customMapVertex*)malloc(1); - projInfo->gfxInfo.indices = (uint16_t*)malloc(1); + char* gfxMapData = new char[gfxFile.m_length]; + gfxFile.m_stream->seekg(0); + gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); - for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + ufbx_error error; + ufbx_load_opts opts; + opts.target_axes = ufbx_axes_right_handed_y_up; + opts.generate_missing_normals = true; + opts.allow_missing_vertex_position = false; + gfxScene = ufbx_load_memory(gfxMapData, gfxFile.m_length, NULL, &error); + if (!gfxScene) { - objl::Mesh* currMesh = &OBJloader.LoadedMeshes[i]; - worldSurface* currSurface = &projInfo->gfxInfo.surfaces[i]; + fprintf(stderr, "Failed to load map gfx fbx file: %s\n", error.description.data); + return NULL; + } - currSurface->materialName = currMesh->MeshMaterial.name; - currSurface->reflectionProbeIndex = 0; - currSurface->primaryLightIndex = 0; - currSurface->lightmapIndex = 0; - currSurface->flags = 0; + std::string colFbxPath = "custom_map/map_col.fbx"; + auto colFile = searchPath.Open(colFbxPath); + if (!colFile.IsOpen()) + { + printf("Failed to open map collison fbx file: %s. map gfx will be used for collision instead.\n", colFbxPath.c_str()); + colScene = gfxScene; + } + else + { + char* colMapData = new char[colFile.m_length]; + colFile.m_stream->seekg(0); + colFile.m_stream->read(colMapData, colFile.m_length); - parseMesh(currMesh, currSurface, projInfo); + ufbx_error error; + ufbx_load_opts opts; + opts.target_axes = ufbx_axes_right_handed_y_up; + opts.generate_missing_normals = true; + opts.allow_missing_vertex_position = false; + colScene = ufbx_load_memory(colMapData, colFile.m_length, NULL, &error); + if (!colScene) + { + fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); + return NULL; + } } + customMapInfo* projInfo = new customMapInfo; + + projInfo->name = projectName; + projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + + parseGFXData(gfxScene, projInfo); + parseCollisionData(colScene, projInfo); + + ufbx_free_scene(gfxScene); + if (gfxScene != colScene) + ufbx_free_scene(colScene); + return projInfo; } - diff --git a/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h b/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h new file mode 100644 index 000000000..4e417c412 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Util.h" +#include "CustomMapConsts.h" + +using TriangleGroup = std::vector; // Group of triangle indices +using AdjacencyList = std::vector; // Triangle-to-triangle connections + +class TriangleSort +{ +private: + static void computeTriCentroid(vec3_t& result, std::vector& vertices, uint16_t i0, uint16_t i1, uint16_t i2) + { + result.x = 0.0f; + result.y = 0.0f; + result.z = 0.0f; + + result.x += vertices[i0].x; + result.y += vertices[i0].y; + result.z += vertices[i0].z; + result.x += vertices[i1].x; + result.y += vertices[i1].y; + result.z += vertices[i1].z; + result.x += vertices[i2].x; + result.y += vertices[i2].y; + result.z += vertices[i2].z; + + result.x /= 3.0f; + result.y /= 3.0f; + result.z /= 3.0f; + } + + static AdjacencyList buildAdjacencyList(const std::vector& indices, std::vector& vertices, int maxTrianglesPerGroup) + { + size_t numTriangles = indices.size() / 3; + AdjacencyList adjList(numTriangles); + + std::vector centroids; + for (size_t i = 0; i < numTriangles; i++) + { + vec3_t center; + computeTriCentroid(center, vertices, indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]); + centroids.push_back(center); + } + + std::vector visited(numTriangles, false); + for (size_t i = 0; i < numTriangles; ++i) + { + _ASSERT(i < MAX_COL_VERTS); + + if (visited[i] == true) + continue; + visited[i] = true; + + // Store distances to other triangles + std::vector> distances; + for (size_t j = 0; j < numTriangles; ++j) + { + if (visited[j] == true) + continue; + + if (i != j) + { + float dist = CMUtil::distBetweenPoints(centroids[i], centroids[j]); + distances.emplace_back(dist, j); + } + } + + // Sort by distance and take the closest maxNeighbors + std::sort(distances.begin(), distances.end()); + for (size_t k = 0; k < std::min((size_t)maxTrianglesPerGroup, distances.size()); ++k) + { + unsigned int neighbour = distances[k].second; + adjList[i].push_back(neighbour); + visited[neighbour] = true; + } + } + + for (size_t i = 0; i < visited.size(); i++) + { + if (visited[i] == false) + printf("WARN: missing triangle: %i\n", i); + } + + return adjList; + } + +public: + static AdjacencyList groupTriangles(std::vector& indices, std::vector& vertices, int maxTrianglesPerGroup) + { + return buildAdjacencyList(indices, vertices, maxTrianglesPerGroup); + } +}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h index 216fa4bfd..a30ad7adb 100644 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -1,5 +1,6 @@ #pragma once +#include #include "Game/T6/T6.h" using namespace T6; @@ -73,4 +74,50 @@ class CMUtil { return ((size + 127) & 0xFFFFFF80); } + + static float distBetweenPoints(vec3_t p1, vec3_t p2) + { + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); + } + + // angles are in euler degrees + static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis) + { + float xRadians = angles->x * 0.017453292; // M_PI / 180.0f + float yRadians = angles->y * 0.017453292; // M_PI / 180.0f + float zRadians = angles->z * 0.017453292; // M_PI / 180.0f + + float cosX = cos(xRadians); + float sinX = sin(xRadians); + float cosY = cos(yRadians); + float sinY = sin(yRadians); + float cosZ = cos(zRadians); + float sinZ = sin(zRadians); + + axis[0].x = cosX * cosY; + axis[0].y = cosX * sinY; + axis[0].z = -sinX; + axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); + axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); + axis[1].z = sinZ * cosX; + axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); + axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); + axis[2].z = cosZ * cosX; + } + + static void matrixTranspose3x3(const vec3_t* in, vec3_t* out) + { + out[0].x = in[0].x; + out[0].y = in[1].x; + out[0].z = in[2].x; + out[1].x = in[0].y; + out[1].y = in[1].y; + out[1].z = in[2].y; + out[2].x = in[0].z; + out[2].y = in[1].z; + out[2].z = in[2].z; + } }; diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c b/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c new file mode 100644 index 000000000..cd7273d88 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c @@ -0,0 +1,33093 @@ +#ifndef UFBX_UFBX_C_INCLUDED +#define UFBX_UFBX_C_INCLUDED + +#if defined(UFBX_HEADER_PATH) + #include UFBX_HEADER_PATH +#else + #include "ufbx.h" +#endif + +// -- User configuration + +// User configuration: +// UFBX_REGRESSION Enable regression mode for development +// UFBX_UBSAN Explicitly enable undefined behavior sanitizer workarounds +// UFBX_NO_UNALIGNED_LOADS Do not use unaligned loads even when they are supported +// UFBX_USE_UNALIGNED_LOADS Forcibly use unaligned loads on unknown platforms +// UFBX_USE_SSE Explicitly enable SSE2 support (for x86) +// UFBX_HAS_FTELLO Allow ufbx to use `ftello()` to measure file size +// UFBX_WASM_32BIT Optimize WASM for 32-bit architectures +// UFBX_TRACE Log calls of `ufbxi_check()` for tracing execution +// UFBX_LITTLE_ENDIAN=0/1 Explicitly define little/big endian architecture +// UFBX_PATH_SEPARATOR='' Specify default platform path separator +// UFBX_NO_SSE Do not try to include SSE + +// Dependencies: +// UFBX_NO_MALLOC Disable default malloc/realloc/free +// UFBX_NO_STDIO Disable stdio FILE API +// UFBX_EXTERNAL_MALLOC Link to external ufbx_malloc() interface +// UFBX_EXTERNAL_STDIO Link to external ufbx_stdio_() interface +// UFBX_EXTERNAL_MATH Link to external interface +// UFBX_EXTERNAL_STRING Link to external interface + +// Freestanding: +// UFBX_MATH_PREFIX='ufbx_' Prefix for external functions used +// UFBX_STRING_PREFIX='ufbx_' Prefix for external functions used +// UFBX_NO_LIBC Do not include libc (implies UFBX_EXTERNAL_MATH/STRING/MALLOC/STDIO by default) +// UFBX_NO_LIBC_TYPES Do not include any libc headers, you must define all types in + +// Mostly internal for debugging: +// UFBX_STATIC_ANALYSIS Enable static analysis augmentation +// UFBX_DEBUG_BINARY_SEARCH Force using binary search for debugging +// UFBX_EXTENSIVE_THREADING Use threads for small inputs +// UFBX_POINTER_SIZE Allow specifying sizeof(void*) as a preprocessor constant +// UFBX_MAXIMUM_ALIGNMENT Maximum alignment used for allocation + +#if defined(UFBX_CONFIG_SOURCE) + #include UFBX_CONFIG_SOURCE +#endif + +// -- Configuration + +#define UFBXI_MAX_NON_ARRAY_VALUES 8 +#define UFBXI_MAX_NODE_DEPTH 32 +#define UFBXI_MAX_XML_DEPTH 32 +#define UFBXI_MAX_SKIP_SIZE 0x40000000 +#define UFBXI_MAP_MAX_SCAN 32 +#define UFBXI_KD_FAST_DEPTH 6 +#define UFBXI_HUGE_MAX_SCAN 16 +#define UFBXI_MIN_FILE_FORMAT_LOOKAHEAD 32 +#define UFBXI_FACE_GROUP_HASH_BITS 8 +#define UFBXI_MIN_THREADED_DEFLATE_BYTES 256 +#define UFBXI_MIN_THREADED_ASCII_VALUES 64 +#define UFBXI_GEOMETRY_CACHE_BUFFER_SIZE 512 + +#ifndef UFBXI_MAX_NURBS_ORDER +#define UFBXI_MAX_NURBS_ORDER 128 +#endif + +// By default enough to have squares be non-denormal +#ifndef UFBX_EPSILON +#define UFBX_EPSILON (sizeof(ufbx_real) == sizeof(float) ? \ + (ufbx_real)1.0842021795674597e-19f : (ufbx_real)1.4916681462400413e-154) +#endif + +// -- Feature exclusion + +#if !defined(UFBX_MINIMAL) + #if !defined(UFBX_NO_SUBDIVISION) + #define UFBXI_FEATURE_SUBDIVISION 1 + #endif + #if !defined(UFBX_NO_TESSELLATION) + #define UFBXI_FEATURE_TESSELLATION 1 + #endif + #if !defined(UFBX_NO_GEOMETRY_CACHE) + #define UFBXI_FEATURE_GEOMETRY_CACHE 1 + #endif + #if !defined(UFBX_NO_SCENE_EVALUATION) + #define UFBXI_FEATURE_SCENE_EVALUATION 1 + #endif + #if !defined(UFBX_NO_SKINNING_EVALUATION) + #define UFBXI_FEATURE_SKINNING_EVALUATION 1 + #endif + #if !defined(UFBX_NO_ANIMATION_BAKING) + #define UFBXI_FEATURE_ANIMATION_BAKING 1 + #endif + #if !defined(UFBX_NO_TRIANGULATION) + #define UFBXI_FEATURE_TRIANGULATION 1 + #endif + #if !defined(UFBX_NO_INDEX_GENERATION) + #define UFBXI_FEATURE_INDEX_GENERATION 1 + #endif + #if !defined(UFBX_NO_FORMAT_OBJ) + #define UFBXI_FEATURE_FORMAT_OBJ 1 + #endif +#endif + +#if defined(UFBX_DEV) + #if !defined(UFBX_NO_ERROR_STACK) + #define UFBXI_FEATURE_ERROR_STACK 1 + #endif +#endif + +#if !defined(UFBXI_FEATURE_SUBDIVISION) && defined(UFBX_ENABLE_SUBDIVISION) + #define UFBXI_FEATURE_SUBDIVISION 1 +#endif +#if !defined(UFBXI_FEATURE_TESSELLATION) && defined(UFBX_ENABLE_TESSELLATION) + #define UFBXI_FEATURE_TESSELLATION 1 +#endif +#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE) && defined(UFBX_ENABLE_GEOMETRY_CACHE) + #define UFBXI_FEATURE_GEOMETRY_CACHE 1 +#endif +#if !defined(UFBXI_FEATURE_SCENE_EVALUATION) && defined(UFBX_ENABLE_SCENE_EVALUATION) + #define UFBXI_FEATURE_SCENE_EVALUATION 1 +#endif +#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION) && defined(UFBX_ENABLE_SKINNING_EVALUATION) + #define UFBXI_FEATURE_SKINNING_EVALUATION 1 +#endif +#if !defined(UFBXI_FEATURE_ANIMATION_BAKING) && defined(UFBX_ENABLE_ANIMATION_BAKING) + #define UFBXI_FEATURE_ANIMATION_BAKING 1 +#endif +#if !defined(UFBXI_FEATURE_TRIANGULATION) && defined(UFBX_ENABLE_TRIANGULATION) + #define UFBXI_FEATURE_TRIANGULATION 1 +#endif +#if !defined(UFBXI_FEATURE_INDEX_GENERATION) && defined(UFBX_ENABLE_INDEX_GENERATION) + #define UFBXI_FEATURE_INDEX_GENERATION 1 +#endif +#if !defined(UFBXI_FEATURE_FORMAT_OBJ) && defined(UFBX_ENABLE_FORMAT_OBJ) + #define UFBXI_FEATURE_FORMAT_OBJ 1 +#endif +#if !defined(UFBXI_FEATURE_ERROR_STACK) && defined(UFBX_ENABLE_ERROR_STACK) + #define UFBXI_FEATURE_ERROR_STACK 1 +#endif + +#if !defined(UFBXI_FEATURE_SUBDIVISION) + #define UFBXI_FEATURE_SUBDIVISION 0 +#endif +#if !defined(UFBXI_FEATURE_TESSELLATION) + #define UFBXI_FEATURE_TESSELLATION 0 +#endif +#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE) + #define UFBXI_FEATURE_GEOMETRY_CACHE 0 +#endif +#if !defined(UFBXI_FEATURE_SCENE_EVALUATION) + #define UFBXI_FEATURE_SCENE_EVALUATION 0 +#endif +#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION) + #define UFBXI_FEATURE_SKINNING_EVALUATION 0 +#endif +#if !defined(UFBXI_FEATURE_ANIMATION_BAKING) + #define UFBXI_FEATURE_ANIMATION_BAKING 0 +#endif +#if !defined(UFBXI_FEATURE_TRIANGULATION) + #define UFBXI_FEATURE_TRIANGULATION 0 +#endif +#if !defined(UFBXI_FEATURE_INDEX_GENERATION) + #define UFBXI_FEATURE_INDEX_GENERATION 0 +#endif +#if !defined(UFBXI_FEATURE_FORMAT_OBJ) + #define UFBXI_FEATURE_FORMAT_OBJ 0 +#endif +#if !defined(UFBXI_FEATURE_ERROR_STACK) + #define UFBXI_FEATURE_ERROR_STACK 0 +#endif + +// Derived features + +#if UFBXI_FEATURE_GEOMETRY_CACHE + #define UFBXI_FEATURE_XML 1 +#else + #define UFBXI_FEATURE_XML 0 +#endif + +#if UFBXI_FEATURE_TRIANGULATION + #define UFBXI_FEATURE_KD 1 +#else + #define UFBXI_FEATURE_KD 0 +#endif + +#if !UFBXI_FEATURE_SUBDIVISION || !UFBXI_FEATURE_TESSELLATION || !UFBXI_FEATURE_GEOMETRY_CACHE || !UFBXI_FEATURE_SCENE_EVALUATION || !UFBXI_FEATURE_SKINNING_EVALUATION || !UFBXI_FEATURE_ANIMATION_BAKING || !UFBXI_FEATURE_TRIANGULATION || !UFBXI_FEATURE_INDEX_GENERATION || !UFBXI_FEATURE_XML || !UFBXI_FEATURE_KD || !UFBXI_FEATURE_FORMAT_OBJ + #define UFBXI_PARTIAL_FEATURES 1 +#endif + +// -- Headers + +// Legacy mapping +#if !defined(UFBX_EXTERNAL_MATH) && defined(UFBX_NO_MATH_H) + #define UFBX_EXTERNAL_MATH +#endif + +#if !defined(UFBX_NO_LIBC_TYPES) + #include +#endif + +#if !defined(UFBX_NO_LIBC) + #if !defined(UFBX_NO_FLOAT_H) + #include + #endif + #if !defined(UFBX_EXTERNAL_MATH) + #include + #endif + #if !defined(UFBX_EXTERNAL_STRING) + #include + #endif + #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + #include + #endif + #if !defined(UFBX_NO_MALLOC) && !defined(UFBX_EXTERNAL_MALLOC) + #include + #endif +#else + #if !defined(UFBX_EXTERNAL_MATH) && !defined(UFBX_NO_EXTERNAL_MATH) + #define UFBX_EXTERNAL_MATH + #endif + #if !defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_NO_EXTERNAL_STRING) + #define UFBX_EXTERNAL_STRING + #endif + #if !defined(UFBX_EXTERNAL_MALLOC) && !defined(UFBX_NO_EXTERNAL_MALLOC) && !defined(UFBX_NO_MALLOC) + #define UFBX_EXTERNAL_MALLOC + #endif + #if !defined(UFBX_EXTERNAL_STDIO) && !defined(UFBX_NO_EXTERNAL_STDIO) && !defined(UFBX_NO_STDIO) + #define UFBX_EXTERNAL_STDIO + #endif +#endif + +#if defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_STRING_PREFIX) + #define UFBX_STRING_PREFIX ufbx_ +#endif + +#if !defined(UFBX_EXTERNAL_MATH) + #if !defined(UFBX_MATH_PREFIX) + #define UFBX_MATH_PREFIX + #endif +#endif + +#define ufbxi_pre_cat2(a, b) a##b +#define ufbxi_pre_cat(a, b) ufbxi_pre_cat2(a, b) + +// -- External functions + +#ifndef ufbx_extern_abi + #if defined(UFBX_STATIC) + #define ufbx_extern_abi static + #else + #define ufbx_extern_abi + #endif +#endif + +#if defined(UFBX_MATH_PREFIX) + #define ufbxi_math_fn(name) ufbxi_pre_cat(UFBX_MATH_PREFIX, name) + #define ufbx_sqrt ufbxi_math_fn(sqrt) + #define ufbx_fabs ufbxi_math_fn(fabs) + #define ufbx_pow ufbxi_math_fn(pow) + #define ufbx_sin ufbxi_math_fn(sin) + #define ufbx_cos ufbxi_math_fn(cos) + #define ufbx_tan ufbxi_math_fn(tan) + #define ufbx_asin ufbxi_math_fn(asin) + #define ufbx_acos ufbxi_math_fn(acos) + #define ufbx_atan ufbxi_math_fn(atan) + #define ufbx_atan2 ufbxi_math_fn(atan2) + #define ufbx_copysign ufbxi_math_fn(copysign) + #define ufbx_fmin ufbxi_math_fn(fmin) + #define ufbx_fmax ufbxi_math_fn(fmax) + #define ufbx_nextafter ufbxi_math_fn(nextafter) + #define ufbx_rint ufbxi_math_fn(rint) + #define ufbx_floor ufbxi_math_fn(floor) + #define ufbx_ceil ufbxi_math_fn(ceil) + #define ufbx_isnan ufbxi_math_fn(isnan) +#endif + +#if !defined(UFBX_NO_EXTERNAL_DEFINES) + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(UFBX_EXTERNAL_MATH) + ufbx_extern_abi double ufbx_sqrt(double x); + ufbx_extern_abi double ufbx_sin(double x); + ufbx_extern_abi double ufbx_cos(double x); + ufbx_extern_abi double ufbx_tan(double x); + ufbx_extern_abi double ufbx_asin(double x); + ufbx_extern_abi double ufbx_acos(double x); + ufbx_extern_abi double ufbx_atan(double x); + ufbx_extern_abi double ufbx_atan2(double y, double x); + ufbx_extern_abi double ufbx_pow(double x, double y); + ufbx_extern_abi double ufbx_fmin(double a, double b); + ufbx_extern_abi double ufbx_fmax(double a, double b); + ufbx_extern_abi double ufbx_fabs(double x); + ufbx_extern_abi double ufbx_copysign(double x, double y); + ufbx_extern_abi double ufbx_nextafter(double x, double y); + ufbx_extern_abi double ufbx_rint(double x); + ufbx_extern_abi double ufbx_floor(double x); + ufbx_extern_abi double ufbx_ceil(double x); + ufbx_extern_abi int ufbx_isnan(double x); +#endif + +#if defined(UFBX_EXTERNAL_STRING) + ufbx_extern_abi size_t ufbx_strlen(const char *str); + ufbx_extern_abi void *ufbx_memcpy(void *dst, const void *src, size_t count); + ufbx_extern_abi void *ufbx_memmove(void *dst, const void *src, size_t count); + ufbx_extern_abi void *ufbx_memset(void *dst, int ch, size_t count); + ufbx_extern_abi const void *ufbx_memchr(const void *ptr, int value, size_t count); + ufbx_extern_abi int ufbx_memcmp(const void *a, const void *b, size_t count); + ufbx_extern_abi int ufbx_strcmp(const char *a, const char *b); + ufbx_extern_abi int ufbx_strncmp(const char *a, const char *b, size_t count); +#endif + +#if defined(UFBX_EXTERNAL_MALLOC) + ufbx_extern_abi void *ufbx_malloc(size_t size); + ufbx_extern_abi void *ufbx_realloc(void *ptr, size_t old_size, size_t new_size); + ufbx_extern_abi void ufbx_free(void *ptr, size_t old_size); +#endif + +#if defined(UFBX_EXTERNAL_STDIO) + ufbx_extern_abi void *ufbx_stdio_open(const char *path, size_t path_len); + ufbx_extern_abi size_t ufbx_stdio_read(void *file, void *data, size_t size); + ufbx_extern_abi bool ufbx_stdio_skip(void *file, size_t size); + ufbx_extern_abi uint64_t ufbx_stdio_size(void *file); + ufbx_extern_abi void ufbx_stdio_close(void *file); +#endif + +#if defined(__cplusplus) +} +#endif + +#endif + +#if !defined(UFBX_INFINITY) + #if defined(INFINITY) + #define UFBX_INFINITY INFINITY + #else + #define UFBX_INFINITY (1e+300 * 1e+300) + #endif +#endif +#if !defined(UFBX_NAN) + #if defined(NAN) + #define UFBX_NAN NAN + #else + #define UFBX_NAN (UFBX_INFINITY * 0.0f) + #endif +#endif +#if !defined(UFBX_FLT_EPSILON) + #if defined(FLT_EPSILON) + #define UFBX_FLT_EPSILON FLT_EPSILON + #else + #define UFBX_FLT_EPSILON 1.192092896e-07f + #endif +#endif +#if !defined(UFBX_FLT_EVAL_METHOD) + #if defined(FLT_EVAL_METHOD) + #define UFBX_FLT_EVAL_METHOD FLT_EVAL_METHOD + #elif defined(__FLT_EVAL_METHOD__) + #define UFBX_FLT_EVAL_METHOD __FLT_EVAL_METHOD__ + #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #define UFBX_FLT_EVAL_METHOD 0 + #else + #define UFBX_FLT_EVAL_METHOD -1 + #endif +#endif + +#if defined(ufbx_malloc) || defined(ufbx_realloc) || defined(ufbx_free) + // User provided allocators + #if !defined(ufbx_malloc) || !defined(ufbx_realloc) || !defined(ufbx_free) + #error Inconsistent custom global allocator + #endif +#elif defined(UFBX_NO_MALLOC) + #define ufbx_malloc(size) ((void)(size), (void)NULL) + #define ufbx_realloc(ptr, old_size, new_size) ((void)(ptr), (void)(old_size), (void)(new_size), (void)NULL) + #define ufbx_free(ptr, old_size) ((void)(ptr), (void)(old_size)) +#elif defined(UFBX_EXTERNAL_MALLOC) + // Nop +#else + #define ufbx_malloc(size) malloc((size)) + #define ufbx_realloc(ptr, old_size, new_size) realloc((ptr), (new_size)) + #define ufbx_free(ptr, old_size) free((ptr)) +#endif + +#if !defined(ufbx_panic_handler) + static void ufbxi_panic_handler(const char *message) + { + (void)message; + #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + fprintf(stderr, "ufbx panic: %s\n", message); + #endif + ufbx_assert(false && "ufbx panic: See stderr for more information"); + } + #define ufbx_panic_handler ufbxi_panic_handler +#endif + +// -- Platform + +#if defined(_MSC_VER) + #define UFBXI_MSC_VER _MSC_VER +#else + #define UFBXI_MSC_VER 0 +#endif + +#if defined(__GNUC__) + #define UFBXI_GNUC __GNUC__ + #define UFBXI_GNUC_VERSION ufbx_pack_version(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else + #define UFBXI_GNUC 0 + #define UFBXI_GNUC_VERSION 0 +#endif + +#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #define ufbxi_noinline __declspec(noinline) + #define ufbxi_forceinline __forceinline + #define ufbxi_restrict __restrict + #if defined(_Check_return_) + #define ufbxi_nodiscard _Check_return_ + #else + #define ufbxi_nodiscard + #endif + #define ufbxi_unused + #define ufbxi_unlikely(cond) (cond) +#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) + #define ufbxi_noinline __attribute__((noinline)) + #define ufbxi_forceinline inline __attribute__((always_inline)) + #define ufbxi_restrict __restrict + #define ufbxi_nodiscard __attribute__((warn_unused_result)) + #define ufbxi_unused __attribute__((unused)) + #define ufbxi_unlikely(cond) __builtin_expect((cond), 0) +#else + #define ufbxi_noinline + #define ufbxi_forceinline + #define ufbxi_nodiscard + #define ufbxi_restrict + #define ufbxi_unused + #define ufbxi_unlikely(cond) (cond) +#endif + +#if !defined(UFBX_STANDARD_C) && defined(__clang__) + #define ufbxi_nounroll _Pragma("clang loop unroll(disable)") _Pragma("clang loop vectorize(disable)") +#elif !defined(UFBX_STANDARD_C) && UFBXI_GNUC >= 8 + #define ufbxi_nounroll _Pragma("GCC unroll 0") +#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #define ufbxi_nounroll __pragma(loop(no_vector)) +#else + #define ufbxi_nounroll +#endif + +#if defined(__GNUC__) && !defined(__clang__) + #define ufbxi_ignore(cond) (void)!(cond) +#else + #define ufbxi_ignore(cond) (void)(cond) +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4061) // enumerator 'ENUM' in switch of enum 'enum' is not explicitly handled by a case label + #pragma warning(disable: 4200) // nonstandard extension used: zero-sized array in struct/union + #pragma warning(disable: 4201) // nonstandard extension used: nameless struct/union + #pragma warning(disable: 4210) // nonstandard extension used: function given file scope + #pragma warning(disable: 4127) // conditional expression is constant + #pragma warning(disable: 4706) // assignment within conditional expression + #pragma warning(disable: 4789) // buffer 'type_and_name' of size 8 bytes will be overrun; 16 bytes will be written starting at offset 0 + #pragma warning(disable: 4820) // type': 'N' bytes padding added after data member 'member' + #if defined(UFBX_STANDARD_C) + #pragma warning(disable: 4996) // 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. + #endif + #if defined(UFBXI_PARTIAL_FEATURES) + #pragma warning(disable: 4100) // 'name': unreferenced formal parameter + #pragma warning(disable: 4505) // 'func': unreferenced function with internal linkage has been removed + #endif +#endif + +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmissing-field-initializers" + #pragma clang diagnostic ignored "-Wmissing-braces" + #pragma clang diagnostic ignored "-Wdouble-promotion" + #pragma clang diagnostic ignored "-Wpedantic" + #pragma clang diagnostic ignored "-Wcast-qual" + #pragma clang diagnostic ignored "-Wcast-align" + #pragma clang diagnostic ignored "-Wcovered-switch-default" + #pragma clang diagnostic ignored "-Wpadded" + #pragma clang diagnostic ignored "-Wswitch-enum" + #pragma clang diagnostic ignored "-Wfloat-equal" + #pragma clang diagnostic ignored "-Wformat-nonliteral" + #if __has_warning("-Watomic-implicit-seq-cst") + #pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" + #endif + #if defined(UFBX_STANDARD_C) + #pragma clang diagnostic ignored "-Wunused-function" + #endif + #if defined(UFBXI_PARTIAL_FEATURES) + #pragma clang diagnostic ignored "-Wunused-function" + #pragma clang diagnostic ignored "-Wunused-parameter" + #endif + #if defined(__cplusplus) + #pragma clang diagnostic ignored "-Wold-style-cast" + #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" + #else + #pragma clang diagnostic ignored "-Wdeclaration-after-statement" + #pragma clang diagnostic ignored "-Wbad-function-cast" + #endif +#endif + +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" + #pragma GCC diagnostic ignored "-Wmissing-braces" + #pragma GCC diagnostic ignored "-Wdouble-promotion" + #pragma GCC diagnostic ignored "-Wpedantic" + #pragma GCC diagnostic ignored "-Wcast-qual" + #pragma GCC diagnostic ignored "-Wcast-align" + #pragma GCC diagnostic ignored "-Wpadded" + #pragma GCC diagnostic ignored "-Wswitch-enum" + #pragma GCC diagnostic ignored "-Wfloat-equal" + #pragma GCC diagnostic ignored "-Wformat-nonliteral" + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(UFBX_STANDARD_C) + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + #if defined(UFBXI_PARTIAL_FEATURES) + #pragma GCC diagnostic ignored "-Wunused-function" + #pragma GCC diagnostic ignored "-Wunused-parameter" + #endif + #if defined(__cplusplus) + #pragma GCC diagnostic ignored "-Wold-style-cast" + #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + #else + #pragma GCC diagnostic ignored "-Wdeclaration-after-statement" + #pragma GCC diagnostic ignored "-Wbad-function-cast" + #if __GNUC__ >= 5 + #pragma GCC diagnostic ignored "-Wc90-c99-compat" + #pragma GCC diagnostic ignored "-Wc99-c11-compat" + #endif + #endif + // MSC isnan() definition triggers this error on MinGW GCC + #if defined(__MINGW32__) + #pragma GCC diagnostic ignored "-Wfloat-conversion" + #endif + // `-Warray-bounds` results in warnings if UBsan is enabled and pre-GCC-14 has no way of detecting it.. + #if UFBXI_GNUC_VERSION >= ufbx_pack_version(4, 3, 0) && UFBXI_GNUC_VERSION < ufbx_pack_version(14, 0, 0) + #pragma GCC diagnostic ignored "-Warray-bounds" + #endif +#endif + +#if !defined(ufbx_static_assert) + #if defined(__cplusplus) && __cplusplus >= 201103 + #define ufbx_static_assert(desc, cond) static_assert(cond, #desc ": " #cond) + #else + #define ufbx_static_assert(desc, cond) typedef char ufbxi_static_assert_##desc[(cond)?1:-1] + #endif +#endif + +#if defined(__has_feature) + #if __has_feature(undefined_behavior_sanitizer) && !defined(UFBX_UBSAN) + #define UFBX_UBSAN 1 + #endif +#endif + +#if defined(__SANITIZE_UNDEFINED__) && !defined(UFBX_UBSAN) + #define UFBX_UBSAN 1 +#endif + +// Don't use unaligned loads with UB-sanitizer +#if defined(UFBX_UBSAN) && !defined(UFBX_NO_UNALIGNED_LOADS) + #define UFBX_NO_UNALIGNED_LOADS +#endif + +#if defined(__clang_analyzer__) && !defined(UFBX_STATIC_ANALYSIS) + #define UFBX_STATIC_ANALYSIS 1 +#endif + +#if defined(UFBX_STATIC_ANALYSIS) + bool ufbxi_analysis_opaque; + #define ufbxi_maybe_null(ptr) (ufbxi_analysis_opaque ? (ptr) : NULL) + #define ufbxi_analysis_assert(cond) ufbx_assert(cond) +#else + #define ufbxi_maybe_null(ptr) (ptr) + #define ufbxi_analysis_assert(cond) (void)0 +#endif + +#if defined(UFBX_STATIC_ANALYSIS) || defined(UFBX_UBSAN) + #define ufbxi_maybe_uninit(cond, value, def) ((cond) ? (value) : (def)) +#else + #define ufbxi_maybe_uninit(cond, value, def) (value) +#endif + +#if !defined(ufbxi_trace) + #if defined(UFBX_TRACE) + #define ufbxi_trace(desc) (fprintf(stderr, "ufbx trace: %s:%d: %s\n", __FILE__, __LINE__, #desc), fflush(stderr), desc) + #else + #define ufbxi_trace(desc) (desc) + #endif +#endif + +#ifndef UFBX_PATH_SEPARATOR + #if defined(_WIN32) + #define UFBX_PATH_SEPARATOR '\\' + #else + #define UFBX_PATH_SEPARATOR '/' + #endif +#endif + +#if !defined(UFBX_STANDARD_C) && defined(_POSIX_C_SOURCE) + #if _POSIX_C_SOURCE >= 200112l + #ifndef UFBX_HAS_FTELLO + #define UFBX_HAS_FTELLO + #endif + #endif +#endif + +#if !defined(UFBX_STANDARD_C) && ((defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC)) || ((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__))) + #define UFBXI_ARCH_X64 1 +#else + #define UFBXI_ARCH_X64 0 +#endif + +#if defined(UFBX_USE_SSE) || (!defined(UFBX_STANDARD_C) && !defined(UFBX_NO_SSE) && UFBXI_ARCH_X64) + #define UFBXI_HAS_SSE 1 + #include + #include +#else + #define UFBXI_HAS_SSE 0 +#endif + +// -- Atomic counter + +#define UFBXI_THREAD_SAFE 1 + +#if defined(__cplusplus) + #define ufbxi_extern_c extern "C" +#else + #define ufbxi_extern_c +#endif + +#if !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) + typedef size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) __sync_fetch_and_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) __sync_fetch_and_sub((ptr), 1) + #define ufbxi_atomic_counter_load(ptr) __sync_fetch_and_add((ptr), 0) // TODO: Proper atomic load +#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #if defined(_M_X64) || defined(_M_ARM64) + ufbxi_extern_c __int64 _InterlockedIncrement64(__int64 volatile * lpAddend); + ufbxi_extern_c __int64 _InterlockedDecrement64(__int64 volatile * lpAddend); + ufbxi_extern_c __int64 _InterlockedExchangeAdd64(__int64 volatile * lpAddend, __int64 Value); + typedef volatile __int64 ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement64(ptr) - 1) + #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement64(ptr) + 1) + #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd64((ptr), 0)) + #else + ufbxi_extern_c long __cdecl _InterlockedIncrement(long volatile * lpAddend); + ufbxi_extern_c long __cdecl _InterlockedDecrement(long volatile * lpAddend); + ufbxi_extern_c long __cdecl _InterlockedExchangeAdd(long volatile * lpAddend, long Value); + typedef volatile long ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement(ptr) - 1) + #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement(ptr) + 1) + #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd((ptr), 0)) + #endif +#elif !defined(UFBX_STANDARD_C) && defined(__TINYC__) + #if defined(__x86_64__) || defined(_AMD64_) + static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + #elif defined(__i386__) || defined(_X86_) + static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + #else + #error Unexpected TCC architecture + #endif + typedef volatile size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ufbxi_tcc_atomic_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) ufbxi_tcc_atomic_add((ptr), SIZE_MAX) + #define ufbxi_atomic_counter_load(ptr) ufbxi_tcc_atomic_add((ptr), 0) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #include + #include + typedef struct { alignas(std::atomic_size_t) char data[sizeof(std::atomic_size_t)]; } ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (new (&(ptr)->data) std::atomic_size_t(0)) + #define ufbxi_atomic_counter_free(ptr) (((std::atomic_size_t*)(ptr)->data)->~atomic()) + #define ufbxi_atomic_counter_inc(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_add(1) + #define ufbxi_atomic_counter_dec(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_sub(1) + #define ufbxi_atomic_counter_load(ptr) ((std::atomic_size_t*)(ptr)->data)->load(std::memory_order_acquire) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) + #include + typedef volatile atomic_size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) atomic_init(ptr, 0) + #define ufbxi_atomic_counter_free(ptr) (void)(ptr) + #define ufbxi_atomic_counter_inc(ptr) atomic_fetch_add((ptr), 1) + #define ufbxi_atomic_counter_dec(ptr) atomic_fetch_sub((ptr), 1) + #define ufbxi_atomic_counter_load(ptr) atomic_load_explicit((ptr), memory_order_acquire) +#else + typedef volatile size_t ufbxi_atomic_counter; + #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) + #define ufbxi_atomic_counter_inc(ptr) ((*(ptr))++) + #define ufbxi_atomic_counter_dec(ptr) ((*(ptr))--) + #define ufbxi_atomic_counter_load(ptr) (*(ptr)) + #undef UFBXI_THREAD_SAFE + #define UFBXI_THREAD_SAFE 0 +#endif + +// ^^ No references to before this point ^^ +// vv No more includes past this point vv + +#if defined(UFBX_STRING_PREFIX) + #define ufbxi_string_fn(name) ufbxi_pre_cat(UFBX_STRING_PREFIX, name) + #define strlen ufbxi_string_fn(strlen) + #define memcpy ufbxi_string_fn(memcpy) + #define memmove ufbxi_string_fn(memmove) + #define memset ufbxi_string_fn(memset) + #define memchr ufbxi_string_fn(memchr) + #define memcmp ufbxi_string_fn(memcmp) + #define strcmp ufbxi_string_fn(strcmp) + #define strncmp ufbxi_string_fn(strncmp) +#endif + +#if !defined(UFBX_LITTLE_ENDIAN) + #if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__)) + #define UFBX_LITTLE_ENDIAN 1 + #else + #define UFBX_LITTLE_ENDIAN 0 + #endif +#endif + +// Unaligned little-endian load functions +// On platforms that support unaligned access natively (x86, x64, ARM64) just use normal loads, +// with unaligned attributes, otherwise do manual byte-wise load. + +#define ufbxi_read_u8(ptr) (*(const uint8_t*)(ptr)) + +// Detect support for `__attribute__((aligned(1)))` +#if !defined(UFBX_STANDARD_C) && (defined(__clang__) && defined(__APPLE__)) + // Apple overrides Clang versioning, 5.0 here maps to 3.3 + #if __clang_major__ >= 5 + #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 + #endif +#elif !defined(UFBX_STANDARD_C) && defined(__clang__) + #if (__clang_major__ >= 4) || (__clang_major__ == 3 && __clang_minor__ >= 3) + #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 + #endif +#elif !defined(UFBX_STANDARD_C) && defined(__GNUC__) + #if __GNUC__ >= 5 + #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 + #endif +#endif + +#if defined(UFBXI_HAS_ATTRIBUTE_ALIGNED) + #define UFBXI_HAS_UNALIGNED 1 + #define UFBXI_HAS_ALIASING 1 + #define ufbxi_unaligned + typedef uint16_t __attribute__((aligned(1))) ufbxi_unaligned_u16; + typedef uint32_t __attribute__((aligned(1))) ufbxi_unaligned_u32; + typedef uint64_t __attribute__((aligned(1))) ufbxi_unaligned_u64; + typedef float __attribute__((aligned(1))) ufbxi_unaligned_f32; + typedef double __attribute__((aligned(1))) ufbxi_unaligned_f64; + typedef uint32_t __attribute__((may_alias)) ufbxi_aliasing_u32; +#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #define UFBXI_HAS_UNALIGNED 1 + #if defined(_M_IX86) + // MSVC seems to assume all pointers are unaligned for x86 + #define ufbxi_unaligned + #else + #define ufbxi_unaligned __unaligned + #endif + typedef uint16_t ufbxi_unaligned_u16; + typedef uint32_t ufbxi_unaligned_u32; + typedef uint64_t ufbxi_unaligned_u64; + typedef float ufbxi_unaligned_f32; + typedef double ufbxi_unaligned_f64; + // MSVC doesn't have aliasing types in theory, but it works in practice.. + #define UFBXI_HAS_ALIASING 1 + typedef uint32_t ufbxi_aliasing_u32; +#endif + +#if (defined(UFBXI_HAS_UNALIGNED) && UFBX_LITTLE_ENDIAN && !defined(UFBX_NO_UNALIGNED_LOADS)) || defined(UFBX_USE_UNALIGNED_LOADS) + #define ufbxi_read_u16(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u16*)(ptr)) + #define ufbxi_read_u32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u32*)(ptr)) + #define ufbxi_read_u64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u64*)(ptr)) + #define ufbxi_read_f32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f32*)(ptr)) + #define ufbxi_read_f64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f64*)(ptr)) +#else + static ufbxi_forceinline uint16_t ufbxi_read_u16(const void *ptr) { + const char *p = (const char*)ptr; + return (uint16_t)( + (unsigned)(uint8_t)p[0] << 0u | + (unsigned)(uint8_t)p[1] << 8u ); + } + static ufbxi_forceinline uint32_t ufbxi_read_u32(const void *ptr) { + const char *p = (const char*)ptr; + return (uint32_t)( + (unsigned)(uint8_t)p[0] << 0u | + (unsigned)(uint8_t)p[1] << 8u | + (unsigned)(uint8_t)p[2] << 16u | + (unsigned)(uint8_t)p[3] << 24u ); + } + static ufbxi_forceinline uint64_t ufbxi_read_u64(const void *ptr) { + const char *p = (const char*)ptr; + return (uint64_t)( + (uint64_t)(uint8_t)p[0] << 0u | + (uint64_t)(uint8_t)p[1] << 8u | + (uint64_t)(uint8_t)p[2] << 16u | + (uint64_t)(uint8_t)p[3] << 24u | + (uint64_t)(uint8_t)p[4] << 32u | + (uint64_t)(uint8_t)p[5] << 40u | + (uint64_t)(uint8_t)p[6] << 48u | + (uint64_t)(uint8_t)p[7] << 56u ); + } + static ufbxi_forceinline float ufbxi_read_f32(const void *ptr) { + uint32_t u = ufbxi_read_u32(ptr); + float f; + memcpy(&f, &u, 4); + return f; + } + static ufbxi_forceinline double ufbxi_read_f64(const void *ptr) { + uint64_t u = ufbxi_read_u64(ptr); + double f; + memcpy(&f, &u, 8); + return f; + } +#endif + +#define ufbxi_read_i8(ptr) (int8_t)(ufbxi_read_u8(ptr)) +#define ufbxi_read_i16(ptr) (int16_t)(ufbxi_read_u16(ptr)) +#define ufbxi_read_i32(ptr) (int32_t)(ufbxi_read_u32(ptr)) +#define ufbxi_read_i64(ptr) (int64_t)(ufbxi_read_u64(ptr)) + +ufbx_static_assert(sizeof_bool, sizeof(bool) == 1); +ufbx_static_assert(sizeof_char, sizeof(char) == 1); +ufbx_static_assert(sizeof_i8, sizeof(int8_t) == 1); +ufbx_static_assert(sizeof_i16, sizeof(int16_t) == 2); +ufbx_static_assert(sizeof_i32, sizeof(int32_t) == 4); +ufbx_static_assert(sizeof_i64, sizeof(int64_t) == 8); +ufbx_static_assert(sizeof_u8, sizeof(uint8_t) == 1); +ufbx_static_assert(sizeof_u16, sizeof(uint16_t) == 2); +ufbx_static_assert(sizeof_u32, sizeof(uint32_t) == 4); +ufbx_static_assert(sizeof_u64, sizeof(uint64_t) == 8); +ufbx_static_assert(sizeof_f32, sizeof(float) == 4); +ufbx_static_assert(sizeof_f64, sizeof(double) == 8); + +// -- Alignment + +#ifndef UFBX_MAXIMUM_ALIGNMENT +enum { UFBX_MAXIMUM_ALIGNMENT = sizeof(void*) > 8 ? sizeof(void*) : 8 }; +#endif + +#if !defined(UFBX_POINTER_SIZE) && !defined(UFBX_STANDARD_C) + #if (defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__)) && !defined(__CHERI__) + #define UFBX_POINTER_SIZE 8 + #elif defined(__wasm__) || defined(__EMSCRIPTEN__) + #define UFBX_POINTER_SIZE 4 + #endif +#endif +#if defined(UFBX_POINTER_SIZE) + ufbx_static_assert(pointer_size, UFBX_POINTER_SIZE == sizeof(void*)); +#else + #define UFBX_POINTER_SIZE 0 +#endif + +// -- Version + +#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 20, 0) +ufbx_abi_data_def const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION; + +ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u); + +// -- Fast copy + +#if UFBXI_HAS_SSE + #define ufbxi_copy_16_bytes(dst, src) _mm_storeu_si128((__m128i*)(dst), _mm_loadu_si128((const __m128i*)(src))) +#elif defined(UFBXI_HAS_UNALIGNED) + #define ufbxi_copy_16_bytes(dst, src) do { \ + ufbxi_unaligned ufbxi_unaligned_u64 *mi_dst = (ufbxi_unaligned ufbxi_unaligned_u64 *)(dst); \ + const ufbxi_unaligned ufbxi_unaligned_u64 *mi_src = (const ufbxi_unaligned ufbxi_unaligned_u64 *)src; \ + mi_dst[0] = mi_src[0]; \ + mi_dst[1] = mi_src[1]; \ + } while (0) +#else + #define ufbxi_copy_16_bytes(dst, src) memcpy((dst), (src), 16) +#endif + + +// -- Large fast integer + +#if !defined(UFBX_STANDARD_C) && (defined(__wasm__) || defined(__EMSCRIPTEN__)) && !defined(UFBX_WASM_32BIT) + typedef uint64_t ufbxi_fast_uint; +#else + typedef size_t ufbxi_fast_uint; +#endif + +// -- Wrapping right shift + +#if !defined(UFBX_UBSAN) && UFBXI_ARCH_X64 + #define ufbxi_wrap_shr64(a, b) ((a) >> (b)) +#else + #define ufbxi_wrap_shr64(a, b) ((a) >> ((b) & 63)) +#endif + +// -- Bit manipulation + +#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + ufbxi_extern_c unsigned char _BitScanReverse(unsigned long * _Index, unsigned long _Mask); + ufbxi_extern_c unsigned char _BitScanReverse64(unsigned long * _Index, unsigned __int64 _Mask); + static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { + unsigned long index; + _BitScanReverse(&index, (unsigned long)v); + return 31 - (uint32_t)index; + } + static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { + unsigned long index; + #if defined(_M_X64) + _BitScanReverse64(&index, (unsigned __int64)v); + #else + uint32_t hi = (uint32_t)(v >> 32u); + uint32_t hi_nonzero = hi != 0 ? 1 : 0; + uint32_t part = hi_nonzero ? hi : (uint32_t)v; + _BitScanReverse(&index, (unsigned long)part); + index += hi_nonzero * 32u; + #endif + return 63 - (uint32_t)index; + } +#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) + #define ufbxi_lzcnt32(v) ((uint32_t)__builtin_clz((unsigned)(v))) + #define ufbxi_lzcnt64(v) ((uint32_t)__builtin_clzll((unsigned long long)(v))) +#else + // DeBrujin table lookup + static const uint8_t ufbxi_lzcnt32_table[] = { + 31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1, 23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0, + }; + static const uint8_t ufbxi_lzcnt64_table[] = { + 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, 9, 5, 28, 11, 13, 21, 42, + 19, 25, 31, 34, 40, 46, 52, 59, 1, 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, + 53, 18, 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0, + }; + static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ufbxi_lzcnt32_table[(v * 0x07c4acddu) >> 27]; + } + static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + return ufbxi_lzcnt64_table[(v * UINT64_C(0x03f79d71b4cb0a89)) >> 58]; + } +#endif + +// -- Bit conversion + +#if defined(__cplusplus) + #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) memcpy(&(m_dst), &(m_src), sizeof(m_dst_type)) +#else + #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) do { \ + union { m_dst_type mi_dst; m_src_type mi_src; } mi_union; \ + mi_union.mi_src = (m_src); (m_dst) = mi_union.mi_dst; } while (0) +#endif + +// -- Pointer alignment + +#if !defined(UFBX_STANDARD_C) && defined(__GNUC__) && defined(__has_builtin) + #if __has_builtin(__builtin_is_aligned) + #define ufbxi_is_aligned(m_ptr, m_align) __builtin_is_aligned((m_ptr), (m_align)) + #define ufbxi_is_aligned_mask(m_ptr, m_align) __builtin_is_aligned((m_ptr), (m_align) + 1) + #endif +#endif +#ifndef ufbxi_is_aligned + #define ufbxi_is_aligned(m_ptr, m_align) (((uintptr_t)(m_ptr) & ((m_align) - 1)) == 0) + #define ufbxi_is_aligned_mask(m_ptr, m_align) (((uintptr_t)(m_ptr) & (m_align)) == 0) +#endif + +// -- Debug + +#if defined(UFBX_DEBUG_BINARY_SEARCH) || defined(UFBX_REGRESSION) + #define ufbxi_clamp_linear_threshold(v) (2) +#else + #define ufbxi_clamp_linear_threshold(v) (v) +#endif + +#if defined(UFBX_REGRESSION) + #undef UFBXI_MAX_SKIP_SIZE + #define UFBXI_MAX_SKIP_SIZE 128 + + #undef UFBXI_MAP_MAX_SCAN + #define UFBXI_MAP_MAX_SCAN 2 + + #undef UFBXI_KD_FAST_DEPTH + #define UFBXI_KD_FAST_DEPTH 2 + + #undef UFBXI_FACE_GROUP_HASH_BITS + #define UFBXI_FACE_GROUP_HASH_BITS 2 +#endif + +#if defined(UFBX_REGRESSION) || defined(UFBX_EXTENSIVE_THREADING) + #undef UFBXI_MIN_THREADED_DEFLATE_BYTES + #define UFBXI_MIN_THREADED_DEFLATE_BYTES 2 + + #undef UFBXI_MIN_THREADED_ASCII_VALUES + #define UFBXI_MIN_THREADED_ASCII_VALUES 2 +#endif + +#if defined(UFBX_REGRESSION) + #define ufbxi_regression_assert(cond) ufbx_assert(cond) +#else + #define ufbxi_regression_assert(cond) (void)0 +#endif + +#if defined(UFBX_REGRESSION) || defined(UFBX_DEV) || defined(UFBX_UBSAN) + #define ufbxi_dev_assert(cond) ufbx_assert(cond) +#else + #define ufbxi_dev_assert(cond) (void)0 +#endif + +#define ufbxi_unreachable(reason) do { ufbx_assert(0 && reason); } while (0) + +#if defined(UFBX_REGRESSION) + #define UFBXI_IS_REGRESSION 1 +#else + #define UFBXI_IS_REGRESSION 0 +#endif + +#if defined(_MSC_VER) + #define ufbxi_thread_local __declspec(thread) +#elif defined(__GNUC__) || defined(__clang__) + #define ufbxi_thread_local __thread +#elif UFBXI_HAS_CPP11 + #define ufbxi_thread_local thread_local +#elif UFBX_STDC >= 201112L + #define ufbxi_thread_local _Thread_local +#endif + +#if defined(UFBXI_ANALYSIS_RECURSIVE) + #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth); + #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth); +#elif UFBXI_IS_REGRESSION && defined(ufbxi_thread_local) + #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) \ + { \ + m_ret m_name##_rec m_params; \ + static ufbxi_thread_local unsigned ufbxi_recursion_depth; \ + ufbx_assert(ufbxi_recursion_depth < m_max_depth); \ + ++ufbxi_recursion_depth; \ + m_ret ret = m_name##_rec m_args; \ + --ufbxi_recursion_depth; \ + return ret; \ + } \ + m_ret m_name##_rec m_params + #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) \ + { \ + void m_name##_rec m_params; \ + static ufbxi_thread_local unsigned ufbxi_recursion_depth; \ + ufbx_assert(ufbxi_recursion_depth < m_max_depth); \ + ++ufbxi_recursion_depth; \ + m_name##_rec m_args; \ + --ufbxi_recursion_depth; \ + } \ + void m_name##_rec m_params +#else + #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) + #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) +#endif + +// -- Utility + +#if defined(UFBX_UBSAN) + static void ufbxi_assert_zero(size_t offset) { ufbx_assert(offset == 0); } + #define ufbxi_add_ptr(ptr, offset) ((ptr) ? (ptr) + (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr))) + #define ufbxi_sub_ptr(ptr, offset) ((ptr) ? (ptr) - (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr))) +#else + #define ufbxi_add_ptr(ptr, offset) ((ptr) + (offset)) + #define ufbxi_sub_ptr(ptr, offset) ((ptr) - (offset)) +#endif + +#define ufbxi_arraycount(arr) (sizeof(arr) / sizeof(*(arr))) +#define ufbxi_for(m_type, m_name, m_begin, m_num) for (m_type *m_name = m_begin, *m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++) +#define ufbxi_for_ptr(m_type, m_name, m_begin, m_num) for (m_type **m_name = m_begin, **m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++) + +// WARNING: Evaluates `m_list` twice! +#define ufbxi_for_list(m_type, m_name, m_list) for (m_type *m_name = (m_list).data, *m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++) +#define ufbxi_for_ptr_list(m_type, m_name, m_list) for (m_type **m_name = (m_list).data, **m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++) + +#define ufbxi_string_literal(str) { str, sizeof(str) - 1 } + +static ufbxi_forceinline uint32_t ufbxi_min32(uint32_t a, uint32_t b) { return a < b ? a : b; } +static ufbxi_forceinline uint32_t ufbxi_max32(uint32_t a, uint32_t b) { return a < b ? b : a; } +static ufbxi_forceinline uint64_t ufbxi_min64(uint64_t a, uint64_t b) { return a < b ? a : b; } +static ufbxi_forceinline uint64_t ufbxi_max64(uint64_t a, uint64_t b) { return a < b ? b : a; } +static ufbxi_forceinline size_t ufbxi_min_sz(size_t a, size_t b) { return a < b ? a : b; } +static ufbxi_forceinline size_t ufbxi_max_sz(size_t a, size_t b) { return a < b ? b : a; } +static ufbxi_forceinline ufbx_real ufbxi_min_real(ufbx_real a, ufbx_real b) { return a < b ? a : b; } +static ufbxi_forceinline ufbx_real ufbxi_max_real(ufbx_real a, ufbx_real b) { return a < b ? b : a; } + +static ufbxi_forceinline int32_t ufbxi_f64_to_i32(double value) +{ + if (ufbx_fabs(value) <= (double)INT32_MAX) { + return (int32_t)value; + } else { + return value >= 0.0 ? INT32_MAX : INT32_MIN; + } +} + +static ufbxi_forceinline int64_t ufbxi_f64_to_i64(double value) +{ + if (ufbx_fabs(value) <= (double)INT64_MAX) { + return (int64_t)value; + } else { + return value >= 0.0 ? INT64_MAX : INT64_MIN; + } +} + +#if defined(UFBX_REGRESSION) + static size_t ufbxi_to_size(ptrdiff_t delta) { + ufbx_assert(delta >= 0); + return (size_t)delta; + } +#else + #define ufbxi_to_size(delta) ((size_t)(delta)) +#endif + +// Stable sort array `m_type m_data[m_size]` using the predicate `m_cmp_lambda(a, b)` +// `m_linear_size` is a hint for how large blocks handle initially do with insertion sort +// `m_tmp` must be a memory buffer with at least the same size and alignment as `m_data` +#define ufbxi_macro_stable_sort(m_type, m_linear_size, m_data, m_tmp, m_size, m_cmp_lambda) do { \ + typedef m_type mi_type; \ + mi_type *mi_src = (mi_type*)(m_tmp); \ + mi_type *mi_data = m_data, *mi_dst = mi_data; \ + size_t mi_block_size = ufbxi_clamp_linear_threshold(m_linear_size), mi_size = m_size; \ + /* Insertion sort in `m_linear_size` blocks */ \ + for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size) { \ + size_t mi_i_end = mi_base + mi_block_size; \ + if (mi_i_end > mi_size) mi_i_end = mi_size; \ + for (size_t mi_i = mi_base + 1; mi_i < mi_i_end; mi_i++) { \ + size_t mi_j = mi_i; \ + mi_src[0] = mi_dst[mi_i]; \ + for (; mi_j != mi_base; --mi_j) { \ + mi_type *a = &mi_src[0], *b = &mi_dst[mi_j - 1]; \ + if (!( m_cmp_lambda )) break; \ + mi_dst[mi_j] = mi_dst[mi_j - 1]; \ + } \ + mi_dst[mi_j] = mi_src[0]; \ + } \ + } \ + /* Merge sort ping-ponging between `m_data` and `m_tmp` */ \ + for (; mi_block_size < mi_size; mi_block_size *= 2) { \ + mi_type *mi_swap = mi_dst; mi_dst = mi_src; mi_src = mi_swap; \ + for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size * 2) { \ + size_t mi_i = mi_base, mi_i_end = mi_base + mi_block_size; \ + size_t mi_j = mi_i_end, mi_j_end = mi_j + mi_block_size; \ + size_t mi_k = mi_base; \ + if (mi_i_end > mi_size) mi_i_end = mi_size; \ + if (mi_j_end > mi_size) mi_j_end = mi_size; \ + while ((mi_i < mi_i_end) & (mi_j < mi_j_end)) { \ + mi_type *a = &mi_src[mi_j], *b = &mi_src[mi_i]; \ + if ( m_cmp_lambda ) { \ + mi_dst[mi_k] = *a; mi_j++; \ + } else { \ + mi_dst[mi_k] = *b; mi_i++; \ + } \ + mi_k++; \ + } \ + while (mi_i < mi_i_end) mi_dst[mi_k++] = mi_src[mi_i++]; \ + while (mi_j < mi_j_end) mi_dst[mi_k++] = mi_src[mi_j++]; \ + } \ + } \ + /* Copy the result to `m_data` if we ended up in `m_tmp` */ \ + if (mi_dst != mi_data) memcpy((void*)mi_data, mi_dst, sizeof(mi_type) * mi_size); \ + } while (0) + +#define ufbxi_macro_lower_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_cmp_lambda, m_eq_lambda) do { \ + typedef m_type mi_type; \ + const mi_type *mi_data = (m_data); \ + size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \ + ufbx_assert(mi_linear_size > 1); \ + /* Binary search until we get down to `m_linear_size` elements */ \ + while (mi_hi - mi_lo > mi_linear_size) { \ + size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \ + const mi_type *a = &mi_data[mi_mid]; \ + if ( m_cmp_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \ + } \ + /* Linearly scan until we find the edge */ \ + for (; mi_lo < mi_hi; mi_lo++) { \ + const mi_type *a = &mi_data[mi_lo]; \ + if ( m_eq_lambda ) { *(m_result_ptr) = mi_lo; break; } \ + } \ + } while (0) + +#define ufbxi_macro_upper_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_eq_lambda) do { \ + typedef m_type mi_type; \ + const mi_type *mi_data = (m_data); \ + size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \ + ufbx_assert(mi_linear_size > 1); \ + /* Linearly scan with galloping */ \ + for (size_t mi_step = 1; mi_step < 100 && mi_hi - mi_lo > mi_step; mi_step *= 2) { \ + const mi_type *a = &mi_data[mi_lo + mi_step]; \ + if (!( m_eq_lambda )) { mi_hi = mi_lo + mi_step; break; } \ + mi_lo += mi_step; \ + } \ + /* Binary search until we get down to `m_linear_size` elements */ \ + while (mi_hi - mi_lo > mi_linear_size) { \ + size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \ + const mi_type *a = &mi_data[mi_mid]; \ + if ( m_eq_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \ + } \ + /* Linearly scan until we find the edge */ \ + for (; mi_lo < mi_hi; mi_lo++) { \ + const mi_type *a = &mi_data[mi_lo]; \ + if (!( m_eq_lambda )) break; \ + } \ + *(m_result_ptr) = mi_lo; \ + } while (0) + +typedef bool ufbxi_less_fn(void *user, const void *a, const void *b); + +static ufbxi_noinline void ufbxi_stable_sort(size_t stride, size_t linear_size, void *in_data, void *in_tmp, size_t size, ufbxi_less_fn *less_fn, void *less_user) +{ + (void)linear_size; + + char *src = (char*)in_tmp; + char *data = (char*)in_data, *dst = (char*)data; + size_t block_size = ufbxi_clamp_linear_threshold(linear_size); + /* Insertion sort in `linear_size` blocks */ + for (size_t base = 0; base < size; base += block_size) { + size_t i_end = base + block_size; + if (i_end > size) i_end = size; + for (size_t i = base + 1; i < i_end; i++) { + + { + char *a = dst + i * stride, *b = dst + (i - 1) * stride; + if (!less_fn(less_user, a, b)) continue; + } + + size_t j = i - 1; + memcpy(src, dst + i * stride, stride); + memcpy(dst + i * stride, dst + j * stride, stride); + for (; j != base; --j) { + char *a = src, *b = dst + (j - 1) * stride; + if (!less_fn(less_user, a, b)) break; + memcpy(dst + j * stride, dst + (j - 1) * stride, stride); + } + memcpy(dst + j * stride, src, stride); + } + } + /* Merge sort ping-ponging between `data` and `tmp` */ + for (; block_size < size; block_size *= 2) { + char *swap = dst; dst = src; src = swap; + for (size_t base = 0; base < size; base += block_size * 2) { + size_t i = base, i_end = base + block_size; + size_t j = i_end, j_end = j + block_size; + size_t k = base; + if (i_end > size) i_end = size; + if (j_end > size) j_end = size; + while ((i < i_end) & (j < j_end)) { + char *a = src + j * stride, *b = src + i * stride; + if (less_fn(less_user, a, b)) { + memcpy(dst + k * stride, a, stride); + j++; + } else { + memcpy(dst + k * stride, b, stride); + i++; + } + k++; + } + + memcpy(dst + k * stride, src + i * stride, (i_end - i) * stride); + if (j < j_end) { + memcpy(dst + (k + (i_end - i)) * stride, src + j * stride, (j_end - j) * stride); + } + } + } + /* Copy the result to `data` if we ended up in `tmp` */ + if (dst != data) memcpy((void*)data, dst, size * stride); +} + +static ufbxi_forceinline void ufbxi_swap(void *a, void *b, size_t size) +{ +#if UFBXI_HAS_ALIASING && !defined(__CHERI__) // CHERI needs to copy pointer metadata tag bits.. + ufbxi_dev_assert(size % 4 == 0 && (uintptr_t)a % 4 == 0 && (uintptr_t)b % 4 == 0); + char *ca = (char*)a, *cb = (char*)b; + for (size_t i = 0; i < size; i += 4, ca += 4, cb += 4) { + ufbxi_aliasing_u32 *ua = (ufbxi_aliasing_u32*)ca, *ub = (ufbxi_aliasing_u32*)cb; + uint32_t va = *ua, vb = *ub; + *ua = vb; + *ub = va; + } +#else + union { + void *align_ptr; + uintptr_t align_uptr; + uint64_t align_u64; + char data[256]; + } tmp; + ufbxi_dev_assert(size <= sizeof(tmp)); + memcpy(tmp.data, a, size); + memcpy(a, b, size); + memcpy(b, tmp.data, size); +#endif +} + +static ufbxi_noinline void ufbxi_unstable_sort(void *in_data, size_t size, size_t stride, ufbxi_less_fn *less_fn, void *less_user) +{ + if (size <= 1) return; + + char *data = (char*)in_data; + size_t start = (size - 1) >> 1; + size_t end = size - 1; + for (;;) { + size_t root = start; + size_t child; + while ((child = root*2 + 1) <= end) { + size_t next = less_fn(less_user, data + child * stride, data + root * stride) ? root : child; + if (child + 1 <= end && less_fn(less_user, data + next * stride, data + (child + 1) * stride)) { + next = child + 1; + } + if (next == root) break; + ufbxi_swap(data + root * stride, data + next * stride, stride); + root = next; + } + + if (start > 0) { + start--; + } else if (end > 0) { + ufbxi_swap(data + end * stride, data, stride); + end--; + } else { + break; + } + } +} + +// -- Float parsing + +#define UFBXI_BIGINT_LIMB_BITS 32 +#define UFBXI_BIGINT_ACCUM_BITS (UFBXI_BIGINT_LIMB_BITS * 2) +#define UFBXI_BIGINT_LIMB_MAX (ufbxi_bigint_limb)(((ufbxi_bigint_accum)1 << UFBXI_BIGINT_LIMB_BITS) - 1) +typedef uint32_t ufbxi_bigint_limb; +typedef uint64_t ufbxi_bigint_accum; + +typedef struct { + ufbxi_bigint_limb *limbs; + uint32_t capacity; + uint32_t length; +} ufbxi_bigint; + +static ufbxi_bigint ufbxi_bigint_make(ufbxi_bigint_limb *limbs, size_t capacity) +{ + ufbxi_bigint bi = { limbs, (uint32_t)capacity }; + return bi; +} + +#define ufbxi_bigint_array(arr) ufbxi_bigint_make((arr), sizeof(arr) / sizeof(*(arr))) + +static const uint64_t ufbxi_pow5_tab[] = { + UINT64_C(0x1), UINT64_C(0x5), UINT64_C(0x19), UINT64_C(0x7d), UINT64_C(0x271), UINT64_C(0xc35), UINT64_C(0x3d09), UINT64_C(0x1312d), UINT64_C(0x5f5e1), + UINT64_C(0x1dcd65), UINT64_C(0x9502f9), UINT64_C(0x2e90edd), UINT64_C(0xe8d4a51), UINT64_C(0x48c27395), UINT64_C(0x16bcc41e9), UINT64_C(0x71afd498d), + UINT64_C(0x2386f26fc1), UINT64_C(0xb1a2bc2ec5), UINT64_C(0x3782dace9d9), UINT64_C(0x1158e460913d), UINT64_C(0x56bc75e2d631), UINT64_C(0x1b1ae4d6e2ef5), + UINT64_C(0x878678326eac9), UINT64_C(0x2a5a058fc295ed), UINT64_C(0xd3c21bcecceda1), UINT64_C(0x422ca8b0a00a425), UINT64_C(0x14adf4b7320334b9), UINT64_C(0x6765c793fa10079d), +}; + +static const double ufbxi_pow10_tab_f64[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, +}; + +static ufbxi_noinline void ufbxi_bigint_mad(ufbxi_bigint *bigint, ufbxi_bigint_accum multiplicand, ufbxi_bigint_accum addend) +{ + ufbxi_dev_assert((multiplicand | addend) >> (UFBXI_BIGINT_ACCUM_BITS - 1) == 0); + ufbxi_bigint b = *bigint; + ufbxi_bigint_limb m_lo = (ufbxi_bigint_limb)multiplicand; + ufbxi_bigint_limb m_hi = (ufbxi_bigint_limb)(multiplicand >> UFBXI_BIGINT_LIMB_BITS); + ufbxi_bigint_accum carry = addend; + for (uint32_t i = 0; i < b.length; i++) { + ufbxi_bigint_accum limb = (ufbxi_bigint_accum)b.limbs[i]; + ufbxi_bigint_accum lo = limb * m_lo + (carry & UFBXI_BIGINT_LIMB_MAX); + ufbxi_bigint_accum hi = limb * m_hi; + b.limbs[i] = (ufbxi_bigint_limb)lo; + carry = (carry >> 32u) + (lo >> 32u) + hi; + } + while (carry) { + b.limbs[b.length++] = (ufbxi_bigint_limb)carry; + ufbxi_dev_assert(b.length < b.capacity); + carry >>= 32u; + } + bigint->length = b.length; +} + +static ufbxi_noinline bool ufbxi_bigint_div(ufbxi_bigint *q, ufbxi_bigint *u, ufbxi_bigint *v) +{ + int32_t n = (int32_t)v->length; + int32_t m = (int32_t)u->length - n; + ufbxi_bigint_limb v_hi = v->limbs[v->length - 1]; + ufbxi_bigint_limb *un = u->limbs, *vn = v->limbs; + ufbxi_dev_assert(n >= 2 && m >= 1 && v_hi >> (UFBXI_BIGINT_LIMB_BITS - 1) != 0 && un[n+m - 1] >> (UFBXI_BIGINT_LIMB_BITS - 1) == 0); + un[n + m] = 0; + q->length = 0; + for (int32_t j = m - 1; j >= 0; j--) { + ufbxi_bigint_accum u_hi = ((ufbxi_bigint_accum)un[n+j] << UFBXI_BIGINT_LIMB_BITS) | un[n+j-1]; + ufbxi_bigint_accum t, qhat = u_hi / v_hi, rhat = u_hi % v_hi; + while (qhat >> UFBXI_BIGINT_LIMB_BITS != 0 || qhat*vn[n-2] > ((rhat<> UFBXI_BIGINT_LIMB_BITS != 0) break; + } + ufbxi_bigint_limb carry = 0; + for (int32_t i = 0; i < n; i++) { + ufbxi_bigint_accum p = qhat * vn[i]; + t = (ufbxi_bigint_accum)un[i+j] - carry - (ufbxi_bigint_limb)p; + un[i+j] = (ufbxi_bigint_limb)t; + carry = (ufbxi_bigint_limb)((p >> UFBXI_BIGINT_LIMB_BITS) - (t >> UFBXI_BIGINT_LIMB_BITS)); + } + t = (ufbxi_bigint_accum)un[j+n] - carry; + un[j+n] = (ufbxi_bigint_limb)t; + if (t >> UFBXI_BIGINT_LIMB_BITS != 0) { + qhat -= 1; + carry = 0; + for (int32_t i = 0; i < n; i++) { + t = (ufbxi_bigint_accum)un[i+j] + vn[i] + carry; + un[i+j] = (ufbxi_bigint_limb)t; + carry = (ufbxi_bigint_limb)(t >> UFBXI_BIGINT_LIMB_BITS); + } + un[j+n] += carry; + } + q->limbs[j] = (ufbxi_bigint_limb)qhat; + if (qhat && !q->length) { + ufbxi_dev_assert(j + 1 < (int32_t)q->capacity); + q->length = (uint32_t)(j + 1); + } + } + for (int32_t i = 0; i < n; i++) { + if (un[i]) return true; + } + return false; +} + +static void ufbxi_bigint_mul_pow5(ufbxi_bigint *b, uint32_t power) +{ + for (; power > 27; power -= 27) { + ufbxi_bigint_mad(b, ufbxi_pow5_tab[27], 0); + } + ufbxi_bigint_mad(b, ufbxi_pow5_tab[power], 0); +} + +static ufbxi_noinline void ufbxi_bigint_shift_left(ufbxi_bigint *bigint, uint32_t amount) +{ + uint32_t words = amount / UFBXI_BIGINT_LIMB_BITS, bits = amount % UFBXI_BIGINT_LIMB_BITS; + ufbxi_bigint b = *bigint; + ufbxi_dev_assert(b.length + words + 1 < b.capacity && b.capacity >= 4); + uint32_t bits_down = UFBXI_BIGINT_LIMB_BITS - bits - 1; + bigint->length += words + (b.limbs[b.length - 1] >> 1 >> bits_down != 0 ? 1 : 0); + b.limbs[b.length] = 0; + if (b.length <= 3 && words <= 3) { + ufbxi_bigint_limb l0 = b.limbs[0]; + ufbxi_bigint_limb l1 = ufbxi_maybe_uninit(b.length >= 1, b.limbs[1], ~0u); + ufbxi_bigint_limb l2 = ufbxi_maybe_uninit(b.length >= 2, b.limbs[2], ~0u); + b.limbs[0] = 0; + b.limbs[1] = 0; + b.limbs[2] = 0; + b.limbs[words + 0] = l0 << bits; + b.limbs[words + 1] = (l1 << bits) | (l0 >> 1 >> bits_down); + b.limbs[words + 2] = (l2 << bits) | (l1 >> 1 >> bits_down); + b.limbs[words + 3] = (l2 >> 1 >> bits_down); + } else { + for (uint32_t i = b.length + 1; i-- > 1; ) { + b.limbs[i + words] = (b.limbs[i] << bits) | (b.limbs[i - 1] >> 1 >> bits_down); + } + b.limbs[words] = b.limbs[0] << bits; + for (uint32_t i = 0; i < words; i++) { + b.limbs[i] = 0; + } + } +} + +static ufbxi_bigint_limb ufbxi_bigint_top_limb(const ufbxi_bigint b, uint32_t index) { + return index < b.length ? b.limbs[b.length - 1 - index] : 0; +} + +static ufbxi_noinline uint64_t ufbxi_bigint_extract_high(const ufbxi_bigint b, int32_t *p_exponent, bool *p_tail) +{ + ufbxi_dev_assert(b.length != 0); + uint64_t result = 0; + const uint32_t limb_count = 64 / UFBXI_BIGINT_LIMB_BITS; + for (uint32_t i = 0; i < limb_count; i++) { + result = (result << UFBXI_BIGINT_LIMB_BITS) | ufbxi_bigint_top_limb(b, i); + } + uint32_t shift = ufbxi_lzcnt64(result); + result <<= shift; + ufbxi_bigint_limb lo = ufbxi_bigint_top_limb(b, limb_count); + if (shift > 0) { + result |= lo >> (UFBXI_BIGINT_LIMB_BITS - shift); + } + *p_tail |= (ufbxi_bigint_limb)(lo << shift) != 0; + for (uint32_t i = limb_count + 1; i < b.length; i++) { + *p_tail |= ufbxi_bigint_top_limb(b, i) != 0; + } + *p_exponent += (int32_t)(b.length * UFBXI_BIGINT_LIMB_BITS - shift - 1); + return result; +} + +static uint64_t ufbxi_shift_right_round(uint64_t value, uint32_t shift, bool tail) +{ + if (shift == 0) return value; + if (shift > 64) return 0; + uint64_t result = value >> (shift - 1); + uint64_t tail_mask = (UINT64_C(1) << (shift - 1)) - 1; + + bool r_odd = (result & 0x2) != 0; + bool r_round = (result & 0x1) != 0; + bool r_tail = tail || (value & tail_mask) != 0; + uint64_t round_bit = (r_round && (r_odd || r_tail)) ? 1u : 0u; + + return (result >> 1u) + round_bit; +} + +typedef enum { + UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH = 0x1, + UFBXI_PARSE_DOUBLE_AS_BINARY32 = 0x2, +} ufbxi_parse_double_flag; + +static bool ufbxi_scan_ignorecase(const char *p, const char *end, const char *fmt) +{ + for (const char *f = fmt; *f; f++, p++) { + if (p >= end) return false; + if ((*p | 0x20) != *f) return false; + } + return true; +} + +static ufbxi_noinline bool ufbxi_parse_inf_nan(double *p_result, const char *str, size_t max_length, char **p_end) +{ + bool negative = false; + const char *p = str, *end = p + max_length; + if (p != end && (*p == '+' || *p == '-')) { + negative = *p++ == '-'; + } + + uint32_t top_bits = 0; + if (end - p >= 3 && (p[0] >= '0' && p[0] <= '9') && p[1] == '.' && p[2] == '#') { + // Legacy MSVC 1.#NAN + p += 3; + if (ufbxi_scan_ignorecase(p, end, "inf")) { + p += 3; + top_bits = 0x7ff0; + } else if (ufbxi_scan_ignorecase(p, end, "nan") || ufbxi_scan_ignorecase(p, end, "ind")) { + p += 3; + top_bits = 0x7ff8; + } else { + return false; + } + while (p != end && *p >= '0' && *p <= '9') { + p++; + } + } else { + // Standard + if (ufbxi_scan_ignorecase(p, end, "nan")) { + p += 3; + top_bits = 0x7ff8; + if (p != end && *p == '(') { + p++; + while (p != end && *p != ')') { + char c = *p; + if (!((c>='0'&&c<='9') || (c>='a'&&c<='z') || (c>='A'&&c<='Z'))) { + return false; + } + p++; + } + if (p == end) return false; + p++; + } + } else if (ufbxi_scan_ignorecase(p, end, "inf")) { + p += ufbxi_scan_ignorecase(p + 3, end, "inity") ? 8 : 3; + top_bits = 0x7ff0; + } + } + + *p_end = (char*)p; + top_bits |= negative ? 0x8000 : 0; + uint64_t bits = (uint64_t)top_bits << 48; + double result; + ufbxi_bit_cast(double, result, uint64_t, bits); + *p_result = result; + return true; +} + +static ufbxi_noinline double ufbxi_parse_double(const char *str, size_t max_length, char **p_end, uint32_t flags) +{ + const uint32_t max_limbs = 14; + + ufbxi_bigint_limb mantissa_limbs[42], divisor_limbs[42], quotient_limbs[42]; + ufbxi_bigint big_mantissa = ufbxi_bigint_array(mantissa_limbs); + ufbxi_bigint big_quotient = ufbxi_bigint_array(quotient_limbs); + int32_t dec_exponent = 0, has_dot = 0; + bool negative = false, tail = false, digits_valid = true; + uint64_t digits = 0; + uint32_t num_digits = 0; + + const char *p = str, *end = p + max_length; + if (p != end && (*p == '+' || *p == '-')) { + negative = *p++ == '-'; + } + while (p != end) { + char c = *p; + if (c >= '0' && c <= '9') { + if (big_mantissa.length < max_limbs) { + digits = digits * 10 + (uint64_t)(c - '0'); + num_digits++; + if (num_digits >= 18) { + ufbxi_dev_assert(num_digits < ufbxi_arraycount(ufbxi_pow5_tab)); + ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); + digits = 0; + num_digits = 0; + digits_valid = false; + } + dec_exponent -= has_dot; + } else { + dec_exponent += 1 - has_dot; + } + p++; + } else if (c == '.' && !has_dot) { + has_dot = true; + p++; + } else { + break; + } + } + if (p != end && (*p == 'e' || *p == 'E')) { + p++; + bool exp_negative = false; + if (p != end && (*p == '+' || *p == '-')) { + exp_negative = *p == '-'; + p++; + } + int32_t exp = 0; + while (p != end) { + char c = *p; + if (c >= '0' && c <= '9') { + p++; + exp = exp * 10 + (c - '0'); + if (exp >= 10000) break; + } else { + break; + } + } + dec_exponent += exp_negative ? -exp : exp; + } + + if (p != end) { + char c = *p; + if (c == '#' || c == 'i' || c == 'I' || c == 'n' || c == 'N') { + double result; + if (ufbxi_parse_inf_nan(&result, str, max_length, p_end)) { + return result; + } + } + } + + *p_end = (char*)p; + + // Both power of 10 and integer are exactly representable as doubles + // Powers of 10 are factored as 2*5, and 2^N can be always exactly represented. + if ((flags & UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH) != 0 && big_mantissa.length == 0 && dec_exponent >= -22 && dec_exponent <= 22 && (digits >> 53) == 0) { + double value; + if (dec_exponent < 0) { + value = (double)digits / ufbxi_pow10_tab_f64[-dec_exponent]; + } else { + value = (double)digits * ufbxi_pow10_tab_f64[dec_exponent]; + } + return negative ? -value : value; + } + + if (big_mantissa.length == 0) { + big_mantissa.limbs[0] = (ufbxi_bigint_limb)digits; + big_mantissa.limbs[1] = (ufbxi_bigint_limb)(digits >> 32u); + big_mantissa.length = (digits >> 32u) ? 2 : digits ? 1 : 0; + if (big_mantissa.length == 0) return negative ? -0.0 : 0.0; + } else { + ufbxi_dev_assert(num_digits < ufbxi_arraycount(ufbxi_pow5_tab)); + ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); + } + + uint32_t enc_sign_shift = 63; + uint32_t enc_mantissa_bits = 53; + int32_t enc_max_exponent = 1023; + if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { + enc_sign_shift = 31; + enc_mantissa_bits = 24; + enc_max_exponent = 127; + } + + int32_t exponent = 0; + if (dec_exponent < 0) { + if (dec_exponent + (int32_t)big_mantissa.length * 10 <= -325) return negative ? -0.0 : 0.0; + + ufbxi_bigint big_divisor = ufbxi_bigint_array(divisor_limbs); + uint32_t pow5 = (uint32_t)-dec_exponent; + uint32_t initial_pow5 = pow5 <= 27 ? pow5 : 27; + uint64_t pow5_value = ufbxi_pow5_tab[initial_pow5]; + pow5 -= initial_pow5; + exponent += dec_exponent; + + if (pow5 == 0 && digits_valid && digits >> 63 == 0) { + uint32_t divisor_zeros = ufbxi_lzcnt64(pow5_value); + uint64_t mantissa_zeros = ufbxi_lzcnt64(digits) - 1; + uint64_t divisor_bits = pow5_value << divisor_zeros; + uint64_t mantissa_bits = digits << mantissa_zeros; + big_divisor.limbs[0] = (ufbxi_bigint_limb)divisor_bits; + big_divisor.limbs[1] = (ufbxi_bigint_limb)(divisor_bits >> 32u); + big_divisor.length = 2; + big_mantissa.limbs[0] = 0; + big_mantissa.limbs[1] = 0; + big_mantissa.limbs[2] = (ufbxi_bigint_limb)mantissa_bits; + big_mantissa.limbs[3] = (ufbxi_bigint_limb)(mantissa_bits >> 32u); + big_mantissa.length = 4; + exponent += (int32_t)divisor_zeros - (int32_t)mantissa_zeros - 64; + } else { + big_divisor.limbs[0] = (ufbxi_bigint_limb)pow5_value; + big_divisor.limbs[1] = (ufbxi_bigint_limb)(pow5_value >> 32u); + big_divisor.length = (pow5_value >> 32u) != 0 ? 2 : 1; + if (pow5 > 0) { + ufbxi_bigint_mul_pow5(&big_divisor, pow5); + } + + uint32_t divisor_zeros = ufbxi_lzcnt32(big_divisor.limbs[big_divisor.length - 1]); + if (big_divisor.length == 1) divisor_zeros += UFBXI_BIGINT_LIMB_BITS; + ufbxi_bigint_shift_left(&big_divisor, divisor_zeros); + uint32_t divisor_bits = big_divisor.length * UFBXI_BIGINT_LIMB_BITS; + + uint32_t mantissa_zeros = ufbxi_lzcnt32(big_mantissa.limbs[big_mantissa.length - 1]); + uint32_t mantissa_bits = big_mantissa.length * UFBXI_BIGINT_LIMB_BITS - mantissa_zeros; + uint32_t mantissa_min_bits = divisor_bits + enc_mantissa_bits + 2; + uint32_t mantissa_shift = mantissa_bits < mantissa_min_bits ? mantissa_min_bits - mantissa_bits : 0; + // Align mantissa to never have a high bit, this means we can skip the first digit during division. + mantissa_shift += ((mantissa_shift - mantissa_zeros) & (UFBXI_BIGINT_LIMB_BITS - 1)) == 0 ? 1 : 0; + if (mantissa_shift > 0) { + ufbxi_bigint_shift_left(&big_mantissa, mantissa_shift); + } + exponent += (int32_t)divisor_zeros - (int32_t)mantissa_shift; + } + + tail = ufbxi_bigint_div(&big_quotient, &big_mantissa, &big_divisor); + big_mantissa = big_quotient; + } else if (dec_exponent > 0) { + if (dec_exponent + (int32_t)(big_mantissa.length - 1) * 9 >= 310) return negative ? -UFBX_INFINITY : UFBX_INFINITY; + + exponent += dec_exponent; + ufbxi_bigint_mul_pow5(&big_mantissa, (uint32_t)dec_exponent); + } + + uint64_t mantissa = ufbxi_bigint_extract_high(big_mantissa, &exponent, &tail); + uint64_t sign_bit = (uint64_t)(negative ? 1u : 0u) << enc_sign_shift; + + uint32_t mantissa_shift = 64 - enc_mantissa_bits; + if (exponent > enc_max_exponent) { + return negative ? -UFBX_INFINITY : UFBX_INFINITY; + } else if (exponent <= -enc_max_exponent) { + mantissa_shift += (uint32_t)(-enc_max_exponent + 1 - exponent); + exponent = -enc_max_exponent + 1; + } + + mantissa = ufbxi_shift_right_round(mantissa, mantissa_shift, tail); + if (mantissa == 0) return negative ? -0.0 : 0.0; + + uint64_t bits = mantissa; + bits += (uint64_t)(exponent + enc_max_exponent - 1) << (enc_mantissa_bits - 1); + bits |= sign_bit; + + if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { + uint32_t bits_lo = (uint32_t)bits; + float result; + ufbxi_bit_cast(float, result, uint32_t, bits_lo); + return result; + } else { + double result; + ufbxi_bit_cast(double, result, uint64_t, bits); + return result; + } +} + +static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags(void) +{ + // We require evaluation in double precision, either for doubles (0) or always (1) + // and rounding to nearest, which we can check for with `1 + eps == 1 - eps`. + #if UFBX_FLT_EVAL_METHOD == 0 || UFBX_FLT_EVAL_METHOD == 1 + static volatile double ufbxi_volatile_eps = 2.2250738585072014e-308; + if (1.0 + ufbxi_volatile_eps == 1.0 - ufbxi_volatile_eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH; + #endif + + return 0; +} + +static ufbxi_forceinline int64_t ufbxi_parse_int64(const char *str, char **end) +{ + uint64_t abs_val = 0; + bool negative = *str == '-'; + bool positive = *str == '+'; + + size_t init_len = (negative | positive) ? 1 : 0; + size_t len = init_len; + for (; len < 30; len++) { + char c = str[len]; + if (!(c >= '0' && c <= '9')) break; + abs_val = 10 * abs_val + (uint64_t)(c - '0'); + } + if (len == 30 || len == init_len) { + *end = NULL; + return 0; + } + + // TODO: Wrap/clamp? + *end = (char*)str + len; + return negative ? (int64_t)(0 - abs_val) : (int64_t)abs_val; +} + +static ufbxi_noinline uint32_t ufbxi_parse_uint32_radix(const char *str, uint32_t radix) +{ + uint32_t value = 0; + for (const char *p = str; ; p++) { + char c = *p; + if (c >= '0' && c <= '9') { + value = value * radix + (uint32_t)(c - '0'); + } else if (radix == 16 && (c >= 'a' && c <= 'f')) { + value = value * radix + (uint32_t)(c + (10 - 'a')); + } else if (radix == 16 && (c >= 'A' && c <= 'F')) { + value = value * radix + (uint32_t)(c + (10 - 'A')); + } else { + break; + } + } + return value; +} + +// -- DEFLATE implementation + +#if !defined(ufbx_inflate) + +// Lookup data: [0:5] extra bits [5:8] flags [16:32] base value +// Generated by `misc/deflate_lut.py` +static const uint32_t ufbxi_deflate_length_lut[] = { + 0x00000020, 0x00030040, 0x00040040, 0x00050040, 0x00060040, 0x00070040, 0x00080040, 0x00090040, + 0x000a0040, 0x000b0041, 0x000d0041, 0x000f0041, 0x00110041, 0x00130042, 0x00170042, 0x001b0042, + 0x001f0042, 0x00230043, 0x002b0043, 0x00330043, 0x003b0043, 0x00430044, 0x00530044, 0x00630044, + 0x00730044, 0x00830045, 0x00a30045, 0x00c30045, 0x00e30045, 0x01020040, 0x00010020, 0x00010020, +}; +static const uint32_t ufbxi_deflate_dist_lut[] = { + 0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050001, 0x00070001, 0x00090002, 0x000d0002, + 0x00110003, 0x00190003, 0x00210004, 0x00310004, 0x00410005, 0x00610005, 0x00810006, 0x00c10006, + 0x01010007, 0x01810007, 0x02010008, 0x03010008, 0x04010009, 0x06010009, 0x0801000a, 0x0c01000a, + 0x1001000b, 0x1801000b, 0x2001000c, 0x3001000c, 0x4001000d, 0x6001000d, 0x00010020, 0x00010020, +}; + +static const uint8_t ufbxi_deflate_code_length_permutation[] = { + 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15, +}; + +#define UFBXI_INFLATE_FAST_MIN_IN 8 +#define UFBXI_INFLATE_FAST_MIN_OUT 2 + +#define UFBXI_HUFF_MAX_BITS 16 +#define UFBXI_HUFF_MAX_VALUE 288 +#define UFBXI_HUFF_FAST_BITS 10 +#define UFBXI_HUFF_FAST_SIZE (1 << UFBXI_HUFF_FAST_BITS) +#define UFBXI_HUFF_FAST_MASK (UFBXI_HUFF_FAST_SIZE - 1) +#define UFBXI_HUFF_MAX_LONG_BITS 5 +#define UFBXI_HUFF_MAX_LONG_SYMS 380 + +#define UFBXI_HUFF_CODELEN_FAST_BITS 8 +#define UFBXI_HUFF_CODELEN_FAST_MASK ((1<0] Base offset (halved) to `long_sym[]` +// [8:16] code_prefix // [fast_sym, fast=0, extra_mask=0] First 8-bits of the code (reverse of the lookup) +// +// (*1) Not allowing `end` if `fast` serves a dual purpose: It allows us to omit a check for the end symbol in the +// fast path and allows using the symbol as a 64-bit shift amount (x64/ARM64/WASM have native modulo 64 shifts). +// +// Valid bit patterns, all other patterns are forbidden (`sorted_to_sym[]` contains same patterns as `long_sym[]`): +// +// tree b e m f v +// +// lit_length.fast_sym[] N 0 0 1 L // Short N bit code (no extra allowed) for literal byte L +// lit_length.fast_sym[] N 0 1 1 I // Short N bit code (huff+extra bits) for length index I +// lit_length.fast_sym[] M 0 0 0 X // Long code at `lit_length.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` +// lit_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `lit_length.sorted_to_sym[]` to resolve (*1) +// lit_length.fast_sym[] N 1 0 0 0 // Short N bit code for end-of-block (256) symbol +// lit_length.fast_sym[] 0 1 0 0 1 // Invalid lit_length code +// +// lit_length.long_sym[] N 0 0 0 L // Long N bit code (no extra allowed) for literal byte L +// lit_length.long_sym[] N 0 1 0 I // Long N bit code (huff+extra bits) for length index I +// lit_length.long_sym[] N 1 0 0 0 // Long N bit code for end-of-block (256) symbol +// lit_length.long_sym[] 0 1 0 0 1 // Invalid lit_length code +// +// dist.fast_sym[] N 0 0 1 L // Short N bit code (huff+extra bits) for distance index I +// dist.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` +// dist.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `dist.sorted_to_sym[]` to resolve (*1) +// dist.fast_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code +// +// dist.long_sym[] N 0 0 0 I // Long N bit code (huff+extra bits) for distance index I +// dist.long_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code +// +// code_length.fast_sym[] N 0 0 1 B // Short N bit code (huff only, extra handled explicitly) for symbol bit count B +// code_length.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` +// code_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `code_length.sorted_to_sym[]` to resolve (*1) +// +// code_length.long_sym[] N 0 0 0 B // Long N bit code (huff only, extra handled explicitly) for symbol bit count B +// +// (*1) Never necessary if `fast_bits >= 10` due to `long_sym[]` covering all possible codes, +// +typedef uint16_t ufbxi_huff_sym; + +#define ufbxi_huff_sym_total_bits(sym) ((uint32_t)(sym) & 0x1f) +#define ufbxi_huff_sym_long_mask(sym) ((uint32_t)(sym) & 0x1f) +#define ufbxi_huff_sym_long_offset(sym) ((uint32_t)(sym) >> 7u) +#define ufbxi_huff_sym_value(sym) ((uint32_t)(sym) >> 8u) + +enum { + UFBXI_HUFF_SYM_END = 0x20, + UFBXI_HUFF_SYM_MATCH = 0x40, + UFBXI_HUFF_SYM_FAST = 0x80, +}; + +#define UFBXI_HUFF_ERROR_SYM ((ufbxi_huff_sym)0x0120) // Error symbol, END (value 1) +#define UFBXI_HUFF_UNINITIALIZED_SYM ((ufbxi_huff_sym)0x0220) // Uninitialized symbol for regression, END (value 2) + +typedef struct { + ufbxi_huff_sym fast_sym[UFBXI_HUFF_FAST_SIZE]; // < Lookup from N bytes to symbol information + ufbxi_huff_sym long_sym[UFBXI_HUFF_MAX_LONG_SYMS]; // < Fast long symbol lookup + ufbxi_huff_sym sorted_to_sym[UFBXI_HUFF_MAX_VALUE]; // < Symbol information per sorted index + + uint32_t extra_shift_base[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < [0:6] shift [16:32] base value + uint16_t extra_mask[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < Mask for extra bits + + uint16_t past_max_code[UFBXI_HUFF_MAX_BITS]; // < One past maximum code value per bit length + int16_t code_to_sorted[UFBXI_HUFF_MAX_BITS]; // < Code to sorted symbol index per bit length + uint32_t num_symbols; + + uint32_t end_of_block_bits; +} ufbxi_huff_tree; + +typedef struct { + union { + struct { + ufbxi_huff_tree lit_length; + ufbxi_huff_tree dist; + }; + ufbxi_huff_tree trees[2]; + }; + uint32_t fast_bits; +} ufbxi_trees; + +typedef struct { + bool initialized; + ufbxi_trees static_trees; +} ufbxi_inflate_retain_imp; + +ufbx_static_assert(inflate_retain_size, sizeof(ufbxi_inflate_retain_imp) <= sizeof(ufbx_inflate_retain)); + +typedef struct { + ufbxi_bit_stream stream; + uint32_t fast_bits; + + char *out_begin; + char *out_ptr; + char *out_end; +} ufbxi_deflate_context; + +static ufbxi_forceinline uint32_t +ufbxi_bit_reverse(uint32_t mask, uint32_t num_bits) +{ + ufbxi_dev_assert(num_bits <= 16); + uint32_t x = mask; + x = (((x & 0xaaaa) >> 1) | ((x & 0x5555) << 1)); + x = (((x & 0xcccc) >> 2) | ((x & 0x3333) << 2)); + x = (((x & 0xf0f0) >> 4) | ((x & 0x0f0f) << 4)); + x = (((x & 0xff00) >> 8) | ((x & 0x00ff) << 8)); + return x >> (16 - num_bits); +} + +static ufbxi_noinline const char * +ufbxi_bit_chunk_refill(ufbxi_bit_stream *s, const char *ptr) +{ + // Copy any left-over data to the beginning of `buffer` + size_t left = ufbxi_to_size(s->chunk_real_end - ptr); + ufbxi_dev_assert(left < 64); + if (left > 0) memmove(s->buffer, ptr, left); + + s->num_read_before_chunk += ufbxi_to_size(ptr - s->chunk_begin); + + // Read more user data if the user supplied a `read_fn()`, otherwise + // we assume the initial data chunk is the whole input buffer. + if (s->read_fn && !s->cancelled) { + size_t to_read = ufbxi_min_sz(s->input_left, s->buffer_size - left); + if (to_read > 0) { + size_t num_read = s->read_fn(s->read_user, s->buffer + left, to_read); + // TODO: IO error, should unify with (currently broken) cancel logic + if (num_read > to_read) num_read = 0; + ufbxi_dev_assert(s->input_left >= num_read); + s->input_left -= num_read; + left += num_read; + } + } + + // Pad the rest with zeros + if (left < 64) { + memset(s->buffer + left, 0, 64 - left); + left = 64; + } + + s->chunk_begin = s->buffer; + s->chunk_ptr = s->buffer; + s->chunk_end = s->buffer + left - 8; + s->chunk_real_end = s->buffer + left; + return s->buffer; +} + +static ufbxi_noinline void ufbxi_bit_stream_init(ufbxi_bit_stream *s, const ufbx_inflate_input *input) +{ + size_t data_size = input->data_size; + if (data_size > input->total_size) { + data_size = input->total_size; + } + + s->read_fn = input->read_fn; + s->read_user = input->read_user; + s->progress_cb = input->progress_cb; + s->chunk_begin = (const char*)input->data; + s->chunk_ptr = (const char*)input->data; + s->chunk_end = ufbxi_add_ptr((const char*)input->data, ufbxi_max_sz(8, data_size) - 8); + s->chunk_real_end = ufbxi_add_ptr((const char*)input->data, data_size); + s->input_left = input->total_size - data_size; + + // Use the user buffer if it's large enough, otherwise `local_buffer` + if (input->buffer_size > sizeof(s->local_buffer)) { + s->buffer = (char*)input->buffer; + s->buffer_size = input->buffer_size; + } else { + s->buffer = s->local_buffer; + s->buffer_size = sizeof(s->local_buffer); + } + s->num_read_before_chunk = 0; + s->progress_bias = input->progress_size_before; + s->progress_total = input->total_size + input->progress_size_before + input->progress_size_after; + if (!s->progress_cb.fn || input->progress_interval_hint >= SIZE_MAX) { + s->progress_interval = SIZE_MAX; + } else if (input->progress_interval_hint > 0) { + s->progress_interval = (size_t)input->progress_interval_hint; + } else { + s->progress_interval = 0x4000; + } + s->cancelled = false; + + // Clear the initial bit buffer + s->bits = 0; + s->left = 0; + + // If the initial data buffer is not large enough to be read directly + // from refill the chunk once. + if (data_size < 64) { + ufbxi_bit_chunk_refill(s, s->chunk_begin); + } + + if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - s->chunk_ptr) > s->progress_interval + 8) { + s->chunk_yield = s->chunk_ptr + s->progress_interval; + } else { + s->chunk_yield = s->chunk_end; + } +} + +static ufbxi_noinline const char * +ufbxi_bit_yield(ufbxi_bit_stream *s, const char *ptr) +{ + if (ptr > s->chunk_end) { + ptr = ufbxi_bit_chunk_refill(s, ptr); + } + + if (s->progress_cb.fn) { + size_t num_read = s->num_read_before_chunk + ufbxi_to_size(ptr - s->chunk_begin); + + ufbx_progress progress = { s->progress_bias + num_read, s->progress_total }; + uint32_t result = (uint32_t)s->progress_cb.fn(s->progress_cb.user, &progress); + ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL); + if (result == UFBX_PROGRESS_CANCEL) { + s->cancelled = true; + ptr = s->local_buffer; + s->buffer = s->local_buffer; + s->buffer_size = sizeof(s->local_buffer); + s->chunk_begin = ptr; + s->chunk_ptr = ptr; + s->chunk_end = ptr + sizeof(s->local_buffer) - 8; + s->chunk_real_end = ptr + sizeof(s->local_buffer); + memset(s->local_buffer, 0, sizeof(s->local_buffer)); + } + } + + if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - ptr) > s->progress_interval + 8) { + s->chunk_yield = ptr + s->progress_interval; + } else { + s->chunk_yield = s->chunk_end; + } + + return ptr; +} + +static ufbxi_forceinline void +ufbxi_bit_refill(uint64_t *p_bits, size_t *p_left, const char **p_data, ufbxi_bit_stream *s) +{ + if (*p_data > s->chunk_yield) { + *p_data = ufbxi_bit_yield(s, *p_data); + if (s->cancelled) { + // Force an end-of-block symbol when cancelled so we don't need an + // extra branch in the chunk decoding loop. + *p_bits = s->cancel_bits; + } + } + + // See https://fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/ + // variant 4. This branchless refill guarantees [56,63] bits to be valid in `*p_bits`. + ufbxi_regression_assert(*p_left <= 64); \ + *p_bits |= ufbxi_read_u64(*p_data) << *p_left; + *p_data += (63 - *p_left) >> 3; + *p_left |= 56; +} + +// See `ufbxi_bit_refill()` +#define ufbxi_macro_bit_refill_fast(m_bits, m_left, m_data, m_refill_bits) do { \ + ufbxi_regression_assert(m_left <= 64); \ + m_bits |= m_refill_bits << m_left; \ + m_data += (63 - m_left) >> 3; \ + m_left |= 56; \ + } while (0) + +static ufbxi_noinline int +ufbxi_bit_copy_bytes(void *dst, ufbxi_bit_stream *s, size_t len) +{ + ufbx_assert(s->left % 8 == 0); + char *ptr = (char*)dst; + + // Copy the buffered bits first + while (len > 0 && s->left > 0) { + *ptr++ = (char)(uint8_t)s->bits; + len -= 1; + s->bits >>= 8; + s->left -= 8; + } + + // We need to clear the top bits as there may be data + // read ahead past `s->left` in some cases + s->bits = 0; + + // Copy the current chunk + size_t chunk_left = ufbxi_to_size(s->chunk_real_end - s->chunk_ptr); + if (chunk_left >= len) { + memcpy(ptr, s->chunk_ptr, len); + s->chunk_ptr += len; + return 1; + } else { + memcpy(ptr, s->chunk_ptr, chunk_left); + s->chunk_ptr += chunk_left; + ptr += chunk_left; + len -= chunk_left; + } + + // Read extra bytes from user + if (len > s->input_left) return 0; + size_t num_read = 0; + if (s->read_fn) { + num_read = s->read_fn(s->read_user, ptr, len); + s->input_left -= num_read; + } + return num_read == len; +} + +// 0: Success +// -1: Overfull +// -2: Underfull +static ufbxi_noinline ptrdiff_t +ufbxi_huff_build_imp(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits, uint32_t *bits_counts) +{ + uint32_t fast_mask = (1u << fast_bits) - 1; + + ufbx_assert(sym_count <= UFBXI_HUFF_MAX_VALUE); + tree->num_symbols = sym_count; + + uint32_t nonzero_sym_count = sym_count - bits_counts[0]; + + uint32_t total_syms[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit + uint32_t first_code[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit + + tree->code_to_sorted[0] = INT16_MAX; + tree->past_max_code[0] = 0; + total_syms[0] = 0; + + // Clear to uninitialized symbols + #if defined(UFBX_REGRESSION) + { + for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) { + tree->fast_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; + } + for (size_t i = 0; i < UFBXI_HUFF_MAX_VALUE; i++) { + tree->sorted_to_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; + } + for (size_t i = 0; i < UFBXI_HUFF_MAX_LONG_SYMS; i++) { + tree->long_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; + } + } + #endif + + uint32_t last_valid_prefix = 0; + + // Resolve the maximum code per bit length and ensure that the tree is not + // overfull or underfull. + { + int num_codes_left = 1; + uint32_t code = 0; + uint32_t prev_count = 0; + uint32_t long_offset = 0; + for (uint32_t bits = 1; bits < UFBXI_HUFF_MAX_BITS; bits++) { + uint32_t count = bits_counts[bits]; + code = (code + prev_count) << 1; + first_code[bits] = code; + tree->past_max_code[bits] = (uint16_t)(code + count); + + uint32_t prev_syms = total_syms[bits - 1]; + total_syms[bits] = prev_syms + count; + + // Each bit level doubles the amount of codes and potentially removes some + num_codes_left = (num_codes_left << 1) - (int32_t)count; + if (num_codes_left < 0) { + return -1; + } + + if (count > 0 && bits > fast_bits && bits - fast_bits <= UFBXI_HUFF_MAX_LONG_BITS) { + uint32_t shift = bits - fast_bits; + uint32_t last_inclusive = num_codes_left == 0 ? (1u<> shift; + uint32_t last_prefix = (code + count + last_inclusive) >> shift; + uint32_t mask = (1u << shift) - 1u; + uint32_t half_step = 1u << (shift - 1u); + for (uint32_t prefix = first_prefix; prefix < last_prefix; prefix++) { + uint32_t rev_prefix = ufbxi_bit_reverse(prefix, fast_bits); + tree->fast_sym[rev_prefix] = (ufbxi_huff_sym)(mask | (long_offset << 8)); + long_offset += half_step; + } + + last_valid_prefix = last_prefix; + } + + if (count > 0) { + tree->code_to_sorted[bits] = (int16_t)((int)prev_syms - (int)code); + } else { + tree->code_to_sorted[bits] = INT16_MAX; + } + prev_count = count; + } + + // All codes should be used if there's more than one symbol, if there's only one symbol there should be + // only a single 1-bit code. + if (nonzero_sym_count > 1 && num_codes_left != 0) { + return -2; + } else if (nonzero_sym_count == 1 && total_syms[1] != 1) { + return -2; + } + + // We should always have enough space for long symbols as we support up to 5 (UFBXI_HUFF_MAX_LONG_BITS) + // bits and the largest tree has 286 symbols. For each bit we may waste at most 2^bits slots (conservative) + // and in the end we may waste 2^5 slots giving us `286+2+4+8+16+32+32 = 380` (UFBXI_HUFF_MAX_LONG_SYMS) + ufbx_assert(long_offset <= UFBXI_HUFF_MAX_LONG_SYMS); + } + + tree->end_of_block_bits = 0; + uint32_t num_extra = 0; + tree->extra_shift_base[0] = 0; + tree->extra_mask[0] = 0; + + // Fill `fast_sym[]` with error symbols if necessary, we don't need to do this if we have two or more symbols + // as the tree is guaranteed to be full, which means we will populate the whole `fast_sym[]` + if (nonzero_sym_count <= 1) { + for (uint32_t i = 0; i <= fast_mask; i++) { + tree->fast_sym[i] = UFBXI_HUFF_ERROR_SYM; + } + } + + // Generate per-length sorted-to-symbol and fast lookup tables + uint32_t bits_index[UFBXI_HUFF_MAX_BITS] = { 0 }; + for (uint32_t i = 0; i < sym_count; i++) { + uint32_t bits = sym_bits[i]; + if (bits == 0) continue; + + uint32_t sym = i << 8 | bits; + if (i >= sym_extra_offset) { + uint32_t extra = sym_extra[i - sym_extra_offset]; + sym += extra; + + // Store length/distance codes with extra values in a table. + // TODO: This is unnecessary for small values + if ((extra & 0xffff001f) != 0 && (extra & 0x20) == 0) { + uint32_t ix = ++num_extra; + tree->extra_shift_base[ix] = (extra & 0xffff0000) | bits; + tree->extra_mask[ix] = (uint16_t)((1u << (extra & 0x1f)) - 1); + sym = (sym & 0xff) | ix << 8; + } + + } + + uint32_t index = bits_index[bits]++; + uint32_t sorted = total_syms[bits - 1] + index; + tree->sorted_to_sym[sorted] = (ufbxi_huff_sym)sym; + + // Reverse the code and fill all fast lookups with the reversed prefix + uint32_t code = first_code[bits] + index; + uint32_t rev_code = ufbxi_bit_reverse(code, bits); + + if (bits <= fast_bits) { + uint32_t fast_sym = sym; + // The `end` and `fast` flags are mutually exclusive + if ((fast_sym & UFBXI_HUFF_SYM_END) == 0) { + fast_sym |= UFBXI_HUFF_SYM_FAST; + } + uint32_t hi_max = 1u << (fast_bits - bits); + for (uint32_t hi = 0; hi < hi_max; hi++) { + ufbxi_regression_assert(nonzero_sym_count <= 1 || tree->fast_sym[rev_code | hi << bits] == UFBXI_HUFF_UNINITIALIZED_SYM); + tree->fast_sym[rev_code | hi << bits] = (ufbxi_huff_sym)fast_sym; + } + } else if (bits <= fast_bits + UFBXI_HUFF_MAX_LONG_BITS && (code >> (bits - fast_bits)) < last_valid_prefix) { + uint32_t fast_sym = tree->fast_sym[rev_code & fast_mask]; + ufbxi_regression_assert(fast_sym != UFBXI_HUFF_UNINITIALIZED_SYM); + uint32_t long_bits = 0; + + uint32_t long_mask = fast_sym; + while (long_bits < UFBXI_HUFF_MAX_LONG_BITS && (long_mask & 1) != 0) { + long_mask >>= 1; + long_bits += 1; + } + ufbxi_dev_assert(long_bits >= 1); + + uint32_t long_base = fast_sym >> 7u; // aka (fast_sym >> 8) * 2 + uint32_t lo_bits = bits - fast_bits; + uint32_t hi_max = 1u << (long_bits - lo_bits); + uint32_t rev_suffix = rev_code >> fast_bits; + for (uint32_t hi = 0; hi < hi_max; hi++) { + ufbxi_regression_assert(tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] == UFBXI_HUFF_UNINITIALIZED_SYM); + tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] = (ufbxi_huff_sym)sym; + } + } else { + uint32_t fast_sym = (code >> (bits - fast_bits)) << 8; + ufbxi_regression_assert( + tree->fast_sym[rev_code & fast_mask] == UFBXI_HUFF_UNINITIALIZED_SYM || + tree->fast_sym[rev_code & fast_mask] == (ufbxi_huff_sym)fast_sym); + tree->fast_sym[rev_code & fast_mask] = (ufbxi_huff_sym)fast_sym; + } + + // Make sure the end-of-block symbol goes through the slow path + // Also store the end-of-block code so we can interrupt decoding + if (i == 256) { + tree->end_of_block_bits = rev_code; + } + } + + // Make sure all `fast_sym[]` are filled with an initialized value. + #if defined(UFBX_REGRESSION) + { + for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) { + if (i <= fast_mask) { + ufbx_assert(tree->fast_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM); + } else { + ufbx_assert(tree->fast_sym[i] == UFBXI_HUFF_UNINITIALIZED_SYM); + } + } + for (size_t i = 0; i < nonzero_sym_count; i++) { + ufbx_assert(tree->sorted_to_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM); + } + } + #endif + + return 0; +} + +// 0: Success +// -1: Overfull +// -2: Underfull +static ufbxi_noinline ptrdiff_t +ufbxi_huff_build(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits) +{ + // Count the number of codes per bit length + // `bits_counts[0]` contains the number of non-used symbols + uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit + memset(bits_counts, 0, sizeof(bits_counts)); + for (uint32_t i = 0; i < sym_count; i++) { + uint32_t bits = sym_bits[i]; + ufbx_assert(bits < UFBXI_HUFF_MAX_BITS); + bits_counts[bits]++; + } + + return ufbxi_huff_build_imp(tree, sym_bits, sym_count, sym_extra, sym_extra_offset, fast_bits, bits_counts); +} + +static ufbxi_forceinline ufbxi_huff_sym +ufbxi_huff_decode_bits(const ufbxi_huff_tree *tree, uint64_t bits, uint32_t fast_bits, uint32_t fast_mask) +{ + ufbxi_huff_sym sym = tree->fast_sym[bits & fast_mask]; + ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); + + if ((sym & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) != 0) { + return sym; + } + + uint32_t tail = (uint32_t)(bits >> fast_bits); + uint32_t long_mask = ufbxi_huff_sym_long_mask(sym); + if (long_mask) { + sym = tree->long_sym[ufbxi_huff_sym_long_offset(sym) + (tail & long_mask)]; + ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); + return sym; + } + + ufbxi_dev_assert(fast_bits <= 8); + + uint32_t code = ufbxi_huff_sym_value(sym); + uint32_t num_bits = fast_bits; + for (;;) { + code = code << 1 | (tail & 1); + tail >>= 1; + num_bits++; + + ufbxi_regression_assert(num_bits < UFBXI_HUFF_MAX_BITS); + if (code < tree->past_max_code[num_bits]) { + sym = tree->sorted_to_sym[(int32_t)code + (int32_t)tree->code_to_sorted[num_bits]]; + ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); + return sym; + } + } +} + +static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx_inflate_input *input) +{ + ptrdiff_t err = 0; + + // Override `fast_bits` if necessary, this must always be valid as it's checked in the beginning of `ufbx_inflate()`. + if (input && input->internal_fast_bits != 0) { + trees->fast_bits = (uint32_t)input->internal_fast_bits; + ufbx_assert(!(trees->fast_bits < 1 || trees->fast_bits == 9 || trees->fast_bits > 10)); + } else { + trees->fast_bits = UFBXI_HUFF_FAST_BITS; + } + + // 0-143: 8 bits, 144-255: 9 bits, 256-279: 7 bits, 280-287: 8 bits + uint8_t lit_length_bits[288]; // ufbxi_uninit + memset(lit_length_bits + 0, 8, 144 - 0); + memset(lit_length_bits + 144, 9, 256 - 144); + memset(lit_length_bits + 256, 7, 280 - 256); + memset(lit_length_bits + 280, 8, 288 - 280); + err |= ufbxi_huff_build(&trees->lit_length, lit_length_bits, sizeof(lit_length_bits), ufbxi_deflate_length_lut, 256, trees->fast_bits); + + // "Distance codes 0-31 are represented by (fixed-length) 5-bit codes" + uint8_t dist_bits[32]; // ufbxi_uninit + memset(dist_bits + 0, 5, 32 - 0); + err |= ufbxi_huff_build(&trees->dist, dist_bits, sizeof(dist_bits), ufbxi_deflate_dist_lut, 0, trees->fast_bits); + + // Building the static trees cannot fail as we use pre-defined code lengths. + ufbxi_ignore(err); + ufbx_assert(err == 0); +} + +// 0: Success +// -1: Huffman Overfull +// -2: Huffman Underfull +// -3: Code 16 repeat overflow +// -4: Code 17 repeat overflow +// -5: Code 18 repeat overflow +// -6: Bad length code +// -7: Cancelled +static ufbxi_noinline ptrdiff_t ufbxi_init_dynamic_huff_tree(ufbxi_deflate_context *dc, const ufbxi_huff_tree *huff_code_length, ufbxi_huff_tree *tree, + uint32_t num_symbols, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits) +{ + uint8_t code_lengths[UFBXI_HUFF_MAX_VALUE]; // ufbxi_uninit + ufbx_assert(num_symbols <= UFBXI_HUFF_MAX_VALUE); + + uint64_t bits = dc->stream.bits; + size_t left = dc->stream.left; + const char *data = dc->stream.chunk_ptr; + uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit + memset(bits_counts, 0, sizeof(bits_counts)); + + uint32_t symbol_index = 0; + uint8_t prev = 0; + while (symbol_index < num_symbols) { + ufbxi_bit_refill(&bits, &left, &data, &dc->stream); + if (dc->stream.cancelled) return -7; + + ufbxi_huff_sym sym = ufbxi_huff_decode_bits(huff_code_length, bits, UFBXI_HUFF_CODELEN_FAST_BITS, UFBXI_HUFF_CODELEN_FAST_MASK); + ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); + + uint32_t inst = ufbxi_huff_sym_value(sym); + uint32_t sym_len = ufbxi_huff_sym_total_bits(sym); + + bits >>= sym_len; + left -= sym_len; + + if (inst <= 15) { + // "0 - 15: Represent code lengths of 0 - 15" + prev = (uint8_t)inst; + code_lengths[symbol_index++] = (uint8_t)inst; + bits_counts[(int32_t)inst]++; + } else if (inst == 16) { + // "16: Copy the previous code length 3 - 6 times. The next 2 bits indicate repeat length." + uint32_t num = 3 + ((uint32_t)bits & 0x3); + bits >>= 2; + left -= 2; + if (symbol_index + num > num_symbols) return -3; + memset(code_lengths + symbol_index, prev, num); + symbol_index += num; + bits_counts[(int32_t)prev] += num; + } else if (inst == 17) { + // "17: Repeat a code length of 0 for 3 - 10 times. (3 bits of length)" + uint32_t num = 3 + ((uint32_t)bits & 0x7); + bits >>= 3; + left -= 3; + if (symbol_index + num > num_symbols) return -4; + memset(code_lengths + symbol_index, 0, num); + symbol_index += num; + prev = 0; + bits_counts[0] += num; + } else if (inst == 18) { + // "18: Repeat a code length of 0 for 11 - 138 times (7 bits of length)" + uint32_t num = 11 + ((uint32_t)bits & 0x7f); + bits >>= 7; + left -= 7; + if (symbol_index + num > num_symbols) return -5; + memset(code_lengths + symbol_index, 0, num); + symbol_index += num; + prev = 0; + bits_counts[0] += num; + } else { + return -6; + } + } + + ptrdiff_t err = ufbxi_huff_build_imp(tree, code_lengths, num_symbols, sym_extra, sym_extra_offset, fast_bits, bits_counts); + if (err != 0) return err; + + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + + return 0; +} + +static ufbxi_noinline ptrdiff_t +ufbxi_init_dynamic_huff(ufbxi_deflate_context *dc, ufbxi_trees *trees) +{ + uint64_t bits = dc->stream.bits; + size_t left = dc->stream.left; + const char *data = dc->stream.chunk_ptr; + ufbxi_bit_refill(&bits, &left, &data, &dc->stream); + if (dc->stream.cancelled) return -28; + + trees->fast_bits = dc->fast_bits; + + // The header contains the number of Huffman codes in each of the three trees. + uint32_t num_lit_lengths = 257 + (uint32_t)(bits & 0x1f); + uint32_t num_dists = 1 + (uint32_t)(bits >> 5 & 0x1f); + uint32_t num_code_lengths = 4 + (uint32_t)(bits >> 10 & 0xf); + bits >>= 14; + left -= 14; + + // Code lengths for the "code length" Huffman tree are represented literally + // 3 bits in order of: 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 up to + // `num_code_lengths`, rest of the code lengths are 0 (unused) + uint8_t code_lengths[19]; // ufbxi_uninit + memset(code_lengths, 0, sizeof(code_lengths)); + for (size_t len_i = 0; len_i < num_code_lengths; len_i++) { + if (len_i == 14) { + ufbxi_bit_refill(&bits, &left, &data, &dc->stream); + if (dc->stream.cancelled) return -28; + } + code_lengths[ufbxi_deflate_code_length_permutation[len_i]] = (uint32_t)bits & 0x7; + bits >>= 3; + left -= 3; + } + + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + + ufbxi_huff_tree huff_code_length; // ufbxi_uninit + ptrdiff_t err; // ufbxi_uninit + + // Build the temporary "code length" Huffman tree used to encode the actual + // trees used to compress the data. Use that to build the literal/length and + // distance trees. + err = ufbxi_huff_build(&huff_code_length, code_lengths, ufbxi_arraycount(code_lengths), NULL, INT32_MAX, UFBXI_HUFF_CODELEN_FAST_BITS); + if (err) return -14 + 1 + err; + err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->lit_length, num_lit_lengths, ufbxi_deflate_length_lut, 256, dc->fast_bits); + if (err) return err == -7 ? -28 : -16 + 1 + err; + err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->dist, num_dists, ufbxi_deflate_dist_lut, 0, dc->fast_bits); + if (err) return err == -7 ? -28 : -22 + 1 + err; + + return 0; +} + +static ufbxi_noinline uint32_t ufbxi_adler32(const void *data, size_t size) +{ + ufbxi_fast_uint a = 1, b = 0; + const char *p = (const char*)data; + + // Adler-32 consists of two running sums modulo 65521. As an optimization + // we can accumulate N sums before applying the modulo, where N depends on + // the size of the type holding the sum. + const ufbxi_fast_uint num_before_wrap = sizeof(ufbxi_fast_uint) == 8 ? 380368439u : 5552u; + + ufbxi_fast_uint size_left = size; + while (size_left > 0) { + ufbxi_fast_uint num = size_left <= num_before_wrap ? size_left : num_before_wrap; + size_left -= num; + const char *end = p + num; + + // Align to 16 bytes + while (p != end && !ufbxi_is_aligned(p, 16)) { + a += (ufbxi_fast_uint)(uint8_t)p[0]; b += a; + p++; + } + +#if UFBXI_HAS_SSE + static const uint16_t factors[2][8] = { + { 16, 15, 14, 13, 12, 11, 10, 9, }, + { 8, 7, 6, 5, 4, 3, 2, 1, }, + }; + + const __m128i zero = _mm_setzero_si128(); + const __m128i factor_1 = _mm_set1_epi16(1); + const __m128i factor_16 = _mm_set1_epi16(16); + const __m128i factor_lo = _mm_loadu_si128((const __m128i*)factors[0]); + const __m128i factor_hi = _mm_loadu_si128((const __m128i*)factors[1]); + + for (;;) { + size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 5803) & ~(size_t)0xff; + if (chunk_size == 0) break; + const char *chunk_end = p + chunk_size; + + __m128i s1 = zero; + __m128i s2 = zero; + + while (p != chunk_end) { + __m128i s1_lo = zero, s1_hi = zero; + __m128i tmp_lo = zero, tmp_hi = zero; + + ufbxi_nounroll for (size_t i = 0; i < 256; i += 32) { + __m128i d0 = _mm_load_si128((const __m128i*)(p + i + 0)); + __m128i d1 = _mm_load_si128((const __m128i*)(p + i + 16)); + + tmp_lo = _mm_add_epi16(tmp_lo, s1_lo); + tmp_hi = _mm_add_epi16(tmp_hi, s1_hi); + s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d0, zero)); + s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d0, zero)); + + tmp_lo = _mm_add_epi16(tmp_lo, s1_lo); + tmp_hi = _mm_add_epi16(tmp_hi, s1_hi); + s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d1, zero)); + s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d1, zero)); + } + + s2 = _mm_add_epi32(s2, _mm_slli_epi32(s1, 8)); + s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_lo, factor_1)); + s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_hi, factor_1)); + + s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_lo, factor_16)); + s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_hi, factor_16)); + s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_lo, factor_lo)); + s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_hi, factor_hi)); + + p += 256; + } + + s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(2,3,0,1))); + s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(2,3,0,1))); + s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(1,0,3,2))); + s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(1,0,3,2))); + + b += chunk_size * a; + a += (uint32_t)_mm_cvtsi128_si32(s1); + b += (uint32_t)_mm_cvtsi128_si32(s2); + } +#elif UFBX_LITTLE_ENDIAN + for (;;) { + size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 256*8/4) & ~(size_t)0xf; + if (chunk_size == 0) break; + const char *chunk_end = p + chunk_size; + + uint64_t s1_lo = 0, s1_hi = 0; + uint64_t tmp, s2 = 0; + uint64_t mask8 = UINT64_C(0x00ff00ff00ff00ff); + uint64_t mask16 = UINT64_C(0x0000ffff0000ffff); + + while (p != chunk_end) { + uint64_t d0 = *(const uint64_t*)p; + uint64_t d1 = *(const uint64_t*)(p + 8); + + tmp = s1_lo + s1_hi; + s1_lo += d0 & mask8; + s1_hi += (d0 >> 8) & mask8; + + tmp += s1_lo + s1_hi; + s1_lo += d1 & mask8; + s1_hi += (d1 >> 8) & mask8; + + s2 += (tmp & mask16) + ((tmp >> 16) & mask16); + p += 16; + } + + uint64_t s1 = s1_lo + s1_hi; + s1 = (s1 & mask16) + ((s1 >> 16u) & mask16); + ufbxi_fast_uint s1_sum = (ufbxi_fast_uint)(s1 + (s1 >> 32u)); + + ufbxi_fast_uint s2_sum = (ufbxi_fast_uint)(s2 + (s2 >> 32u)) * 8; + s2_sum += ((ufbxi_fast_uint)(s1_lo >> 0) & 0xffff) * 8; + s2_sum += ((ufbxi_fast_uint)(s1_hi >> 0) & 0xffff) * 7; + s2_sum += ((ufbxi_fast_uint)(s1_lo >> 16) & 0xffff) * 6; + s2_sum += ((ufbxi_fast_uint)(s1_hi >> 16) & 0xffff) * 5; + s2_sum += ((ufbxi_fast_uint)(s1_lo >> 32) & 0xffff) * 4; + s2_sum += ((ufbxi_fast_uint)(s1_hi >> 32) & 0xffff) * 3; + s2_sum += ((ufbxi_fast_uint)(s1_lo >> 48) & 0xffff) * 2; + s2_sum += ((ufbxi_fast_uint)(s1_hi >> 48) & 0xffff) * 1; + + b += chunk_size * a; + a += s1_sum & 0xffffffffu; + b += s2_sum & 0xffffffffu; + } +#endif + + while (p != end) { + a += (size_t)(uint8_t)p[0]; b += a; + p++; + } + + a %= 65521u; + b %= 65521u; + } + + return (uint32_t)((b << 16) | (a & 0xffff)); +} + +static ufbxi_noinline int +ufbxi_inflate_block_slow(ufbxi_deflate_context *dc, ufbxi_trees *trees, size_t max_symbols) +{ + char *out_ptr = dc->out_ptr; + char *const out_begin = dc->out_begin; + char *const out_end = dc->out_end; + + uint32_t fast_bits = trees->fast_bits; + uint32_t fast_mask = (1u << fast_bits) - 1; + + uint64_t bits = dc->stream.bits; + size_t left = dc->stream.left; + const char *data = dc->stream.chunk_ptr; + + for (;;) { + if (max_symbols-- == 0) break; + + ufbxi_bit_refill(&bits, &left, &data, &dc->stream); + uint64_t sym_bits = bits; + + ufbxi_huff_sym sym0 = ufbxi_huff_decode_bits(&trees->lit_length, bits, fast_bits, fast_mask); + ufbxi_regression_assert(sym0 != UFBXI_HUFF_UNINITIALIZED_SYM); + + uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0); + + bits >>= sym0_bits; + left -= sym0_bits; + if (sym0 & UFBXI_HUFF_SYM_END) { + if (ufbxi_huff_sym_value(sym0) != 0) return -13; + + dc->out_ptr = out_ptr; + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + return 0; + } else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) { + if (out_ptr == out_end) return -10; + *out_ptr++ = (char)ufbxi_huff_sym_value(sym0); + continue; + } + + uint32_t sym0_value = ufbxi_huff_sym_value(sym0); + uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value]; + uint16_t len_mask = trees->lit_length.extra_mask[sym0_value]; + uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, len_shift_base) & len_mask); + + ufbxi_huff_sym sym1 = ufbxi_huff_decode_bits(&trees->dist, bits, fast_bits, fast_mask); + ufbxi_regression_assert(sym1 != UFBXI_HUFF_UNINITIALIZED_SYM); + if (sym1 & UFBXI_HUFF_SYM_END) return -11; + + uint32_t sym1_bits = ufbxi_huff_sym_total_bits(sym1); + + bits >>= sym1_bits; + left -= sym1_bits; + + uint32_t sym1_value = ufbxi_huff_sym_value(sym1); + uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value]; + uint16_t dist_mask = trees->dist.extra_mask[sym1_value]; + uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, dist_shift_base + sym0) & dist_mask); + + // Bounds checking + size_t out_space = ufbxi_to_size(out_end - out_ptr); + if ((ptrdiff_t)distance > out_ptr - out_begin || length > out_space) { + return -12; + } + + // Copy the match + const char *src = out_ptr - distance; + char *dst = out_ptr; + char *end = dst + length; + out_ptr += length; + + if (out_space >= length + 16) { + uint32_t min_dist = length < 16 ? length : 16; + if (distance >= min_dist) { + ufbxi_copy_16_bytes(dst, src); + while (length > 16) { + src += 16; + dst += 16; + length -= 16; + ufbxi_copy_16_bytes(dst, src); + } + } else { + while (dst != end) { + *dst++ = *src++; + } + } + } else { + while (dst != end) { + *dst++ = *src++; + } + } + } + + dc->out_ptr = out_ptr; + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + return 1; +} + +ufbx_static_assert(inflate_huff_fast_bits, UFBXI_HUFF_FAST_BITS <= 11); // `fast lit, fast len, slow dist` in 56 bits +ufbx_static_assert(inflate_huff_long_bits, UFBXI_HUFF_FAST_BITS + UFBXI_HUFF_MAX_LONG_BITS >= 15); // Largest code fits in a single long lookup + +// Optimized version of `ufbxi_inflate_block_slow()`. +// Has a lot of assumptions (see asserts) and does not call _any_ (even forceinlined) functions. +static ufbxi_noinline int +ufbxi_inflate_block_fast(ufbxi_deflate_context *dc, ufbxi_trees *trees) +{ + ufbxi_dev_assert(!dc->stream.cancelled); + ufbxi_dev_assert(trees->fast_bits == UFBXI_HUFF_FAST_BITS); + ufbxi_dev_assert(dc->stream.chunk_yield - dc->stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN); + ufbxi_dev_assert(dc->out_end - dc->out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT); + + char *out_ptr = dc->out_ptr; + char *const out_begin = dc->out_begin; + char *const out_end = dc->out_end - UFBXI_INFLATE_FAST_MIN_OUT; + + const ufbxi_huff_tree *tree_lit_length = &trees->lit_length; + const ufbxi_huff_tree *tree_dist = &trees->dist; + + uint64_t bits = dc->stream.bits; + size_t left = dc->stream.left; + const char *data = dc->stream.chunk_ptr; + const char *data_end = dc->stream.chunk_yield - UFBXI_INFLATE_FAST_MIN_IN; + + uint64_t sym01_bits; + ufbxi_huff_sym sym0, sym1; + uint64_t refill_bits = ufbxi_read_u64(data); + + #define ufbxi_fast_inflate_refill_and_decode() do { \ + ufbxi_macro_bit_refill_fast(bits, left, data, refill_bits); \ + sym01_bits = bits; \ + sym0 = tree_lit_length->fast_sym[sym01_bits & UFBXI_HUFF_FAST_MASK]; \ + sym1 = ((sym0 & UFBXI_HUFF_SYM_MATCH) ? tree_dist : tree_lit_length)->fast_sym[ufbxi_wrap_shr64(sym01_bits, sym0) & UFBXI_HUFF_FAST_MASK]; \ + refill_bits = ufbxi_read_u64(data); \ + } while (0) + + #define ufbxi_fast_inflate_should_continue() \ + (((data_end - data) | (out_end - out_ptr)) >= 0) + + ufbxi_fast_inflate_refill_and_decode(); + + for (;;) { + if ((sym0 & sym1) & UFBXI_HUFF_SYM_FAST) { + bits = ufbxi_wrap_shr64(sym01_bits, sym0 + sym1); + left -= (sym0 + sym1) & 0x3f; + + if (((sym0 | sym1) & UFBXI_HUFF_SYM_MATCH) == 0) { + // Literal, Literal + // -> Output the two literals and loop back to start. + + out_ptr[0] = (char)ufbxi_huff_sym_value(sym0); + out_ptr[1] = (char)ufbxi_huff_sym_value(sym1); + out_ptr += 2; + + ufbxi_fast_inflate_refill_and_decode(); + if (ufbxi_fast_inflate_should_continue()) continue; + break; + + } else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) { + // Literal, Match, (Distance) + // -> Output a single literal, decode the missing distance and fall through to match. + + out_ptr[0] = (char)ufbxi_huff_sym_value(sym0); + out_ptr += 1; + + sym01_bits = ufbxi_wrap_shr64(sym01_bits, sym0); + + // This must fit as literals never have extra bits and the match length is fast so: + // 10 (lit) + 10 (len code) + 5 (len extra) + 15 (dist code) + 13 (dist extra) = 53 <= 56 + sym0 = sym1; + sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK]; + + if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) { + // Slow sym1 + if (sym1 & UFBXI_HUFF_SYM_END) return -11; + uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS); + uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1); + sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)]; + if (sym1 & UFBXI_HUFF_SYM_END) return -11; + } + + bits = ufbxi_wrap_shr64(bits, sym1); + left -= sym1 & 0x3f; + } else { + // Match, Distance + // -> Fall through to match copy. + } + + } else { + if ((sym0 & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) == 0) { + // Slow sym0 + uint32_t tail = (uint32_t)(sym01_bits >> UFBXI_HUFF_FAST_BITS); + uint32_t long_mask = ufbxi_huff_sym_long_mask(sym0); + sym0 = tree_lit_length->long_sym[ufbxi_huff_sym_long_offset(sym0) + (tail & long_mask)]; + } + + uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0); + bits >>= sym0_bits; + left -= sym0_bits; + + if (sym0 & UFBXI_HUFF_SYM_END) { + if (ufbxi_huff_sym_value(sym0) != 0) return -13; + dc->out_ptr = out_ptr; + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + return 0; + } + + if (sym0 & UFBXI_HUFF_SYM_MATCH) { + sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK]; + + if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) { + // Slow sym1 + if (sym1 & UFBXI_HUFF_SYM_END) return -11; + uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS); + uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1); + sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)]; + if (sym1 & UFBXI_HUFF_SYM_END) return -11; + } + + bits = ufbxi_wrap_shr64(bits, sym1); + left -= sym1 & 0x3f; + } else { + *out_ptr++ = (char)ufbxi_huff_sym_value(sym0); + + ufbxi_fast_inflate_refill_and_decode(); + if (ufbxi_fast_inflate_should_continue()) continue; + break; + } + } + + uint32_t sym0_value = ufbxi_huff_sym_value(sym0); + uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value]; + uint16_t len_mask = trees->lit_length.extra_mask[sym0_value]; + uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, len_shift_base) & len_mask); + + uint32_t sym1_value = ufbxi_huff_sym_value(sym1); + uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value]; + uint16_t dist_mask = trees->dist.extra_mask[sym1_value]; + uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, dist_shift_base + sym0) & dist_mask); + + ufbxi_fast_inflate_refill_and_decode(); + + // Bounds checking: We don't actually handle the error here, just bail out to the slow implementation + ptrdiff_t dst_space = out_end - out_ptr - (ptrdiff_t)length + UFBXI_INFLATE_FAST_MIN_OUT; + ptrdiff_t src_space = out_ptr - out_begin - (ptrdiff_t)distance; + if ((dst_space | src_space) < 0) { + return -12; + } + + const char *src = out_ptr - distance; + char *dst = out_ptr; + char *end = dst + length; + out_ptr += length; + + // Copy the match + + uint32_t min_dist = length < 16 ? length : 16; + if (distance >= min_dist && dst_space >= 16) { + ufbxi_copy_16_bytes(dst, src); + while (length > 16) { + src += 16; + dst += 16; + length -= 16; + ufbxi_copy_16_bytes(dst, src); + } + } else { + while (dst != end) { + *dst++ = *src++; + } + } + + if (ufbxi_fast_inflate_should_continue()) continue; + break; + } + + dc->out_ptr = out_ptr; + dc->stream.bits = bits; + dc->stream.left = left; + dc->stream.chunk_ptr = data; + return 1; + + #undef ufbxi_fast_inflate_refill_and_decode + #undef ufbxi_fast_inflate_should_continue +} + +static void ufbxi_inflate_init_retain(ufbx_inflate_retain *retain) +{ + ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain; + if (!ret_imp->initialized) { + ufbxi_init_static_huff(&ret_imp->static_trees, NULL); + ret_imp->initialized = true; + } +} + +// TODO: Error codes should have a quick test if the destination buffer overflowed +// Returns actual number of decompressed bytes or negative error: +// -1: Bad compression method (ZLIB header) +// -2: Requires dictionary (ZLIB header) +// -3: Bad FCHECK (ZLIB header) +// -4: Bad NLEN (Uncompressed LEN != ~NLEN) +// -5: Uncompressed source overflow +// -6: Uncompressed destination overflow +// -7: Bad block type +// -8: Truncated checksum (deprecated, reported as -9) +// -9: Checksum mismatch +// -10: Literal destination overflow +// -11: Bad distance code or distance of (30..31) +// -12: Match out of bounds +// -13: Bad lit/length code +// -14: Codelen Huffman Overfull +// -15: Codelen Huffman Underfull +// -16 - -21: Litlen Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code +// -22 - -27: Distance Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code +// -28: Cancelled +// -29: Invalid ufbx_inflate_input.internal_fast_bits value +ufbxi_extern_c ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain) +{ + ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain; + + ptrdiff_t err; + ufbxi_deflate_context dc; + ufbxi_bit_stream_init(&dc.stream, input); + dc.out_begin = (char*)dst; + dc.out_ptr = (char*)dst; + dc.out_end = (char*)dst + dst_size; + if (input->internal_fast_bits != 0) { + dc.fast_bits = (uint32_t)input->internal_fast_bits; + if (dc.fast_bits < 1 || dc.fast_bits == 9 || dc.fast_bits > 10) return -29; + } else { + // TODO: Profile this + dc.fast_bits = input->total_size > 2048 ? 10 : 8; + } + + uint64_t bits = dc.stream.bits; + size_t left = dc.stream.left; + const char *data = dc.stream.chunk_ptr; + + ufbxi_bit_refill(&bits, &left, &data, &dc.stream); + if (dc.stream.cancelled) return -28; + + // Zlib header + if (!input->no_header) { + size_t cmf = (size_t)(bits & 0xff); + size_t flg = (size_t)(bits >> 8) & 0xff; + bits >>= 16; + left -= 16; + + if ((cmf & 0xf) != 0x8) return -1; + if ((flg & 0x20) != 0) return -2; + if ((cmf << 8 | flg) % 31u != 0) return -3; + } + + for (;;) { + ufbxi_bit_refill(&bits, &left, &data, &dc.stream); + if (dc.stream.cancelled) return -28; + + // Block header: [0:1] BFINAL [1:3] BTYPE + size_t header = (size_t)bits & 0x7; + bits >>= 3; + left -= 3; + + size_t type = header >> 1; + if (type == 0) { + + // Round up to the next byte + size_t align_bits = left & 0x7; + bits >>= align_bits; + left -= align_bits; + + size_t len = (size_t)(bits & 0xffff); + size_t nlen = (size_t)((bits >> 16) & 0xffff); + if ((len ^ nlen) != 0xffff) return -4; + if (dc.out_end - dc.out_ptr < (ptrdiff_t)len) return -6; + bits >>= 32; + left -= 32; + + dc.stream.bits = bits; + dc.stream.left = left; + dc.stream.chunk_ptr = data; + + // Copy `len` bytes of literal data + if (!ufbxi_bit_copy_bytes(dc.out_ptr, &dc.stream, len)) return -5; + + dc.out_ptr += len; + + } else if (type <= 2) { + + dc.stream.bits = bits; + dc.stream.left = left; + dc.stream.chunk_ptr = data; + + ufbxi_trees tree_data; // ufbxi_uninit + ufbxi_trees *trees; // ufbxi_uninit + if (type == 1) { + // Static Huffman: Initialize the trees once and cache them in `retain`. + if (!ret_imp->initialized) { + ufbxi_init_static_huff(&ret_imp->static_trees, input); + ret_imp->initialized = true; + } + trees = &ret_imp->static_trees; + } else { + // Dynamic Huffman + err = ufbxi_init_dynamic_huff(&dc, &tree_data); + if (err) return err; + trees = &tree_data; + } + + for (;;) { + bool fast_viable = trees->fast_bits == UFBXI_HUFF_FAST_BITS && dc.out_end - dc.out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT; + + // `ufbxi_inflate_block_fast()` needs a bit more upfront setup, see asserts on top of the function + if (fast_viable && dc.stream.chunk_yield - dc.stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN) { + err = ufbxi_inflate_block_fast(&dc, trees); + } else { + err = ufbxi_inflate_block_slow(&dc, trees, fast_viable ? 32 : SIZE_MAX); + } + + if (err < 0) return err; + + // `ufbxi_inflate_block()` returns normally on cancel so check it here + if (dc.stream.cancelled) return -28; + + if (err == 0) break; + } + + } else { + // 0b11 - reserved (error) + return -7; + } + + bits = dc.stream.bits; + left = dc.stream.left; + data = dc.stream.chunk_ptr; + + // BFINAL: End of stream + if (header & 1) break; + } + + // Check Adler-32 + { + // Round up to the next byte + size_t align_bits = left & 0x7; + bits >>= align_bits; + left -= align_bits; + ufbxi_bit_refill(&bits, &left, &data, &dc.stream); + if (dc.stream.cancelled) return -28; + + if (!input->no_checksum) { + uint32_t ref = (uint32_t)bits; + ref = (ref>>24) | ((ref>>8)&0xff00) | ((ref<<8)&0xff0000) | (ref<<24); + + uint32_t checksum = ufbxi_adler32(dc.out_begin, ufbxi_to_size(dc.out_ptr - dc.out_begin)); + if (ref != checksum) { + return -9; + } + } + } + + return dc.out_ptr - dc.out_begin; +} + +#endif // !defined(ufbx_inflate) + +// -- Printf + +typedef struct { + char *dst; + size_t length; + size_t pos; +} ufbxi_print_buffer; + +#define UFBXI_PRINT_UNSIGNED 0x1 +#define UFBXI_PRINT_STRING 0x2 +#define UFBXI_PRINT_SIZE_T 0x10 + +static void ufbxi_print_append(ufbxi_print_buffer *buf, size_t min_width, size_t max_width, const char *str) +{ + size_t width = 0; + for (width = 0; width < max_width; width++) { + if (!str[width]) break; + } + size_t pad = min_width > width ? min_width - width : 0; + for (size_t i = 0; i < pad; i++) { + if (buf->pos < buf->length) buf->dst[buf->pos++] = ' '; + } + for (size_t i = 0; i < width; i++) { + if (buf->pos < buf->length) buf->dst[buf->pos++] = str[i]; + } +} + +static char *ufbxi_print_format_int(char *buffer, uint64_t value) +{ + *--buffer = '\0'; + do { + uint32_t digit = (uint32_t)(value % 10); + value = value / 10; + *--buffer = (char)('0' + digit); + } while (value > 0); + return buffer; +} + +static void ufbxi_vprint(ufbxi_print_buffer *buf, const char *fmt, va_list args) +{ + char buffer[96]; // ufbxi_uninit + for (const char *p = fmt; *p;) { + if (*p == '%' && *++p != '%') { + size_t min_width = 0, max_width = SIZE_MAX; + if (*p == '*') { + p++; + min_width = (size_t)va_arg(args, int); + } + if (*p == '.') { + ufbxi_dev_assert(p[1] == '*'); + p += 2; + max_width = (size_t)va_arg(args, int); + } + uint32_t flags = 0; + switch (*p) { + case 'z': p++; flags |= UFBXI_PRINT_SIZE_T; break; + default: break; + } + switch (*p++) { + case 'u': flags |= UFBXI_PRINT_UNSIGNED; break; + case 's': flags |= UFBXI_PRINT_STRING; break; + default: break; + } + if (flags & UFBXI_PRINT_STRING) { + const char *str = va_arg(args, const char*); + ufbxi_print_append(buf, min_width, max_width, str); + } else if (flags & UFBXI_PRINT_UNSIGNED) { + uint64_t value = (flags & UFBXI_PRINT_SIZE_T) != 0 ? (uint64_t)va_arg(args, size_t) : (uint64_t)va_arg(args, uint32_t); + char *str = ufbxi_print_format_int(buffer + sizeof(buffer), value); + ufbxi_print_append(buf, min_width, max_width, str); + } else { + ufbxi_unreachable("Bad printf format"); + } + } else { + if (buf->pos < buf->length) buf->dst[buf->pos++] = *p; + p++; + } + } + if (buf->length && buf->dst) { + size_t end = buf->pos <= buf->length - 1 ? buf->pos : buf->length - 1; + buf->dst[end] = '\0'; + } +} + +// -- Errors + +static const char ufbxi_empty_char[1] = { '\0' }; + +static ufbxi_noinline int ufbxi_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list args) +{ + ufbxi_print_buffer buffer = { buf, buf_size }; + ufbxi_vprint(&buffer, fmt, args); + return (int)ufbxi_min_sz(buffer.pos, buf_size - 1); +} + +static ufbxi_noinline int ufbxi_snprintf(char *buf, size_t buf_size, const char *fmt, ...) +{ + va_list args; // ufbxi_uninit + va_start(args, fmt); + int result = ufbxi_vsnprintf(buf, buf_size, fmt, args); + va_end(args); + return result; +} + +static ufbxi_noinline void ufbxi_panicf_imp(ufbx_panic *panic, const char *fmt, ...) +{ + if (panic && panic->did_panic) return; + + va_list args; // ufbxi_uninit + + if (panic) { + va_start(args, fmt); + panic->did_panic = true; + panic->message_length = (size_t)ufbxi_vsnprintf(panic->message, sizeof(panic->message), fmt, args); + va_end(args); + } else { + va_start(args, fmt); + char message[UFBX_PANIC_MESSAGE_LENGTH]; + ufbxi_vsnprintf(message, sizeof(message), fmt, args); + va_end(args); + + ufbx_panic_handler(message); + } +} + +#define ufbxi_panicf(panic, cond, ...) \ + ((cond) ? false : (ufbxi_panicf_imp((panic), __VA_ARGS__), true)) + +// Prefix the error condition with $Description\0 for a human readable description +#define ufbxi_error_msg(cond, msg) "$" msg "\0" cond + +static ufbxi_noinline int ufbxi_fail_imp_err(ufbx_error *err, const char *cond, const char *func, uint32_t line) +{ + if (cond && cond[0] == '$') { + if (!err->description.data) { + err->description.data = cond + 1; + err->description.length = strlen(err->description.data); + } + +#if UFBXI_FEATURE_ERROR_STACK + // Skip the description part if adding to a stack + cond = cond + strlen(cond) + 1; +#endif + } + + // NOTE: This is the base function all fails boil down to, place a breakpoint here to + // break at the first error +#if UFBXI_FEATURE_ERROR_STACK + ufbx_assert(cond); + ufbx_assert(func); + if (err->stack_size < UFBX_ERROR_STACK_MAX_DEPTH) { + ufbx_error_frame *frame = &err->stack[err->stack_size++]; + frame->description.data = cond; + frame->description.length = strlen(cond); + frame->function.data = func; + frame->function.length = strlen(func); + frame->source_line = line; + } +#else + ufbxi_ignore(func); + ufbxi_ignore(line); +#endif + + return 0; +} + +ufbxi_nodiscard static ufbxi_noinline size_t ufbxi_utf8_valid_length(const char *str, size_t length) +{ + size_t index = 0; + while (index < length) { + uint8_t c = (uint8_t)str[index]; + size_t left = length - index; + + if ((c & 0x80) == 0) { + if (c != 0) { + index += 1; + continue; + } + } else if ((c & 0xe0) == 0xc0 && left >= 2) { + uint8_t t0 = (uint8_t)str[index + 1]; + uint32_t code = (uint32_t)c << 8 | (uint32_t)t0; + if ((code & 0xc0) == 0x80 && code >= 0xc280) { + index += 2; + continue; + } + } else if ((c & 0xf0) == 0xe0 && left >= 3) { + uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2]; + uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1; + if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) { + index += 3; + continue; + } + } else if ((c & 0xf8) == 0xf0 && left >= 4) { + uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3]; + uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2; + if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) { + index += 4; + continue; + } + } + + break; + } + + ufbx_assert(index <= length); + return index; +} + +static ufbxi_noinline void ufbxi_clean_string_utf8(char *str, size_t length) +{ + size_t pos = 0; + for (;;) { + pos += ufbxi_utf8_valid_length(str + pos, length); + if (pos == length) break; + str[pos++] = '?'; + } +} + +static ufbxi_noinline void ufbxi_set_err_info(ufbx_error *err, const char *data, size_t length) +{ + if (!err) return; + + if (length == SIZE_MAX) length = strlen(data); + size_t to_copy = ufbxi_min_sz(sizeof(err->info) - 1, length); + memcpy(err->info, data, to_copy); + err->info[to_copy] = '\0'; + err->info_length = to_copy; + ufbxi_clean_string_utf8(err->info, err->info_length); +} + +static ufbxi_noinline void ufbxi_fmt_err_info(ufbx_error *err, const char *fmt, ...) +{ + if (!err) return; + + va_list args; // ufbxi_uninit + va_start(args, fmt); + err->info_length = (size_t)ufbxi_vsnprintf(err->info, sizeof(err->info), fmt, args); + va_end(args); + ufbxi_clean_string_utf8(err->info, err->info_length); +} + +static ufbxi_noinline void ufbxi_clear_error(ufbx_error *err) +{ + if (!err) return; + + err->type = UFBX_ERROR_NONE; + err->description.data = ufbxi_empty_char; + err->description.length = 0; + err->stack_size = 0; + err->info[0] = '\0'; + err->info_length = 0; +} + +#if UFBXI_FEATURE_ERROR_STACK + #define ufbxi_function __FUNCTION__ + #define ufbxi_line __LINE__ + #define ufbxi_cond_str(cond) #cond +#else + #define ufbxi_function NULL + #define ufbxi_line 0 + #define ufbxi_cond_str(cond) "" +#endif + +#if UFBXI_FEATURE_ERROR_STACK + #define ufbxi_fail_err_no_msg(err, cond, func, line) ufbxi_fail_imp_err((err), (cond), (func), (line)) +#else + static ufbxi_noinline int ufbxi_fail_imp_err_no_stack(ufbx_error *err) { return ufbxi_fail_imp_err(err, NULL, NULL, 0); } + #define ufbxi_fail_err_no_msg(err, cond, func, line) ufbxi_fail_imp_err_no_stack((err)) +#endif + +#define ufbxi_check_err(err, cond) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_err_no_msg((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return 0; } } while (0) +#define ufbxi_check_return_err(err, cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_err_no_msg((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0) +#define ufbxi_fail_err(err, desc) return ufbxi_fail_err_no_msg(err, desc, ufbxi_function, ufbxi_line) + +#define ufbxi_check_err_msg(err, cond, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return 0; } } while (0) +#define ufbxi_check_return_err_msg(err, cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0) +#define ufbxi_fail_err_msg(err, desc, msg) return ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) +#define ufbxi_report_err_msg(err, desc, msg) (void)ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) + +static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *default_desc, ufbx_error *p_error) +{ + const char *desc = error->description.data; + if (!desc) desc = default_desc; + error->type = UFBX_ERROR_UNKNOWN; + if (!strcmp(desc, "Out of memory")) { + error->type = UFBX_ERROR_OUT_OF_MEMORY; + } else if (!strcmp(desc, "Memory limit exceeded")) { + error->type = UFBX_ERROR_MEMORY_LIMIT; + } else if (!strcmp(desc, "Allocation limit exceeded")) { + error->type = UFBX_ERROR_ALLOCATION_LIMIT; + } else if (!strcmp(desc, "Truncated file")) { + error->type = UFBX_ERROR_TRUNCATED_FILE; + } else if (!strcmp(desc, "IO error")) { + error->type = UFBX_ERROR_IO; + } else if (!strcmp(desc, "Cancelled")) { + error->type = UFBX_ERROR_CANCELLED; + } else if (!strcmp(desc, "Unrecognized file format")) { + error->type = UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT; + } else if (!strcmp(desc, "File not found")) { + error->type = UFBX_ERROR_FILE_NOT_FOUND; + } else if (!strcmp(desc, "Empty file")) { + error->type = UFBX_ERROR_EMPTY_FILE; + } else if (!strcmp(desc, "External file not found")) { + error->type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND; + } else if (!strcmp(desc, "Uninitialized options")) { + error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS; + } else if (!strcmp(desc, "Zero vertex size")) { + error->type = UFBX_ERROR_ZERO_VERTEX_SIZE; + } else if (!strcmp(desc, "Truncated vertex stream")) { + error->type = UFBX_ERROR_TRUNCATED_VERTEX_STREAM; + } else if (!strcmp(desc, "Invalid UTF-8")) { + error->type = UFBX_ERROR_INVALID_UTF8; + } else if (!strcmp(desc, "Feature disabled")) { + error->type = UFBX_ERROR_FEATURE_DISABLED; + } else if (!strcmp(desc, "Bad NURBS geometry")) { + error->type = UFBX_ERROR_BAD_NURBS; + } else if (!strcmp(desc, "Bad index")) { + error->type = UFBX_ERROR_BAD_INDEX; + } else if (!strcmp(desc, "Node depth limit exceeded")) { + error->type = UFBX_ERROR_NODE_DEPTH_LIMIT; + } else if (!strcmp(desc, "Threaded ASCII parse error")) { + error->type = UFBX_ERROR_THREADED_ASCII_PARSE; + } else if (!strcmp(desc, "Unsafe options")) { + error->type = UFBX_ERROR_UNSAFE_OPTIONS; + } else if (!strcmp(desc, "Duplicate override")) { + error->type = UFBX_ERROR_DUPLICATE_OVERRIDE; + } + error->description.data = desc; + error->description.length = strlen(desc); + if (p_error) { + memcpy(p_error, error, sizeof(ufbx_error)); + } +} + +// -- Allocator + +// Returned for zero size allocations, place in the constant data +// to catch writes to bad allocations. +#if defined(UFBX_REGRESSION) +static const char ufbxi_zero_size_buffer[4096] = { 0 }; +#else +static const char ufbxi_zero_size_buffer[64] = { 0 }; +#endif + +static ufbxi_forceinline size_t ufbxi_align_to_mask(size_t value, size_t align_mask) +{ + return value + (((size_t)0 - value) & align_mask); +} + +static ufbxi_forceinline size_t ufbxi_size_align_mask(size_t size) +{ + // Align to the all bits below the lowest set one in `size` up to the maximum alignment. + return ((size ^ (size - 1)) >> 1) & (UFBX_MAXIMUM_ALIGNMENT - 1); +} + +typedef struct { + ufbx_error *error; + size_t current_size; + size_t max_size; + size_t num_allocs; + size_t max_allocs; + size_t huge_size; + size_t chunk_max; + ufbx_allocator_opts ator; + const char *name; +} ufbxi_allocator; + +static ufbxi_forceinline bool ufbxi_does_overflow(size_t total, size_t a, size_t b) +{ + // If `a` and `b` have at most 4 bits per `size_t` byte, the product can't overflow. + if (((a | b) >> sizeof(size_t)*4) != 0) { + if (a != 0 && total / a != b) return true; + } + return false; +} + +static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, size_t n) +{ + // Always succeed with an empty non-NULL buffer for empty allocations + ufbx_assert(size > 0); + if (n == 0) return (void*)ufbxi_zero_size_buffer; + + size_t total = size * n; + ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL); + ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations + if (!(total < ator->max_size - ator->current_size)) { + ufbxi_report_err_msg(ator->error, "total <= ator->max_size - ator->current_size", "Memory limit exceeded"); + ufbxi_fmt_err_info(ator->error, "%s", ator->name); + return NULL; + } + if (!(ator->num_allocs < ator->max_allocs)) { + ufbxi_report_err_msg(ator->error, "ator->num_allocs < ator->max_allocs", "Allocation limit exceeded"); + ufbxi_fmt_err_info(ator->error, "%s", ator->name); + return NULL; + } + ator->num_allocs++; + + void *ptr; + if (ator->ator.allocator.alloc_fn) { + ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total); + } else if (ator->ator.allocator.realloc_fn) { + ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, NULL, 0, total); + } else { + ptr = ufbx_malloc(total); + } + + if (!ptr) { + ufbxi_report_err_msg(ator->error, "ptr", "Out of memory"); + ufbxi_fmt_err_info(ator->error, "%s", ator->name); + return NULL; + } + ufbx_assert(ufbxi_is_aligned_mask(ptr, ufbxi_size_align_mask(total))); + + ator->current_size += total; + + return ptr; +} + +static void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n); +static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t size, void *old_ptr, size_t old_n, size_t n) +{ + ufbx_assert(size > 0); + // realloc() with zero old/new size is equivalent to alloc()/free() + if (old_n == 0) return ufbxi_alloc_size(ator, size, n); + if (n == 0) { ufbxi_free_size(ator, size, old_ptr, old_n); return NULL; } + + size_t old_total = size * old_n; + size_t total = size * n; + + // The old values have been checked by a previous allocate call + ufbx_assert(!ufbxi_does_overflow(old_total, size, old_n)); + ufbx_assert(old_total <= ator->current_size); + + ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL); + ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations + ufbxi_check_return_err_msg(ator->error, total <= ator->max_size - ator->current_size, NULL, "Memory limit exceeded"); + ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); + ator->num_allocs++; + + void *ptr; + if (ator->ator.allocator.realloc_fn) { + ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, old_ptr, old_total, total); + } else if (ator->ator.allocator.alloc_fn) { + // Use user-provided alloc_fn() and free_fn() + ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total); + if (ptr) memcpy(ptr, old_ptr, old_total); + if (ator->ator.allocator.free_fn) { + ator->ator.allocator.free_fn(ator->ator.allocator.user, old_ptr, old_total); + } + } else { + ptr = ufbx_realloc(old_ptr, old_total, total); + } + + ufbxi_check_return_err_msg(ator->error, ptr, NULL, "Out of memory"); + ufbx_assert(ufbxi_is_aligned_mask(ptr, ufbxi_size_align_mask(total))); + + ator->current_size += total; + ator->current_size -= old_total; + + return ptr; +} + +static ufbxi_noinline void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n) +{ + ufbx_assert(size > 0); + if (n == 0) return; + ufbx_assert(ptr); + + size_t total = size * n; + + // The old values have been checked by a previous allocate call + ufbx_assert(!ufbxi_does_overflow(total, size, n)); + ufbx_assert(total <= ator->current_size); + + ator->current_size -= total; + + if (ator->ator.allocator.alloc_fn || ator->ator.allocator.realloc_fn) { + // Don't call default free() if there is an user-provided `alloc_fn()` + if (ator->ator.allocator.free_fn) { + ator->ator.allocator.free_fn(ator->ator.allocator.user, ptr, total); + } else if (ator->ator.allocator.realloc_fn) { + ator->ator.allocator.realloc_fn(ator->ator.allocator.user, ptr, total, 0); + } + } else { + ufbx_free(ptr, total); + } +} + +ufbxi_noinline ufbxi_nodiscard static bool ufbxi_grow_array_size(ufbxi_allocator *ator, size_t size, void *p_ptr, size_t *p_cap, size_t n) +{ + #if defined(UFBX_REGRESSION) + { + ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded"); + ator->num_allocs++; + } + #endif + + if (n <= *p_cap) return true; + void *ptr = *(void**)p_ptr; + size_t old_n = *p_cap; + if (old_n >= n) return true; + size_t new_n = ufbxi_max_sz(old_n * 2, n); + void *new_ptr = ufbxi_realloc_size(ator, size, ptr, old_n, new_n); + if (!new_ptr) return false; + *(void**)p_ptr = new_ptr; + *p_cap = new_n; + return true; +} + +static ufbxi_noinline void ufbxi_free_ator(ufbxi_allocator *ator) +{ + ufbx_assert(ator->current_size == 0); + + ufbx_free_allocator_fn *free_fn = ator->ator.allocator.free_allocator_fn; + if (free_fn) { + void *user = ator->ator.allocator.user; + free_fn(user); + } +} + +#define ufbxi_alloc(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_size((ator), sizeof(type), (n))) +#define ufbxi_alloc_zero(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_zero_size((ator), sizeof(type), (n))) +#define ufbxi_realloc(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_size((ator), sizeof(type), (old_ptr), (old_n), (n))) +#define ufbxi_realloc_zero(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_zero_size((ator), sizeof(type), (old_ptr), (old_n), (n))) +#define ufbxi_free(ator, type, ptr, n) ufbxi_free_size((ator), sizeof(type), (ptr), (n)) + +#define ufbxi_grow_array(ator, p_ptr, p_cap, n) ufbxi_grow_array_size((ator), sizeof(**(p_ptr)), (p_ptr), (p_cap), (n)) + +#define UFBXI_SCENE_IMP_MAGIC 0x58424655 +#define UFBXI_MESH_IMP_MAGIC 0x48534d55 +#define UFBXI_LINE_CURVE_IMP_MAGIC 0x55434c55 +#define UFBXI_CACHE_IMP_MAGIC 0x48434355 +#define UFBXI_ANIM_IMP_MAGIC 0x494e4155 +#define UFBXI_BAKED_ANIM_IMP_MAGIC 0x4b414255 +#define UFBXI_REFCOUNT_IMP_MAGIC 0x46455255 +#define UFBXI_BUF_CHUNK_IMP_MAGIC 0x46554255 + +// -- Memory buffer +// +// General purpose memory buffer that can be used either as a chunked linear memory +// allocator or a non-contiguous stack. You can convert the contents of `ufbxi_buf` +// to a contiguous range of memory by calling `ufbxi_make_array[_all]()` + +typedef struct ufbxi_buf_padding ufbxi_buf_padding; +typedef struct ufbxi_buf_chunk ufbxi_buf_chunk; + +struct ufbxi_buf_padding { + size_t original_pos; // < Original position before aligning + size_t prev_padding; // < Starting offset of the previous `ufbxi_buf_padding` +}; + +struct ufbxi_buf_chunk { + + // Linked list of nodes + ufbxi_buf_chunk *root; + ufbxi_buf_chunk *prev; + ufbxi_buf_chunk *next; + + union { + size_t magic; // < Magic for debugging + void *align_0; // < Align to 4x pointer size (16/32 bytes) + }; + + size_t size; // < Size of the chunk `data`, excluding this header + size_t pushed_pos; // < Size of valid data when pushed to the list + size_t next_size; // < Next geometrically growing chunk size to allocate + size_t padding_pos; // < One past the offset of the most recent `ufbxi_buf_padding` + + char data[]; // < Must be aligned to 8 bytes +}; + +ufbx_static_assert(buf_chunk_align, offsetof(ufbxi_buf_chunk, data) % 8 == 0); + +typedef struct { + ufbxi_allocator *ator; + + // Current chunks for normal and huge allocations. + // Ordered buffers (`!ufbxi_buf.unordered`) never use `chunks[1]` + ufbxi_buf_chunk *chunks[2]; + + // Inline state for non-huge chunks + size_t pos; // < Next offset to allocate from + size_t size; // < Size of the current chunk ie. `chunks[0]->size` (or 0 if `chunks[0] == NULL`) + + size_t num_items; // < Number of individual items pushed to the buffer + + size_t pushed_size; // < Cumulative size of pushed chunks, not tracked across pops + + bool unordered; // < Does not support popping from the buffer + bool clearable; // < Supports clearing the whole buffer even if `unordered` +} ufbxi_buf; + +typedef struct { + ufbxi_buf_chunk *chunk; + size_t pos; + size_t num_items; +} ufbxi_buf_state; + +static ufbxi_noinline void *ufbxi_push_size_new_block(ufbxi_buf *b, size_t size) +{ + bool huge = size >= b->ator->huge_size; + + // Use the second chunk "list" for huge unordered chunks. + // The state of these chunks is not tracked by `ufbxi_buf.pos/size`. + uint32_t list_ix = ((uint32_t)b->unordered & (uint32_t)huge); + + ufbxi_buf_chunk *chunk = b->chunks[list_ix]; + if (chunk) { + if (list_ix == 0) { + // Store the final position for the retired chunk and scan free + // chunks in case we find one the allocation fits in. + b->pushed_size += b->pos; + chunk->pushed_pos = b->pos; + ufbxi_buf_chunk *next = chunk->next; + while (next != NULL) { + ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + chunk = next; + ufbx_assert(b->unordered || chunk->pushed_pos == 0); + chunk->pushed_pos = 0; + if (size <= chunk->size) { + b->chunks[0] = chunk; + b->pos = (uint32_t)size; + b->size = chunk->size; + return chunk->data; + } + next = chunk->next; + } + } else if (b->clearable) { + // Keep track of the `UFBXI_HUGE_MAX_SCAN` largest chunks and + // retain them. Overflowing chunks are freed in `ufbxi_buf_clear()` + size_t align_mask = ufbxi_size_align_mask(size); + ufbxi_buf_chunk *next = chunk; + + ufbxi_buf_chunk *best_chunk = NULL; + size_t best_space = SIZE_MAX; + + // Clearable huge chunks are sorted by descending size. Check the first N + // chunks for reuse and find the place a new block should be inserted if + // no suitable space is found. Chunk ordering in the tail doesn't matter + // as those chunks are never reused. + // Unreachable chunks in the tail are freed in `ufbxi_buf_clear()`. + for (size_t i = 0; next && i < UFBXI_HUGE_MAX_SCAN; i++) { + ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + if (next->size < size) break; + chunk = next; + + // Try to reuse chunks using a best-fit strategy. + size_t pos = ufbxi_align_to_mask(chunk->pushed_pos, align_mask); + size_t space = chunk->size - pos; + if (size <= space) { + if (space < best_space) { + best_chunk = chunk; + best_space = space; + } + } + + next = chunk->next; + } + + // Early return if we found a slot. + if (best_chunk) { + size_t pos = ufbxi_align_to_mask(best_chunk->pushed_pos, align_mask); + best_chunk->pushed_pos = pos + size; + b->pushed_size += size; + return best_chunk->data + pos; + } + } + } + + // Allocate a new chunk, grow `next_size` geometrically but don't double + // the current or previous user sizes if they are larger. + size_t chunk_size, next_size; + + // If `size` is larger than `huge_size` don't grow `next_size` geometrically, + // but use a dedicated allocation. + if (huge) { + next_size = chunk ? chunk->next_size : 4096; + if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max; + chunk_size = size; + } else { + next_size = chunk ? chunk->next_size * 2 : 4096; + if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max; + chunk_size = next_size - sizeof(ufbxi_buf_chunk); + if (chunk_size < size) chunk_size = size; + } + + // Align chunk sizes to 16 bytes + chunk_size = ufbxi_align_to_mask(chunk_size, 0xf); + + ufbxi_buf_chunk *new_chunk = (ufbxi_buf_chunk*)ufbxi_alloc_size(b->ator, 1, sizeof(ufbxi_buf_chunk) + chunk_size); + if (!new_chunk) return NULL; + + new_chunk->prev = chunk; + new_chunk->size = chunk_size; + new_chunk->next_size = next_size; + new_chunk->magic = UFBXI_BUF_CHUNK_IMP_MAGIC; + new_chunk->padding_pos = 0; + new_chunk->pushed_pos = 0; + + // Link the chunk to the list and set it as the active one + if (chunk) { + ufbxi_buf_chunk *next = chunk->next; + if (next) next->prev = new_chunk; + new_chunk->next = next; + chunk->next = new_chunk; + new_chunk->root = chunk->root; + } else { + new_chunk->next = NULL; + new_chunk->root = new_chunk; + } + + if (list_ix == 0) { + b->chunks[0] = new_chunk; + b->pos = size; + b->size = chunk_size; + } else { + ufbxi_buf_chunk *root = b->chunks[1]; + b->pushed_size += size; + if (!root) { + b->chunks[1] = new_chunk; + } else if (root->size < chunk_size) { + // Swap root and self if necessary, we should have bailed out + // in the search loop in the first iteration so `new_chunk` should + // directly follow `root`. + // HACK: This ends up with `chunks[1]` entries having inconsistent + // `ufbxi_buf_chunk.root` pointers but other code only reads `chunks[1].root` + // TODO: Move roots out of the chunks? + ufbx_assert(root->next == new_chunk); + ufbx_assert(new_chunk->prev == root); + if (new_chunk->next) new_chunk->next->prev = root; + root->next = new_chunk->next; + new_chunk->next = root; + new_chunk->prev = NULL; + new_chunk->root = new_chunk; + b->chunks[1] = new_chunk; + } + new_chunk->pushed_pos = size; + } + + return new_chunk->data; +} + +static ufbxi_noinline void *ufbxi_push_size(ufbxi_buf *b, size_t size, size_t n) +{ + // Always succeed with an empty non-NULL buffer for empty allocations + ufbx_assert(size > 0); + if (n == 0) return (void*)ufbxi_zero_size_buffer; + + size_t total = size * n; + if (ufbxi_does_overflow(total, size, n)) return NULL; + + #if defined(UFBX_REGRESSION) + { + ufbxi_allocator *ator = b->ator; + ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); + ator->num_allocs++; + } + #endif + + b->num_items += n; + + // Align to the natural alignment based on the size + size_t align_mask = ufbxi_size_align_mask(size); + size_t pos = ufbxi_align_to_mask(b->pos, align_mask); + + if (!b->unordered && pos != b->pos) { + // Alignment mismatch in an unordered block. Align to 16 bytes to guarantee + // sufficient alignment for anything afterwards and mark the padding. + // If we overflow the current block we don't need to care as the block + // boundaries are not contiguous. + pos = ufbxi_align_to_mask(b->pos, 0xf); + if (total < SIZE_MAX - 16 && total + 16 <= b->size - pos) { + ufbxi_buf_chunk *chunk = b->chunks[0]; + ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + pos); + padding->original_pos = b->pos; + padding->prev_padding = chunk->padding_pos; + chunk->padding_pos = pos + 16 + 1; + b->pos = pos + 16 + total; + return (char*)padding + 16; + } else { + return ufbxi_push_size_new_block(b, total); + } + } else { + // Try to push to the current block. Allocate a new block + // if the aligned size doesn't fit. + if (total <= b->size - pos) { + b->pos = pos + total; + return b->chunks[0]->data + pos; + } else { + return ufbxi_push_size_new_block(b, total); + } + } +} + +static ufbxi_forceinline void *ufbxi_push_size_fast(ufbxi_buf *b, size_t size, size_t n) +{ + // Always succeed with an empty non-NULL buffer for empty allocations + ufbxi_regression_assert(size > 0); + ufbxi_regression_assert(n > 0); + + size_t total = size * n; + ufbxi_regression_assert(!ufbxi_does_overflow(total, size, n)); + + #if defined(UFBX_REGRESSION) + { + ufbxi_allocator *ator = b->ator; + ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); + ator->num_allocs++; + } + #endif + + b->num_items += n; + + // Homogeneous arrays should always be aligned + size_t pos = b->pos; + ufbxi_regression_assert((pos & ufbxi_size_align_mask(size)) == 0); + + // Try to push to the current block. Allocate a new block + // if the aligned size doesn't fit. + if (total <= b->size - pos) { + b->pos = pos + total; + return b->chunks[0]->data + pos; + } else { + return ufbxi_push_size_new_block(b, total); + } +} + +static ufbxi_noinline void *ufbxi_push_size_zero(ufbxi_buf *b, size_t size, size_t n) +{ + void *ptr = ufbxi_push_size(b, size, n); + if (ptr) memset(ptr, 0, size * n); + return ptr; +} + +ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_size_copy(ufbxi_buf *b, size_t size, size_t n, const void *data) +{ + // Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL` + ufbx_assert(size > 0); + if (n == 0) return (void*)ufbxi_zero_size_buffer; + + ufbx_assert(data); + void *ptr = ufbxi_push_size(b, size, n); + if (ptr) memcpy(ptr, data, size * n); + return ptr; +} + +ufbxi_nodiscard static ufbxi_forceinline void *ufbxi_push_size_copy_fast(ufbxi_buf *b, size_t size, size_t n, const void *data) +{ + // Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL` + ufbx_assert(size > 0); + if (n == 0) return (void*)ufbxi_zero_size_buffer; + + ufbx_assert(data); + void *ptr = ufbxi_push_size_fast(b, size, n); + if (ptr) memcpy(ptr, data, size * n); + return ptr; +} + +static ufbxi_noinline void ufbxi_buf_free_unused(ufbxi_buf *b) +{ + ufbx_assert(!b->unordered); + + ufbxi_buf_chunk *chunk = b->chunks[0]; + if (!chunk) return; + + ufbxi_buf_chunk *next = chunk->next; + while (next) { + ufbxi_buf_chunk *to_free = next; + next = next->next; + ufbx_assert(to_free->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + to_free->magic = 0; + ufbxi_free_size(b->ator, 1, to_free, sizeof(ufbxi_buf_chunk) + to_free->size); + } + chunk->next = NULL; + + while (b->pos == 0 && chunk) { + ufbxi_buf_chunk *prev = chunk->prev; + ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + chunk->magic = 0; + ufbxi_free_size(b->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size); + chunk = prev; + b->chunks[0] = prev; + if (prev) { + prev->next = NULL; + b->pos = prev->pushed_pos; + b->size = prev->size; + } else { + b->pos = 0; + b->size = 0; + } + } +} + +static ufbxi_noinline void ufbxi_pop_size(ufbxi_buf *b, size_t size, size_t n, void *dst, bool peek) +{ + ufbx_assert(!b->unordered); + ufbx_assert(size > 0); + ufbx_assert(b->num_items >= n); + if (!peek) b->num_items -= n; + + char *ptr = (char*)dst; + size_t bytes_left = size * n; + + // We've already pushed this, it better not overflow + ufbx_assert(!ufbxi_does_overflow(bytes_left, size, n)); + + if (ptr) { + ptr += bytes_left; + size_t pos = b->pos; + ufbxi_buf_chunk *chunk = b->chunks[0]; + for (;;) { + if (bytes_left <= pos) { + // Rest of the data is in this single chunk + pos -= bytes_left; + if (!peek) b->pos = pos; + ptr -= bytes_left; + if (bytes_left > 0) { + memcpy(ptr, chunk->data + pos, bytes_left); + } + break; + } else { + // Pop the whole chunk + ptr -= pos; + bytes_left -= pos; + memcpy(ptr, chunk->data, pos); + if (!peek) { + chunk->pushed_pos = 0; + chunk = chunk->prev; + b->chunks[0] = chunk; + b->size = chunk->size; + } else { + chunk = chunk->prev; + } + pos = chunk->pushed_pos; + } + } + } else { + size_t pos = b->pos; + ufbxi_buf_chunk *chunk = b->chunks[0]; + for (;;) { + if (bytes_left <= pos) { + // Rest of the data is in this single chunk + pos -= bytes_left; + if (!peek) b->pos = pos; + break; + } else { + // Pop the whole chunk + bytes_left -= pos; + if (!peek) { + chunk->pushed_pos = 0; + chunk = chunk->prev; + b->chunks[0] = chunk; + b->size = chunk->size; + } else { + chunk = chunk->prev; + } + pos = chunk->pushed_pos; + } + } + } + + if (!peek) { + // Check if we need to rewind past some alignment padding + ufbxi_buf_chunk *chunk = b->chunks[0]; + if (chunk) { + size_t pos = b->pos, padding_pos = chunk->padding_pos; + if (pos < padding_pos) { + ufbx_assert(pos + 1 == padding_pos); + ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + padding_pos - 1 - 16); + b->pos = padding->original_pos; + chunk->padding_pos = padding->prev_padding; + } + } + + // Immediately free popped items if all the allocations are huge + // as it means we want to have dedicated allocations for each push. + if (b->ator->huge_size <= 1) { + ufbxi_buf_free_unused(b); + } + } +} + +static ufbxi_noinline void *ufbxi_push_pop_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n) +{ + void *data = ufbxi_push_size(dst, size, n); + if (!data) return NULL; + ufbxi_pop_size(src, size, n, data, false); + return data; +} + +static ufbxi_noinline void *ufbxi_push_peek_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n) +{ + void *data = ufbxi_push_size(dst, size, n); + if (!data) return NULL; + ufbxi_pop_size(src, size, n, data, true); + return data; +} + +static ufbxi_noinline void ufbxi_buf_free(ufbxi_buf *buf) +{ + ufbxi_nounroll for (size_t i = 0; i < 2; i++) { + ufbxi_buf_chunk *chunk = buf->chunks[i]; + if (chunk) { + chunk = chunk->root; + while (chunk) { + ufbxi_buf_chunk *next = chunk->next; + ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + chunk->magic = 0; + ufbxi_free_size(buf->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size); + chunk = next; + } + } + buf->chunks[i] = NULL; + } + buf->pos = 0; + buf->size = 0; + buf->num_items = 0; +} + +static ufbxi_noinline void ufbxi_buf_clear(ufbxi_buf *buf) +{ + // Only unordered or clearable buffers can be cleared + ufbx_assert(!buf->unordered || buf->clearable); + + // Free the memory if using ASAN + if (buf->ator->huge_size <= 1) { + ufbxi_buf_free(buf); + return; + } + + // Reset the non-huge chunks as `chunk->next` is always free. + ufbxi_buf_chunk *chunk = buf->chunks[0]; + if (chunk) { + ufbxi_buf_chunk *root = chunk->root; + buf->chunks[0] = root; + buf->pos = 0; + buf->size = root->size; + } + buf->num_items = 0; + buf->pushed_size = 0; + + // Huge chunks are always sorted by descending size and + // `chunks[1]` points to the largest one. + ufbxi_buf_chunk *huge = buf->chunks[1]; + if (huge) { + // Reset the first N ones that are tracked. + for (size_t i = 0; huge && i < UFBXI_HUGE_MAX_SCAN; i++) { + huge->pushed_pos = 0; + huge = huge->next; + } + + // Got unreachable tail that should be freed: Unlink from the last + // tracked chunk and free the rest. + if (huge) { + huge->prev->next = NULL; + while (huge) { + ufbxi_buf_chunk *next = huge->next; + ufbx_assert(huge->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); + huge->magic = 0; + ufbxi_free_size(buf->ator, 1, huge, sizeof(ufbxi_buf_chunk) + huge->size); + huge = next; + } + } + } +} + +#define ufbxi_push(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size((b), sizeof(type), (n))) +#define ufbxi_push_zero(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_zero((b), sizeof(type), (n))) +#define ufbxi_push_copy(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy((b), sizeof(type), (n), (data))) +#define ufbxi_push_copy_fast(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy_fast((b), sizeof(type), (n), (data))) +#define ufbxi_push_fast(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_fast((b), sizeof(type), (n))) +#define ufbxi_pop(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), false) +#define ufbxi_peek(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), true) +#define ufbxi_push_pop(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_pop_size((dst), (src), sizeof(type), (n))) +#define ufbxi_push_peek(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_peek_size((dst), (src), sizeof(type), (n))) + +// -- Hash map +// +// The actual element comparison is left to the user of `ufbxi_map`, see usage below. +// +// NOTES: +// ufbxi_map_insert() does not support duplicate values, use find first if duplicates are possible! +// Inserting duplicate elements fails with an assertion if `UFBX_REGRESSION` is enabled. + +typedef struct ufbxi_aa_node ufbxi_aa_node; + +typedef int ufbxi_cmp_fn(void *user, const void *a, const void *b); + +struct ufbxi_aa_node { + ufbxi_aa_node *left, *right; + uint32_t level; + uint32_t index; +}; + +typedef struct { + ufbxi_allocator *ator; + size_t data_size; + + void *items; + uint64_t *entries; + uint32_t mask; + + uint32_t capacity; + uint32_t size; + + ufbxi_cmp_fn *cmp_fn; + void *cmp_user; + + ufbxi_buf aa_buf; + ufbxi_aa_node *aa_root; + +} ufbxi_map; + +static ufbxi_noinline void ufbxi_map_init(ufbxi_map *map, ufbxi_allocator *ator, ufbxi_cmp_fn *cmp_fn, void *cmp_user) +{ + map->ator = ator; +#if defined(UFBX_REGRESSION) + // HACK: Maps contain pointers that are not stable between runs, in regression + // mode this causes instability in allocation patterns due to different AA trees + // being built, which is a problem in fuzz checks that need to have deterministic + // allocation counts. We can work around this using a local allocator that doesn't + // count the allocations. + { + ufbxi_allocator *regression_ator = (ufbxi_allocator*)ufbx_malloc(sizeof(ufbxi_allocator)); + ufbx_assert(regression_ator); + memset(regression_ator, 0, sizeof(ufbxi_allocator)); + regression_ator->name = "regression"; + regression_ator->error = ator->error; + regression_ator->huge_size = ator->huge_size; + regression_ator->max_size = SIZE_MAX; + regression_ator->max_allocs = SIZE_MAX; + regression_ator->chunk_max = 0x1000000; + map->aa_buf.ator = regression_ator; + } +#else + map->aa_buf.ator = ator; +#endif + map->cmp_fn = cmp_fn; + map->cmp_user = cmp_user; +} + +static ufbxi_noinline void ufbxi_map_free(ufbxi_map *map) +{ +#if defined(UFBX_REGRESSION) + ufbxi_allocator *regression_ator = map->aa_buf.ator; +#endif + + ufbxi_buf_free(&map->aa_buf); + ufbxi_free(map->ator, char, map->entries, map->data_size); + map->entries = NULL; + map->items = NULL; + map->aa_root = NULL; + map->mask = map->capacity = map->size = 0; + +#if defined(UFBX_REGRESSION) + if (regression_ator) { + ufbxi_free_ator(regression_ator); + ufbx_free(regression_ator, sizeof(ufbxi_allocator)); + } +#endif +} + +// Recursion limit: log2(2^64 / sizeof(ufbxi_aa_node)) +static ufbxi_noinline ufbxi_aa_node *ufbxi_aa_tree_insert(ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size) + ufbxi_recursive_function(ufbxi_aa_node *, ufbxi_aa_tree_insert, (map, node, value, index, item_size), 59, + (ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size)) +{ + if (!node) { + ufbxi_aa_node *new_node = ufbxi_push(&map->aa_buf, ufbxi_aa_node, 1); + if (!new_node) return NULL; + new_node->left = NULL; + new_node->right = NULL; + new_node->level = 1; + new_node->index = index; + return new_node; + } + + void *entry = (char*)map->items + node->index * item_size; + int cmp = map->cmp_fn(map->cmp_user, value, entry); + if (cmp < 0) { + node->left = ufbxi_aa_tree_insert(map, node->left, value, index, item_size); + } else if (cmp >= 0) { + node->right = ufbxi_aa_tree_insert(map, node->right, value, index, item_size); + } + + if (node->left && node->left->level == node->level) { + ufbxi_aa_node *left = node->left; + node->left = left->right; + left->right = node; + node = left; + } + + if (node->right && node->right->right && node->right->right->level == node->level) { + ufbxi_aa_node *right = node->right; + node->right = right->left; + right->left = node; + right->level += 1; + node = right; + } + + return node; +} + +static ufbxi_noinline void *ufbxi_aa_tree_find(ufbxi_map *map, const void *value, size_t item_size) +{ + ufbxi_aa_node *node = map->aa_root; + while (node) { + void *entry = (char*)map->items + node->index * item_size; + int cmp = map->cmp_fn(map->cmp_user, value, entry); + if (cmp < 0) { + node = node->left; + } else if (cmp > 0) { + node = node->right; + } else { + return entry; + } + } + return NULL; +} + +static ufbxi_noinline bool ufbxi_map_grow_size_imp(ufbxi_map *map, size_t item_size, size_t min_size) +{ + ufbx_assert(min_size > 0); + const double load_factor = 0.7; + + // Find the lowest power of two size that fits `min_size` within `load_factor` + size_t num_entries = map->mask + 1; + size_t new_size = (size_t)((double)num_entries * load_factor); + if (min_size < map->capacity + 1) min_size = map->capacity + 1; + while (new_size < min_size) { + num_entries *= 2; + new_size = (size_t)((double)num_entries * load_factor); + } + + // Check for overflow + ufbxi_check_return_err(map->ator->error, SIZE_MAX / num_entries > sizeof(uint64_t), false); + size_t alloc_size = num_entries * sizeof(uint64_t); + + // Allocate a combined entry/item memory block + ufbxi_check_return_err(map->ator->error, (SIZE_MAX - alloc_size) / new_size > item_size, false); + size_t data_size = alloc_size + new_size * item_size; + + char *data = ufbxi_alloc(map->ator, char, data_size); + ufbxi_check_return_err(map->ator->error, data, false); + + // Copy the previous user items over + uint64_t *old_entries = map->entries; + uint64_t *new_entries = (uint64_t*)data; + void *new_items = data + alloc_size; + if (map->size > 0) { + memcpy(new_items, map->items, item_size * map->size); + } + + // Re-hash the entries + uint32_t old_mask = map->mask; + uint32_t new_mask = (uint32_t)(num_entries) - 1; + memset(new_entries, 0, sizeof(uint64_t) * num_entries); + if (old_mask) { + for (uint32_t i = 0; i <= old_mask; i++) { + uint64_t entry, new_entry = old_entries[i]; + if (!new_entry) continue; + + // Reconstruct the hash of the old entry at `i` + uint32_t old_scan = (uint32_t)(new_entry & old_mask) - 1; + uint32_t hash = ((uint32_t)new_entry & ~old_mask) | ((i - old_scan) & old_mask); + uint32_t slot = hash & new_mask; + new_entry &= ~(uint64_t)new_mask; + + // Scan forward until we find an empty slot, potentially swapping + // `new_element` if it has a shorter scan distance (Robin Hood). + uint32_t scan = 1; + while ((entry = new_entries[slot]) != 0) { + uint32_t entry_scan = (uint32_t)(entry & new_mask); + if (entry_scan < scan) { + new_entries[slot] = new_entry + scan; + new_entry = (entry & ~(uint64_t)new_mask); + scan = entry_scan; + } + scan += 1; + slot = (slot + 1) & new_mask; + } + new_entries[slot] = new_entry + scan; + } + } + + // And finally free the previous allocation + ufbxi_free(map->ator, char, (char*)old_entries, map->data_size); + map->items = new_items; + map->data_size = data_size; + map->entries = new_entries; + map->mask = new_mask; + map->capacity = (uint32_t)new_size; + + return true; +} + +static ufbxi_forceinline bool ufbxi_map_grow_size(ufbxi_map *map, size_t size, size_t min_size) +{ + #if defined(UFBX_REGRESSION) + { + ufbxi_allocator *ator = map->ator; + ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded"); + ator->num_allocs++; + } + #endif + + if (map->size < map->capacity && map->capacity >= min_size) return true; + return ufbxi_map_grow_size_imp(map, size, min_size); +} + +static ufbxi_noinline void *ufbxi_map_find_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value) +{ + uint64_t *entries = map->entries; + uint32_t mask = map->mask, scan = 0; + + uint32_t ref = hash & ~mask; + if (!mask || scan == UINT32_MAX) return 0; + + // Scan entries until we find an exact match of the hash or until we hit + // an element that has lower scan distance than our search (Robin Hood). + // The encoding guarantees that zero slots also terminate with the same test. + for (;;) { + uint64_t entry = entries[(hash + scan) & mask]; + scan += 1; + if ((uint32_t)entry == ref + scan) { + uint32_t index = (uint32_t)(entry >> 32u); + void *data = (char*)map->items + size * index; + int cmp = map->cmp_fn(map->cmp_user, value, data); + if (cmp == 0) return data; + } else if ((entry & mask) < scan) { + if (map->aa_root) { + return ufbxi_aa_tree_find(map, value, size); + } else { + return NULL; + } + } + } +} + +static ufbxi_noinline void *ufbxi_map_insert_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value) +{ + if (!ufbxi_map_grow_size(map, size, 64)) return NULL; + + ufbxi_regression_assert(ufbxi_map_find_size(map, size, hash, value) == NULL); + + uint32_t index = map->size++; + + uint64_t *entries = map->entries; + uint32_t mask = map->mask; + + // Scan forward until we find an empty slot, potentially swapping + // `new_element` if it has a shorter scan distance (Robin Hood). + uint32_t slot = hash & mask; + uint64_t entry, new_entry = (uint64_t)index << 32u | (hash & ~mask); + uint32_t scan = 1; + while ((entry = entries[slot]) != 0) { + uint32_t entry_scan = (uint32_t)(entry & mask); + if (entry_scan < scan) { + entries[slot] = new_entry + scan; + new_entry = (entry & ~(uint64_t)mask); + scan = entry_scan; + } + scan += 1; + slot = (slot + 1) & mask; + + if (scan > UFBXI_MAP_MAX_SCAN) { + uint32_t new_index = (uint32_t)(new_entry >> 32u); + const void *new_value = new_index == index ? value : (const void*)((char*)map->items + size * new_index); + map->aa_root = ufbxi_aa_tree_insert(map, map->aa_root, new_value, new_index, size); + return (char*)map->items + size * index; + } + } + entries[slot] = new_entry + scan; + + return (char*)map->items + size * index; +} + +#define ufbxi_map_grow(map, type, min_size) ufbxi_map_grow_size((map), sizeof(type), (min_size)) +#define ufbxi_map_find(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_find_size((map), sizeof(type), (hash), (value))) +#define ufbxi_map_insert(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_insert_size((map), sizeof(type), (hash), (value))) + +static int ufbxi_map_cmp_uint64(void *user, const void *va, const void *vb) +{ + (void)user; + uint64_t a = *(const uint64_t*)va, b = *(const uint64_t*)vb; + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +static int ufbxi_map_cmp_const_char_ptr(void *user, const void *va, const void *vb) +{ + (void)user; + const char *a = *(const char **)va, *b = *(const char **)vb; + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +static int ufbxi_map_cmp_uintptr(void *user, const void *va, const void *vb) +{ + (void)user; + uintptr_t a = *(const uintptr_t*)va, b = *(const uintptr_t*)vb; + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +typedef struct { + uintptr_t ptr; + uint64_t id; +} ufbxi_ptr_id; + +static int ufbxi_map_cmp_ptr_id(void *user, const void *va, const void *vb) +{ + (void)user; + ufbxi_ptr_id a = *(const ufbxi_ptr_id*)va, b = *(const ufbxi_ptr_id*)vb; + if (a.id != b.id) return a.id < b.id ? -1 : +1; + if (a.ptr != b.ptr) return a.ptr < b.ptr ? -1 : +1; + return 0; +} + +// -- Hash functions + +static ufbxi_noinline uint32_t ufbxi_hash_string(const char *str, size_t length) +{ + uint32_t hash = (uint32_t)length; + uint32_t seed = UINT32_C(0x9e3779b9); + if (length >= 4) { + do { + uint32_t word = ufbxi_read_u32(str); + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + str += 4; + length -= 4; + } while (length >= 4); + + uint32_t word = ufbxi_read_u32(str + length - 4); + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + } else { + uint32_t word = 0; + if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0; + if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8; + if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16; + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + } + hash ^= hash >> 16; + hash *= UINT32_C(0x7feb352d); + hash ^= hash >> 15; + return hash; +} + +// NOTE: _Must_ match `ufbxi_hash_string()` +static ufbxi_noinline uint32_t ufbxi_hash_string_check_ascii(const char *str, size_t length, bool *p_non_ascii) +{ + uint32_t ascii_mask = 0; + uint32_t zero_mask = 0; + + ufbx_assert(length > 0); + + uint32_t hash = (uint32_t)length; + uint32_t seed = UINT32_C(0x9e3779b9); + if (length >= 4) { + do { + uint32_t word = ufbxi_read_u32(str); + ascii_mask |= word; + zero_mask |= UINT32_C(0x80808080) - word; + + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + str += 4; + length -= 4; + } while (length >= 4); + + uint32_t word = ufbxi_read_u32(str + length - 4); + ascii_mask |= word; + zero_mask |= UINT32_C(0x80808080) - word; + + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + } else { + uint32_t word = 0; + if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0; + if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8; + if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16; + + ascii_mask |= word; + zero_mask |= (UINT32_C(0x80808080) >> ((4u - length) * 8u)) - word; + + hash = ((hash << 5u | hash >> 27u) ^ word) * seed; + } + + // If any character has high bit set or is zero we're not ASCII + if (((ascii_mask | zero_mask) & 0x80808080u) != 0) { + *p_non_ascii = true; + } + + hash ^= hash >> 16; + hash *= UINT32_C(0x7feb352d); + hash ^= hash >> 15; + + return hash; +} + +static ufbxi_unused ufbxi_forceinline uint32_t ufbxi_hash32(uint32_t x) +{ + x ^= x >> 16; + x *= UINT32_C(0x7feb352d); + x ^= x >> 15; + x *= UINT32_C(0x846ca68b); + x ^= x >> 16; + return x; +} + +static ufbxi_unused ufbxi_forceinline uint32_t ufbxi_hash64(uint64_t x) +{ + x ^= x >> 32; + x *= UINT64_C(0xd6e8feb86659fd93); + x ^= x >> 32; + x *= UINT64_C(0xd6e8feb86659fd93); + x ^= x >> 32; + return (uint32_t)x; +} + +static ufbxi_forceinline uint32_t ufbxi_hash_uptr(uintptr_t ptr) +{ +#if UFBX_POINTER_SIZE == 8 + return ufbxi_hash64((uint64_t)ptr); +#elif UFBX_POINTER_SIZE == 4 + return ufbxi_hash32((uint32_t)ptr); +#else + if (sizeof(ptr) == 8) return ufbxi_hash64((uint64_t)ptr); + else if (sizeof(ptr) == 4) return ufbxi_hash32((uint32_t)ptr); + else return ufbxi_hash_string((const char*)&ptr, sizeof(uintptr_t)); +#endif +} + +static ufbxi_forceinline uint32_t ufbxi_hash_ptr_id(ufbxi_ptr_id id) +{ + // Trivial reduction is fine: Only `ptr` or `id` is defined. + return ufbxi_hash_uptr(id.ptr) ^ ufbxi_hash64(id.id); +} + +#define ufbxi_hash_ptr(ptr) ufbxi_hash_uptr((uintptr_t)(ptr)) + +// -- Warnings + +typedef struct { + ufbx_error *error; + ufbxi_buf *result; + ufbxi_buf tmp_stack; + uint32_t deferred_element_id_plus_one; + // Separate lists for specific and non-specific warnings + ufbx_warning *prev_warnings[UFBX_WARNING_TYPE_COUNT][2]; +} ufbxi_warnings; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_vwarnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, va_list args) +{ + if (!ws) return 1; + + // HACK(warning-element): Encode potential deferred element ID into `ufbx_warning.element_id`, + // `ws->element_id_index_plus_one` contains index to `uc->tmp_element_id`. + // Tag deferred indices with the high bit. + if (element_id == ~0u && ws->deferred_element_id_plus_one > 0) { + element_id = (ws->deferred_element_id_plus_one - 1) | 0x80000000u; + } + + uint32_t has_element_id = element_id != ~0u; + if (type >= UFBX_WARNING_TYPE_FIRST_DEDUPLICATED) { + ufbx_warning *prev = ws->prev_warnings[type][has_element_id]; + if (prev && prev->element_id == element_id) { + prev->count++; + return 1; + } + } + + char desc[256]; + size_t desc_len = (size_t)ufbxi_vsnprintf(desc, sizeof(desc), fmt, args); + + ufbxi_clean_string_utf8(desc, desc_len); + + char *desc_copy = ufbxi_push_copy(ws->result, char, desc_len + 1, desc); + ufbxi_check_err(ws->error, desc_copy); + + ufbx_warning *warning = ufbxi_push(&ws->tmp_stack, ufbx_warning, 1); + ufbxi_check_err(ws->error, warning); + + warning->type = type; + warning->description.data = desc_copy; + warning->description.length = desc_len; + warning->element_id = element_id; + warning->count = 1; + ws->prev_warnings[type][has_element_id] = warning; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_warnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, ...) +{ + // NOTE: `ws` may be `NULL` here, handled by `ufbxi_vwarnf()` + va_list args; // ufbxi_uninit + va_start(args, fmt); + int ok = ufbxi_vwarnf_imp(ws, type, element_id, fmt, args); + va_end(args); + return ok; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_pop_warnings(ufbxi_warnings *ws, ufbx_warning_list *warnings, bool *p_has_warning) +{ + warnings->count = ws->tmp_stack.num_items; + warnings->data = ufbxi_push_pop(ws->result, &ws->tmp_stack, ufbx_warning, warnings->count); + ufbxi_check_err(ws->error, warnings->data); + ufbxi_for_list(ufbx_warning, warning, *warnings) { + p_has_warning[warning->type] = true; + } + return 1; +} + +// -- String pool + +// All strings found in FBX files are interned for deduplication and fast +// comparison. Our fixed internal strings (`ufbxi_String`) are considered the +// canonical pointers for said strings so we can compare them by address. + +typedef struct { + ufbx_error *error; + ufbxi_buf buf; // < Buffer for the actual string data + ufbxi_map map; // < Map of `ufbxi_string` + size_t initial_size; // < Number of initial entries + char *temp_str; // < Temporary string buffer of `temp_cap` + size_t temp_cap; // < Capacity of the temporary buffer + ufbx_unicode_error_handling error_handling; + ufbxi_warnings *warnings; +} ufbxi_string_pool; + +typedef struct { + const char *raw_data; // < UTF-8 data follows at `raw_length+1` if `utf8_length > 0` + uint32_t raw_length; // < Length of the non-sanitized original string + uint32_t utf8_length; // < Length of sanitized UTF-8 string (or zero) +} ufbxi_sanitized_string; + +static ufbxi_forceinline bool ufbxi_str_equal(ufbx_string a, ufbx_string b) +{ + return a.length == b.length && !memcmp(a.data, b.data, a.length); +} + +static ufbxi_forceinline bool ufbxi_str_less(ufbx_string a, ufbx_string b) +{ + size_t len = ufbxi_min_sz(a.length, b.length); + int cmp = memcmp(a.data, b.data, len); + if (cmp != 0) return cmp < 0; + return a.length < b.length; +} + +static ufbxi_forceinline int ufbxi_str_cmp(ufbx_string a, ufbx_string b) +{ + size_t len = ufbxi_min_sz(a.length, b.length); + int cmp = memcmp(a.data, b.data, len); + if (cmp != 0) return cmp; + if (a.length != b.length) return a.length < b.length ? -1 : 1; + return 0; +} + +static ufbxi_forceinline ufbx_string ufbxi_str_c(const char *str) +{ + ufbx_string s = { str, strlen(str) }; + return s; +} + +static ufbxi_noinline uint32_t ufbxi_get_concat_key(const ufbx_string *parts, size_t num_parts) +{ + uint32_t key = 0, shift = 32; + ufbxi_for(const ufbx_string, part, parts, num_parts) { + size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data); + for (size_t i = 0; i < length; i++) { + shift -= 8; + key |= (uint32_t)(uint8_t)part->data[i] << shift; + if (shift == 0) return key; + } + } + return key; +} + +static ufbxi_noinline int ufbxi_concat_str_cmp(const ufbx_string *ref, const ufbx_string *parts, size_t num_parts) +{ + const char *ptr = ref->data, *end = ptr + ref->length; + ufbxi_for(const ufbx_string, part, parts, num_parts) { + size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data); + size_t to_cmp = ufbxi_min_sz(ufbxi_to_size(end - ptr), length); + int cmp = to_cmp > 0 ? memcmp(ptr, part->data, to_cmp) : 0; + if (cmp != 0) return cmp; + if (to_cmp != length) return -1; + ptr += length; + } + return ptr == end ? 0 : +1; +} + +static ufbxi_forceinline bool ufbxi_starts_with(ufbx_string str, ufbx_string prefix) +{ + return str.length >= prefix.length && !memcmp(str.data, prefix.data, prefix.length); +} + +static ufbxi_forceinline bool ufbxi_ends_with(ufbx_string str, ufbx_string suffix) +{ + return str.length >= suffix.length && !memcmp(str.data + str.length - suffix.length, suffix.data, suffix.length); +} + +static ufbxi_noinline bool ufbxi_remove_prefix_len(ufbx_string *str, const char *prefix, size_t prefix_len) +{ + ufbx_string prefix_str = { prefix, prefix_len }; + if (ufbxi_starts_with(*str, prefix_str)) { + str->data += prefix_len; + str->length -= prefix_len; + return true; + } + return false; +} + +static ufbxi_noinline bool ufbxi_remove_suffix_len(ufbx_string *str, const char *suffix, size_t suffix_len) +{ + ufbx_string suffix_str = { suffix, suffix_len }; + if (ufbxi_ends_with(*str, suffix_str)) { + str->length -= suffix_len; + return true; + } + return false; +} + +static ufbxi_forceinline bool ufbxi_remove_prefix_str(ufbx_string *str, ufbx_string prefix) +{ + return ufbxi_remove_prefix_len(str, prefix.data, prefix.length); +} + +static ufbxi_forceinline bool ufbxi_remove_suffix_c(ufbx_string *str, const char *suffix) +{ + return ufbxi_remove_suffix_len(str, suffix, strlen(suffix)); +} + +static int ufbxi_map_cmp_string(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_string *a = (const ufbx_string*)va, *b = (const ufbx_string*)vb; + return ufbxi_str_cmp(*a, *b); +} + +static ufbxi_forceinline ufbx_string ufbxi_safe_string(const char *data, size_t length) +{ + ufbx_string str = { length > 0 ? data : ufbxi_empty_char, length }; + return str; +} + +static void ufbxi_string_pool_temp_free(ufbxi_string_pool *pool) +{ + ufbxi_free(pool->map.ator, char, pool->temp_str, pool->temp_cap); + ufbxi_map_free(&pool->map); +} + +ufbxi_nodiscard static size_t ufbxi_add_replacement_char(ufbxi_string_pool *pool, char *dst, char c) +{ + switch (pool->error_handling) { + + case UFBX_UNICODE_ERROR_HANDLING_REPLACEMENT_CHARACTER: + dst[0] = (char)(uint8_t)0xefu; + dst[1] = (char)(uint8_t)0xbfu; + dst[2] = (char)(uint8_t)0xbdu; + return 3; + + case UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE: + dst[0] = '_'; + return 1; + + case UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK: + dst[0] = '?'; + return 1; + + case UFBX_UNICODE_ERROR_HANDLING_REMOVE: + return 0; + + case UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE: + dst[0] = c; + return 1; + + default: + return 0; + + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_sanitize_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, size_t valid_length, bool push_both) +{ + // Handle only invalid cases here + ufbx_assert(valid_length < length); + ufbxi_check_err_msg(pool->error, pool->error_handling != UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING, "Invalid UTF-8"); + ufbxi_check_err(pool->error, ufbxi_warnf_imp(pool->warnings, UFBX_WARNING_BAD_UNICODE, ~0u, "Bad UTF-8 string")); + + size_t index = valid_length; + size_t dst_len = index; + if (push_both) { + // Copy both the full raw string and the initial valid part + ufbxi_check_err(pool->error, length <= SIZE_MAX / 2 - 64); + ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length * 2 + 64)); + memcpy(pool->temp_str, str, length); + pool->temp_str[length] = '\0'; + memcpy(pool->temp_str + length + 1, str, index); + dst_len += length + 1; + } else { + + // Copy the initial valid part + ufbxi_check_err(pool->error, length <= SIZE_MAX - 64); + ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length + 64)); + memcpy(pool->temp_str, str, index); + } + + char *dst = pool->temp_str; + while (index < length) { + uint8_t c = (uint8_t)str[index]; + size_t left = length - index; + + // Not optimal but not the worst thing ever + if (pool->temp_cap - dst_len < 16) { + ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, dst_len + 16)); + dst = pool->temp_str; + } + + if ((c & 0x80) == 0) { + if (c != 0) { + dst[dst_len] = (char)c; + dst_len += 1; + index += 1; + continue; + } + } else if ((c & 0xe0) == 0xc0 && left >= 2) { + uint8_t t0 = (uint8_t)str[index + 1]; + uint32_t code = (uint32_t)c << 8 | (uint32_t)t0 << 0; + if ((code & 0xc0) == 0x80 && code >= 0xc280) { + dst[dst_len + 0] = (char)c; + dst[dst_len + 1] = (char)t0; + dst_len += 2; + index += 2; + continue; + } + } else if ((c & 0xf0) == 0xe0 && left >= 3) { + uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2]; + uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1; + if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) { + dst[dst_len + 0] = (char)c; + dst[dst_len + 1] = (char)t0; + dst[dst_len + 2] = (char)t1; + dst_len += 3; + index += 3; + continue; + } + } else if ((c & 0xf8) == 0xf0 && left >= 4) { + uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3]; + uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2; + if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) { + dst[dst_len + 0] = (char)c; + dst[dst_len + 1] = (char)t0; + dst[dst_len + 2] = (char)t1; + dst[dst_len + 3] = (char)t2; + dst_len += 4; + index += 4; + continue; + } + } + + dst_len += ufbxi_add_replacement_char(pool, dst + dst_len, (char)c); + index++; + } + + // Sanitized strings are packed to 32-bit integers, in practice this should be fine + // as strings are limited to 32-bit length in FBX itself. + // The only problem case is a massive string that is full of unicode errors, ie. + // >1GB binary blob, but these should never be sanitized. + ufbxi_check_err(pool->error, length <= UINT32_MAX); + sanitized->raw_data = pool->temp_str; + if (push_both) { + // Reserve `UINT32_MAX` for invalid UTF-8 without sanitization + size_t utf8_length = dst_len - (length + 1); + ufbxi_check_err(pool->error, utf8_length < UINT32_MAX); + sanitized->raw_length = (uint32_t)length; + sanitized->utf8_length = (uint32_t)utf8_length; + } else { + ufbxi_check_err(pool->error, dst_len <= UINT32_MAX); + sanitized->raw_length = (uint32_t)dst_len; + sanitized->utf8_length = 0; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_sanitized_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, uint32_t hash, bool raw) +{ + ufbxi_regression_assert(hash == ufbxi_hash_string(str, length)); + + ufbxi_check_err(pool->error, length <= UINT32_MAX); + ufbxi_check_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size)); + + const char *total_data = str; + size_t total_length = length; + + sanitized->raw_length = (uint32_t)length; + sanitized->utf8_length = 0; + + if (!raw) { + size_t valid_length = ufbxi_utf8_valid_length(str, length); + if (valid_length != length) { + ufbxi_check_err(pool->error, ufbxi_sanitize_string(pool, sanitized, str, length, valid_length, true)); + total_data = sanitized->raw_data; + total_length = sanitized->raw_length + sanitized->utf8_length + 1; + hash = ufbxi_hash_string(str, length); + } + } + + ufbx_string ref = { total_data, total_length }; + + ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref); + if (entry) { + sanitized->raw_data = entry->data; + } else { + entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref); + ufbxi_check_err(pool->error, entry); + entry->length = total_length; + char *dst = ufbxi_push(&pool->buf, char, total_length + 1); + ufbxi_check_err(pool->error, dst); + memcpy(dst, total_data, total_length); + dst[total_length] = '\0'; + entry->data = dst; + sanitized->raw_data = dst; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_push_string_imp(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool copy, bool raw) +{ + if (length == 0) return ufbxi_empty_char; + + ufbxi_check_return_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size), NULL); + + uint32_t hash; + if (raw) { + hash = ufbxi_hash_string(str, length); + } else { + bool non_ascii = false; + hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); + if (non_ascii) { + size_t valid_length = ufbxi_utf8_valid_length(str, length); + if (valid_length < length) { + ufbxi_sanitized_string sanitized; + ufbxi_check_return_err(pool->error, ufbxi_sanitize_string(pool, &sanitized, str, length, valid_length, false), NULL); + str = sanitized.raw_data; + length = sanitized.raw_length; + hash = ufbxi_hash_string(str, length); + *p_out_length = length; + } + } + } + + ufbx_string ref = { str, length }; + + ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref); + if (entry) return entry->data; + entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref); + ufbxi_check_return_err(pool->error, entry, NULL); + entry->length = length; + if (copy) { + char *dst = ufbxi_push(&pool->buf, char, length + 1); + ufbxi_check_return_err(pool->error, dst, NULL); + memcpy(dst, str, length); + dst[length] = '\0'; + entry->data = dst; + } else { + entry->data = str; + } + return entry->data; +} + +ufbxi_nodiscard static ufbxi_forceinline const char *ufbxi_push_string(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool raw) +{ + return ufbxi_push_string_imp(pool, str, length, p_out_length, true, raw); +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_push_string_place(ufbxi_string_pool *pool, const char **p_str, size_t *p_length, bool raw) +{ + const char *str = *p_str; + size_t length = *p_length; + ufbxi_check_err(pool->error, str || length == 0); + str = ufbxi_push_string(pool, str, length, p_length, raw); + ufbxi_check_err(pool->error, str); + *p_str = str; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_str(ufbxi_string_pool *pool, ufbx_string *p_str, bool raw) +{ + ufbxi_check_err(pool->error, p_str); + return ufbxi_push_string_place(pool, &p_str->data, &p_str->length, raw); +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_blob(ufbxi_string_pool *pool, ufbx_blob *p_blob, bool raw) +{ + if (p_blob->size == 0) { + p_blob->data = NULL; + return 1; + } + p_blob->data = ufbxi_push_string(pool, (const char*)p_blob->data, p_blob->size, &p_blob->size, raw); + ufbxi_check_err(pool->error, p_blob->data); + return 1; +} + +// -- String constants +// +// All strings in FBX files are pooled so by having canonical string constant +// addresses we can compare strings to these constants by comparing pointers. +// Keep the list alphabetically sorted! + +static const char ufbxi_AllSame[] = "AllSame"; +static const char ufbxi_Alphas[] = "Alphas"; +static const char ufbxi_AmbientColor[] = "AmbientColor"; +static const char ufbxi_AnimationCurveNode[] = "AnimationCurveNode"; +static const char ufbxi_AnimationCurve[] = "AnimationCurve"; +static const char ufbxi_AnimationLayer[] = "AnimationLayer"; +static const char ufbxi_AnimationStack[] = "AnimationStack"; +static const char ufbxi_ApertureFormat[] = "ApertureFormat"; +static const char ufbxi_ApertureMode[] = "ApertureMode"; +static const char ufbxi_AreaLightShape[] = "AreaLightShape"; +static const char ufbxi_AspectH[] = "AspectH"; +static const char ufbxi_AspectHeight[] = "AspectHeight"; +static const char ufbxi_AspectRatioMode[] = "AspectRatioMode"; +static const char ufbxi_AspectW[] = "AspectW"; +static const char ufbxi_AspectWidth[] = "AspectWidth"; +static const char ufbxi_Audio[] = "Audio"; +static const char ufbxi_AudioLayer[] = "AudioLayer"; +static const char ufbxi_BaseLayer[] = "BaseLayer"; +static const char ufbxi_BinaryData[] = "BinaryData"; +static const char ufbxi_BindPose[] = "BindPose"; +static const char ufbxi_BindingTable[] = "BindingTable"; +static const char ufbxi_Binormals[] = "Binormals"; +static const char ufbxi_BinormalsIndex[] = "BinormalsIndex"; +static const char ufbxi_BinormalsW[] = "BinormalsW"; +static const char ufbxi_BlendMode[] = "BlendMode"; +static const char ufbxi_BlendModes[] = "BlendModes"; +static const char ufbxi_BlendShapeChannel[] = "BlendShapeChannel"; +static const char ufbxi_BlendShape[] = "BlendShape"; +static const char ufbxi_BlendWeights[] = "BlendWeights"; +static const char ufbxi_BoundaryRule[] = "BoundaryRule"; +static const char ufbxi_Boundary[] = "Boundary"; +static const char ufbxi_ByEdge[] = "ByEdge"; +static const char ufbxi_ByPolygonVertex[] = "ByPolygonVertex"; +static const char ufbxi_ByPolygon[] = "ByPolygon"; +static const char ufbxi_ByVertex[] = "ByVertex"; +static const char ufbxi_ByVertice[] = "ByVertice"; +static const char ufbxi_Cache[] = "Cache"; +static const char ufbxi_CameraProjectionType[] = "CameraProjectionType"; +static const char ufbxi_CameraStereo[] = "CameraStereo"; +static const char ufbxi_CameraSwitcher[] = "CameraSwitcher"; +static const char ufbxi_Camera[] = "Camera"; +static const char ufbxi_CastLight[] = "CastLight"; +static const char ufbxi_CastShadows[] = "CastShadows"; +static const char ufbxi_Channel[] = "Channel"; +static const char ufbxi_Character[] = "Character"; +static const char ufbxi_Children[] = "Children"; +static const char ufbxi_Cluster[] = "Cluster"; +static const char ufbxi_CollectionExclusive[] = "CollectionExclusive"; +static const char ufbxi_Collection[] = "Collection"; +static const char ufbxi_ColorIndex[] = "ColorIndex"; +static const char ufbxi_Color[] = "Color"; +static const char ufbxi_Colors[] = "Colors"; +static const char ufbxi_Cone_angle[] = "Cone angle"; +static const char ufbxi_ConeAngle[] = "ConeAngle"; +static const char ufbxi_Connections[] = "Connections"; +static const char ufbxi_Constraint[] = "Constraint"; +static const char ufbxi_Content[] = "Content"; +static const char ufbxi_CoordAxisSign[] = "CoordAxisSign"; +static const char ufbxi_CoordAxis[] = "CoordAxis"; +static const char ufbxi_Count[] = "Count"; +static const char ufbxi_Creator[] = "Creator"; +static const char ufbxi_CurrentTextureBlendMode[] = "CurrentTextureBlendMode"; +static const char ufbxi_CurrentTimeMarker[] = "CurrentTimeMarker"; +static const char ufbxi_CustomFrameRate[] = "CustomFrameRate"; +static const char ufbxi_DecayType[] = "DecayType"; +static const char ufbxi_DefaultCamera[] = "DefaultCamera"; +static const char ufbxi_Default[] = "Default"; +static const char ufbxi_Definitions[] = "Definitions"; +static const char ufbxi_DeformPercent[] = "DeformPercent"; +static const char ufbxi_Deformer[] = "Deformer"; +static const char ufbxi_DiffuseColor[] = "DiffuseColor"; +static const char ufbxi_Dimension[] = "Dimension"; +static const char ufbxi_Dimensions[] = "Dimensions"; +static const char ufbxi_DisplayLayer[] = "DisplayLayer"; +static const char ufbxi_Document[] = "Document"; +static const char ufbxi_Documents[] = "Documents"; +static const char ufbxi_EdgeCrease[] = "EdgeCrease"; +static const char ufbxi_EdgeIndexArray[] = "EdgeIndexArray"; +static const char ufbxi_Edges[] = "Edges"; +static const char ufbxi_EmissiveColor[] = "EmissiveColor"; +static const char ufbxi_Entry[] = "Entry"; +static const char ufbxi_FBXHeaderExtension[] = "FBXHeaderExtension"; +static const char ufbxi_FBXHeaderVersion[] = "FBXHeaderVersion"; +static const char ufbxi_FBXVersion[] = "FBXVersion"; +static const char ufbxi_FKEffector[] = "FKEffector"; +static const char ufbxi_FarPlane[] = "FarPlane"; +static const char ufbxi_FbxPropertyEntry[] = "FbxPropertyEntry"; +static const char ufbxi_FbxSemanticEntry[] = "FbxSemanticEntry"; +static const char ufbxi_FieldOfViewX[] = "FieldOfViewX"; +static const char ufbxi_FieldOfViewY[] = "FieldOfViewY"; +static const char ufbxi_FieldOfView[] = "FieldOfView"; +static const char ufbxi_FileName[] = "FileName"; +static const char ufbxi_Filename[] = "Filename"; +static const char ufbxi_FilmHeight[] = "FilmHeight"; +static const char ufbxi_FilmSqueezeRatio[] = "FilmSqueezeRatio"; +static const char ufbxi_FilmWidth[] = "FilmWidth"; +static const char ufbxi_FlipNormals[] = "FlipNormals"; +static const char ufbxi_FocalLength[] = "FocalLength"; +static const char ufbxi_Form[] = "Form"; +static const char ufbxi_Freeze[] = "Freeze"; +static const char ufbxi_FrontAxisSign[] = "FrontAxisSign"; +static const char ufbxi_FrontAxis[] = "FrontAxis"; +static const char ufbxi_FullWeights[] = "FullWeights"; +static const char ufbxi_GateFit[] = "GateFit"; +static const char ufbxi_GeometricRotation[] = "GeometricRotation"; +static const char ufbxi_GeometricScaling[] = "GeometricScaling"; +static const char ufbxi_GeometricTranslation[] = "GeometricTranslation"; +static const char ufbxi_GeometryUVInfo[] = "GeometryUVInfo"; +static const char ufbxi_Geometry[] = "Geometry"; +static const char ufbxi_GlobalSettings[] = "GlobalSettings"; +static const char ufbxi_Hole[] = "Hole"; +static const char ufbxi_HotSpot[] = "HotSpot"; +static const char ufbxi_IKEffector[] = "IKEffector"; +static const char ufbxi_ImageData[] = "ImageData"; +static const char ufbxi_Implementation[] = "Implementation"; +static const char ufbxi_Indexes[] = "Indexes"; +static const char ufbxi_InheritType[] = "InheritType"; +static const char ufbxi_InnerAngle[] = "InnerAngle"; +static const char ufbxi_Intensity[] = "Intensity"; +static const char ufbxi_IsTheNodeInSet[] = "IsTheNodeInSet"; +static const char ufbxi_KeyAttrDataFloat[] = "KeyAttrDataFloat"; +static const char ufbxi_KeyAttrFlags[] = "KeyAttrFlags"; +static const char ufbxi_KeyAttrRefCount[] = "KeyAttrRefCount"; +static const char ufbxi_KeyCount[] = "KeyCount"; +static const char ufbxi_KeyTime[] = "KeyTime"; +static const char ufbxi_KeyValueFloat[] = "KeyValueFloat"; +static const char ufbxi_Key[] = "Key"; +static const char ufbxi_KnotVectorU[] = "KnotVectorU"; +static const char ufbxi_KnotVectorV[] = "KnotVectorV"; +static const char ufbxi_KnotVector[] = "KnotVector"; +static const char ufbxi_LayerElementBinormal[] = "LayerElementBinormal"; +static const char ufbxi_LayerElementColor[] = "LayerElementColor"; +static const char ufbxi_LayerElementEdgeCrease[] = "LayerElementEdgeCrease"; +static const char ufbxi_LayerElementHole[] = "LayerElementHole"; +static const char ufbxi_LayerElementMaterial[] = "LayerElementMaterial"; +static const char ufbxi_LayerElementNormal[] = "LayerElementNormal"; +static const char ufbxi_LayerElementPolygonGroup[] = "LayerElementPolygonGroup"; +static const char ufbxi_LayerElementSmoothing[] = "LayerElementSmoothing"; +static const char ufbxi_LayerElementTangent[] = "LayerElementTangent"; +static const char ufbxi_LayerElementUV[] = "LayerElementUV"; +static const char ufbxi_LayerElementVertexCrease[] = "LayerElementVertexCrease"; +static const char ufbxi_LayerElementVisibility[] = "LayerElementVisibility"; +static const char ufbxi_LayerElement[] = "LayerElement"; +static const char ufbxi_Layer[] = "Layer"; +static const char ufbxi_LayeredTexture[] = "LayeredTexture"; +static const char ufbxi_Lcl_Rotation[] = "Lcl Rotation"; +static const char ufbxi_Lcl_Scaling[] = "Lcl Scaling"; +static const char ufbxi_Lcl_Translation[] = "Lcl Translation"; +static const char ufbxi_LeftCamera[] = "LeftCamera"; +static const char ufbxi_LightType[] = "LightType"; +static const char ufbxi_Light[] = "Light"; +static const char ufbxi_LimbLength[] = "LimbLength"; +static const char ufbxi_LimbNode[] = "LimbNode"; +static const char ufbxi_Limb[] = "Limb"; +static const char ufbxi_Line[] = "Line"; +static const char ufbxi_Link[] = "Link"; +static const char ufbxi_LocalStart[] = "LocalStart"; +static const char ufbxi_LocalStop[] = "LocalStop"; +static const char ufbxi_LocalTime[] = "LocalTime"; +static const char ufbxi_LodGroup[] = "LodGroup"; +static const char ufbxi_MappingInformationType[] = "MappingInformationType"; +static const char ufbxi_Marker[] = "Marker"; +static const char ufbxi_MaterialAssignation[] = "MaterialAssignation"; +static const char ufbxi_Material[] = "Material"; +static const char ufbxi_Materials[] = "Materials"; +static const char ufbxi_Matrix[] = "Matrix"; +static const char ufbxi_Media[] = "Media"; +static const char ufbxi_Mesh[] = "Mesh"; +static const char ufbxi_Model[] = "Model"; +static const char ufbxi_Name[] = "Name"; +static const char ufbxi_NearPlane[] = "NearPlane"; +static const char ufbxi_NodeAttributeName[] = "NodeAttributeName"; +static const char ufbxi_NodeAttribute[] = "NodeAttribute"; +static const char ufbxi_Node[] = "Node"; +static const char ufbxi_Normals[] = "Normals"; +static const char ufbxi_NormalsIndex[] = "NormalsIndex"; +static const char ufbxi_NormalsW[] = "NormalsW"; +static const char ufbxi_Null[] = "Null"; +static const char ufbxi_NurbsCurve[] = "NurbsCurve"; +static const char ufbxi_NurbsSurfaceOrder[] = "NurbsSurfaceOrder"; +static const char ufbxi_NurbsSurface[] = "NurbsSurface"; +static const char ufbxi_Nurbs[] = "Nurbs"; +static const char ufbxi_OO[] = "OO\0"; +static const char ufbxi_OP[] = "OP\0"; +static const char ufbxi_ObjectMetaData[] = "ObjectMetaData"; +static const char ufbxi_ObjectType[] = "ObjectType"; +static const char ufbxi_Objects[] = "Objects"; +static const char ufbxi_Order[] = "Order"; +static const char ufbxi_OriginalUnitScaleFactor[] = "OriginalUnitScaleFactor"; +static const char ufbxi_OriginalUpAxis[] = "OriginalUpAxis"; +static const char ufbxi_OriginalUpAxisSign[] = "OriginalUpAxisSign"; +static const char ufbxi_OrthoZoom[] = "OrthoZoom"; +static const char ufbxi_OtherFlags[] = "OtherFlags"; +static const char ufbxi_OuterAngle[] = "OuterAngle"; +static const char ufbxi_PO[] = "PO\0"; +static const char ufbxi_PP[] = "PP\0"; +static const char ufbxi_PointsIndex[] = "PointsIndex"; +static const char ufbxi_Points[] = "Points"; +static const char ufbxi_PolygonGroup[] = "PolygonGroup"; +static const char ufbxi_PolygonIndexArray[] = "PolygonIndexArray"; +static const char ufbxi_PolygonVertexIndex[] = "PolygonVertexIndex"; +static const char ufbxi_PoseNode[] = "PoseNode"; +static const char ufbxi_Pose[] = "Pose"; +static const char ufbxi_Post_Extrapolation[] = "Post-Extrapolation"; +static const char ufbxi_PostRotation[] = "PostRotation"; +static const char ufbxi_Pre_Extrapolation[] = "Pre-Extrapolation"; +static const char ufbxi_PreRotation[] = "PreRotation"; +static const char ufbxi_PreviewDivisionLevels[] = "PreviewDivisionLevels"; +static const char ufbxi_Properties60[] = "Properties60"; +static const char ufbxi_Properties70[] = "Properties70"; +static const char ufbxi_PropertyTemplate[] = "PropertyTemplate"; +static const char ufbxi_R[] = "R\0\0"; +static const char ufbxi_ReferenceStart[] = "ReferenceStart"; +static const char ufbxi_ReferenceStop[] = "ReferenceStop"; +static const char ufbxi_ReferenceTime[] = "ReferenceTime"; +static const char ufbxi_RelativeFileName[] = "RelativeFileName"; +static const char ufbxi_RelativeFilename[] = "RelativeFilename"; +static const char ufbxi_RenderDivisionLevels[] = "RenderDivisionLevels"; +static const char ufbxi_Repetition[] = "Repetition"; +static const char ufbxi_RightCamera[] = "RightCamera"; +static const char ufbxi_RootNode[] = "RootNode"; +static const char ufbxi_Root[] = "Root"; +static const char ufbxi_RotationAccumulationMode[] = "RotationAccumulationMode"; +static const char ufbxi_RotationOffset[] = "RotationOffset"; +static const char ufbxi_RotationOrder[] = "RotationOrder"; +static const char ufbxi_RotationPivot[] = "RotationPivot"; +static const char ufbxi_Rotation[] = "Rotation"; +static const char ufbxi_S[] = "S\0\0"; +static const char ufbxi_ScaleAccumulationMode[] = "ScaleAccumulationMode"; +static const char ufbxi_ScalingOffset[] = "ScalingOffset"; +static const char ufbxi_ScalingPivot[] = "ScalingPivot"; +static const char ufbxi_Scaling[] = "Scaling"; +static const char ufbxi_SceneInfo[] = "SceneInfo"; +static const char ufbxi_SelectionNode[] = "SelectionNode"; +static const char ufbxi_SelectionSet[] = "SelectionSet"; +static const char ufbxi_ShadingModel[] = "ShadingModel"; +static const char ufbxi_Shape[] = "Shape"; +static const char ufbxi_Shininess[] = "Shininess"; +static const char ufbxi_Show[] = "Show"; +static const char ufbxi_Size[] = "Size"; +static const char ufbxi_Skin[] = "Skin"; +static const char ufbxi_SkinningType[] = "SkinningType"; +static const char ufbxi_Smoothing[] = "Smoothing"; +static const char ufbxi_Smoothness[] = "Smoothness"; +static const char ufbxi_SnapOnFrameMode[] = "SnapOnFrameMode"; +static const char ufbxi_SpecularColor[] = "SpecularColor"; +static const char ufbxi_Step[] = "Step"; +static const char ufbxi_SubDeformer[] = "SubDeformer"; +static const char ufbxi_T[] = "T\0\0"; +static const char ufbxi_TCDefinition[] = "TCDefinition"; +static const char ufbxi_Take[] = "Take"; +static const char ufbxi_Takes[] = "Takes"; +static const char ufbxi_Tangents[] = "Tangents"; +static const char ufbxi_TangentsIndex[] = "TangentsIndex"; +static const char ufbxi_TangentsW[] = "TangentsW"; +static const char ufbxi_Texture[] = "Texture"; +static const char ufbxi_Texture_alpha[] = "Texture alpha"; +static const char ufbxi_TextureId[] = "TextureId"; +static const char ufbxi_TextureRotationPivot[] = "TextureRotationPivot"; +static const char ufbxi_TextureScalingPivot[] = "TextureScalingPivot"; +static const char ufbxi_TextureUV[] = "TextureUV"; +static const char ufbxi_TextureUVVerticeIndex[] = "TextureUVVerticeIndex"; +static const char ufbxi_Thumbnail[] = "Thumbnail"; +static const char ufbxi_TimeMarker[] = "TimeMarker"; +static const char ufbxi_TimeMode[] = "TimeMode"; +static const char ufbxi_TimeProtocol[] = "TimeProtocol"; +static const char ufbxi_TimeSpanStart[] = "TimeSpanStart"; +static const char ufbxi_TimeSpanStop[] = "TimeSpanStop"; +static const char ufbxi_TransformLink[] = "TransformLink"; +static const char ufbxi_Transform[] = "Transform"; +static const char ufbxi_Translation[] = "Translation"; +static const char ufbxi_TrimNurbsSurface[] = "TrimNurbsSurface"; +static const char ufbxi_Type[] = "Type"; +static const char ufbxi_TypedIndex[] = "TypedIndex"; +static const char ufbxi_UVIndex[] = "UVIndex"; +static const char ufbxi_UVSet[] = "UVSet"; +static const char ufbxi_UVSwap[] = "UVSwap"; +static const char ufbxi_UV[] = "UV\0"; +static const char ufbxi_UnitScaleFactor[] = "UnitScaleFactor"; +static const char ufbxi_UpAxisSign[] = "UpAxisSign"; +static const char ufbxi_UpAxis[] = "UpAxis"; +static const char ufbxi_Version5[] = "Version5"; +static const char ufbxi_VertexCacheDeformer[] = "VertexCacheDeformer"; +static const char ufbxi_VertexCrease[] = "VertexCrease"; +static const char ufbxi_VertexCreaseIndex[] = "VertexCreaseIndex"; +static const char ufbxi_VertexIndexArray[] = "VertexIndexArray"; +static const char ufbxi_Vertices[] = "Vertices"; +static const char ufbxi_Video[] = "Video"; +static const char ufbxi_Visibility[] = "Visibility"; +static const char ufbxi_Weight[] = "Weight"; +static const char ufbxi_Weights[] = "Weights"; +static const char ufbxi_WrapModeU[] = "WrapModeU"; +static const char ufbxi_WrapModeV[] = "WrapModeV"; +static const char ufbxi_X[] = "X\0\0"; +static const char ufbxi_Y[] = "Y\0\0"; +static const char ufbxi_Z[] = "Z\0\0"; +static const char ufbxi_d_X[] = "d|X"; +static const char ufbxi_d_Y[] = "d|Y"; +static const char ufbxi_d_Z[] = "d|Z"; + +static const ufbx_string ufbxi_strings[] = { + { ufbxi_AllSame, 7 }, + { ufbxi_Alphas, 6 }, + { ufbxi_AmbientColor, 12 }, + { ufbxi_AnimationCurve, 14 }, + { ufbxi_AnimationCurveNode, 18 }, + { ufbxi_AnimationLayer, 14 }, + { ufbxi_AnimationStack, 14 }, + { ufbxi_ApertureFormat, 14 }, + { ufbxi_ApertureMode, 12 }, + { ufbxi_AreaLightShape, 14 }, + { ufbxi_AspectH, 7 }, + { ufbxi_AspectHeight, 12 }, + { ufbxi_AspectRatioMode, 15 }, + { ufbxi_AspectW, 7 }, + { ufbxi_AspectWidth, 11 }, + { ufbxi_Audio, 5 }, + { ufbxi_AudioLayer, 10 }, + { ufbxi_BaseLayer, 9 }, + { ufbxi_BinaryData, 10 }, + { ufbxi_BindPose, 8 }, + { ufbxi_BindingTable, 12 }, + { ufbxi_Binormals, 9 }, + { ufbxi_BinormalsIndex, 14 }, + { ufbxi_BinormalsW, 10 }, + { ufbxi_BlendMode, 9 }, + { ufbxi_BlendModes, 10 }, + { ufbxi_BlendShape, 10 }, + { ufbxi_BlendShapeChannel, 17 }, + { ufbxi_BlendWeights, 12 }, + { ufbxi_Boundary, 8 }, + { ufbxi_BoundaryRule, 12 }, + { ufbxi_ByEdge, 6 }, + { ufbxi_ByPolygon, 9 }, + { ufbxi_ByPolygonVertex, 15 }, + { ufbxi_ByVertex, 8 }, + { ufbxi_ByVertice, 9 }, + { ufbxi_Cache, 5 }, + { ufbxi_Camera, 6 }, + { ufbxi_CameraProjectionType, 20 }, + { ufbxi_CameraStereo, 12 }, + { ufbxi_CameraSwitcher, 14 }, + { ufbxi_CastLight, 9 }, + { ufbxi_CastShadows, 11 }, + { ufbxi_Channel, 7 }, + { ufbxi_Character, sizeof(ufbxi_Character) - 1 }, + { ufbxi_Children, 8 }, + { ufbxi_Cluster, 7 }, + { ufbxi_Collection, 10 }, + { ufbxi_CollectionExclusive, 19 }, + { ufbxi_Color, 5 }, + { ufbxi_ColorIndex, 10 }, + { ufbxi_Colors, 6 }, + { ufbxi_Cone_angle, 10 }, + { ufbxi_ConeAngle, 9 }, + { ufbxi_Connections, 11 }, + { ufbxi_Constraint, sizeof(ufbxi_Constraint) - 1 }, + { ufbxi_Content, 7 }, + { ufbxi_CoordAxis, 9 }, + { ufbxi_CoordAxisSign, 13 }, + { ufbxi_Count, 5 }, + { ufbxi_Creator, 7 }, + { ufbxi_CurrentTextureBlendMode, 23 }, + { ufbxi_CurrentTimeMarker, 17 }, + { ufbxi_CustomFrameRate, 15 }, + { ufbxi_DecayType, 9 }, + { ufbxi_Default, 7 }, + { ufbxi_DefaultCamera, 13 }, + { ufbxi_Definitions, 11 }, + { ufbxi_DeformPercent, 13 }, + { ufbxi_Deformer, 8 }, + { ufbxi_DiffuseColor, 12 }, + { ufbxi_Dimension, 9 }, + { ufbxi_Dimensions, 10 }, + { ufbxi_DisplayLayer, 12 }, + { ufbxi_Document, 8 }, + { ufbxi_Documents, 9 }, + { ufbxi_EdgeCrease, 10 }, + { ufbxi_EdgeIndexArray, 14 }, + { ufbxi_Edges, 5 }, + { ufbxi_EmissiveColor, 13 }, + { ufbxi_Entry, 5 }, + { ufbxi_FBXHeaderExtension, 18 }, + { ufbxi_FBXHeaderVersion, 16 }, + { ufbxi_FBXVersion, 10 }, + { ufbxi_FKEffector, 10 }, + { ufbxi_FarPlane, 8 }, + { ufbxi_FbxPropertyEntry, 16 }, + { ufbxi_FbxSemanticEntry, 16 }, + { ufbxi_FieldOfView, 11 }, + { ufbxi_FieldOfViewX, 12 }, + { ufbxi_FieldOfViewY, 12 }, + { ufbxi_FileName, 8 }, + { ufbxi_Filename, 8 }, + { ufbxi_FilmHeight, 10 }, + { ufbxi_FilmSqueezeRatio, 16 }, + { ufbxi_FilmWidth, 9 }, + { ufbxi_FlipNormals, 11 }, + { ufbxi_FocalLength, 11 }, + { ufbxi_Form, 4 }, + { ufbxi_Freeze, 6 }, + { ufbxi_FrontAxis, 9 }, + { ufbxi_FrontAxisSign, 13 }, + { ufbxi_FullWeights, 11 }, + { ufbxi_GateFit, 7 }, + { ufbxi_GeometricRotation, 17 }, + { ufbxi_GeometricScaling, 16 }, + { ufbxi_GeometricTranslation, 20 }, + { ufbxi_Geometry, 8 }, + { ufbxi_GeometryUVInfo, 14 }, + { ufbxi_GlobalSettings, 14 }, + { ufbxi_Hole, 4 }, + { ufbxi_HotSpot, 7 }, + { ufbxi_IKEffector, 10 }, + { ufbxi_ImageData, 9 }, + { ufbxi_Implementation, 14 }, + { ufbxi_Indexes, 7 }, + { ufbxi_InheritType, 11 }, + { ufbxi_InnerAngle, 10 }, + { ufbxi_Intensity, 9 }, + { ufbxi_IsTheNodeInSet, 14 }, + { ufbxi_Key, 3 }, + { ufbxi_KeyAttrDataFloat, 16 }, + { ufbxi_KeyAttrFlags, 12 }, + { ufbxi_KeyAttrRefCount, 15 }, + { ufbxi_KeyCount, 8 }, + { ufbxi_KeyTime, 7 }, + { ufbxi_KeyValueFloat, 13 }, + { ufbxi_KnotVector, 10 }, + { ufbxi_KnotVectorU, 11 }, + { ufbxi_KnotVectorV, 11 }, + { ufbxi_Layer, 5 }, + { ufbxi_LayerElement, 12 }, + { ufbxi_LayerElementBinormal, 20 }, + { ufbxi_LayerElementColor, 17 }, + { ufbxi_LayerElementEdgeCrease, 22 }, + { ufbxi_LayerElementHole, 16 }, + { ufbxi_LayerElementMaterial, 20 }, + { ufbxi_LayerElementNormal, 18 }, + { ufbxi_LayerElementPolygonGroup, 24 }, + { ufbxi_LayerElementSmoothing, 21 }, + { ufbxi_LayerElementTangent, 19 }, + { ufbxi_LayerElementUV, 14 }, + { ufbxi_LayerElementVertexCrease, 24 }, + { ufbxi_LayerElementVisibility, 22 }, + { ufbxi_LayeredTexture, 14 }, + { ufbxi_Lcl_Rotation, 12 }, + { ufbxi_Lcl_Scaling, 11 }, + { ufbxi_Lcl_Translation, 15 }, + { ufbxi_LeftCamera, 10 }, + { ufbxi_Light, 5 }, + { ufbxi_LightType, 9 }, + { ufbxi_Limb, 4 }, + { ufbxi_LimbLength, 10 }, + { ufbxi_LimbNode, 8 }, + { ufbxi_Line, 4 }, + { ufbxi_Link, 4 }, + { ufbxi_LocalStart, 10 }, + { ufbxi_LocalStop, 9 }, + { ufbxi_LocalTime, 9 }, + { ufbxi_LodGroup, 8 }, + { ufbxi_MappingInformationType, 22 }, + { ufbxi_Marker, 6 }, + { ufbxi_Material, 8 }, + { ufbxi_MaterialAssignation, 19 }, + { ufbxi_Materials, 9 }, + { ufbxi_Matrix, 6 }, + { ufbxi_Media, 5 }, + { ufbxi_Mesh, 4 }, + { ufbxi_Model, 5 }, + { ufbxi_Name, 4 }, + { ufbxi_NearPlane, 9 }, + { ufbxi_Node, 4 }, + { ufbxi_NodeAttribute, 13 }, + { ufbxi_NodeAttributeName, 17 }, + { ufbxi_Normals, 7 }, + { ufbxi_NormalsIndex, 12 }, + { ufbxi_NormalsW, 8 }, + { ufbxi_Null, 4 }, + { ufbxi_Nurbs, 5 }, + { ufbxi_NurbsCurve, 10 }, + { ufbxi_NurbsSurface, 12 }, + { ufbxi_NurbsSurfaceOrder, 17 }, + { ufbxi_OO, 2 }, + { ufbxi_OP, 2 }, + { ufbxi_ObjectMetaData, 14 }, + { ufbxi_ObjectType, 10 }, + { ufbxi_Objects, 7 }, + { ufbxi_Order, 5 }, + { ufbxi_OriginalUnitScaleFactor, 23 }, + { ufbxi_OriginalUpAxis, 14 }, + { ufbxi_OriginalUpAxisSign, 18 }, + { ufbxi_OrthoZoom, 9 }, + { ufbxi_OtherFlags, 10 }, + { ufbxi_OuterAngle, 10 }, + { ufbxi_PO, 2 }, + { ufbxi_PP, 2 }, + { ufbxi_Points, 6 }, + { ufbxi_PointsIndex, 11 }, + { ufbxi_PolygonGroup, 12 }, + { ufbxi_PolygonIndexArray, 17 }, + { ufbxi_PolygonVertexIndex, 18 }, + { ufbxi_Pose, 4 }, + { ufbxi_PoseNode, 8 }, + { ufbxi_Post_Extrapolation, 18 }, + { ufbxi_PostRotation, 12 }, + { ufbxi_Pre_Extrapolation, 17 }, + { ufbxi_PreRotation, 11 }, + { ufbxi_PreviewDivisionLevels, 21 }, + { ufbxi_Properties60, 12 }, + { ufbxi_Properties70, 12 }, + { ufbxi_PropertyTemplate, 16 }, + { ufbxi_R, 1 }, + { ufbxi_ReferenceStart, 14 }, + { ufbxi_ReferenceStop, 13 }, + { ufbxi_ReferenceTime, 13 }, + { ufbxi_RelativeFileName, 16 }, + { ufbxi_RelativeFilename, 16 }, + { ufbxi_RenderDivisionLevels, 20 }, + { ufbxi_Repetition, 10 }, + { ufbxi_RightCamera, 11 }, + { ufbxi_Root, 4 }, + { ufbxi_RootNode, 8 }, + { ufbxi_Rotation, 8 }, + { ufbxi_RotationAccumulationMode, 24 }, + { ufbxi_RotationOffset, 14 }, + { ufbxi_RotationOrder, 13 }, + { ufbxi_RotationPivot, 13 }, + { ufbxi_S, 1 }, + { ufbxi_ScaleAccumulationMode, 21 }, + { ufbxi_Scaling, 7 }, + { ufbxi_ScalingOffset, 13 }, + { ufbxi_ScalingPivot, 12 }, + { ufbxi_SceneInfo, 9 }, + { ufbxi_SelectionNode, 13 }, + { ufbxi_SelectionSet, 12 }, + { ufbxi_ShadingModel, 12 }, + { ufbxi_Shape, 5 }, + { ufbxi_Shininess, 9 }, + { ufbxi_Show, 4 }, + { ufbxi_Size, 4 }, + { ufbxi_Skin, 4 }, + { ufbxi_SkinningType, 12 }, + { ufbxi_Smoothing, 9 }, + { ufbxi_Smoothness, 10 }, + { ufbxi_SnapOnFrameMode, 15 }, + { ufbxi_SpecularColor, 13 }, + { ufbxi_Step, 4 }, + { ufbxi_SubDeformer, 11 }, + { ufbxi_T, 1 }, + { ufbxi_TCDefinition, 12 }, + { ufbxi_Take, 4 }, + { ufbxi_Takes, 5 }, + { ufbxi_Tangents, 8 }, + { ufbxi_TangentsIndex, 13 }, + { ufbxi_TangentsW, 9 }, + { ufbxi_Texture, 7 }, + { ufbxi_Texture_alpha, 13 }, + { ufbxi_TextureId, 9 }, + { ufbxi_TextureRotationPivot, 20 }, + { ufbxi_TextureScalingPivot, 19 }, + { ufbxi_TextureUV, 9 }, + { ufbxi_TextureUVVerticeIndex, 21 }, + { ufbxi_Thumbnail, 9 }, + { ufbxi_TimeMarker, 10 }, + { ufbxi_TimeMode, 8 }, + { ufbxi_TimeProtocol, 12 }, + { ufbxi_TimeSpanStart, 13 }, + { ufbxi_TimeSpanStop, 12 }, + { ufbxi_Transform, 9 }, + { ufbxi_TransformLink, 13 }, + { ufbxi_Translation, 11 }, + { ufbxi_TrimNurbsSurface, 16 }, + { ufbxi_Type, 4 }, + { ufbxi_TypedIndex, 10 }, + { ufbxi_UV, 2 }, + { ufbxi_UVIndex, 7 }, + { ufbxi_UVSet, 5 }, + { ufbxi_UVSwap, 6 }, + { ufbxi_UnitScaleFactor, 15 }, + { ufbxi_UpAxis, 6 }, + { ufbxi_UpAxisSign, 10 }, + { ufbxi_Version5, 8 }, + { ufbxi_VertexCacheDeformer, 19 }, + { ufbxi_VertexCrease, 12 }, + { ufbxi_VertexCreaseIndex, 17 }, + { ufbxi_VertexIndexArray, 16 }, + { ufbxi_Vertices, 8 }, + { ufbxi_Video, 5 }, + { ufbxi_Visibility, 10 }, + { ufbxi_Weight, 6 }, + { ufbxi_Weights, 7 }, + { ufbxi_WrapModeU, 9 }, + { ufbxi_WrapModeV, 9 }, + { ufbxi_X, 1 }, + { ufbxi_Y, 1 }, + { ufbxi_Z, 1 }, + { ufbxi_d_X, 3 }, + { ufbxi_d_Y, 3 }, + { ufbxi_d_Z, 3 }, +}; + +static const ufbx_vec3 ufbxi_one_vec3 = { 1.0f, 1.0f, 1.0f }; + +#define UFBXI_PI ((ufbx_real)3.14159265358979323846) +#define UFBXI_DPI (3.14159265358979323846) +#define UFBXI_DEG_TO_RAD ((ufbx_real)(UFBXI_PI / 180.0)) +#define UFBXI_RAD_TO_DEG ((ufbx_real)(180.0 / UFBXI_PI)) +#define UFBXI_DEG_TO_RAD_DOUBLE (UFBXI_DPI / 180.0) +#define UFBXI_RAD_TO_DEG_DOUBLE (180.0 / UFBXI_DPI) +#define UFBXI_MM_TO_INCH ((ufbx_real)0.0393700787) + +ufbx_inline ufbx_vec3 ufbxi_add3(ufbx_vec3 a, ufbx_vec3 b) { + ufbx_vec3 v = { a.x + b.x, a.y + b.y, a.z + b.z }; + return v; +} + +ufbx_inline ufbx_vec3 ufbxi_sub3(ufbx_vec3 a, ufbx_vec3 b) { + ufbx_vec3 v = { a.x - b.x, a.y - b.y, a.z - b.z }; + return v; +} + +ufbx_inline ufbx_vec3 ufbxi_mul3(ufbx_vec3 a, ufbx_real b) { + ufbx_vec3 v = { a.x * b, a.y * b, a.z * b }; + return v; +} + +ufbx_inline ufbx_vec3 ufbxi_lerp3(ufbx_vec3 a, ufbx_vec3 b, ufbx_real t) { + ufbx_real u = 1.0f - t; + ufbx_vec3 v = { a.x*u + b.x*t, a.y*u + b.y*t, a.z*u + b.z*t }; + return v; +} + +ufbx_inline ufbx_real ufbxi_dot3(ufbx_vec3 a, ufbx_vec3 b) { + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +ufbx_inline ufbx_real ufbxi_length3(ufbx_vec3 v) +{ + return (ufbx_real)ufbx_sqrt(v.x*v.x + v.y*v.y + v.z*v.z); +} + +ufbx_inline ufbx_real ufbxi_min3(ufbx_vec3 v) +{ + return ufbxi_min_real(ufbxi_min_real(v.x, v.y), v.z); +} + +ufbx_inline ufbx_vec3 ufbxi_cross3(ufbx_vec3 a, ufbx_vec3 b) { + ufbx_vec3 v = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; + return v; +} + +ufbx_inline ufbx_vec3 ufbxi_normalize3(ufbx_vec3 a) { + ufbx_real len = (ufbx_real)ufbx_sqrt(ufbxi_dot3(a, a)); + if (len > UFBX_EPSILON) { + return ufbxi_mul3(a, (ufbx_real)1.0 / len); + } else { + ufbx_vec3 zero = { (ufbx_real)0 }; + return zero; + } +} + +ufbx_inline ufbx_vec3 ufbxi_neg3(ufbx_vec3 a) { + ufbx_vec3 v = { -a.x, -a.y, -a.z }; + return v; +} + +ufbx_inline ufbx_real ufbxi_distsq2(ufbx_vec2 a, ufbx_vec2 b) { + ufbx_real dx = a.x - b.x, dy = a.y - b.y; + return dx*dx + dy*dy; +} + +static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalize3(const ufbx_vec3 *a) { + return ufbxi_normalize3(*a); +} + +static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalized_cross3(const ufbx_vec3 *a, const ufbx_vec3 *b) { + return ufbxi_normalize3(ufbxi_cross3(*a, *b)); +} + +// -- Threading + +typedef struct ufbxi_task ufbxi_task; +typedef struct ufbxi_thread_pool ufbxi_thread_pool; + +typedef bool ufbxi_task_fn(ufbxi_task *task); + +struct ufbxi_task { + void *data; + const char *error; +}; + +typedef struct { + ufbxi_task task; + ufbxi_task_fn *fn; +} ufbxi_task_imp; + +typedef struct { + uint32_t max_index; + uint32_t wait_index; +} ufbxi_task_group; + +struct ufbxi_thread_pool { + ufbx_thread_opts opts; + ufbxi_allocator *ator; + ufbx_error *error; + void *user_ptr; + + bool enabled; + bool failed; + const char *error_desc; + + uint32_t start_index; + uint32_t execute_index; + uint32_t wait_index; + + ufbxi_task_group groups[UFBX_THREAD_GROUP_COUNT]; + uint32_t group; + + uint32_t num_tasks; + ufbxi_task_imp *tasks; +}; + +static void ufbxi_thread_pool_execute(ufbxi_thread_pool *pool, uint32_t index) +{ + ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks]; + if (imp->fn(&imp->task)) { + imp->task.error = NULL; + } else if (!imp->task.error) { + imp->task.error = ""; + } +} + +ufbxi_noinline static void ufbxi_thread_pool_update_finished(ufbxi_thread_pool *pool, uint32_t max_index) +{ + while (pool->wait_index < max_index) { + ufbxi_task_imp *task = &pool->tasks[pool->wait_index % pool->num_tasks]; + if (!pool->failed && task->task.error) { + pool->failed = true; + pool->error_desc = task->task.error; + } + pool->wait_index += 1; + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_imp(ufbxi_thread_pool *pool, uint32_t group, bool can_fail) +{ + uint32_t max_index = pool->groups[group].max_index; + + if (pool->groups[group].wait_index < max_index) { + pool->opts.pool.wait_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, max_index); + pool->groups[group].wait_index = max_index; + } + ufbxi_thread_pool_update_finished(pool, max_index); + + if (pool->failed && can_fail) { + ufbx_error *error = pool->error; + if (pool->error_desc) { + error->description.data = pool->error_desc; + error->description.length = strlen(pool->error_desc); + } + ufbxi_fail_err(error, "Task failed"); + } + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_group(ufbxi_thread_pool *pool) +{ + ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true)); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_all(ufbxi_thread_pool *pool) +{ + for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { + ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true)); + pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT; + } + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_init(ufbxi_thread_pool *pool, ufbx_error *error, ufbxi_allocator *ator, const ufbx_thread_opts *opts) +{ + if (!(opts->pool.run_fn && opts->pool.wait_fn)) return 1; + pool->enabled = true; + + uint32_t num_tasks = (uint32_t)ufbxi_min_sz(opts->num_tasks, INT32_MAX); + if (num_tasks == 0) { + num_tasks = 2048; + } + + pool->opts = *opts; + if (pool->opts.pool.init_fn) { + ufbx_thread_pool_info info; // ufbxi_uninit + info.max_concurrent_tasks = num_tasks; + ufbxi_check_err(error, pool->opts.pool.init_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, &info)); + } + pool->ator = ator; + pool->error = error; + + pool->num_tasks = num_tasks; + pool->tasks = ufbxi_alloc(ator, ufbxi_task_imp, num_tasks); + ufbxi_check_err(error, pool->tasks); + + return 1; +} + +ufbxi_noinline static void ufbxi_thread_pool_free(ufbxi_thread_pool *pool) +{ + if (!pool->enabled) return; + + // Wait for all pending tasks + for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { + pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT; + ufbxi_ignore(ufbxi_thread_pool_wait_imp(pool, pool->group, false)); + } + + if (pool->opts.pool.free_fn) { + pool->opts.pool.free_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool); + } + + ufbxi_free(pool->ator, ufbxi_task_imp, pool->tasks, pool->num_tasks); +} + +ufbxi_nodiscard ufbxi_noinline static uint32_t ufbxi_thread_pool_available_tasks(ufbxi_thread_pool *pool) +{ + return pool->num_tasks - (pool->start_index - pool->wait_index); +} + +ufbxi_noinline static void ufbxi_thread_pool_flush_group(ufbxi_thread_pool *pool) +{ + uint32_t group = pool->group; + uint32_t start_index = pool->execute_index; + uint32_t count = pool->start_index - start_index; + if (count > 0) { + if (pool->opts.pool.run_fn) { + pool->opts.pool.run_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, start_index, count); + } + pool->groups[group].max_index = start_index + count; + pool->execute_index = start_index + count; + } + pool->group = (group + 1) % UFBX_THREAD_GROUP_COUNT; +} + +ufbxi_nodiscard ufbxi_noinline static ufbxi_task *ufbxi_thread_pool_create_task(ufbxi_thread_pool *pool, ufbxi_task_fn *fn) +{ + uint32_t index = pool->start_index; + if (index - pool->wait_index >= pool->num_tasks) { + if (index - pool->wait_index >= pool->num_tasks) { + // No space left + return NULL; + } + } else if (index == INT32_MAX) { + // TODO: Expand to 64 bits if possible? + return NULL; + } + + ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks]; + if (index < pool->num_tasks) { + memset(imp, 0, sizeof(ufbxi_task_imp)); + } + + imp->fn = fn; + + return &imp->task; +} + +static void ufbxi_thread_pool_run_task(ufbxi_thread_pool *pool, ufbxi_task *task) +{ + (void)task; + uint32_t index = pool->start_index; + ufbx_assert(task == &pool->tasks[index % pool->num_tasks].task); + pool->start_index = index + 1; +} + +// -- Type definitions + +typedef struct ufbxi_node ufbxi_node; + +typedef enum { + UFBXI_VALUE_NONE, + UFBXI_VALUE_NUMBER, + UFBXI_VALUE_STRING, + UFBXI_VALUE_ARRAY, +} ufbxi_value_type; + +typedef union { + struct { double f; int64_t i; }; // < if `UFBXI_PROP_NUMBER` + ufbxi_sanitized_string s; // < if `UFBXI_PROP_STRING` +} ufbxi_value; + +typedef struct { + void *data; // < Pointer to `size` bool/int32_t/int64_t/float/double elements + size_t size; // < Number of elements + char type; // < FBX type code: b/i/l/f/d +} ufbxi_value_array; + +struct ufbxi_node { + const char *name; // < Name of the node (pooled, compare with == to ufbxi_* strings) + uint32_t num_children; // < Number of child nodes + uint8_t name_len; // < Length of `name` in bytes + + // If `value_type_mask == UFBXI_PROP_ARRAY` then the node is an array + // (`array` field is valid) otherwise the node has N values in `vals` + // where the type of each value is stored in 2 bits per value from LSB. + // ie. `vals[ix]` type is `(value_type_mask >> (ix*2)) & 0x3` + uint16_t value_type_mask; + + ufbxi_node *children; + union { + ufbxi_value_array *array; // if `prop_type_mask == UFBXI_PROP_ARRAY` + ufbxi_value *vals; // otherwise + }; +}; + +typedef struct ufbxi_refcount ufbxi_refcount; + +struct ufbxi_refcount { + ufbxi_refcount *parent; + void *align_0; + uint32_t self_magic; + uint32_t type_magic; + ufbxi_buf buf; + ufbxi_allocator ator; + uint64_t zero_pad_pre[8]; + ufbxi_atomic_counter refcount; + uint64_t zero_pad_post[8]; +}; + +static ufbxi_noinline void ufbxi_init_ref(ufbxi_refcount *refcount, uint32_t magic, ufbxi_refcount *parent); +static ufbxi_noinline void ufbxi_retain_ref(ufbxi_refcount *refcount); + +#define ufbxi_get_imp(type, ptr) ((type*)((char*)ptr - sizeof(ufbxi_refcount))) + +typedef struct { + ufbxi_refcount refcount; + ufbx_scene scene; + uint32_t magic; + + ufbxi_buf string_buf; +} ufbxi_scene_imp; + +ufbx_static_assert(scene_imp_offset, offsetof(ufbxi_scene_imp, scene) == sizeof(ufbxi_refcount)); + +typedef struct { + ufbxi_refcount refcount; + ufbx_mesh mesh; + uint32_t magic; +} ufbxi_mesh_imp; + +ufbx_static_assert(mesh_imp_offset, offsetof(ufbxi_mesh_imp, mesh) == sizeof(ufbxi_refcount)); + +typedef struct { + // Semantic string data and length eg. for a string token + // this string doesn't include the quotes. + char *str_data; + size_t str_len; + size_t str_cap; + + // Type of the token, either single character such as '{' or ':' + // or one of UFBXI_ASCII_* defines. + char type; + + // Sign for integer if negative. + bool negative; + + // Parsed semantic value + union { + double f64; + int64_t i64; + size_t name_len; + } value; +} ufbxi_ascii_token; + +typedef struct { + size_t max_token_length; + + const char *src; + const char *src_yield; + const char *src_end; + + bool read_first_comment; + bool found_version; + bool parse_as_f32; + bool src_is_retained; + + ufbxi_buf *retain_buf; + ufbxi_buf *src_buf; + + ufbxi_ascii_token prev_token; + ufbxi_ascii_token token; +} ufbxi_ascii; + +typedef struct { + const char *type; + ufbx_string sub_type; + ufbx_props props; +} ufbxi_template; + +typedef struct { + uint64_t fbx_id; + uint32_t element_id; + uint32_t user_id; +} ufbxi_fbx_id_entry; + +typedef struct { + ufbxi_ptr_id ptr_id; + uint64_t fbx_id; +} ufbxi_ptr_fbx_id_entry; + +typedef struct { + uint64_t node_fbx_id; + uint64_t attr_fbx_id; +} ufbxi_fbx_attr_entry; + +// Temporary connection before we resolve the element pointers +typedef struct { + uint64_t src, dst; + ufbx_string src_prop; + ufbx_string dst_prop; +} ufbxi_tmp_connection; + +typedef struct { + uint64_t fbx_id; + ufbx_string name; + ufbx_props props; + ufbx_dom_node *dom_node; +} ufbxi_element_info; + +typedef struct { + uint64_t bone_fbx_id; + ufbx_matrix bone_to_world; +} ufbxi_tmp_bone_pose; + +typedef struct { + ufbx_string prop_name; + uint32_t *face_texture; + size_t num_faces; + bool all_same; +} ufbxi_tmp_mesh_texture; + +typedef struct { + ufbxi_tmp_mesh_texture *texture_arr; + size_t texture_count; +} ufbxi_mesh_extra; + +typedef struct { + int32_t material_id; + int32_t texture_id; + ufbx_string prop_name; +} ufbxi_tmp_material_texture; + +typedef struct { + int32_t *blend_modes; + size_t num_blend_modes; + + ufbx_real *alphas; + size_t num_alphas; +} ufbxi_texture_extra; + +typedef enum { + UFBXI_OBJ_ATTRIB_POSITION, + UFBXI_OBJ_ATTRIB_UV, + UFBXI_OBJ_ATTRIB_NORMAL, + UFBXI_OBJ_ATTRIB_COLOR, +} ufbxi_obj_attrib; + +#define UFBXI_OBJ_NUM_ATTRIBS 3 +#define UFBXI_OBJ_NUM_ATTRIBS_EXT 4 + +typedef struct { + uint64_t min_ix, max_ix; +} ufbxi_obj_index_range; + +typedef struct { + size_t num_faces; + size_t num_indices; + ufbxi_obj_index_range vertex_range[UFBXI_OBJ_NUM_ATTRIBS]; + + ufbx_node *fbx_node; + ufbx_mesh *fbx_mesh; + + uint64_t fbx_node_id; + uint64_t fbx_mesh_id; + + uint32_t usemtl_base; + + uint32_t num_groups; +} ufbxi_obj_mesh; + +typedef struct { + const char *name; + uint32_t local_id; + uint32_t mesh_id; +} ufbxi_obj_group_entry; + +typedef struct { + uint64_t *indices; + size_t num_left; +} ufbxi_obj_fast_indices; + +// Temporary pointer to a `ufbx_anim_stack` by name used to patch start/stop +// time from "Takes" if necessary. +typedef struct { + const char *name; + ufbx_anim_stack *stack; +} ufbxi_tmp_anim_stack; + +typedef struct { + ufbx_string absolute_filename; + ufbx_blob content; +} ufbxi_file_content; + +typedef struct { + + // Current line and tokens. + // NOTE: `line` and `tokens` are not NULL-terminated nor UTF-8! + // `line` is guaranteed to be terminated by a `\n` + ufbx_string line; + ufbx_string *tokens; + size_t tokens_cap; + size_t num_tokens; + + ufbxi_obj_fast_indices fast_indices[UFBXI_OBJ_NUM_ATTRIBS]; + + size_t vertex_count[UFBXI_OBJ_NUM_ATTRIBS_EXT]; + ufbxi_buf tmp_vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT]; + ufbxi_buf tmp_indices[UFBXI_OBJ_NUM_ATTRIBS_EXT]; + ufbxi_buf tmp_color_valid; + ufbxi_buf tmp_faces; + ufbxi_buf tmp_face_smoothing; + ufbxi_buf tmp_face_group; + ufbxi_buf tmp_face_group_infos; + ufbxi_buf tmp_face_material; + ufbxi_buf tmp_meshes; + ufbxi_buf tmp_props; + + ufbxi_map group_map; + + size_t read_progress; + + ufbxi_obj_mesh *mesh; + + uint64_t usemtl_fbx_id; + uint32_t usemtl_index; + + uint32_t face_material; + + uint32_t face_group; + bool has_face_group; + + bool face_smoothing; + bool has_face_smoothing; + + bool has_vertex_color; + size_t mrgb_vertex_count; + + bool eof; + bool initialized; + + ufbx_blob mtllib_relative_path; + + ufbx_material **tmp_materials; + size_t tmp_materials_cap; + + ufbx_string object; + ufbx_string group; + bool material_dirty; + bool object_dirty; + bool group_dirty; + bool face_group_dirty; + +} ufbxi_obj_context; + +typedef struct { + + ufbx_error error; + uint32_t version; + ufbx_exporter exporter; + uint32_t exporter_version; + bool from_ascii; + bool local_big_endian; + bool file_big_endian; + bool sure_fbx; + bool retain_mesh_parts; + bool read_legacy_settings; + uint32_t double_parse_flags; + + ufbx_load_opts opts; + + // IO + uint64_t data_offset; + ufbx_read_fn *read_fn; + ufbx_skip_fn *skip_fn; + void *read_user; + + char *read_buffer; + size_t read_buffer_size; + + const char *data_begin; + const char *data; + size_t yield_size; + size_t data_size; + + // Allocators + ufbxi_allocator ator_result; + ufbxi_allocator ator_tmp; + + // Temporary maps + ufbxi_map prop_type_map; // < `ufbxi_prop_type_name` Property type to enum + ufbxi_map fbx_id_map; // < `ufbxi_fbx_id_entry` FBX ID to local ID + ufbxi_map ptr_fbx_id_map; // < `ufbxi_ptr_fbx_id_entry` Pointer/negative ID to FBX ID + ufbxi_map texture_file_map; // < `ufbxi_texture_file_entry` absolute raw filename to element ID + ufbxi_map anim_stack_map; // < `ufbxi_tmp_anim_stack` anim stacks by name before finalization + + // 6x00 specific maps + ufbxi_map fbx_attr_map; // < `ufbxi_fbx_attr_entry` Node ID to attrib ID + ufbxi_map node_prop_set; // < `const char*` Node property names + + // DOM nodes + ufbxi_map dom_node_map; // < `const char*` Node property names + + // Temporary array + char *tmp_arr; + size_t tmp_arr_size; + char *swap_arr; + size_t swap_arr_size; + + // Generated index buffers + size_t max_zero_indices; + size_t max_consecutive_indices; + + // Temporary buffers + ufbxi_buf tmp; + ufbxi_buf tmp_parse; + ufbxi_buf tmp_stack; + ufbxi_buf tmp_connections; + ufbxi_buf tmp_node_ids; + ufbxi_buf tmp_elements; + ufbxi_buf tmp_element_offsets; + ufbxi_buf tmp_element_fbx_ids; + ufbxi_buf tmp_element_ptrs; + ufbxi_buf tmp_typed_element_offsets[UFBX_ELEMENT_TYPE_COUNT]; + ufbxi_buf tmp_mesh_textures; + ufbxi_buf tmp_full_weights; + ufbxi_buf tmp_dom_nodes; + ufbxi_buf tmp_element_id; + ufbxi_buf tmp_ascii_spans; + ufbxi_buf tmp_thread_parse[UFBX_THREAD_GROUP_COUNT]; + size_t tmp_element_byte_offset; + + ufbxi_template *templates; + size_t num_templates; + + ufbx_dom_node *dom_parse_toplevel; + size_t dom_parse_num_children; + + uint32_t *p_element_id; + + // String pool + ufbxi_string_pool string_pool; + + // Result buffers, these are retained in `ufbx_scene` returned to user. + ufbxi_buf result; + + // Top-level state + ufbxi_node *top_nodes; + size_t top_nodes_len, top_nodes_cap; + bool parsed_to_end; + + // "Focused" top-level node and child index, if `top_child_index == SIZE_MAX` + // the children are parsed on demand. + ufbxi_node *top_node; + size_t top_child_index; + ufbxi_node top_child; + bool has_next_child; + + // Shared consecutive and all-zero index buffers + uint32_t *zero_indices; + uint32_t *consecutive_indices; + + // Call progress function periodically + ptrdiff_t progress_timer; + uint64_t progress_bytes_total; + uint64_t latest_progress_bytes; + size_t progress_interval; + + // Extra data on the side of elements + void **element_extra_arr; + size_t element_extra_cap; + + // Temporary per-element flags + uint8_t *tmp_element_flag; + + // IO (cold) + ufbx_close_fn *close_fn; + ufbx_size_fn *size_fn; + + ufbxi_ascii ascii; + + uint64_t synthetic_id_counter; + + bool has_geometry_transform_nodes; + bool has_scale_helper_nodes; + bool retain_vertex_w; + bool blender_full_weights; + + ufbx_mirror_axis mirror_axis; + + ufbxi_node root; + + ufbx_scene scene; + ufbxi_scene_imp *scene_imp; + + ufbx_inflate_retain *inflate_retain; + + // Per-mesh consecutive indices used by `ufbxi_flip_winding()`. + uint32_t *tmp_mesh_consecutive_indices; + + uint64_t root_id; + uint32_t num_elements; + + ufbxi_node legacy_node; + uint64_t legacy_implicit_anim_layer_id; + + ufbxi_file_content *file_content; + size_t num_file_content; + + int64_t ktime_sec; + double ktime_sec_double; + + bool eof; + ufbxi_obj_context obj; + + ufbx_matrix axis_matrix; + ufbx_real unit_scale; + + ufbxi_warnings warnings; + + bool deferred_failure; + bool deferred_load; + + const char *load_filename; + size_t load_filename_len; + + bool parse_threaded; + ufbxi_thread_pool thread_pool; + + uint8_t *base64_table; + +} ufbxi_context; + +static ufbxi_noinline int ufbxi_fail_imp(ufbxi_context *uc, const char *cond, const char *func, uint32_t line) +{ + return ufbxi_fail_imp_err(&uc->error, cond, func, line); +} + +#if UFBXI_FEATURE_ERROR_STACK + #define ufbxi_fail_no_msg(uc, cond, func, line) ufbxi_fail_imp((uc), (cond), (func), (line)) +#else + static ufbxi_noinline int ufbxi_fail_imp_no_stack(ufbxi_context *uc) { return ufbxi_fail_imp_err(&uc->error, NULL, NULL, 0); } + #define ufbxi_fail_no_msg(uc, cond, func, line) ufbxi_fail_imp_no_stack((uc)) +#endif + +#define ufbxi_check(cond) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_no_msg(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line) +#define ufbxi_check_return(cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_no_msg(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0) +#define ufbxi_fail(desc) return ufbxi_fail_no_msg(uc, desc, ufbxi_function, ufbxi_line) +#define ufbxi_fail_return(desc, ret) do { ufbxi_fail_no_msg(uc, desc, ufbxi_function, ufbxi_line); return ret; } while (0) + +#define ufbxi_check_msg(cond, msg) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line) +#define ufbxi_check_return_msg(cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0) +#define ufbxi_fail_msg(desc, msg) return ufbxi_fail_imp(uc, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) + +#define ufbxi_warnf(type, ...) ufbxi_warnf_imp(&uc->warnings, type, ~0u, __VA_ARGS__) +#define ufbxi_warnf_tag(type, element_id, ...) ufbxi_warnf_imp(&uc->warnings, type, (element_id), __VA_ARGS__) + +// -- Progress + +static ufbxi_forceinline uint64_t ufbxi_get_read_offset(ufbxi_context *uc) +{ + return uc->data_offset + ufbxi_to_size(uc->data - uc->data_begin); +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_report_progress(ufbxi_context *uc) +{ + if (!uc->opts.progress_cb.fn) return 1; + + uint64_t read_offset = ufbxi_get_read_offset(uc); + uc->latest_progress_bytes = read_offset; + + ufbx_progress progress = { 0 }; + progress.bytes_read = read_offset; + progress.bytes_total = uc->progress_bytes_total; + if (progress.bytes_total < progress.bytes_read) { + progress.bytes_total = progress.bytes_read; + } + + uc->progress_timer = 1024; + uint32_t result = (uint32_t)uc->opts.progress_cb.fn(uc->opts.progress_cb.user, &progress); + ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL); + ufbxi_check_msg(result != UFBX_PROGRESS_CANCEL, "Cancelled"); + return 1; +} + +// TODO: Remove `ufbxi_unused` when it's not needed anymore +ufbxi_unused ufbxi_nodiscard static ufbxi_forceinline int ufbxi_progress(ufbxi_context *uc, size_t work_units) +{ + if (!uc->opts.progress_cb.fn) return 1; + ptrdiff_t left = uc->progress_timer - (ptrdiff_t)work_units; + uc->progress_timer = left; + if (left > 0) return 1; + return ufbxi_report_progress(uc); +} + +// -- IO + +static ufbxi_noinline const char *ufbxi_refill(ufbxi_context *uc, size_t size, bool require_size) +{ + ufbx_assert(uc->data_size < size); + ufbxi_check_return(!uc->eof, NULL); + if (require_size) { + ufbxi_check_return_msg(uc->read_fn || uc->data_size > 0, NULL, "Empty file"); + ufbxi_check_return_msg(uc->read_fn, NULL, "Truncated file"); + } else if (!uc->read_fn) { + uc->eof = true; + return uc->data; + } + + void *data_to_free = NULL; + size_t size_to_free = 0; + + // Grow the read buffer if necessary, data is copied over below with the + // usual path so the free is deferred (`size_to_free`, `data_to_free`) + if (size > uc->read_buffer_size) { + size_t new_size = ufbxi_max_sz(size, uc->opts.read_buffer_size); + new_size = ufbxi_max_sz(new_size, uc->read_buffer_size * 2); + size_to_free = uc->read_buffer_size; + data_to_free = uc->read_buffer; + char *new_buffer = ufbxi_alloc(&uc->ator_tmp, char, new_size); + ufbxi_check_return(new_buffer, NULL); + uc->read_buffer = new_buffer; + uc->read_buffer_size = new_size; + } + + // Copy the remains of the previous buffer to the beginning of the new one + size_t data_size = uc->data_size; + if (data_size > 0) { + ufbx_assert(uc->read_buffer != NULL && uc->data != NULL); + memmove(uc->read_buffer, uc->data, data_size); + } + + if (size_to_free) { + ufbxi_free(&uc->ator_tmp, char, data_to_free, size_to_free); + } + + // Fill the rest of the buffer with user data + size_t data_capacity = uc->read_buffer_size; + while (data_size < data_capacity) { + size_t to_read = data_capacity - data_size; + size_t read_result = uc->read_fn(uc->read_user, uc->read_buffer + data_size, to_read); + ufbxi_check_return_msg(read_result != SIZE_MAX, NULL, "IO error"); + ufbxi_check_return(read_result <= to_read, NULL); + data_size += read_result; + if (read_result == 0) { + uc->eof = true; + break; + } + } + + if (require_size) { + if (uc->data_offset == 0) { + ufbxi_check_return_msg(data_size > 0, NULL, "Empty file"); + } + ufbxi_check_return_msg(data_size >= size, NULL, "Truncated file"); + } + + uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin); + uc->data_begin = uc->data = uc->read_buffer; + uc->data_size = data_size; + + return uc->read_buffer; +} + +static ufbxi_forceinline void ufbxi_pause_progress(ufbxi_context *uc) +{ + uc->data_size += uc->yield_size; + uc->yield_size = 0; +} + +static ufbxi_noinline int ufbxi_resume_progress(ufbxi_context *uc) +{ + uc->yield_size = ufbxi_min_sz(uc->data_size, uc->progress_interval); + uc->data_size -= uc->yield_size; + + if (ufbxi_get_read_offset(uc) - uc->latest_progress_bytes >= uc->progress_interval) { + ufbxi_check(ufbxi_report_progress(uc)); + } + + return 1; +} + +static ufbxi_noinline const char *ufbxi_yield(ufbxi_context *uc, size_t size) +{ + const char *ret; + uc->data_size += uc->yield_size; + if (uc->data_size >= size) { + ret = uc->data; + } else { + ret = ufbxi_refill(uc, size, true); + } + uc->yield_size = ufbxi_min_sz(uc->data_size, ufbxi_max_sz(size, uc->progress_interval)); + uc->data_size -= uc->yield_size; + + ufbxi_check_return(ufbxi_report_progress(uc), NULL); + return ret; +} + +static ufbxi_forceinline const char *ufbxi_peek_bytes(ufbxi_context *uc, size_t size) +{ + if (uc->yield_size >= size) { + return uc->data; + } else { + return ufbxi_yield(uc, size); + } +} + +static ufbxi_forceinline const char *ufbxi_read_bytes(ufbxi_context *uc, size_t size) +{ + // Refill the current buffer if necessary + const char *ret; + if (uc->yield_size >= size) { + ret = uc->data; + } else { + ret = ufbxi_yield(uc, size); + if (!ret) return NULL; + } + + // Advance the read position inside the current buffer + uc->yield_size -= size; + uc->data = ret + size; + return ret; +} + +static ufbxi_forceinline void ufbxi_consume_bytes(ufbxi_context *uc, size_t size) +{ + // Bytes must have been checked first with `ufbxi_peek_bytes()` + ufbx_assert(size <= uc->yield_size); + uc->yield_size -= size; + uc->data += size; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_skip_bytes(ufbxi_context *uc, uint64_t size) +{ + if (uc->skip_fn) { + ufbxi_pause_progress(uc); + + if (size > uc->data_size) { + size -= uc->data_size; + uc->data += uc->data_size; + uc->data_size = 0; + + uc->data_offset += size; + while (size >= UFBXI_MAX_SKIP_SIZE) { + size -= UFBXI_MAX_SKIP_SIZE; + ufbxi_check_msg(uc->skip_fn(uc->read_user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file"); + + // Check that we can read at least one byte in case the file is broken + // and causes us to seek indefinitely forwards as `fseek()` does not + // report if we hit EOF... + char single_byte[1]; // ufbxi_uninit + size_t num_read = uc->read_fn(uc->read_user, single_byte, 1); + ufbxi_check_msg(num_read <= 1, "IO error"); + ufbxi_check_msg(num_read == 1, "Truncated file"); + } + + if (size > 0) { + ufbxi_check_msg(uc->skip_fn(uc->read_user, (size_t)size), "Truncated file"); + } + + } else { + uc->data += (size_t)size; + uc->data_size -= (size_t)size; + } + + ufbxi_check(ufbxi_resume_progress(uc)); + } else { + // Read and discard bytes in reasonable chunks + uint64_t skip_size = ufbxi_max64(uc->read_buffer_size, uc->opts.read_buffer_size); + while (size > 0) { + uint64_t to_skip = ufbxi_min64(size, skip_size); + ufbxi_check(ufbxi_read_bytes(uc, (size_t)to_skip)); + size -= to_skip; + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_to(ufbxi_context *uc, void *dst, size_t size) +{ + char *ptr = (char*)dst; + + ufbxi_pause_progress(uc); + + // Copy data from the current buffer first + size_t len = ufbxi_min_sz(uc->data_size, size); + memcpy(ptr, uc->data, len); + uc->data += len; + uc->data_size -= len; + ptr += len; + size -= len; + + // If there's data left to copy try to read from user IO + if (size > 0) { + uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin); + + uc->data_begin = uc->data = NULL; + uc->data_size = 0; + ufbxi_check(uc->read_fn); + + while (size > 0) { + size_t read_result = uc->read_fn(uc->read_user, ptr, size); + ufbxi_check_msg(read_result != SIZE_MAX, "IO error"); + ufbxi_check(read_result != 0); + + ptr += read_result; + size -= read_result; + uc->data_offset += read_result; + } + } + + ufbxi_check(ufbxi_resume_progress(uc)); + + return 1; +} + +static ufbxi_noinline void ufbxi_init_ator(ufbx_error *error, ufbxi_allocator *ator, const ufbx_allocator_opts *opts, const char *name) +{ + ufbx_allocator_opts zero_opts; + if (!opts) { + memset(&zero_opts, 0, sizeof(zero_opts)); + opts = &zero_opts; + } + + // `opts` is either passed in or `zero_opts`. + // cppcheck-suppress uninitvar + ator->ator = *opts; + ator->error = error; + ator->max_size = opts->memory_limit ? opts->memory_limit : SIZE_MAX; + ator->max_allocs = opts->allocation_limit ? opts->allocation_limit : SIZE_MAX; + ator->huge_size = opts->huge_threshold ? opts->huge_threshold : 0x100000; + ator->chunk_max = opts->max_chunk_size ? opts->max_chunk_size : 0x1000000; + ator->name = name; +} + +typedef struct { + ufbx_error error; + + ufbxi_allocator *parent_ator; + ufbxi_allocator ator; +} ufbxi_file_context; + +static ufbxi_noinline void ufbxi_begin_file_context(ufbxi_file_context *fc, ufbx_open_file_context ctx, const ufbx_allocator_opts *ator_opts) +{ + memset(fc, 0, sizeof(ufbxi_file_context)); + if (ctx) { + fc->parent_ator = (ufbxi_allocator*)ctx; + fc->ator = *fc->parent_ator; + fc->ator.error = &fc->error; + } else { + ufbxi_init_ator(&fc->error, &fc->ator, ator_opts, "file"); + } +} + +static ufbxi_noinline void ufbxi_end_file_context(ufbxi_file_context *fc, ufbx_error *error, bool ok) +{ + if (fc->parent_ator) { + fc->ator.error = fc->parent_ator->error; + *fc->parent_ator = fc->ator; + } else { + ufbxi_free_ator(&fc->ator); + } + if (error) { + if (!ok) { + ufbxi_fix_error_type(&fc->error, "Failed to open file", error); + } else { + ufbxi_clear_error(error); + } + } +} + +// -- File IO + +#if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) + +static ufbxi_noinline FILE *ufbxi_fopen(ufbxi_file_context *fc, const char *path, size_t path_len, bool null_terminated) +{ + FILE *file = NULL; +#if !defined(UFBX_STANDARD_C) && defined(_WIN32) + (void)null_terminated; + wchar_t wpath_buf[256], *wpath = NULL; // ufbxi_uninit + if (path_len < ufbxi_arraycount(wpath_buf) - 1) { + wpath = wpath_buf; + } else { + wpath = ufbxi_alloc(&fc->ator, wchar_t, path_len + 1); + if (!wpath) return NULL; + } + + // Convert UTF-8 to UTF-16 but allow stray surrogate pairs as the Windows + // file system encoding allows them as well.. + size_t wlen = 0; + for (size_t i = 0; i < path_len; ) { + uint32_t code = UINT32_MAX; + char c = path[i++]; + if ((c & 0x80) == 0) { + code = (uint32_t)c; + } else if ((c & 0xe0) == 0xc0) { + code = (uint32_t)(c & 0x1f); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + } else if ((c & 0xf0) == 0xe0) { + code = (uint32_t)(c & 0x0f); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + } else if ((c & 0xf8) == 0xf0) { + code = (uint32_t)(c & 0x07); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); + } + if (code < 0x10000) { + wpath[wlen++] = (wchar_t)code; + } else { + code -= 0x10000; + wpath[wlen++] = (wchar_t)(0xd800 + (code >> 10)); + wpath[wlen++] = (wchar_t)(0xdc00 + (code & 0x3ff)); + } + } + wpath[wlen] = 0; + + #if UFBXI_MSC_VER >= 1400 + if (_wfopen_s(&file, wpath, L"rb") != 0) file = NULL; + #else + file = _wfopen(wpath, L"rb"); + #endif + if (wpath != wpath_buf) { + ufbxi_free(&fc->ator, wchar_t, wpath, path_len + 1); + } +#else + char copy_buf[256], *copy = NULL; // ufbxi_uninit + if (null_terminated) { + copy = (char*)path; + } else { + if (path_len < ufbxi_arraycount(copy_buf) - 1) { + copy = copy_buf; + } else { + copy = ufbxi_alloc(&fc->ator, char, path_len + 1); + if (!copy) return NULL; + } + memcpy(copy, path, path_len); + copy[path_len] = '\0'; + } + file = fopen(copy, "rb"); + if (!null_terminated && copy != copy_buf) { + ufbxi_free(&fc->ator, char, copy, path_len + 1); + } +#endif + if (!file) { + ufbxi_set_err_info(&fc->error, path, path_len); + ufbxi_report_err_msg(&fc->error, "file", "File not found"); + } + return file; +} + +static uint64_t ufbxi_ftell(FILE *file) +{ +#if !defined(UFBX_STANDARD_C) && defined(UFBX_HAS_FTELLO) + off_t result = ftello(file); + if (result >= 0) return (uint64_t)result; +#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + int64_t result = _ftelli64(file); + if (result >= 0) return (uint64_t)result; +#else + int64_t result = ftell(file); + if (result >= 0) return (uint64_t)result; +#endif + return UINT64_MAX; +} + +static size_t ufbxi_stdio_read(void *user, void *data, size_t max_size) +{ + FILE *file = (FILE*)user; + if (ferror(file)) return SIZE_MAX; + return fread(data, 1, max_size, file); +} + +static bool ufbxi_stdio_skip(void *user, size_t size) +{ + FILE *file = (FILE*)user; + ufbx_assert(size <= UFBXI_MAX_SKIP_SIZE); + if (fseek(file, (long)size, SEEK_CUR) != 0) return false; + if (ferror(file)) return false; + return true; +} + +static uint64_t ufbxi_stdio_size(void *user) +{ + FILE *file = (FILE*)user; + uint64_t result = 0; + uint64_t begin = ufbxi_ftell(file); + if (begin < UINT64_MAX) { + fpos_t pos; // ufbxi_uninit + if (fgetpos(file, &pos) == 0) { + if (fseek(file, 0, SEEK_END) == 0) { + uint64_t end = ufbxi_ftell(file); + if (end != UINT64_MAX && begin < end) { + result = end - begin; + } + // Both `rewind()` and `fsetpos()` to reset error and EOF + rewind(file); + fsetpos(file, &pos); + } + } + } + return result; +} + +static void ufbxi_stdio_close(void *user) +{ + FILE *file = (FILE*)user; + fclose(file); +} + +static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) +{ + stream->read_fn = &ufbxi_stdio_read; + stream->skip_fn = &ufbxi_stdio_skip; + stream->size_fn = &ufbxi_stdio_size; + stream->close_fn = close ? &ufbxi_stdio_close : NULL; + stream->user = file; +} + +static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) +{ + FILE *file = ufbxi_fopen(fc, path, path_len, null_terminated); + if (!file) return false; + ufbxi_stdio_init(stream, file, true); + return true; +} + +#elif defined(UFBX_EXTERNAL_STDIO) + +static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) +{ + stream->read_fn = &ufbx_stdio_read; + stream->skip_fn = &ufbx_stdio_skip; + stream->size_fn = &ufbx_stdio_size; + stream->close_fn = close ? &ufbx_stdio_close : NULL; + stream->user = file; +} + +static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) +{ + char copy_buf[256], *copy = NULL; // ufbxi_uninit + if (null_terminated) { + copy = (char*)path; + } else { + if (path_len < ufbxi_arraycount(copy_buf) - 1) { + copy = copy_buf; + } else { + copy = ufbxi_alloc(&fc->ator, char, path_len + 1); + if (!copy) return false; + } + memcpy(copy, path, path_len); + copy[path_len] = '\0'; + } + void *file = ufbx_stdio_open(copy, path_len); + if (!null_terminated && copy != copy_buf) { + ufbxi_free(&fc->ator, char, copy, path_len + 1); + } + if (!file) { + ufbxi_set_err_info(&fc->error, path, path_len); + ufbxi_report_err_msg(&fc->error, "file", "File not found"); + return false; + } + ufbxi_stdio_init(stream, file, true); + return true; +} + +#endif + +// -- Memory IO + +typedef struct { + const void *data; + size_t size; + size_t position; + ufbx_close_memory_cb close_cb; + + // Own allocation information + size_t self_size; + ufbxi_allocator *parent_ator; + ufbxi_allocator local_ator; + ufbx_error error; + char data_copy[]; +} ufbxi_memory_stream; + +static size_t ufbxi_memory_read(void *user, void *data, size_t max_size) +{ + ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; + size_t to_read = ufbxi_min_sz(stream->size - stream->position, max_size); + memcpy(data, (const char*)stream->data + stream->position, to_read); + stream->position += to_read; + return to_read; +} + +static bool ufbxi_memory_skip(void *user, size_t size) +{ + ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; + if (stream->size - stream->position < size) return false; + stream->position += size; + return true; +} + +static uint64_t ufbxi_memory_size(void *user) +{ + ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; + return stream->size; +} + +static void ufbxi_memory_close(void *user) +{ + ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; + if (stream->close_cb.fn) { + stream->close_cb.fn(stream->close_cb.user, (void*)stream->data, stream->size); + } + + if (stream->parent_ator) { + ufbxi_free(stream->parent_ator, char, stream, stream->self_size); + } else { + ufbxi_allocator ator = stream->local_ator; + ufbxi_free(&ator, char, stream, stream->self_size); + ufbxi_free_ator(&ator); + } +} + +// -- XML + +#if UFBXI_FEATURE_XML + +typedef struct ufbxi_xml_tag ufbxi_xml_tag; +typedef struct ufbxi_xml_attrib ufbxi_xml_attrib; +typedef struct ufbxi_xml_document ufbxi_xml_document; + +struct ufbxi_xml_attrib { + ufbx_string name; + ufbx_string value; +}; + +struct ufbxi_xml_tag { + ufbx_string name; + ufbx_string text; + + ufbxi_xml_attrib *attribs; + size_t num_attribs; + + ufbxi_xml_tag *children; + size_t num_children; +}; + +struct ufbxi_xml_document { + ufbxi_xml_tag *root; + ufbxi_buf buf; +}; + +typedef struct { + ufbx_error error; + + ufbxi_allocator *ator; + + ufbxi_buf tmp_stack; + ufbxi_buf result; + + ufbxi_xml_document *doc; + + ufbx_read_fn *read_fn; + void *read_user; + + char *tok; + size_t tok_cap; + size_t tok_len; + + const char *pos, *pos_end; + char data[4096]; + + bool io_error; +} ufbxi_xml_context; + +enum { + UFBXI_XML_CTYPE_WHITESPACE = 0x1, + UFBXI_XML_CTYPE_SINGLE_QUOTE = 0x2, + UFBXI_XML_CTYPE_DOUBLE_QUOTE = 0x4, + UFBXI_XML_CTYPE_NAME_END = 0x8, + UFBXI_XML_CTYPE_TAG_START = 0x10, + UFBXI_XML_CTYPE_END_OF_FILE = 0x20, +}; + +// Generated by `misc/gen_xml_ctype.py` +static const uint8_t ufbxi_xml_ctype[256] = { + 32,0,0,0,0,0,0,0,0,9,9,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 9,0,12,0,0,0,0,10,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8, +}; + +static ufbxi_noinline void ufbxi_xml_refill(ufbxi_xml_context *xc) +{ + size_t num = xc->read_fn(xc->read_user, xc->data, sizeof(xc->data)); + if (num == SIZE_MAX || num < sizeof(xc->data)) xc->io_error = true; + if (num < sizeof(xc->data)) { + xc->data[num++] = '\0'; + } + xc->pos = xc->data; + xc->pos_end = xc->data + num; +} + +static ufbxi_forceinline void ufbxi_xml_advance(ufbxi_xml_context *xc) +{ + if (++xc->pos == xc->pos_end) ufbxi_xml_refill(xc); +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_push_token_char(ufbxi_xml_context *xc, char c) +{ + if (xc->tok_len == xc->tok_cap || UFBXI_IS_REGRESSION) { + ufbxi_check_err(&xc->error, ufbxi_grow_array(xc->ator, &xc->tok, &xc->tok_cap, xc->tok_len + 1)); + } + xc->tok[xc->tok_len++] = c; + return 1; +} + +static ufbxi_noinline int ufbxi_xml_accept(ufbxi_xml_context *xc, char ch) +{ + if (*xc->pos == ch) { + ufbxi_xml_advance(xc); + return 1; + } else { + return 0; + } +} + +static ufbxi_noinline void ufbxi_xml_skip_while(ufbxi_xml_context *xc, uint32_t ctypes) +{ + while (ufbxi_xml_ctype[(uint8_t)*xc->pos] & ctypes) { + ufbxi_xml_advance(xc); + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_skip_until_string(ufbxi_xml_context *xc, ufbx_string *dst, const char *suffix) +{ + xc->tok_len = 0; + size_t match_len = 0, ix = 0, suffix_len = strlen(suffix); + char buf[16] = { 0 }; + size_t wrap_mask = sizeof(buf) - 1; + ufbx_assert(suffix_len < sizeof(buf)); + for (;;) { + char c = *xc->pos; + ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file"); + ufbxi_xml_advance(xc); + if (ix >= suffix_len) { + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, buf[(ix - suffix_len) & wrap_mask])); + } + + buf[ix++ & wrap_mask] = c; + for (match_len = 0; match_len < suffix_len; match_len++) { + if (buf[(ix - suffix_len + match_len) & wrap_mask] != suffix[match_len]) { + break; + } + } + if (match_len == suffix_len) break; + } + + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); + if (dst) { + dst->length = xc->tok_len - 1; + dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); + ufbxi_check_err(&xc->error, dst->data); + } + + return 1; +} + +static ufbxi_noinline int ufbxi_xml_read_until(ufbxi_xml_context *xc, ufbx_string *dst, uint32_t ctypes) +{ + xc->tok_len = 0; + for (;;) { + char c = *xc->pos; + + if (c == '&') { + size_t entity_begin = xc->tok_len; + for (;;) { + ufbxi_xml_advance(xc); + c = *xc->pos; + ufbxi_check_err(&xc->error, c != '\0'); + if (c == ';') break; + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c)); + } + ufbxi_xml_advance(xc); + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); + + char *entity = xc->tok + entity_begin; + xc->tok_len = entity_begin; + + if (entity[0] == '#') { + unsigned long code = 0; + if (entity[1] == 'x') { + code = ufbxi_parse_uint32_radix(entity + 2, 16); + } else { + code = ufbxi_parse_uint32_radix(entity + 1, 10); + } + + char bytes[5] = { 0 }; + if (code < 0x80) { + bytes[0] = (char)code; + } else if (code < 0x800) { + bytes[0] = (char)(0xc0 | (code>>6)); + bytes[1] = (char)(0x80 | (code & 0x3f)); + } else if (code < 0x10000) { + bytes[0] = (char)(0xe0 | (code>>12)); + bytes[1] = (char)(0x80 | ((code>>6) & 0x3f)); + bytes[2] = (char)(0x80 | (code & 0x3f)); + } else { + bytes[0] = (char)(0xf0 | (code>>18)); + bytes[1] = (char)(0x80 | ((code>>12) & 0x3f)); + bytes[2] = (char)(0x80 | ((code>>6) & 0x3f)); + bytes[3] = (char)(0x80 | (code & 0x3f)); + } + for (char *b = bytes; *b; b++) { + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, *b)); + } + } else { + char ch = '\0'; + if (!strcmp(entity, "lt")) ch = '<'; + else if (!strcmp(entity, "quot")) ch = '"'; + else if (!strcmp(entity, "amp")) ch = '&'; + else if (!strcmp(entity, "apos")) ch = '\''; + else if (!strcmp(entity, "gt")) ch = '>'; + if (ch) { + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, ch)); + } + } + } else { + if ((ufbxi_xml_ctype[(uint8_t)c] & ctypes) != 0) break; + ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file"); + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c)); + ufbxi_xml_advance(xc); + } + } + + ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); + if (dst) { + dst->length = xc->tok_len - 1; + dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); + ufbxi_check_err(&xc->error, dst->data); + } + + return 1; +} + +// Recursion limited by check at the start +static ufbxi_noinline int ufbxi_xml_parse_tag(ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening) + ufbxi_recursive_function(int, ufbxi_xml_parse_tag, (xc, depth, p_closing, opening), UFBXI_MAX_XML_DEPTH + 1, + (ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening)) +{ + ufbxi_check_err(&xc->error, depth < UFBXI_MAX_XML_DEPTH); + + if (!ufbxi_xml_accept(xc, '<')) { + if (*xc->pos == '\0') { + *p_closing = true; + } else { + ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_TAG_START | UFBXI_XML_CTYPE_END_OF_FILE)); + bool has_text = false; + for (size_t i = 0; i < xc->tok_len; i++) { + if ((ufbxi_xml_ctype[(uint8_t)xc->tok[i]] & UFBXI_XML_CTYPE_WHITESPACE) == 0) { + has_text = true; + break; + } + } + + if (has_text) { + ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); + ufbxi_check_err(&xc->error, tag); + tag->name.data = ufbxi_empty_char; + + tag->text.length = xc->tok_len - 1; + tag->text.data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); + ufbxi_check_err(&xc->error, tag->text.data); + } + } + return 1; + } + + if (ufbxi_xml_accept(xc, '/')) { + ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_NAME_END)); + ufbxi_check_err(&xc->error, opening && !strcmp(xc->tok, opening)); + ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); + if (!ufbxi_xml_accept(xc, '>')) return 0; + *p_closing = true; + return 1; + } else if (ufbxi_xml_accept(xc, '!')) { + if (ufbxi_xml_accept(xc, '[')) { + for (const char *ch = "CDATA["; *ch; ch++) { + if (!ufbxi_xml_accept(xc, *ch)) return 0; + } + + ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); + ufbxi_check_err(&xc->error, tag); + ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, &tag->text, "]]>")); + tag->name.data = ufbxi_empty_char; + + } else if (ufbxi_xml_accept(xc, '-')) { + if (!ufbxi_xml_accept(xc, '-')) return 0; + ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "-->")); + } else { + // TODO: !DOCTYPE + ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, ">")); + } + return 1; + } else if (ufbxi_xml_accept(xc, '?')) { + ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "?>")); + return 1; + } + + ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); + ufbxi_check_err(&xc->error, tag); + ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &tag->name, UFBXI_XML_CTYPE_NAME_END)); + tag->text.data = ufbxi_empty_char; + + bool has_children = false; + + size_t num_attribs = 0; + for (;;) { + ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); + if (ufbxi_xml_accept(xc, '/')) { + if (!ufbxi_xml_accept(xc, '>')) return 0; + break; + } else if (ufbxi_xml_accept(xc, '>')) { + has_children = true; + break; + } else { + ufbxi_xml_attrib *attrib = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_attrib, 1); + ufbxi_check_err(&xc->error, attrib); + ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->name, UFBXI_XML_CTYPE_NAME_END)); + ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); + if (!ufbxi_xml_accept(xc, '=')) return 0; + ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); + uint32_t quote_ctype = 0; + if (ufbxi_xml_accept(xc, '"')) { + quote_ctype = UFBXI_XML_CTYPE_DOUBLE_QUOTE; + } else if (ufbxi_xml_accept(xc, '\'')) { + quote_ctype = UFBXI_XML_CTYPE_SINGLE_QUOTE; + } else { + ufbxi_fail_err(&xc->error, "Bad attrib value"); + } + ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->value, quote_ctype)); + ufbxi_xml_advance(xc); + num_attribs++; + } + } + + tag->num_attribs = num_attribs; + tag->attribs = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_attrib, num_attribs); + ufbxi_check_err(&xc->error, tag->attribs); + + if (has_children) { + size_t children_begin = xc->tmp_stack.num_items; + for (;;) { + bool closing = false; + ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, depth + 1, &closing, tag->name.data)); + if (closing) break; + } + + tag->num_children = xc->tmp_stack.num_items - children_begin; + tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children); + ufbxi_check_err(&xc->error, tag->children); + } + + return 1; +} + +static ufbxi_noinline int ufbxi_xml_parse_root(ufbxi_xml_context *xc) +{ + ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->result, ufbxi_xml_tag, 1); + ufbxi_check_err(&xc->error, tag); + tag->name.data = ufbxi_empty_char; + tag->text.data = ufbxi_empty_char; + + for (;;) { + bool closing = false; + ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, 0, &closing, NULL)); + if (closing) break; + } + + tag->num_children = xc->tmp_stack.num_items; + tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children); + ufbxi_check_err(&xc->error, tag->children); + + xc->doc = ufbxi_push(&xc->result, ufbxi_xml_document, 1); + ufbxi_check_err(&xc->error, xc->doc); + + xc->doc->root = tag; + xc->doc->buf = xc->result; + + return 1; +} + +typedef struct { + ufbxi_allocator *ator; + ufbx_read_fn *read_fn; + void *read_user; + const char *prefix; + size_t prefix_length; +} ufbxi_xml_load_opts; + +static ufbxi_noinline ufbxi_xml_document *ufbxi_load_xml(ufbxi_xml_load_opts *opts, ufbx_error *error) +{ + ufbxi_xml_context xc = { UFBX_ERROR_NONE }; + xc.ator = opts->ator; + xc.read_fn = opts->read_fn; + xc.read_user = opts->read_user; + + xc.tmp_stack.ator = xc.ator; + xc.result.ator = xc.ator; + + xc.result.unordered = true; + + if (opts->prefix_length > 0) { + xc.pos = opts->prefix; + xc.pos_end = opts->prefix + opts->prefix_length; + } else { + ufbxi_xml_refill(&xc); + } + + int ok = ufbxi_xml_parse_root(&xc); + + ufbxi_buf_free(&xc.tmp_stack); + ufbxi_free(xc.ator, char, xc.tok, xc.tok_cap); + + if (ok) { + return xc.doc; + } else { + ufbxi_buf_free(&xc.result); + if (error) { + *error = xc.error; + } + + return NULL; + } +} + +static ufbxi_noinline void ufbxi_free_xml(ufbxi_xml_document *doc) +{ + ufbxi_buf buf = doc->buf; + ufbxi_buf_free(&buf); +} + +static ufbxi_noinline ufbxi_xml_tag *ufbxi_xml_find_child(ufbxi_xml_tag *tag, const char *name) +{ + ufbxi_for(ufbxi_xml_tag, child, tag->children, tag->num_children) { + if (!strcmp(child->name.data, name)) { + return child; + } + } + return NULL; +} + +static ufbxi_noinline ufbxi_xml_attrib *ufbxi_xml_find_attrib(ufbxi_xml_tag *tag, const char *name) +{ + ufbxi_for(ufbxi_xml_attrib, attrib, tag->attribs, tag->num_attribs) { + if (!strcmp(attrib->name.data, name)) { + return attrib; + } + } + return NULL; +} + +#endif + +// -- FBX value type information + +static char ufbxi_normalize_array_type(char type, char bool_type) { + switch (type) { + case 'r': return sizeof(ufbx_real) == sizeof(float) ? 'f' : 'd'; + case 'b': return bool_type; + default: return type; + } +} + +static ufbxi_noinline size_t ufbxi_array_type_size(char type) +{ + switch (type) { + case 'r': return sizeof(ufbx_real); + case 'b': return sizeof(bool); + case 'c': return sizeof(uint8_t); + case 'i': return sizeof(int32_t); + case 'l': return sizeof(int64_t); + case 'f': return sizeof(float); + case 'd': return sizeof(double); + case 's': return sizeof(ufbx_string); + case 'S': return sizeof(ufbx_string); + case 'C': return sizeof(ufbx_string); + default: return 1; + } +} + +// -- Node operations + +static ufbxi_noinline ufbxi_node *ufbxi_find_child(ufbxi_node *node, const char *name) +{ + ufbxi_for(ufbxi_node, c, node->children, node->num_children) { + if (c->name == name) return c; + } + return NULL; +} + +// Retrieve the type of a given value +ufbxi_forceinline static ufbxi_value_type ufbxi_get_val_type(ufbxi_node *node, size_t ix) +{ + return (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3); +} + +// Retrieve values from nodes with type codes: +// Any: '_' (ignore) +// NUMBER: 'I' int32_t 'L' int64_t 'F' float 'D' double 'R' ufbxi_real 'B' bool 'Z' size_t +// STRING: 'S' ufbx_string 'C' const char* (checked) 's' ufbx_string 'c' const char * (unchecked) 'b' ufbx_blob +ufbxi_nodiscard ufbxi_forceinline static int ufbxi_get_val_at(ufbxi_node *node, size_t ix, char fmt, void *v) +{ + ufbxi_dev_assert(ix < UFBXI_MAX_NON_ARRAY_VALUES); + ufbxi_value_type type = (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3); + switch (fmt) { + case '_': return 1; + case 'I': if (type == UFBXI_VALUE_NUMBER) { *(int32_t*)v = (int32_t)node->vals[ix].i; return 1; } else return 0; + case 'L': if (type == UFBXI_VALUE_NUMBER) { *(int64_t*)v = (int64_t)node->vals[ix].i; return 1; } else return 0; + case 'F': if (type == UFBXI_VALUE_NUMBER) { *(float*)v = (float)node->vals[ix].f; return 1; } else return 0; + case 'D': if (type == UFBXI_VALUE_NUMBER) { *(double*)v = (double)node->vals[ix].f; return 1; } else return 0; + case 'R': if (type == UFBXI_VALUE_NUMBER) { *(ufbx_real*)v = (ufbx_real)node->vals[ix].f; return 1; } else return 0; + case 'B': if (type == UFBXI_VALUE_NUMBER) { *(bool*)v = node->vals[ix].i != 0; return 1; } else return 0; + case 'Z': if (type == UFBXI_VALUE_NUMBER) { if (node->vals[ix].i < 0) return 0; *(size_t*)v = (size_t)node->vals[ix].i; return 1; } else return 0; + case 'S': if (type == UFBXI_VALUE_STRING) { + ufbxi_sanitized_string src = node->vals[ix].s; + ufbx_string *dst = (ufbx_string*)v; + if (src.utf8_length > 0) { + if (src.utf8_length == UINT32_MAX) return 0; + dst->data = src.raw_data + src.raw_length + 1; + dst->length = src.utf8_length; + } else { + dst->data = src.raw_data; + dst->length = src.raw_length; + } + return 1; + } else return 0; + case 's': if (type == UFBXI_VALUE_STRING) { + ufbxi_sanitized_string src = node->vals[ix].s; + ufbx_string *dst = (ufbx_string*)v; + dst->data = src.raw_data; + dst->length = src.raw_length; + return 1; + } else return 0; + case 'C': if (type == UFBXI_VALUE_STRING) { + ufbxi_sanitized_string src = node->vals[ix].s; + const char **dst = (const char **)v; + if (src.utf8_length > 0) { + if (src.utf8_length == UINT32_MAX) return 0; + *dst = src.raw_data + src.raw_length + 1; + } else { + *dst = src.raw_data; + } + return 1; + } else return 0; + case 'c': if (type == UFBXI_VALUE_STRING) { + ufbxi_sanitized_string src = node->vals[ix].s; + const char **dst = (const char **)v; + *dst = src.raw_data; + return 1; + } else return 0; + case 'b': if (type == UFBXI_VALUE_STRING) { + ufbxi_sanitized_string src = node->vals[ix].s; + ufbx_blob *dst = (ufbx_blob*)v; + dst->data = src.raw_data; + dst->size = src.raw_length; + return 1; + } else return 0; + default: + ufbxi_unreachable("Bad format char"); + return 0; + } +} + +ufbxi_nodiscard ufbxi_noinline static ufbxi_value_array *ufbxi_get_array(ufbxi_node *node, char fmt) +{ + if (node->value_type_mask != UFBXI_VALUE_ARRAY) return NULL; + ufbxi_value_array *array = node->array; + if (fmt != '?') { + fmt = ufbxi_normalize_array_type(fmt, 'b'); + if (array->type != fmt) return NULL; + } + return array; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val1(ufbxi_node *node, const char *fmt, void *v0) +{ + if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val2(ufbxi_node *node, const char *fmt, void *v0, void *v1) +{ + if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; + if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val3(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2) +{ + if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; + if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; + if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val4(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3) +{ + if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; + if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; + if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; + if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val5(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3, void *v4) +{ + if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; + if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; + if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; + if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0; + if (!ufbxi_get_val_at(node, 4, fmt[4], v4)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val1(ufbxi_node *node, const char *name, const char *fmt, void *v0) +{ + ufbxi_node *child = ufbxi_find_child(node, name); + if (!child) return 0; + if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val2(ufbxi_node *node, const char *name, const char *fmt, void *v0, void *v1) +{ + ufbxi_node *child = ufbxi_find_child(node, name); + if (!child) return 0; + if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0; + if (!ufbxi_get_val_at(child, 1, fmt[1], v1)) return 0; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline ufbxi_value_array *ufbxi_find_array(ufbxi_node *node, const char *name, char fmt) +{ + ufbxi_node *child = ufbxi_find_child(node, name); + if (!child) return NULL; + return ufbxi_get_array(child, fmt); +} + +static ufbxi_node *ufbxi_find_child_strcmp(ufbxi_node *node, const char *name) +{ + char leading = name[0]; + ufbxi_for(ufbxi_node, c, node->children, node->num_children) { + if (c->name[0] != leading) continue; + if (!strcmp(c->name, name)) return c; + } + return NULL; +} + +// -- Element extra data allocation + +ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_element_extra_size(ufbxi_context *uc, uint32_t id, size_t size) +{ + if (uc->element_extra_cap <= id) { + size_t old_cap = uc->element_extra_cap; + ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->element_extra_arr, &uc->element_extra_cap, id + 1), NULL); + memset(uc->element_extra_arr + old_cap, 0, (uc->element_extra_cap - old_cap) * sizeof(void*)); + } + + if (uc->element_extra_arr[id]) return uc->element_extra_arr[id]; + + void *extra = ufbxi_push_size_zero(&uc->tmp, size, 1); + ufbxi_check_return(extra, NULL); + uc->element_extra_arr[id] = extra; + + return extra; +} + +static ufbxi_noinline void *ufbxi_get_element_extra(ufbxi_context *uc, uint32_t id) +{ + if (id < uc->element_extra_cap) { + return uc->element_extra_arr[id]; + } else { + return NULL; + } +} + +#define ufbxi_push_element_extra(uc, id, type) (type*)ufbxi_push_element_extra_size((uc), (id), sizeof(type)) + +// -- Parsing state machine +// +// When reading the file we maintain a coarse representation of the structure so +// that we can resolve array info (type, included in result, etc). Using this info +// we can often read/decompress the contents directly into the right memory area. + +typedef enum { + UFBXI_PARSE_ROOT, + UFBXI_PARSE_FBX_HEADER_EXTENSION, + UFBXI_PARSE_SCENE_INFO, + UFBXI_PARSE_THUMBNAIL, + UFBXI_PARSE_DEFINITIONS, + UFBXI_PARSE_OBJECTS, + UFBXI_PARSE_CONNECTIONS, + UFBXI_PARSE_RELATIONS, + UFBXI_PARSE_TAKES, + UFBXI_PARSE_FBX_VERSION, + UFBXI_PARSE_MODEL, + UFBXI_PARSE_GEOMETRY, + UFBXI_PARSE_NODE_ATTRIBUTE, + UFBXI_PARSE_LEGACY_MODEL, + UFBXI_PARSE_LEGACY_MEDIA, + UFBXI_PARSE_LEGACY_VIDEO, + UFBXI_PARSE_LEGACY_SWITCHER, + UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE, + UFBXI_PARSE_REFERENCES, + UFBXI_PARSE_REFERENCE, + UFBXI_PARSE_ANIMATION_CURVE, + UFBXI_PARSE_DEFORMER, + UFBXI_PARSE_ASSOCIATE_MODEL, + UFBXI_PARSE_LEGACY_LINK, + UFBXI_PARSE_POSE, + UFBXI_PARSE_POSE_NODE, + UFBXI_PARSE_TEXTURE, + UFBXI_PARSE_VIDEO, + UFBXI_PARSE_LAYERED_TEXTURE, + UFBXI_PARSE_SELECTION_NODE, + UFBXI_PARSE_COLLECTION, + UFBXI_PARSE_AUDIO, + UFBXI_PARSE_UNKNOWN_OBJECT, + UFBXI_PARSE_LAYER_ELEMENT_NORMAL, + UFBXI_PARSE_LAYER_ELEMENT_BINORMAL, + UFBXI_PARSE_LAYER_ELEMENT_TANGENT, + UFBXI_PARSE_LAYER_ELEMENT_UV, + UFBXI_PARSE_LAYER_ELEMENT_COLOR, + UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE, + UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE, + UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING, + UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY, + UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP, + UFBXI_PARSE_LAYER_ELEMENT_HOLE, + UFBXI_PARSE_LAYER_ELEMENT_MATERIAL, + UFBXI_PARSE_LAYER_ELEMENT_OTHER, + UFBXI_PARSE_GEOMETRY_UV_INFO, + UFBXI_PARSE_SHAPE, + UFBXI_PARSE_TAKE, + UFBXI_PARSE_TAKE_OBJECT, + UFBXI_PARSE_CHANNEL, + UFBXI_PARSE_UNKNOWN, +} ufbxi_parse_state; + +typedef enum { + UFBXI_ARRAY_FLAG_RESULT = 0x1, // < Allocate the array from the result buffer + UFBXI_ARRAY_FLAG_TMP_BUF = 0x2, // < Allocate the array from the long-term temporary buffer + UFBXI_ARRAY_FLAG_PAD_BEGIN = 0x4, // < Pad the begin of the array with 4 zero elements to guard from invalid -1 index accesses + UFBXI_ARRAY_FLAG_ACCURATE_F32 = 0x8, // < Must be parsed as bit-accurate 32-bit floats +} ufbxi_array_flags; + +typedef struct { + char type; // < FBX type code of the array: b,i,l,f,d (or 'r' meaning ufbx_real '-' ignore, 's'/'S' for strings, 'C' for content) + uint8_t flags; // < Combination of `ufbxi_array_flags` +} ufbxi_array_info; + +static ufbxi_noinline ufbxi_parse_state ufbxi_update_parse_state(ufbxi_parse_state parent, const char *name) +{ + switch (parent) { + + case UFBXI_PARSE_ROOT: + if (name == ufbxi_FBXHeaderExtension) return UFBXI_PARSE_FBX_HEADER_EXTENSION; + if (name == ufbxi_Definitions) return UFBXI_PARSE_DEFINITIONS; + if (name == ufbxi_Objects) return UFBXI_PARSE_OBJECTS; + if (name == ufbxi_Connections) return UFBXI_PARSE_CONNECTIONS; + if (name == ufbxi_Takes) return UFBXI_PARSE_TAKES; + if (name == ufbxi_Model) return UFBXI_PARSE_LEGACY_MODEL; + if (!strcmp(name, "References")) return UFBXI_PARSE_REFERENCES; + if (!strcmp(name, "Relations")) return UFBXI_PARSE_RELATIONS; + if (name == ufbxi_Media) return UFBXI_PARSE_LEGACY_MEDIA; + if (!strcmp(name, "Switcher")) return UFBXI_PARSE_LEGACY_SWITCHER; + if (!strcmp(name, "SceneGenericPersistence")) return UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE; + break; + + case UFBXI_PARSE_FBX_HEADER_EXTENSION: + if (name == ufbxi_FBXVersion) return UFBXI_PARSE_FBX_VERSION; + if (name == ufbxi_SceneInfo) return UFBXI_PARSE_SCENE_INFO; + break; + + case UFBXI_PARSE_SCENE_INFO: + if (name == ufbxi_Thumbnail) return UFBXI_PARSE_THUMBNAIL; + break; + + case UFBXI_PARSE_OBJECTS: + if (name == ufbxi_Model) return UFBXI_PARSE_MODEL; + if (name == ufbxi_Geometry) return UFBXI_PARSE_GEOMETRY; + if (name == ufbxi_NodeAttribute) return UFBXI_PARSE_NODE_ATTRIBUTE; + if (name == ufbxi_AnimationCurve) return UFBXI_PARSE_ANIMATION_CURVE; + if (name == ufbxi_Deformer) return UFBXI_PARSE_DEFORMER; + if (name == ufbxi_Pose) return UFBXI_PARSE_POSE; + if (name == ufbxi_Texture) return UFBXI_PARSE_TEXTURE; + if (name == ufbxi_Video) return UFBXI_PARSE_VIDEO; + if (name == ufbxi_LayeredTexture) return UFBXI_PARSE_LAYERED_TEXTURE; + if (name == ufbxi_SelectionNode) return UFBXI_PARSE_SELECTION_NODE; + if (name == ufbxi_Collection) return UFBXI_PARSE_COLLECTION; + if (name == ufbxi_Audio) return UFBXI_PARSE_AUDIO; + return UFBXI_PARSE_UNKNOWN_OBJECT; + + case UFBXI_PARSE_MODEL: + case UFBXI_PARSE_GEOMETRY: + if (name[0] == 'L') { + if (name == ufbxi_LayerElementNormal) return UFBXI_PARSE_LAYER_ELEMENT_NORMAL; + if (name == ufbxi_LayerElementBinormal) return UFBXI_PARSE_LAYER_ELEMENT_BINORMAL; + if (name == ufbxi_LayerElementTangent) return UFBXI_PARSE_LAYER_ELEMENT_TANGENT; + if (name == ufbxi_LayerElementUV) return UFBXI_PARSE_LAYER_ELEMENT_UV; + if (name == ufbxi_LayerElementColor) return UFBXI_PARSE_LAYER_ELEMENT_COLOR; + if (name == ufbxi_LayerElementVertexCrease) return UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE; + if (name == ufbxi_LayerElementEdgeCrease) return UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE; + if (name == ufbxi_LayerElementSmoothing) return UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING; + if (name == ufbxi_LayerElementVisibility) return UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY; + if (name == ufbxi_LayerElementPolygonGroup) return UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP; + if (name == ufbxi_LayerElementHole) return UFBXI_PARSE_LAYER_ELEMENT_HOLE; + if (name == ufbxi_LayerElementMaterial) return UFBXI_PARSE_LAYER_ELEMENT_MATERIAL; + if (!strncmp(name, "LayerElement", 12)) return UFBXI_PARSE_LAYER_ELEMENT_OTHER; + } + if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE; + break; + + case UFBXI_PARSE_DEFORMER: + if (!strcmp(name, "AssociateModel")) return UFBXI_PARSE_ASSOCIATE_MODEL; + break; + + case UFBXI_PARSE_LEGACY_MEDIA: + if (name == ufbxi_Video) return UFBXI_PARSE_LEGACY_VIDEO; + break; + + case UFBXI_PARSE_LEGACY_VIDEO: + return UFBXI_PARSE_VIDEO; + + case UFBXI_PARSE_LEGACY_MODEL: + if (name == ufbxi_GeometryUVInfo) return UFBXI_PARSE_GEOMETRY_UV_INFO; + if (name == ufbxi_Link) return UFBXI_PARSE_LEGACY_LINK; + if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; + if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE; + break; + + case UFBXI_PARSE_POSE: + if (name == ufbxi_PoseNode) return UFBXI_PARSE_POSE_NODE; + break; + + case UFBXI_PARSE_TAKES: + if (name == ufbxi_Take) return UFBXI_PARSE_TAKE; + break; + + case UFBXI_PARSE_TAKE: + return UFBXI_PARSE_TAKE_OBJECT; + + case UFBXI_PARSE_TAKE_OBJECT: + if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; + break; + + case UFBXI_PARSE_CHANNEL: + if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; + break; + + case UFBXI_PARSE_REFERENCES: + return UFBXI_PARSE_REFERENCE; + + default: + break; + + } + + return UFBXI_PARSE_UNKNOWN; +} + +static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, ufbxi_array_info *info) +{ + info->flags = 0; + + // Retain all arrays if user wants the DOM representation + if (uc->opts.retain_dom) { + info->flags |= UFBXI_ARRAY_FLAG_RESULT; + } + + switch (parent) { + + case UFBXI_PARSE_THUMBNAIL: + if (name == ufbxi_ImageData) { + info->type = 'c'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_GEOMETRY: + case UFBXI_PARSE_MODEL: + if (name == ufbxi_Vertices) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_PolygonVertexIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Edges) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + return true; + } else if (name == ufbxi_Indexes) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Points) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_KnotVector) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_KnotVectorU) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_KnotVectorV) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_PointsIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Normals) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_LEGACY_MODEL: + if (name == ufbxi_Vertices) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_Normals) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_Materials) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_PolygonVertexIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Children) { + info->type = 's'; + return true; + } + break; + + case UFBXI_PARSE_ANIMATION_CURVE: + if (name == ufbxi_KeyTime) { + info->type = uc->opts.ignore_animation ? '-' : 'l'; + return true; + } else if (name == ufbxi_KeyValueFloat) { + info->type = uc->opts.ignore_animation ? '-' : 'r'; + return true; + } else if (name == ufbxi_KeyAttrFlags) { + info->type = uc->opts.ignore_animation ? '-' : 'i'; + return true; + } else if (name == ufbxi_KeyAttrDataFloat) { + // The float data in a keyframe attribute array is represented as integers + // in versions >= 7200 as some of the elements aren't actually floats (!) + info->type = uc->from_ascii && uc->version >= 7200 ? 'i' : 'f'; + if (uc->opts.ignore_animation) info->type = '-'; + if (uc->from_ascii && uc->version < 7200) { + info->flags |= UFBXI_ARRAY_FLAG_ACCURATE_F32; + } + return true; + } else if (name == ufbxi_KeyAttrRefCount) { + info->type = uc->opts.ignore_animation ? '-' : 'i'; + return true; + } + break; + + case UFBXI_PARSE_TEXTURE: + if (!strcmp(name, "ModelUVTranslation") || !strcmp(name, "ModelUVScaling") || !strcmp(name, "Cropping")) { + info->type = uc->opts.retain_dom ? 'r' : '-'; + return true; + } + break; + + case UFBXI_PARSE_VIDEO: + if (name == ufbxi_Content) { + info->type = uc->opts.ignore_embedded ? '-' : 'C'; + return true; + } + break; + + case UFBXI_PARSE_LAYERED_TEXTURE: + if (name == ufbxi_BlendModes) { + info->type = 'i'; + info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; + return true; + } else if (name == ufbxi_Alphas) { + info->type = 'r'; + info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; + return true; + } + break; + + case UFBXI_PARSE_SELECTION_NODE: + if (name == ufbxi_VertexIndexArray) { + info->type = 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_EdgeIndexArray) { + info->type = 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_PolygonIndexArray) { + info->type = 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_NORMAL: + if (name == ufbxi_Normals) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_NormalsIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_NormalsW) { + info->type = uc->retain_vertex_w ? 'r' : '-'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_BINORMAL: + if (name == ufbxi_Binormals) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_BinormalsIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_BinormalsW) { + info->type = uc->retain_vertex_w ? 'r' : '-'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_TANGENT: + if (name == ufbxi_Tangents) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_TangentsIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_TangentsW) { + info->type = uc->retain_vertex_w ? 'r' : '-'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_UV: + if (name == ufbxi_UV) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_UVIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_COLOR: + if (name == ufbxi_Colors) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_ColorIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE: + if (name == ufbxi_VertexCrease) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_VertexCreaseIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE: + if (name == ufbxi_EdgeCrease) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING: + if (name == ufbxi_Smoothing) { + info->type = uc->opts.ignore_geometry ? '-' : 'b'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY: + if (name == ufbxi_Visibility) { + info->type = uc->opts.ignore_geometry ? '-' : 'b'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP: + if (name == ufbxi_PolygonGroup) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_HOLE: + if (name == ufbxi_Hole) { + info->type = uc->opts.ignore_geometry ? '-' : 'b'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_MATERIAL: + if (name == ufbxi_Materials) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_LAYER_ELEMENT_OTHER: + if (name == ufbxi_TextureId) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; + return true; + } else if (name == ufbxi_UV) { + info->type = uc->opts.retain_dom ? 'r' : '-'; + return true; + } else if (name == ufbxi_UVIndex) { + info->type = uc->opts.retain_dom ? 'i' : '-'; + return true; + } + break; + + case UFBXI_PARSE_GEOMETRY_UV_INFO: + if (name == ufbxi_TextureUV) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } else if (name == ufbxi_TextureUVVerticeIndex) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_SHAPE: + if (name == ufbxi_Indexes) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + if (name == ufbxi_Vertices) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + if (name == ufbxi_Normals) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; + return true; + } + break; + + case UFBXI_PARSE_DEFORMER: + if (name == ufbxi_Transform) { + info->type = 'r'; + return true; + } else if (name == ufbxi_TransformLink) { + info->type = 'r'; + return true; + } else if (name == ufbxi_Indexes) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Weights) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_BlendWeights) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_FullWeights) { + info->type = 'r'; + info->flags = (uint8_t)(info->flags | (uc->blender_full_weights ? UFBXI_ARRAY_FLAG_RESULT : UFBXI_ARRAY_FLAG_TMP_BUF)); + return true; + } else if (!strcmp(name, "TransformAssociateModel")) { + info->type = uc->opts.retain_dom ? 'r' : '-'; + return true; + } + break; + + case UFBXI_PARSE_ASSOCIATE_MODEL: + if (name == ufbxi_Transform) { + info->type = uc->opts.retain_dom ? 'r' : '-'; + return true; + } + break; + + case UFBXI_PARSE_LEGACY_LINK: + if (name == ufbxi_Transform) { + info->type = 'r'; + return true; + } else if (name == ufbxi_TransformLink) { + info->type = 'r'; + return true; + } else if (name == ufbxi_Indexes) { + info->type = uc->opts.ignore_geometry ? '-' : 'i'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } else if (name == ufbxi_Weights) { + info->type = uc->opts.ignore_geometry ? '-' : 'r'; + info->flags = UFBXI_ARRAY_FLAG_RESULT; + return true; + } + break; + + case UFBXI_PARSE_POSE_NODE: + if (name == ufbxi_Matrix) { + info->type = 'r'; + return true; + } + break; + + case UFBXI_PARSE_CHANNEL: + if (name == ufbxi_Key) { + info->type = uc->opts.ignore_animation ? '-' : 'd'; + return true; + } + break; + + case UFBXI_PARSE_AUDIO: + if (name == ufbxi_Content) { + info->type = uc->opts.ignore_embedded ? '-' : 'C'; + return true; + } + break; + + default: + if (name == ufbxi_BinaryData) { + info->type = uc->opts.ignore_embedded ? '-' : 'C'; + return true; + } + break; + + } + + return false; +} + +static ufbxi_noinline bool ufbxi_is_raw_string(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, size_t index) +{ + (void)index; + + switch (parent) { + + case UFBXI_PARSE_ROOT: + if (name == ufbxi_Model) return true; + if (!strcmp(name, "FileId")) return true; + break; + + case UFBXI_PARSE_FBX_HEADER_EXTENSION: + if (name == ufbxi_SceneInfo) return true; + break; + + case UFBXI_PARSE_OBJECTS: + return true; + + case UFBXI_PARSE_CONNECTIONS: + case UFBXI_PARSE_RELATIONS: + // Pre-7000 needs raw strings for "Name\x00\x01Type" pairs, post-7000 uses it only + // for properties that are non-raw by default. + return uc->version < 7000; + + case UFBXI_PARSE_MODEL: + if (name == ufbxi_NodeAttributeName) return true; + if (name == ufbxi_Name) return true; + break; + + case UFBXI_PARSE_VIDEO: + if (name == ufbxi_Content) return true; + break; + + case UFBXI_PARSE_TEXTURE: + if (!strcmp(name, "TextureName")) return true; + if (name == ufbxi_Media) return true; + break; + + case UFBXI_PARSE_GEOMETRY: + if (name == ufbxi_NodeAttributeName) return true; + if (name == ufbxi_Name) return true; + break; + + case UFBXI_PARSE_NODE_ATTRIBUTE: + if (name == ufbxi_NodeAttributeName) return true; + if (name == ufbxi_Name) return true; + break; + + case UFBXI_PARSE_POSE_NODE: + if (name == ufbxi_Node) return true; + break; + + case UFBXI_PARSE_SELECTION_NODE: + if (name == ufbxi_Node) return true; + break; + + case UFBXI_PARSE_UNKNOWN_OBJECT: + if (name == ufbxi_NodeAttributeName) return true; + if (name == ufbxi_Name) return true; + break; + + case UFBXI_PARSE_COLLECTION: + if (!strcmp(name, "Member")) return true; + break; + + case UFBXI_PARSE_AUDIO: + if (name == ufbxi_Content) return true; + break; + + case UFBXI_PARSE_LEGACY_MODEL: + if (name == ufbxi_Material) return true; + if (name == ufbxi_Link) return true; + if (name == ufbxi_Name) return true; + break; + + case UFBXI_PARSE_LEGACY_SWITCHER: + if (!strcmp(name, "CameraIndexName")) return true; + break; + + case UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE: + if (name == ufbxi_SceneInfo) return true; + break; + + case UFBXI_PARSE_REFERENCE: + if (!strcmp(name, "Object")) return true; + break; + + case UFBXI_PARSE_TAKE: + if (name == ufbxi_Model) return true; + break; + + default: + break; + + } + + return false; +} + +// -- Binary parsing + +ufbxi_nodiscard static ufbxi_noinline char *ufbxi_swap_endian(ufbxi_context *uc, const void *src, size_t count, size_t elem_size) +{ + ufbxi_dev_assert(elem_size > 1); + size_t total_size = count * elem_size; + ufbxi_check_return(!ufbxi_does_overflow(total_size, count, elem_size), NULL); + if (uc->swap_arr_size < total_size) { + ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->swap_arr, &uc->swap_arr_size, total_size), NULL); + } + char *dst = uc->swap_arr, *d = dst; + + const char *s = (const char*)src; + switch (elem_size) { + case 2: + ufbxi_nounroll for (size_t i = 0; i < count; i++) { + d[0] = s[1]; d[1] = s[0]; + d += 2; s += 2; + } + break; + case 4: + ufbxi_nounroll for (size_t i = 0; i < count; i++) { + d[0] = s[3]; d[1] = s[2]; d[2] = s[1]; d[3] = s[0]; + d += 4; s += 4; + } + break; + case 8: + ufbxi_nounroll for (size_t i = 0; i < count; i++) { + d[0] = s[7]; d[1] = s[6]; d[2] = s[5]; d[3] = s[4]; + d[4] = s[3]; d[5] = s[2]; d[6] = s[1]; d[7] = s[0]; + d += 8; s += 8; + } + break; + default: + ufbxi_unreachable("Bad endian swap size"); + } + + return dst; +} + +// Swap the endianness of an array typed with a lowercase letter +ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_array(ufbxi_context *uc, const void *src, size_t count, char type) +{ + switch (type) { + case 'i': case 'f': return ufbxi_swap_endian(uc, src, count, 4); + case 'l': case 'd': return ufbxi_swap_endian(uc, src, count, 8); + default: return (const char*)src; + } +} + +// Swap the endianness of a single value (shallow, swaps string/array header words) +ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_value(ufbxi_context *uc, const void *src, char type) +{ + switch (type) { + case 'Y': return ufbxi_swap_endian(uc, src, 1, 2); + case 'I': case 'F': return ufbxi_swap_endian(uc, src, 1, 4); + case 'L': case 'D': return ufbxi_swap_endian(uc, src, 1, 8); + case 'S': case 'R': return ufbxi_swap_endian(uc, src, 1, 4); + case 'i': case 'l': case 'f': case 'd': case 'b': return ufbxi_swap_endian(uc, src, 3, 4); + default: return (const char*)src; + } +} + +// Read and convert a post-7000 FBX data array into a different format. `src_type` may be equal to `dst_type` +// if the platform is not binary compatible with the FBX data representation. +ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_convert_array(ufbxi_context *maybe_uc, char src_type, char dst_type, const void *src, void *dst, size_t size) +{ + // TODO: We might want to use the slow path if the machine float/double doesn't match IEEE 754! + // Convert commented out lines under some `#if UFBX_NON_IEE754` define or something. + if (src_type == dst_type) { + ufbx_assert(maybe_uc && maybe_uc->file_big_endian != maybe_uc->local_big_endian); + src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type); + ufbxi_check_err(&maybe_uc->error, src); + memcpy(dst, src, size * ufbxi_array_type_size(dst_type)); + return 1; + } + + if (maybe_uc && maybe_uc->file_big_endian) { + src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type); + ufbxi_check_err(&maybe_uc->error, src); + } + + switch (dst_type) + { + + #define ufbxi_convert_loop_fast(m_dst, m_cast, m_size, m_expr) do { \ + const char *val = (const char*)src, *val_end = val + size*m_size; \ + m_dst *d = (m_dst*)dst; \ + while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \ + } while (0) + + #define ufbxi_convert_loop_slow(m_dst, m_cast, m_size, m_expr) do { \ + const char *val = (const char*)src, *val_end = val + size*m_size; \ + m_dst *d = (m_dst*)dst; \ + ufbxi_nounroll while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \ + } while (0) + + case 'c': + switch (src_type) { + // case 'c': ufbxi_convert_loop_fast(char, (char), 1, *val != 0); break; + case 'i': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_i32(val)); break; + case 'l': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_i64(val)); break; + case 'f': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_f32(val)); break; + case 'd': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_f64(val)); break; + default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; + } + break; + + case 'i': + switch (src_type) { + case 'c': ufbxi_convert_loop_slow(int32_t, (int32_t), 1, *val); break; + // case 'i': ufbxi_convert_loop_slow(int32_t, (int32_t), 4, ufbxi_read_i32(val)); break; + case 'l': ufbxi_convert_loop_slow(int32_t, (int32_t), 8, ufbxi_read_i64(val)); break; + case 'f': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 4, ufbxi_read_f32(val)); break; + case 'd': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 8, ufbxi_read_f64(val)); break; + default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; + } + break; + + case 'l': + switch (src_type) { + case 'c': ufbxi_convert_loop_slow(int64_t, (int64_t), 1, *val); break; + case 'i': ufbxi_convert_loop_slow(int64_t, (int64_t), 4, ufbxi_read_i32(val)); break; + // case 'l': ufbxi_convert_loop_slow(int64_t, (int64_t), 8, ufbxi_read_i64(val)); break; + case 'f': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 4, ufbxi_read_f32(val)); break; + case 'd': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 8, ufbxi_read_f64(val)); break; + default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; + } + break; + + case 'f': + switch (src_type) { + case 'c': ufbxi_convert_loop_slow(float, (float), 1, *val); break; + case 'i': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_i32(val)); break; + case 'l': ufbxi_convert_loop_slow(float, (float), 8, ufbxi_read_i64(val)); break; + // case 'f': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_f32(val)); break; + case 'd': ufbxi_convert_loop_fast(float, (float), 8, ufbxi_read_f64(val)); break; + default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; + } + break; + + case 'd': + switch (src_type) { + case 'c': ufbxi_convert_loop_slow(double, (double), 1, *val); break; + case 'i': ufbxi_convert_loop_slow(double, (double), 4, ufbxi_read_i32(val)); break; + case 'l': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_i64(val)); break; + case 'f': ufbxi_convert_loop_fast(double, (double), 4, ufbxi_read_f32(val)); break; + // case 'd': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_f64(val)); break; + default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; + } + break; + + default: return 0; + + } + + return 1; +} + +// Read pre-7000 separate properties as an array. +ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_parse_multivalue_array(ufbxi_context *uc, char dst_type, void *dst, size_t size, ufbxi_buf *tmp_buf) +{ + if (size == 0) return 1; + const char *val; + size_t val_size; + + bool file_big_endian = uc->file_big_endian; + + #define ufbxi_convert_parse_fast(m_dst, m_type, m_expr) do { \ + m_dst *d = (m_dst*)dst; \ + for (; base < size; base++) { \ + val = ufbxi_peek_bytes(uc, 13); \ + ufbxi_check(val); \ + if (*val != m_type) break; \ + val++; \ + *d++ = (m_dst)(m_expr); \ + ufbxi_consume_bytes(uc, 1 + sizeof(m_dst)); \ + } \ + } while (0) + + // String array special case + if (dst_type == 's' || dst_type == 'S' || dst_type == 'C') { + bool raw = dst_type == 's'; + ufbx_string *d = (ufbx_string*)dst; + for (size_t i = 0; i < size; i++) { + val = ufbxi_peek_bytes(uc, 13); + ufbxi_check(val); + char type = *val++; + ufbxi_check(type == 'S' || type == 'R'); + if (file_big_endian) { + val = ufbxi_swap_endian_value(uc, val, type); + ufbxi_check(val); + } + size_t len = ufbxi_read_u32(val); + ufbxi_consume_bytes(uc, 5); + d->data = ufbxi_read_bytes(uc, len); + d->length = len; + ufbxi_check(d->data); + if (dst_type == 'C') { + ufbxi_buf *buf = size == 1 || uc->opts.retain_dom ? &uc->result : tmp_buf; + d->data = ufbxi_push_copy(buf, char, len, d->data); + ufbxi_check(d->data); + } else { + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, d, raw)); + } + d++; + } + return 1; + } + + // Optimize a couple of common cases + size_t base = 0; + if (!file_big_endian) { + switch (dst_type) { + case 'i': ufbxi_convert_parse_fast(int32_t, 'I', ufbxi_read_i32(val)); break; + case 'l': ufbxi_convert_parse_fast(int64_t, 'L', ufbxi_read_i64(val)); break; + case 'f': ufbxi_convert_parse_fast(float, 'F', ufbxi_read_f32(val)); break; + case 'd': ufbxi_convert_parse_fast(double, 'D', ufbxi_read_f64(val)); break; + default: break; // Fallthrough to rest + } + + // Early return if we handled everything + if (base == size) return 1; + } + + switch (dst_type) + { + + #define ufbxi_convert_parse(m_cast, m_size, m_expr) \ + *d++ = m_cast(m_expr); val_size = m_size + 1; \ + + #define ufbxi_convert_parse_switch(m_dst, m_cast_int, m_cast_float) do { \ + m_dst *d = (m_dst*)dst + base; \ + for (size_t i = base; i < size; i++) { \ + val = ufbxi_peek_bytes(uc, 13); \ + ufbxi_check(val); \ + char type = *val++; \ + if (file_big_endian) { \ + val = ufbxi_swap_endian_value(uc, val, type); \ + ufbxi_check(val); \ + } \ + switch (type) { \ + case 'C': \ + case 'B': ufbxi_convert_parse(m_cast_int, 1, *val); break; \ + case 'Y': ufbxi_convert_parse(m_cast_int, 2, ufbxi_read_i16(val)); break; \ + case 'I': ufbxi_convert_parse(m_cast_int, 4, ufbxi_read_i32(val)); break; \ + case 'L': ufbxi_convert_parse(m_cast_int, 8, ufbxi_read_i64(val)); break; \ + case 'F': ufbxi_convert_parse(m_cast_float, 4, ufbxi_read_f32(val)); break; \ + case 'D': ufbxi_convert_parse(m_cast_float, 8, ufbxi_read_f64(val)); break; \ + default: ufbxi_fail("Bad multivalue array type"); \ + } \ + ufbxi_consume_bytes(uc, val_size); \ + } \ + } while (0) + + case 'c': ufbxi_convert_parse_switch(uint8_t, (uint8_t), (uint8_t)); break; + case 'i': ufbxi_convert_parse_switch(int32_t, (int32_t), ufbxi_f64_to_i32); break; + case 'l': ufbxi_convert_parse_switch(int64_t, (int64_t), ufbxi_f64_to_i64); break; + case 'f': ufbxi_convert_parse_switch(float, (float), (float)); break; + case 'd': ufbxi_convert_parse_switch(double, (double), (double)); break; + + default: return 0; + + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static void *ufbxi_push_array_data(ufbxi_context *uc, const ufbxi_array_info *info, size_t size, ufbxi_buf *tmp_buf) +{ + size_t elem_size = ufbxi_array_type_size(info->type); + uint32_t flags = info->flags; + if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) size += 4; + + // The array may be pushed either to the result or temporary buffer depending + // if it's already in the right format + ufbxi_buf *arr_buf = tmp_buf; + if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result; + else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp; + char *data = (char*)ufbxi_push_size(arr_buf, elem_size, size); + ufbxi_check_return(data, NULL); + + if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) { + memset(data, 0, elem_size * 4); + data += elem_size * 4; + } + + return data; +} + +ufbxi_noinline static void ufbxi_postprocess_bool_array(char *data, size_t size) +{ + ufbxi_for(char, b, (char*)data, size) { + *b = (char)(*b != 0); + } +} + +typedef struct { + size_t encoded_size; + size_t src_elem_size; + size_t array_size; + char src_type; + char dst_type; + char arr_type; + const void *encoded_data; + void *decoded_data; + void *dst_data; + ufbx_inflate_retain *inflate_retain; +} ufbxi_deflate_task; + +static bool ufbxi_deflate_task_fn(ufbxi_task *task) +{ + ufbxi_deflate_task *t = (ufbxi_deflate_task*)task->data; + + ufbx_inflate_input input; // ufbxi_uninit + input.total_size = t->encoded_size; + input.data = t->encoded_data; + input.data_size = t->encoded_size; + input.no_header = false; + input.no_checksum = false; + input.internal_fast_bits = 0; + input.progress_cb.fn = NULL; + input.progress_cb.user = NULL; + input.progress_size_before = 0; + input.progress_size_after = 0; + input.progress_interval_hint = 0; + input.buffer = NULL; + input.buffer_size = 0; + input.read_fn = NULL; + input.read_user = NULL; + + size_t decoded_data_size = t->src_elem_size * t->array_size; + ptrdiff_t res = ufbx_inflate(t->decoded_data, decoded_data_size, &input, t->inflate_retain); + if (res == -28) { + task->error = "Cancelled"; + return false; + } else if (res != (ptrdiff_t)decoded_data_size) { + task->error = "Bad DEFLATE data"; + return false; + } + + if (t->decoded_data != t->dst_data) { + int ok = ufbxi_binary_convert_array(NULL, t->src_type, t->dst_type, t->decoded_data, t->dst_data, t->array_size); + if (!ok) { + task->error = "Failed to convert array"; + return false; + } + } + + if (t->arr_type == 'b') { + ufbxi_postprocess_bool_array((char*)t->dst_data, t->array_size); + } + + return true; +} + +// Recursion limited by check at the start +ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive) + ufbxi_recursive_function(int, ufbxi_binary_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1, + (ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)) +{ + // https://code.blender.org/2013/08/fbx-binary-file-format-specification + // Parse an FBX document node in the binary format + ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH); + + // Parse the node header, post-7500 versions use 64-bit values for most + // header fields. + uint64_t end_offset, num_values64, values_len; + uint8_t name_len; + size_t header_size = (uc->version >= 7500) ? 25 : 13; + const char *header = ufbxi_read_bytes(uc, header_size), *header_words = header; + ufbxi_check(header); + if (uc->version >= 7500) { + if (uc->file_big_endian) { + header_words = ufbxi_swap_endian(uc, header_words, 3, 8); + ufbxi_check(header_words); + } + end_offset = ufbxi_read_u64(header_words + 0); + num_values64 = ufbxi_read_u64(header_words + 8); + values_len = ufbxi_read_u64(header_words + 16); + name_len = ufbxi_read_u8(header + 24); + } else { + if (uc->file_big_endian) { + header_words = ufbxi_swap_endian(uc, header_words, 3, 4); + ufbxi_check(header_words); + } + end_offset = ufbxi_read_u32(header_words + 0); + num_values64 = ufbxi_read_u32(header_words + 4); + values_len = ufbxi_read_u32(header_words + 8); + name_len = ufbxi_read_u8(header + 12); + } + + ufbxi_check(num_values64 <= UINT32_MAX); + uint32_t num_values = (uint32_t)num_values64; + + // If `end_offset` and `name_len` is zero we treat as the node as a NULL-sentinel + // that terminates a node list. + if (end_offset == 0 && name_len == 0) { + *p_end = true; + return 1; + } + + // Update estimated end offset if possible + if (end_offset > uc->progress_bytes_total) { + uc->progress_bytes_total = end_offset; + } + + // Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by + // calling code after its done parsing all of it's children. + ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1); + ufbxi_check(node); + + // Parse and intern the name to the string pool. + const char *name = ufbxi_read_bytes(uc, name_len); + ufbxi_check(name); + name = ufbxi_push_string(&uc->string_pool, name, name_len, NULL, true); + ufbxi_check(name); + node->name_len = name_len; + node->name = name; + + uint64_t values_end_offset = ufbxi_get_read_offset(uc) + values_len; + + // Check if the values of the node we're parsing currently should be + // treated as an array. + ufbxi_array_info arr_info; + if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) { + + // Normalize the array type (eg. 'r' to 'f'/'d' depending on the build) + // and get the per-element size of the array. + // Boolean arrays 'b' are normalized to 'c' as they are postprocessed + // below based on `arr_info.type`. + char dst_type = ufbxi_normalize_array_type(arr_info.type, 'c'); + + ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1); + ufbxi_check(arr); + + node->value_type_mask = UFBXI_VALUE_ARRAY; + node->array = arr; + arr->type = ufbxi_normalize_array_type(arr_info.type, 'b'); + + // Peek the first bytes of the array. We can always look at least 13 bytes + // ahead safely as valid FBX files must end in a 13/25 byte NULL record. + const char *data = ufbxi_peek_bytes(uc, 13); + ufbxi_check(data); + + // Check if the data type is one of the explicit array types (post-7000). + // Otherwise we form the array by concatenating all the normal values of the + // node (pre-7000) + char c = data[0]; + + // HACK: Override the "type" if either the array is empty or we want to + // specifically ignore the contents. + if (num_values == 0) c = '0'; + if (dst_type == '-') c = '-'; + + bool deferred = false; + + if (c=='c' || c=='b' || c=='i' || c=='l' || c =='f' || c=='d') { + + const char *arr_words = data + 1; + if (uc->file_big_endian) { + arr_words = ufbxi_swap_endian(uc, arr_words, 3, 4); + ufbxi_check(arr_words); + } + + // Parse the array header from the prefix we already peeked above. + char src_type = data[0]; + uint32_t size = ufbxi_read_u32(arr_words + 0); + uint32_t encoding = ufbxi_read_u32(arr_words + 4); + uint32_t encoded_size = ufbxi_read_u32(arr_words + 8); + ufbxi_consume_bytes(uc, 13); + + // Normalize the source type as well, but don't convert UFBX-specific + // 'r' to 'f'/'d', but fail later instead. + if (src_type != 'r') src_type = ufbxi_normalize_array_type(src_type, 'c'); + size_t src_elem_size = ufbxi_array_type_size(src_type); + size_t decoded_data_size = src_elem_size * size; + + // Allocate `size` elements for the array. + char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, size, tmp_buf); + ufbxi_check(arr_data); + + uint64_t arr_begin = ufbxi_get_read_offset(uc); + ufbxi_check(UINT64_MAX - encoded_size > arr_begin); + uint64_t arr_end = arr_begin + encoded_size; + if (arr_end > uc->progress_bytes_total) { + uc->progress_bytes_total = arr_end; + } + + // Threading + if (uc->parse_threaded && encoding == 1 && encoded_size >= UFBXI_MIN_THREADED_DEFLATE_BYTES && !uc->file_big_endian && !uc->local_big_endian) { + ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_deflate_task_fn); + if (task) { + ufbxi_deflate_task *t = ufbxi_push_zero(tmp_buf, ufbxi_deflate_task, 1); + ufbxi_check(t); + + ufbxi_inflate_init_retain(uc->inflate_retain); + + t->src_elem_size = src_elem_size; + t->encoded_size = encoded_size; + t->array_size = size; + t->src_type = src_type; + t->dst_type = dst_type; + t->arr_type = arr->type; + t->dst_data = arr_data; + t->inflate_retain = uc->inflate_retain; + + if (!uc->read_fn) { + // From memory, no need to copy + t->encoded_data = uc->data; + } else { + void *encoded_data = ufbxi_push(tmp_buf, char, encoded_size); + ufbxi_check(encoded_data); + ufbxi_check(ufbxi_read_to(uc, encoded_data, encoded_size)); + t->encoded_data = encoded_data; + } + + if (src_type != dst_type) { + t->decoded_data = ufbxi_push_size(tmp_buf, src_elem_size, size); + ufbxi_check(t->decoded_data); + } else { + t->decoded_data = arr_data; + } + + task->data = t; + ufbxi_thread_pool_run_task(&uc->thread_pool, task); + deferred = true; + } + } + + // If the source and destination types are equal and our build is binary-compatible + // with the FBX format we can read the decoded data directly into the array buffer. + // Otherwise we need a temporary buffer to decode the array into before conversion. + void *decoded_data = arr_data; + if (!deferred && (src_type != dst_type || uc->local_big_endian != uc->file_big_endian)) { + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, decoded_data_size)); + decoded_data = uc->tmp_arr; + } + + if (deferred) { + // Nop + } else if (encoding == 0) { + // Encoding 0: Plain binary data. + ufbxi_check(encoded_size == decoded_data_size); + + // If the array is contained in the current read buffer and we need to convert + // the data anyway we can use the read buffer as the decoded array source, otherwise + // do a plain byte copy to the array/conversion buffer. + if (uc->yield_size + uc->data_size >= encoded_size && decoded_data != arr_data) { + // Yield right after this if we crossed the yield threshold + if (encoded_size > uc->yield_size) { + uc->data_size += uc->yield_size; + uc->yield_size = encoded_size; + uc->data_size -= uc->yield_size; + } + + decoded_data = (void*)uc->data; + ufbxi_consume_bytes(uc, encoded_size); + } else { + ufbxi_check(ufbxi_read_to(uc, decoded_data, encoded_size)); + } + } else if (encoding == 1) { + // Encoding 1: DEFLATE + + ufbxi_pause_progress(uc); + + // Inflate the data from the user-provided IO buffer / read callbacks + ufbx_inflate_input input; + input.total_size = encoded_size; + input.data = uc->data; + input.data_size = uc->data_size; + input.no_header = false; + input.no_checksum = false; + input.internal_fast_bits = 0; + + if (uc->opts.progress_cb.fn) { + input.progress_cb = uc->opts.progress_cb; + input.progress_size_before = arr_begin; + input.progress_size_after = uc->progress_bytes_total - arr_end; + input.progress_interval_hint = uc->progress_interval; + } else { + input.progress_cb.fn = NULL; + input.progress_cb.user = NULL; + input.progress_size_before = 0; + input.progress_size_after = 0; + input.progress_interval_hint = 0; + } + + // If the encoded array is larger than the data we have currently buffered + // we need to allow `ufbx_inflate()` to read from the IO callback. We can + // let `ufbx_inflate()` freely clobber our `read_buffer` as all the data + // in the buffer will be consumed. `ufbx_inflate()` always reads exactly + // the amount of bytes needed so we can continue reading from `read_fn` as + // usual (given that we clear the `uc->data/_size` buffer below). + // NOTE: We _cannot_ share `read_buffer` if we plan to read later from it + // as `ufbx_inflate()` overwrites parts of it with zeroes. + if (encoded_size > input.data_size) { + input.buffer = uc->read_buffer; + input.buffer_size = uc->read_buffer_size; + input.read_fn = uc->read_fn; + input.read_user = uc->read_user; + uc->data_offset += encoded_size - input.data_size; + uc->data += input.data_size; + uc->data_size = 0; + } else { + input.buffer = NULL; + input.buffer_size = 0; + input.read_fn = NULL; + input.read_user = NULL; + uc->data += encoded_size; + uc->data_size -= encoded_size; + ufbxi_check(ufbxi_resume_progress(uc)); + } + + ptrdiff_t res = ufbx_inflate(decoded_data, decoded_data_size, &input, uc->inflate_retain); + ufbxi_check_msg(res != -28, "Cancelled"); + ufbxi_check_msg(res == (ptrdiff_t)decoded_data_size, "Bad DEFLATE data"); + + } else { + ufbxi_fail("Bad array encoding"); + } + + // Convert the decoded array if necessary. + if (!deferred && decoded_data != arr_data) { + ufbxi_check(ufbxi_binary_convert_array(uc, src_type, dst_type, decoded_data, arr_data, size)); + } + + arr->data = arr_data; + arr->size = size; + + } else if (c == '0' || c == '-') { + // Ignore the array + arr->type = c == '-' ? '-' : dst_type; + arr->data = (char*)ufbxi_zero_size_buffer + 32; + arr->size = 0; + } else { + // Allocate `num_values` elements for the array and parse single values into it. + char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, num_values, tmp_buf); + ufbxi_check(arr_data); + ufbxi_check(ufbxi_binary_parse_multivalue_array(uc, dst_type, arr_data, num_values, tmp_buf)); + arr->data = arr_data; + arr->size = num_values; + } + + // Post-process boolean arrays + if (!deferred && arr_info.type == 'b') { + ufbxi_postprocess_bool_array((char*)arr->data, arr->size); + } + + } else { + // Parse up to UFBXI_MAX_NON_ARRAY_VALUES as plain values + num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES); + ufbxi_value *vals = ufbxi_push(tmp_buf, ufbxi_value, num_values); + ufbxi_check(vals); + node->vals = vals; + + uint32_t type_mask = 0; + for (size_t i = 0; i < (size_t)num_values; i++) { + // The file must end in a 13/25 byte NULL record, so we can peek + // up to 13 bytes safely here. + const char *data = ufbxi_peek_bytes(uc, 13); + ufbxi_check(data); + + const char *value = data + 1; + + char type = data[0]; + if (uc->file_big_endian) { + value = ufbxi_swap_endian_value(uc, value, type); + ufbxi_check(value); + } + + switch (type) { + + case 'C': case 'B': case 'Z': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].f = (double)(vals[i].i = (int64_t)(uint8_t)value[0]); + ufbxi_consume_bytes(uc, 2); + break; + + case 'Y': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].f = (double)(vals[i].i = ufbxi_read_i16(value)); + ufbxi_consume_bytes(uc, 3); + break; + + case 'I': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].f = (double)(vals[i].i = ufbxi_read_i32(value)); + ufbxi_consume_bytes(uc, 5); + break; + + case 'L': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].f = (double)(vals[i].i = ufbxi_read_i64(value)); + ufbxi_consume_bytes(uc, 9); + break; + + case 'F': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f32(value)); + ufbxi_consume_bytes(uc, 5); + break; + + case 'D': + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); + vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f64(value)); + ufbxi_consume_bytes(uc, 9); + break; + + case 'S': case 'R': + { + uint32_t length = ufbxi_read_u32(value); + ufbxi_consume_bytes(uc, 5); + const char *str = ufbxi_read_bytes(uc, length); + ufbxi_check(str); + + if (length == 0) { + vals[i].s.raw_data = ufbxi_empty_char; + vals[i].s.raw_length = 0; + vals[i].s.utf8_length = 0; + } else { + bool non_ascii = false; + uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); + bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, i); + ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &vals[i].s, str, length, hash, raw)); + + // Mark the data as invalid UTF-8 + if (non_ascii && raw) vals[i].s.utf8_length = UINT32_MAX; + } + + type_mask |= (uint32_t)UFBXI_VALUE_STRING << (i*2); + } + break; + + // Treat arrays as non-values and skip them + case 'c': case 'b': case 'i': case 'l': case 'f': case 'd': + { + uint32_t encoded_size = ufbxi_read_u32(value + 8); + ufbxi_consume_bytes(uc, 13); + ufbxi_check(ufbxi_skip_bytes(uc, encoded_size)); + } + break; + + default: + ufbxi_fail("Bad value type"); + + } + } + + node->value_type_mask = (uint16_t)type_mask; + } + + // Skip over remaining values if necessary if we for example truncated + // the list of values or if there are values after an array + uint64_t offset = ufbxi_get_read_offset(uc); + ufbxi_check(offset <= values_end_offset); + if (offset < values_end_offset) { + ufbxi_check(ufbxi_skip_bytes(uc, values_end_offset - offset)); + } + + if (recursive) { + // Recursively parse the children of this node. Update the parse state + // to provide context for child node parsing. + ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name); + uint32_t num_children = 0; + for (;;) { + // Stop at end offset + uint64_t current_offset = ufbxi_get_read_offset(uc); + if (current_offset >= end_offset) { + ufbxi_check(current_offset == end_offset || end_offset == 0); + break; + } + + bool end = false; + ufbxi_check(ufbxi_binary_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, true)); + if (end) break; + num_children++; + } + + // Pop children from `tmp_stack` to a contiguous array + node->num_children = num_children; + if (num_children > 0) { + node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children); + ufbxi_check(node->children); + } + } else { + uint64_t current_offset = ufbxi_get_read_offset(uc); + uc->has_next_child = (current_offset < end_offset); + } + + return 1; +} + +#define UFBXI_BINARY_MAGIC_SIZE 22 +#define UFBXI_BINARY_HEADER_SIZE 27 +static const char ufbxi_binary_magic[] = "Kaydara FBX Binary \x00\x1a"; + +// -- ASCII parsing + +#define UFBXI_ASCII_END '\0' +#define UFBXI_ASCII_NAME 'N' +#define UFBXI_ASCII_BARE_WORD 'B' +#define UFBXI_ASCII_INT 'I' +#define UFBXI_ASCII_FLOAT 'F' +#define UFBXI_ASCII_STRING 'S' + +static ufbxi_noinline char ufbxi_ascii_refill(ufbxi_context *uc) +{ + ufbxi_ascii *ua = &uc->ascii; + uc->data_offset += ufbxi_to_size(ua->src - uc->data_begin); + if (uc->read_fn) { + char *dst_buffer = NULL; + size_t dst_size = 0; + + if (ua->retain_buf != NULL) { + dst_size = uc->opts.read_buffer_size; + dst_buffer = ufbxi_push(ua->retain_buf, char, dst_size); + ufbxi_check_return(dst_buffer, '\0'); + ua->src_is_retained = true; + ua->src_buf = ua->retain_buf; + } else { + // Grow the read buffer if necessary + if (uc->read_buffer_size < uc->opts.read_buffer_size) { + size_t new_size = uc->opts.read_buffer_size; + ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, new_size), '\0'); + } + dst_buffer = uc->read_buffer; + dst_size = uc->read_buffer_size; + ua->src_is_retained = false; + ua->src_buf = NULL; + } + + // Read user data, return '\0' on EOF + // TODO: Very unoptimal for non-full-size reads in some cases + size_t num_read = uc->read_fn(uc->read_user, dst_buffer, dst_size); + ufbxi_check_return_msg(num_read != SIZE_MAX, '\0', "IO error"); + ufbxi_check_return(num_read <= dst_size, '\0'); + if (num_read == 0) return '\0'; + + uc->data = uc->data_begin = ua->src = dst_buffer; + ua->src_end = dst_buffer + num_read; + return *ua->src; + } else { + // If the user didn't specify a `read_fn()` treat anything + // past the initial data buffer as EOF. + uc->data = uc->data_begin = ua->src = ""; + ua->src_end = ua->src + 1; + return '\0'; + } +} + +static ufbxi_noinline char ufbxi_ascii_yield(ufbxi_context *uc) +{ + ufbxi_ascii *ua = &uc->ascii; + + char ret; + if (ua->src == ua->src_end) { + ret = ufbxi_ascii_refill(uc); + } else { + ret = *ua->src; + } + + if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) { + ua->src_yield = ua->src_end; + } else { + ua->src_yield = ua->src + uc->progress_interval; + } + + // TODO: Unify these properly + uc->data = ua->src; + ufbxi_check_return(ufbxi_report_progress(uc), '\0'); + return ret; +} + +static ufbxi_forceinline char ufbxi_ascii_peek(ufbxi_context *uc) +{ + ufbxi_ascii *ua = &uc->ascii; + if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); + return *ua->src; +} + +static ufbxi_forceinline char ufbxi_ascii_next(ufbxi_context *uc) +{ + ufbxi_ascii *ua = &uc->ascii; + if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); + ua->src++; + if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); + return *ua->src; +} + +static ufbxi_noinline uint32_t ufbxi_ascii_parse_version(ufbxi_context *uc) +{ + uint8_t digits[3]; + uint32_t num_digits = 0; + + char c = ufbxi_ascii_next(uc); + + const char fmt[] = " FBX ?.?.?"; + uint32_t ix = 0; + while (num_digits < 3) { + char ref = fmt[ix++]; + switch (ref) { + + // Digit + case '?': + if (c < '0' || c > '9') return 0; + digits[num_digits++] = (uint8_t)(c - '0'); + c = ufbxi_ascii_next(uc); + break; + + // Whitespace + case ' ': + while (c == ' ' || c == '\t') { + c = ufbxi_ascii_next(uc); + } + break; + + // Literal character + default: + if (c != ref) return 0; + c = ufbxi_ascii_next(uc); + break; + } + } + + if (num_digits != 3) return 0; + return 1000u*(uint32_t)digits[0] + 100u*(uint32_t)digits[1] + 10u*(uint32_t)digits[2]; +} + +static const uint32_t ufbxi_space_mask = + (1u << ((uint32_t)' ' - 1)) | + (1u << ((uint32_t)'\t' - 1)) | + (1u << ((uint32_t)'\r' - 1)) | + (1u << ((uint32_t)'\n' - 1)) ; + +ufbx_static_assert(space_codepoint, + (uint32_t)' ' <= 32u && (uint32_t)'\t' <= 32u && + (uint32_t)'\r' <= 32u && (uint32_t)'\n' <= 32u); + +static ufbxi_forceinline bool ufbxi_is_space(char c) +{ + uint32_t v = (uint32_t)(uint8_t)c - 1; + return v < 32 && ((ufbxi_space_mask >> v) & 0x1) != 0; +} + +static ufbxi_noinline char ufbxi_ascii_skip_whitespace(ufbxi_context *uc) +{ + ufbxi_ascii *ua = &uc->ascii; + + // Ignore whitespace + char c = ufbxi_ascii_peek(uc); + for (;;) { + while (ufbxi_is_space(c)) { + c = ufbxi_ascii_next(uc); + } + + // Line comment + if (c == ';') { + + bool read_magic = false; + // FBX ASCII files begin with a magic comment of form "; FBX 7.7.0 project file" + // Try to extract the version number from the magic comment + if (!ua->read_first_comment) { + ua->read_first_comment = true; + uint32_t version = ufbxi_ascii_parse_version(uc); + if (version) { + uc->version = version; + ua->found_version = true; + read_magic = true; + } + } + + c = ufbxi_ascii_next(uc); + while (c != '\n' && c != '\0') { + c = ufbxi_ascii_next(uc); + } + c = ufbxi_ascii_next(uc); + + // Try to determine if this is a Blender 6100 ASCII file + if (read_magic) { + if (c == ';') { + char line[32]; + size_t line_len = 0; + + c = ufbxi_ascii_next(uc); + while (c != '\n' && c != '\0') { + if (line_len < sizeof(line)) { + line[line_len++] = c; + } + c = ufbxi_ascii_next(uc); + } + + if (line_len >= 19 && !memcmp(line, " Created by Blender", 19)) { + uc->exporter = UFBX_EXPORTER_BLENDER_ASCII; + } + } + } + + } else { + break; + } + } + return c; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_char(ufbxi_context *uc, ufbxi_ascii_token *token, char c) +{ + // Grow the string data buffer if necessary + if (token->str_len == token->str_cap) { + size_t len = ufbxi_max_sz(token->str_len + 1, 256); + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len)); + } + + token->str_data[token->str_len++] = c; + + return 1; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_string(ufbxi_context *uc, ufbxi_ascii_token *token, const char *data, size_t length) +{ + // Grow the string data buffer if necessary + if (token->str_len + length >= token->str_cap) { + size_t len = ufbxi_max_sz(token->str_len + length, 256); + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len)); + } + + memcpy(token->str_data + token->str_len, data, length); + token->str_len += length; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_skip_until(ufbxi_context *uc, char dst) +{ + ufbxi_ascii *ua = &uc->ascii; + + for (;;) { + size_t buffered = ufbxi_to_size(ua->src_yield - ua->src); + const char *match = (const char*)memchr(ua->src, dst, buffered); + if (match) { + ua->src = match; + break; + } else { + ua->src += buffered; + } + if (buffered == 0) { + char c = ufbxi_ascii_yield(uc); + ufbxi_check(c != '\0'); + } + } + + return 1; +} + +typedef struct { + const char *source; + size_t length; +} ufbxi_ascii_span; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_store_array(ufbxi_context *uc, ufbxi_buf *tmp_buf) +{ + ufbxi_ascii *ua = &uc->ascii; + + ua->retain_buf = tmp_buf; + + for (;;) { + size_t buffered = ufbxi_to_size(ua->src_yield - ua->src); + if (buffered == 0) { + char c = ufbxi_ascii_yield(uc); + ufbxi_check(c != '\0'); + continue; + } + + const char *begin = ua->src, *end; + const char *match = (const char*)memchr(begin, '}', buffered); + if (match) { + end = match; + } else { + end = begin + buffered; + } + ua->src = end; + + size_t length = ufbxi_to_size(end - begin); + ufbxi_ascii_span *span = ufbxi_push(&uc->tmp_ascii_spans, ufbxi_ascii_span, 1); + ufbxi_check(span); + // Store the trailing '}' for parsing + if (match) length += 1; + span->length = length; + if (ua->src_is_retained || !uc->read_fn) { + span->source = begin; + } else { + span->source = ufbxi_push_copy(tmp_buf, char, length, begin); + ufbxi_check(span->source); + } + + if (match) break; + } + + ua->retain_buf = NULL; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_try_ignore_string(ufbxi_context *uc, ufbxi_ascii_token *token) +{ + ufbxi_ascii *ua = &uc->ascii; + + char c = ufbxi_ascii_skip_whitespace(uc); + token->str_len = 0; + + if (c == '"') { + // Replace `prev_token` with `token` but swap the buffers so `token` uses + // the now-unused string buffer of the old `prev_token`. + char *swap_data = ua->prev_token.str_data; + size_t swap_cap = ua->prev_token.str_cap; + ua->prev_token = ua->token; + ua->token.str_data = swap_data; + ua->token.str_cap = swap_cap; + + token->type = UFBXI_ASCII_STRING; + // Skip opening quote + ufbxi_ascii_next(uc); + ufbxi_check(ufbxi_ascii_skip_until(uc, '"')); + // Skip closing quote + ufbxi_ascii_next(uc); + return true; + } + + return false; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_next_token(ufbxi_context *uc, ufbxi_ascii_token *token) +{ + ufbxi_ascii *ua = &uc->ascii; + + // Replace `prev_token` with `token` but swap the buffers so `token` uses + // the now-unused string buffer of the old `prev_token`. + char *swap_data = ua->prev_token.str_data; + size_t swap_cap = ua->prev_token.str_cap; + ua->prev_token = ua->token; + ua->token.str_data = swap_data; + ua->token.str_cap = swap_cap; + + char c = ufbxi_ascii_skip_whitespace(uc); + token->str_len = 0; + + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { + token->type = UFBXI_ASCII_BARE_WORD; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '(' || c == ')') { + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); + c = ufbxi_ascii_next(uc); + } + + // Skip whitespace to find if there's a following ':' + c = ufbxi_ascii_skip_whitespace(uc); + if (c == ':') { + token->value.name_len = token->str_len; + token->type = UFBXI_ASCII_NAME; + ufbxi_ascii_next(uc); + } + } else if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') { + token->type = UFBXI_ASCII_INT; + + token->negative = c == '-'; + while ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { + if (c == '.' || c == 'e' || c == 'E') { + token->type = UFBXI_ASCII_FLOAT; + } + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); + c = ufbxi_ascii_next(uc); + } + + bool nan_like = false; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '#' || c == '(' || c == ')') { + nan_like = true; + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); + c = ufbxi_ascii_next(uc); + } + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, '\0')); + if (nan_like) { + token->type = UFBXI_ASCII_FLOAT; + } + + char *end; + if (token->type == UFBXI_ASCII_INT) { + token->value.i64 = ufbxi_parse_int64(token->str_data, &end); + ufbxi_check(end == token->str_data + token->str_len - 1); + } else if (token->type == UFBXI_ASCII_FLOAT) { + uint32_t flags = uc->double_parse_flags; + if (ua->parse_as_f32) flags = UFBXI_PARSE_DOUBLE_AS_BINARY32; + token->value.f64 = ufbxi_parse_double(token->str_data, token->str_len, &end, flags); + ufbxi_check(end == token->str_data + token->str_len - 1); + } + } else if (c == '"') { + token->type = UFBXI_ASCII_STRING; + c = ufbxi_ascii_next(uc); + while (c != '"') { + + // Optimized string parsing for non-special characters + if (ua->src + 1 < ua->src_yield) { + const char *begin = ua->src; + const char *end = ua->src_yield; + const char *quot = (const char*)memchr(begin, '"', ufbxi_to_size(end - begin)); + if (quot) end = quot; + const char *esc = (const char*)memchr(begin, '&', ufbxi_to_size(end - begin)); + if (esc) end = esc; + + if (begin < end) { + ufbxi_check(ufbxi_ascii_push_token_string(uc, token, begin, ufbxi_to_size(end - begin))); + ua->src = end; + c = ufbxi_ascii_peek(uc); + continue; + } + } + + // Escape XML-like elements, funny enough there is no way to escape '&' itself, there is no `&`. + // '"' -> '"' + // '&cr;' -> '\r' + // '&lf;' -> '\n' + if (c == '&') { + const char *entity = NULL; + char replacement = '\0'; + + c = ufbxi_ascii_next(uc); + switch (c) { + case 'q': + entity = """; + replacement = '"'; + break; + case 'c': + entity = "&cr;"; + replacement = '\r'; + break; + case 'l': + entity = "&lf;"; + replacement = '\n'; + break; + default: + // As '&' is not escaped in any way just map '&' -> '&' + entity = "&"; + replacement = '&'; + break; + } + + size_t step = 1; + + ufbxi_dev_assert(entity && *entity); + // `entity` is a NULL terminated string longer than a single character + // cppcheck-suppress arrayIndexOutOfBounds + for (; entity[step]; step++) { + if (c != entity[step]) break; + c = ufbxi_ascii_next(uc); + } + + if (entity[step] == '\0') { + // Full match: Push the replacement character + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, replacement)); + } else { + // Partial match: Push the prefix we have skipped already + for (size_t i = 0; i < step; i++) { + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, entity[i])); + } + } + continue; + } + + ufbxi_check(c != '\0'); + ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); + c = ufbxi_ascii_next(uc); + } + // Skip closing quote + ufbxi_ascii_next(uc); + } else { + // Single character token + token->type = c; + ufbxi_ascii_next(uc); + } + + return 1; +} + +ufbxi_nodiscard static int ufbxi_ascii_accept(ufbxi_context *uc, char type) +{ + ufbxi_ascii *ua = &uc->ascii; + + if (ua->token.type == type) { + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + return 1; + } else { + return 0; + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_int_array(ufbxi_context *uc, char type, size_t *p_num_read) +{ + ufbxi_ascii *ua = &uc->ascii; + if (ua->parse_as_f32) return 1; + size_t initial_items = uc->tmp_stack.num_items; + + int64_t val; + if (ua->token.type == UFBXI_ASCII_INT) { + val = ua->token.value.i64; + } else { + return 1; + } + + const char *src = ua->src; + const char *end = ua->src_yield; + const char *src_scan = src; + + for (;;) { + + // Skip '\s*,\s*' between array elements. If we don't find a comma after an element + // don't push it as we can't be 100% certain whether it's a part of the array. + while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; + if (src_scan == end || *src_scan != ',') break; + src_scan++; + while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; + + // Found comma, commit to the position and push the previous value to the array + src = src_scan; + if (type == 'i') { + int32_t *v = ufbxi_push_fast(&uc->tmp_stack, int32_t, 1); + ufbxi_check(v); + *v = (int32_t)val; + } else if (type == 'l') { + int64_t *v = ufbxi_push_fast(&uc->tmp_stack, int64_t, 1); + ufbxi_check(v); + *v = (int64_t)val; + } + + // Try to parse the next value, we don't commit this until we find a comma after it above. + size_t left = ufbxi_to_size(end - src_scan); + if (left < 32) break; + + val = ufbxi_parse_int64(src_scan, (char**)&src_scan); + if (!src_scan) break; + } + + // Resume conventional parsing if we moved `src`. + if (src != ua->src) { + ua->src = src; + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + } + + *p_num_read = uc->tmp_stack.num_items - initial_items; + return 1; +} + +typedef struct { + void *arr_data; + char arr_type; + size_t arr_size; + const ufbxi_ascii_span *spans; + size_t num_spans; + size_t offset; +} ufbxi_ascii_array_task; + +ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_floats(ufbxi_ascii_array_task *t, const char *src, const char *src_end, uint32_t parse_flags) +{ + size_t offset = t->offset; + float *dst_float = t->arr_type == 'f' ? (float*)t->arr_data + offset : NULL; + double *dst_double = t->arr_type == 'd' ? (double*)t->arr_data + offset : NULL; + ufbx_assert(dst_float || dst_double); + const char *src_begin = src; + + while (src != src_end) { + while (ufbxi_is_space(*src)) src++; + + // Try to parse the next value, we don't commit this until we find a comma after it above. + char *num_end = NULL; + double val = ufbxi_parse_double(src, ufbxi_to_size(src_end - src), &num_end, parse_flags); + if (!num_end) return src_begin; + src = num_end; + + while (ufbxi_is_space(*src)) src++; + if (*src != ',') break; + src++; + src_begin = src; + + if (offset >= t->arr_size) return NULL; + if (dst_double) { + *dst_double++ = val; + } else { + *dst_float++ = (float)val; + } + offset++; + } + + t->offset = offset; + return src_begin; +} + +ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_ints(ufbxi_ascii_array_task *t, const char *src, const char *src_end) +{ + size_t offset = t->offset; + int32_t *dst32 = t->arr_type == 'i' ? (int32_t*)t->arr_data + offset : NULL; + int64_t *dst64 = t->arr_type == 'l' ? (int64_t*)t->arr_data + offset : NULL; + ufbx_assert(dst32 || dst64); + const char *src_begin = src; + + while (src != src_end) { + while (ufbxi_is_space(*src)) src++; + + int64_t val = ufbxi_parse_int64(src, (char**)&src); + if (!src) return NULL; + + while (ufbxi_is_space(*src)) src++; + if (*src != ',') break; + src++; + src_begin = src; + + if (offset >= t->arr_size) return NULL; + if (dst32) { + *dst32++ = (int32_t)val; + } else { + *dst64++ = val; + } + offset++; + } + + t->offset = offset; + return src_begin; +} + +ufbxi_noinline static const char *ufbxi_ascii_array_task_parse(ufbxi_ascii_array_task *t, const char *src, const char *src_end) +{ + if (t->arr_type == 'f' || t->arr_type == 'd') { + uint32_t flags = ufbxi_parse_double_init_flags(); + return ufbxi_ascii_array_task_parse_floats(t, src, src_end, flags); + } else { + return ufbxi_ascii_array_task_parse_ints(t, src, src_end); + } +} + +typedef enum { + UFBXI_ASCII_SCAN_STATE_VALUE, + UFBXI_ASCII_SCAN_STATE_WHITESPACE, + UFBXI_ASCII_SCAN_STATE_COMMENT, + UFBXI_ASCII_SCAN_STATE_COMMA, +} ufbxi_ascii_scan_state; + +ufbxi_noinline static bool ufbxi_ascii_array_task_imp(ufbxi_ascii_array_task *t) +{ + // Temporary buffer for parsing between spans + char buffer[128]; // ufbxi_uninit + size_t buffer_len = 0; + bool buffer_value = false; + + ufbxi_ascii_scan_state state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; + ufbxi_for(const ufbxi_ascii_span, span, t->spans, t->num_spans) { + const char *src = span->source; + const char *end = src + span->length; + + while (src != end) { + + // State machine for skipping whitespace and comments, potentially + // between multiple spans. + while (src != end) { + char c = *src; + if (state == UFBXI_ASCII_SCAN_STATE_VALUE) { + if (buffer_len >= sizeof(buffer) - 1) return false; + if (c == '"') { + return false; + } else if (c == ';' || ufbxi_is_space(c)) { + state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; + buffer[buffer_len] = ' '; + buffer_len++; + } else if (c == ',' || c == '}') { + state = UFBXI_ASCII_SCAN_STATE_COMMA; + buffer[buffer_len] = ','; + buffer_len++; + src++; + break; + } else { + buffer_value = true; + buffer[buffer_len] = c; + buffer_len++; + src++; + } + } else if (state == UFBXI_ASCII_SCAN_STATE_WHITESPACE) { + if (c == ';') { + state = UFBXI_ASCII_SCAN_STATE_COMMENT; + } else if (ufbxi_is_space(c)) { + src++; + } else { + state = UFBXI_ASCII_SCAN_STATE_VALUE; + } + } else if (state == UFBXI_ASCII_SCAN_STATE_COMMENT) { + if (c == '\n') { + state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; + } else { + src++; + } + } else if (state == UFBXI_ASCII_SCAN_STATE_COMMA) { + state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; + } + } + + if (state == UFBXI_ASCII_SCAN_STATE_COMMA) { + // Parse a value from the buffer + if (buffer_value) { + const char *buffer_end = ufbxi_ascii_array_task_parse(t, buffer, buffer + buffer_len); + if (buffer_end == NULL || buffer_end == buffer) { + return false; + } + } + + // If not at end, we are past the last comma, so try to find a + // safe range to parse. + if (src != end) { + const char *parse_end = end; + while (parse_end > src) { + if (parse_end[-1] == ',') break; + parse_end--; + } + if (src < parse_end) { + src = ufbxi_ascii_array_task_parse(t, src, parse_end); + if (src == NULL) return false; + } + } + + buffer_len = 0; + buffer_value = false; + } + } + } + + if (t->offset != t->arr_size) return false; + + return true; +} + +ufbxi_noinline static bool ufbxi_ascii_array_task_fn(ufbxi_task *task) +{ + ufbxi_ascii_array_task *t = (ufbxi_ascii_array_task *)task->data; + if (!ufbxi_ascii_array_task_imp(t)) { + task->error = "Threaded ASCII parse error"; + return false; + } + return true; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_float_array(ufbxi_context *uc, char type, size_t *p_num_read) +{ + ufbxi_ascii *ua = &uc->ascii; + if (ua->parse_as_f32) return 1; + + double val; + if (ua->token.type == UFBXI_ASCII_FLOAT) { + val = ua->token.value.f64; + } else if (ua->token.type == UFBXI_ASCII_INT) { + double fsign = !ua->token.value.i64 && ua->token.negative ? -1.0 : 1.0; + val = (double)ua->token.value.i64 * fsign; + } else { + return 1; + } + + const char *src = ua->src; + const char *end = ua->src_yield; + + uint32_t parse_flags = uc->double_parse_flags; + + size_t initial_items = uc->tmp_stack.num_items; + const char *src_scan = src; + for (;;) { + + // Skip '\s*,\s*' between array elements. If we don't find a comma after an element + // don't push it as we can't be 100% certain whether it's a part of the array. + while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; + if (src_scan == end || *src_scan != ',') break; + src_scan++; + while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; + + // Found comma, commit to the position and push the previous value to the array + src = src_scan; + if (type == 'd') { + double *v = ufbxi_push_fast(&uc->tmp_stack, double, 1); + ufbxi_check(v); + *v = (double)val; + } else if (type == 'f') { + float *v = ufbxi_push_fast(&uc->tmp_stack, float, 1); + ufbxi_check(v); + *v = (float)val; + } + + // Try to parse the next value, we don't commit this until we find a comma after it above. + char *num_end = NULL; + size_t left = ufbxi_to_size(end - src_scan); + val = ufbxi_parse_double(src_scan, left, &num_end, parse_flags); + if (!num_end || num_end == src_scan || num_end >= end) { + break; + } + + src_scan = num_end; + } + + // Resume conventional parsing if we moved `src`. + if (src != ua->src) { + ua->src = src; + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + } + + *p_num_read = uc->tmp_stack.num_items - initial_items; + return 1; +} + +ufbxi_noinline static int ufbxi_setup_base64(ufbxi_context *uc) +{ + uint8_t *table = ufbxi_push(&uc->tmp, uint8_t, 256); + ufbxi_check(table); + uc->base64_table = table; + + memset(table, 0x80, 256); + ufbxi_nounroll for (char c = 'A'; c <= 'Z'; c++) table[(size_t)c] = (uint8_t)(c - 'A'); + ufbxi_nounroll for (char c = 'a'; c <= 'z'; c++) table[(size_t)c] = (uint8_t)(26 + (c - 'a')); + ufbxi_nounroll for (char c = '0'; c <= '9'; c++) table[(size_t)c] = (uint8_t)(52 + (c - '0')); + table[(size_t)'+'] = 62; + table[(size_t)'/'] = 63; + table[(size_t)'='] = 0x40; + + return 1; +} + +ufbxi_noinline static int ufbxi_decode_base64(ufbxi_context *uc, ufbx_string *p_result, const char *src, size_t src_length, bool *p_failed) +{ + if (!uc->base64_table) ufbxi_check(ufbxi_setup_base64(uc)); + + uint8_t *table = uc->base64_table; + uint32_t error_mask = 0, pad_error = 0; + + char *p = (char*)p_result->data; + for (size_t i = 0; i + 4 <= src_length; i += 4) { + uint32_t a = table[(size_t)(uint8_t)src[i + 0]]; + uint32_t b = table[(size_t)(uint8_t)src[i + 1]]; + uint32_t c = table[(size_t)(uint8_t)src[i + 2]]; + uint32_t d = table[(size_t)(uint8_t)src[i + 3]]; + pad_error = error_mask; + error_mask |= a | b | c | d; + + p[0] = (char)(uint8_t)(a << 2 | b >> 4); + p[1] = (char)(uint8_t)(b << 4 | c >> 2); + p[2] = (char)(uint8_t)(c << 6 | d); + p += 3; + } + + if (src_length >= 4) { + const char *end = src + src_length - 4; + uint32_t padding = 0; + padding |= end[0] == '=' ? 0x8 : 0x0; + padding |= end[1] == '=' ? 0x4 : 0x0; + padding |= end[2] == '=' ? 0x2 : 0x0; + padding |= end[3] == '=' ? 0x1 : 0x0; + if (padding <= 0x1) p -= padding; // "xxx=" or "xxxx" + else if (padding == 0x3) p -= 2; // "xx==" + else pad_error |= 0x40; // anything else + } + + if (((error_mask & 0x80) != 0 || (pad_error & 0x40) != 0 || src_length % 4 != 0) && !*p_failed) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_BAD_BASE64_CONTENT, "Ignored bad base64 embedded content")); + *p_failed = true; + } + + p_result->length = ufbxi_to_size(p - p_result->data); + return 1; +} + +// Recursion limited by check at the start +ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive) + ufbxi_recursive_function(int, ufbxi_ascii_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1, + (ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)) +{ + ufbxi_ascii *ua = &uc->ascii; + + if (ua->token.type == '}') { + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + *p_end = true; + return 1; + } + + if (ua->token.type == UFBXI_ASCII_END) { + ufbxi_check_msg(depth == 0, "Truncated file"); + *p_end = true; + return 1; + } + + // Parse the name eg. "Node:" token and intern the name + ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH); + if (!uc->sure_fbx && depth == 0 && ua->token.type != UFBXI_ASCII_NAME) { + ufbxi_fail_msg("Expected a 'Name:' token", "Not an FBX file"); + } + ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME)); + size_t name_len = ua->prev_token.value.name_len; + ufbxi_check(name_len <= 0xff); + const char *name = ufbxi_push_string(&uc->string_pool, ua->prev_token.str_data, ua->prev_token.str_len, NULL, true); + ufbxi_check(name); + + // Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by + // calling code after its done parsing all of it's children. + ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1); + ufbxi_check(node); + node->name = name; + node->name_len = (uint8_t)name_len; + + bool in_ascii_array = false; + + uint32_t num_values = 0; + uint32_t type_mask = 0; + + int arr_type = 0; + ufbxi_buf *arr_buf = NULL; + size_t arr_elem_size = 0; + bool arr_error = false; + + // Check if the values of the node we're parsing currently should be + // treated as an array. + ufbxi_array_info arr_info; + if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) { + uint32_t flags = arr_info.flags; + arr_type = ufbxi_normalize_array_type(arr_info.type, 'b'); + arr_buf = tmp_buf; + if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result; + else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp; + + ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1); + ufbxi_check(arr); + node->value_type_mask = UFBXI_VALUE_ARRAY; + node->array = arr; + arr->type = (char)arr_type; + + // Parse array values using strtof() if the array destination is 32-bit float + // since KeyAttrDataFloat packs integer data (!) into floating point values so we + // should try to be as exact as possible. + if (arr_info.flags & UFBXI_ARRAY_FLAG_ACCURATE_F32) { + ua->parse_as_f32 = true; + } + + arr_elem_size = ufbxi_array_type_size((char)arr_type); + + if (arr_type != '-') { + // Force alignment for array contents: This allows us to use `ufbxi_push_fast()` + // in fast parsing functions. + ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, 8, 1)); + + // Pad with 4 zero elements to make indexing with `-1` safe. + if ((flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) != 0) { + ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, arr_elem_size, 4)); + num_values += 4; + } + } + } + + // Some fields in ASCII may have leading commas eg. `Content: , "base64-string"` + if (ua->token.type == ',') { + // HACK: If we are parsing an "array" that should be ignored, ie. `Content` when + // `opts.ignore_embedded == true` try to skip the next token string if possible. + if (arr_type == '-') { + if (!ufbxi_ascii_try_ignore_string(uc, &ua->token)) { + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + } + } else { + ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); + } + } + + ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name); + ufbxi_value vals[UFBXI_MAX_NON_ARRAY_VALUES]; + + uint32_t deferred_size = 0; + + // NOTE: Infinite loop to allow skipping the comma parsing via `continue`. + for (;;) { + ufbxi_ascii_token *tok = &ua->prev_token; + + if (arr_type) { + size_t num_read = 0; + if (arr_type == 'f' || arr_type == 'd') { + ufbxi_check(ufbxi_ascii_read_float_array(uc, (char)arr_type, &num_read)); + } else if (arr_type == 'i' || arr_type == 'l') { + ufbxi_check(ufbxi_ascii_read_int_array(uc, (char)arr_type, &num_read)); + } + ufbxi_check(UINT32_MAX - num_values > num_read); + num_values += (uint32_t)num_read; + } + + if (ufbxi_ascii_accept(uc, UFBXI_ASCII_STRING)) { + + if (arr_type) { + + if (arr_type == 's' || arr_type == 'S' || arr_type == 'C') { + bool raw = arr_type == 's'; + ufbx_string *v = ufbxi_push(&uc->tmp_stack, ufbx_string, 1); + ufbxi_check(v); + if (arr_type == 'C') { + ufbxi_buf *buf = uc->opts.retain_dom ? &uc->result : tmp_buf; + size_t capacity = tok->str_len / 4 * 3 + 3; + v->data = ufbxi_push(buf, char, capacity); + ufbxi_check(v->data); + ufbxi_check(ufbxi_decode_base64(uc, v, tok->str_data, tok->str_len, &arr_error)); + ufbx_assert(v->length <= capacity); + } else { + v->data = tok->str_data; + v->length = tok->str_len; + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, v, raw)); + } + } else { + // Ignore strings in non-string arrays, decrement `num_values` as it will be + // incremented after the loop iteration is done to ignore it. + num_values--; + } + + } else if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { + type_mask |= (uint32_t)UFBXI_VALUE_STRING << (num_values*2); + ufbxi_value *v = &vals[num_values]; + + const char *str = tok->str_data; + size_t length = tok->str_len; + ufbxi_check(str); + + if (length == 0) { + v->s.raw_data = ufbxi_empty_char; + v->s.raw_length = 0; + v->s.utf8_length = 0; + } else { + bool non_ascii = false; + uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); + bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, num_values); + ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &v->s, str, length, hash, raw)); + if (non_ascii && raw) v->s.utf8_length = UINT32_MAX; + } + } + + } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_INT)) { + int64_t val = tok->value.i64; + ufbx_real fsign = !val && tok->negative ? (ufbx_real)-1.0f : (ufbx_real)1.0f; + + switch (arr_type) { + + case 0: + // Parse version from comment if there was no magic comment + if (!ua->found_version && parse_state == UFBXI_PARSE_FBX_VERSION && num_values == 0) { + if (val >= 6000 && val <= 10000) { + ua->found_version = true; + uc->version = (uint32_t)val; + } + } + + if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); + ufbxi_value *v = &vals[num_values]; + // False positive: `v->f` and `v->i` do not overlap in the union. + // cppcheck-suppress overlappingWriteUnion + v->f = (double)(v->i = val) * (double)fsign; + } + break; + + case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; + case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; + case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break; + case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break; + case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val * (float)fsign; } break; + case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val * (double)fsign; } break; + case '-': num_values--; break; + + default: + ufbxi_fail("Bad array dst type"); + + } + + } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_FLOAT)) { + double val = tok->value.f64; + + switch (arr_type) { + + case 0: + if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); + ufbxi_value *v = &vals[num_values]; + // False positive: `v->f` and `v->i` do not overlap in the union. + // cppcheck-suppress overlappingWriteUnion + v->i = ufbxi_f64_to_i64(v->f = val); + } + break; + + case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; + case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; + case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i32(val); } break; + case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i64(val); } break; + case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val; } break; + case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val; } break; + case '-': num_values--; break; + + default: + ufbxi_fail("Bad array dst type"); + + } + + } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_BARE_WORD)) { + + int64_t val = 0; + double val_f = 0.0; + if (tok->str_len >= 1) { + val = (int64_t)tok->str_data[0]; + val_f = (double)val; + if (tok->str_len > 1 && tok->str_len < 64) { + // Try to parse the bare word as NAN/INF + char str_data[64]; // ufbxi_uninit + size_t str_len = tok->str_len; + memcpy(str_data, tok->str_data, str_len); + str_data[str_len] = '\0'; + double inf_nan; + char *end = NULL; + if (ufbxi_parse_inf_nan(&inf_nan, str_data, str_len, &end) && end == str_data + str_len) { + val = 0; + val_f = inf_nan; + } + } + } + + switch (arr_type) { + + case 0: + if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { + type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); + ufbxi_value *v = &vals[num_values]; + // False positive: `v->f` and `v->i` do not overlap in the union. + // cppcheck-suppress overlappingWriteUnion + v->i = val; + v->f = val_f; + } + break; + + case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; + case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; + case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break; + case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break; + case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val_f; } break; + case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val_f; } break; + case '-': num_values--; break; + + default: + ufbxi_fail("Bad array dst type"); + } + + } else if (ufbxi_ascii_accept(uc, '*')) { + // Parse a post-7000 ASCII array eg. "*3 { 1,2,3 }" + ufbxi_check(!in_ascii_array); + ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_INT)); + int64_t count = ua->prev_token.value.i64; + + if (ufbxi_ascii_accept(uc, '{')) { + ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME)); + in_ascii_array = true; + + // Optimized array skipping and threaded parsing + if (arr_type == '-') { + ufbxi_check(ufbxi_ascii_skip_until(uc, '}')); + } else if (uc->parse_threaded && !uc->opts.force_single_thread_ascii_parsing + && !ua->parse_as_f32 + && (arr_type == 'i' || arr_type == 'l' || arr_type == 'f' || arr_type == 'd')) { + // Don't bother with small arrays due to fixed overhead + if (count >= UFBXI_MIN_THREADED_ASCII_VALUES && count <= UINT32_MAX) { + deferred_size = (uint32_t)count - 1; + ufbxi_check(ufbxi_ascii_store_array(uc, tmp_buf)); + } + } + } + + // NOTE: This `continue` skips incrementing `num_values` and parsing + // a comma, continuing to parse the values in the array. + continue; + } else { + break; + } + + // Add value and keep parsing if there's a comma. This part may be + // skipped if we enter an array block. + num_values++; + ufbxi_check(num_values < UINT32_MAX); + if (!ufbxi_ascii_accept(uc, ',')) break; + } + + // Close the ASCII array if we are in one + if (in_ascii_array) { + ufbxi_check(ufbxi_ascii_accept(uc, '}')); + } + + ua->parse_as_f32 = false; + + if (arr_type) { + if (arr_type == '-') { + node->array->data = NULL; + node->array->size = 0; + } else { + void *arr_data = NULL; + + if (deferred_size > 0) { + arr_data = ufbxi_push_size(arr_buf, arr_elem_size, num_values + deferred_size); + // Pop any previously pushed values + if (num_values > 0) { + ufbxi_pop_size(&uc->tmp_stack, arr_elem_size, num_values, arr_data, false); + } + } else if (arr_error) { + ufbxi_pop_size(&uc->tmp_stack, arr_elem_size, num_values, NULL, false); + num_values = 0; + arr_data = (void*)ufbxi_zero_size_buffer; + } else { + arr_data = ufbxi_push_pop_size(arr_buf, &uc->tmp_stack, arr_elem_size, num_values); + } + ufbxi_check(arr_data); + if (arr_info.flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) { + node->array->data = (char*)arr_data + 4*arr_elem_size; + node->array->size = num_values + deferred_size - 4; + } else { + node->array->data = arr_data; + node->array->size = num_values + deferred_size; + } + + // Pop alignment helper + ufbxi_pop_size(&uc->tmp_stack, 8, 1, NULL, false); + + // Deferred parsing + if (deferred_size > 0) { + size_t num_spans = uc->tmp_ascii_spans.num_items; + ufbxi_ascii_span *spans = ufbxi_push_pop(tmp_buf, &uc->tmp_ascii_spans, ufbxi_ascii_span, num_spans); + ufbxi_check(spans); + + ufbxi_ascii_array_task t; // ufbxi_uninit + t.arr_data = (char*)arr_data + num_values * arr_elem_size; + t.arr_type = (char)arr_type; + t.arr_size = deferred_size; + t.num_spans = num_spans; + t.spans = spans; + t.offset = 0; + + // TODO: Split these further + ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_ascii_array_task_fn); + if (task) { + task->data = ufbxi_push_copy(tmp_buf, ufbxi_ascii_array_task, 1, &t); + ufbxi_check(task->data); + ufbxi_thread_pool_run_task(&uc->thread_pool, task); + } else { + ufbxi_check_msg(ufbxi_ascii_array_task_imp(&t), "Threaded ASCII parse error"); + } + } + } + } else { + num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES); + node->value_type_mask = (uint16_t)type_mask; + node->vals = ufbxi_push_copy(tmp_buf, ufbxi_value, num_values, vals); + ufbxi_check(node->vals); + } + + // Recursively parse the children of this node. Update the parse state + // to provide context for child node parsing. + if (ufbxi_ascii_accept(uc, '{')) { + if (recursive) { + size_t num_children = 0; + for (;;) { + bool end = false; + ufbxi_check(ufbxi_ascii_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, recursive)); + if (end) break; + num_children++; + } + + // Pop children from `tmp_stack` to a contiguous array + node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children); + ufbxi_check(node->children); + node->num_children = (uint32_t)num_children; + } + + uc->has_next_child = true; + } else { + uc->has_next_child = false; + } + + return 1; +} + +// -- DOM retention + +typedef struct { + uintptr_t node_ptr; + ufbx_dom_node *dom_node; +} ufbxi_dom_mapping; + +ufbxi_nodiscard static ufbxi_noinline ufbx_dom_node *ufbxi_get_dom_node_imp(ufbxi_context *uc, ufbxi_node *node) +{ + if (!node) return NULL; + ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL }; + uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr); + ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); + return result ? result->dom_node : NULL; +} + +ufbxi_nodiscard static ufbxi_forceinline ufbx_dom_node *ufbxi_get_dom_node(ufbxi_context *uc, ufbxi_node *node) +{ + if (!uc->opts.retain_dom) return NULL; + return ufbxi_get_dom_node_imp(uc, node); +} + +// Recursion limited by check in ufbxi_[binary/ascii]_parse_node() +ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_dom_node(ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node) + ufbxi_recursive_function(int, ufbxi_retain_dom_node, (uc, node, p_dom_node), UFBXI_MAX_NODE_DEPTH + 1, + (ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node)) +{ + ufbx_dom_node *dst = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); + ufbxi_check(dst); + ufbxi_check(ufbxi_push_copy(&uc->tmp_dom_nodes, ufbx_dom_node*, 1, &dst)); + + if (p_dom_node) { + *p_dom_node = dst; + } + + dst->name.data = node->name; + dst->name.length = node->name_len; + + { + ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL }; + uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr); + ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); + if (!result) { + result = ufbxi_map_insert(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); + ufbxi_check(result); + } + result->node_ptr = (uintptr_t)node; + result->dom_node = dst; + } + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst->name, false)); + + if (node->value_type_mask == UFBXI_VALUE_ARRAY) { + ufbxi_value_array *arr = node->array; + ufbx_dom_value *val = ufbxi_push_zero(&uc->result, ufbx_dom_value, 1); + ufbxi_check(val); + + dst->values.data = val; + dst->values.count = 1; + + size_t elem_size = ufbxi_array_type_size(arr->type); + val->value_str.data = ufbxi_empty_char; + val->value_blob.data = arr->data; + val->value_blob.size = arr->size * elem_size; + val->value_float = (double)(val->value_int = (int64_t)arr->size); + + switch (arr->type) { + case 'c': val->type = UFBX_DOM_VALUE_BLOB; break; + case 'b': val->type = UFBX_DOM_VALUE_BLOB; break; + case 'i': val->type = UFBX_DOM_VALUE_ARRAY_I32; break; + case 'l': val->type = UFBX_DOM_VALUE_ARRAY_I64; break; + case 'f': val->type = UFBX_DOM_VALUE_ARRAY_F32; break; + case 'd': val->type = UFBX_DOM_VALUE_ARRAY_F64; break; + case 's': val->type = UFBX_DOM_VALUE_ARRAY_BLOB; break; + case 'C': val->type = UFBX_DOM_VALUE_ARRAY_BLOB; break; + case '-': val->type = UFBX_DOM_VALUE_ARRAY_IGNORED; break; + default: ufbxi_fail("Bad array type"); + } + } else { + size_t ix; + for (ix = 0; ix < UFBXI_MAX_NON_ARRAY_VALUES; ix++) { + uint32_t mask = (node->value_type_mask >> (2*ix)) & 0x3; + if (!mask) break; + ufbx_dom_value *val = ufbxi_push_zero(&uc->tmp_stack, ufbx_dom_value, 1); + ufbxi_check(val); + val->value_str.data = ufbxi_empty_char; + + if (mask == UFBXI_VALUE_STRING) { + val->type = UFBX_DOM_VALUE_STRING; + ufbxi_ignore(ufbxi_get_val_at(node, ix, 'S', &val->value_str)); + ufbxi_ignore(ufbxi_get_val_at(node, ix, 'b', &val->value_blob)); + } else { + ufbx_assert(mask == UFBXI_VALUE_NUMBER); + val->type = UFBX_DOM_VALUE_NUMBER; + val->value_int = node->vals[ix].i; + val->value_float = node->vals[ix].f; + } + } + + dst->values.count = ix; + dst->values.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_dom_value, ix); + ufbxi_check(dst->values.data); + } + + if (node->num_children > 0) { + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL)); + } + + dst->children.count = node->num_children; + dst->children.data = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, node->num_children); + ufbxi_check(dst->children.data); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel(ufbxi_context *uc, ufbxi_node *node) +{ + if (uc->dom_parse_num_children > 0) { + ufbx_dom_node **children = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, uc->dom_parse_num_children); + ufbxi_check(children); + uc->dom_parse_toplevel->children.data = children; + uc->dom_parse_toplevel->children.count = uc->dom_parse_num_children; + uc->dom_parse_num_children = 0; + } + + if (node) { + ufbxi_check(ufbxi_retain_dom_node(uc, node, &uc->dom_parse_toplevel)); + } else { + uc->dom_parse_toplevel = NULL; + + // Called with NULL argument to finish retaining DOM, collect the final nodes to `ufbx_scene`. + size_t num_top_nodes = uc->tmp_dom_nodes.num_items; + ufbx_dom_node **nodes = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, num_top_nodes); + ufbxi_check(nodes); + + ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); + ufbxi_check(dom_root); + + dom_root->name.data = ufbxi_empty_char; + dom_root->children.data = nodes; + dom_root->children.count = num_top_nodes; + + uc->scene.dom_root = dom_root; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel_child(ufbxi_context *uc, ufbxi_node *child) +{ + ufbx_assert(uc->dom_parse_toplevel); + ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL)); + uc->dom_parse_num_children++; + + return 1; +} + +// -- General parsing + +static ufbxi_noinline bool ufbxi_next_line(ufbx_string *line, ufbx_string *buf, bool skip_space) +{ + if (buf->length == 0) return false; + const char *newline = (const char*)memchr(buf->data, '\n', buf->length); + size_t length = newline ? ufbxi_to_size(newline - buf->data) + 1 : buf->length; + + line->data = buf->data; + line->length = length; + buf->data += length; + buf->length -= length; + + if (skip_space) { + while (line->length > 0 && ufbxi_is_space(line->data[0])) { + line->data++; + line->length--; + } + while (line->length > 0 && ufbxi_is_space(line->data[line->length - 1])) { + line->length--; + } + } + + return true; +} + +// Recursion limited by compile time patterns +static ufbxi_noinline const char *ufbxi_match_skip(const char *fmt, bool alternation) + ufbxi_recursive_function(const char *, ufbxi_match_skip, (fmt, alternation), 4, + (const char *fmt, bool alternation)) +{ + for (;;) { + char c = *fmt++; + switch (c) { + case '(': + fmt = ufbxi_match_skip(fmt, false) + 1; + break; + case '\\': + fmt++; + break; + case '[': + c = *fmt; + while (c != ']') { + c = *fmt++; + if (c == '\\') { + c = *fmt++; + } + } + fmt++; + break; + case '|': + if (alternation) return fmt - 1; + break; + case ')': + case '\0': + return fmt - 1; + default: break; + } + } +} + +// Recursion limited by compile time patterns +static ufbxi_noinline bool ufbxi_match_imp(const char **p_str, const char *end, const char **p_fmt) + ufbxi_recursive_function(bool, ufbxi_match_imp, (p_str, end, p_fmt), 4, + (const char **p_str, const char *end, const char **p_fmt)) +{ + const char *str_original_begin = *p_str; + const char *str = str_original_begin; + const char *fmt_begin = *p_fmt; + const char *fmt = fmt_begin; + bool case_insensitive = false; + + size_t count = 0; + for (;;) { + char c = *fmt++; + if (!c) { + *p_str = str; + *p_fmt = fmt - 1; + return true; + } + + const char *str_begin = str; + char ref = str != end ? *str : '\0'; + + if (case_insensitive) { + if (ref >= 'A' && ref <= 'Z') { + ref = (char)((int)(ref - 'A') + 'a'); + } + } + + bool ok = false; + switch (c) { + + case '\\': { + const char *macro = NULL; + c = *fmt++; + switch (c) { + case 'd': + macro = "[0-9]"; + break; + case 'F': + macro = "[\\-+]?[0-9]+(\\.[0-9]+)?([eE][\\-+]?[0-9]+)?"; + break; + case 's': + if (ufbxi_is_space(ref)) { + ok = true; + str++; + } + break; + case 'S': + if (!ufbxi_is_space(ref)) { + ok = true; + str++; + } + break; + case 'c': + case 'C': + case_insensitive = c == 'c'; + ok = true; + break; + default: + if (ref == c) { + ok = true; + str++; + } + break; + } + if (macro) { + ok = ufbxi_match_imp(&str, end, ¯o); + } + } break; + + case '[': { + while (fmt[0] != ']') { + if (fmt[0] == '\\') { + if (ref == fmt[1]) ok = true; + fmt += 2; + } else if (fmt[1] == '-') { + if (ref >= fmt[0] && ref <= fmt[2]) { + ok = true; + } + fmt += 3; + } else { + if (ref == fmt[0]) ok = true; + fmt += 1; + } + } + fmt++; + if (ok) str++; + } break; + + case '(': + if (ufbxi_match_imp(&str, end, &fmt)) { + ok = true; + } + break; + + case '|': + fmt = ufbxi_match_skip(fmt, false); + ok = true; + break; + + case ')': + *p_str = str; + *p_fmt = fmt; + return true; + + case '.': + if (ref != '\0') { + ok = true; + str++; + } + break; + + default: + if (c == ref) { + str++; + ok = true; + } + break; + } + + bool did_fail = false; + c = *fmt; + switch (c) { + case '*': + fmt++; + if (ok) { + fmt = fmt_begin; + count++; + continue; + } + break; + case '+': + fmt++; + if (ok) { + fmt = fmt_begin; + count++; + continue; + } else if (count == 0) { + did_fail = true; + } + break; + case '?': + fmt++; + break; + default: + did_fail = !ok; + break; + } + + if (did_fail) { + fmt = ufbxi_match_skip(fmt, true); + if (*fmt == '|') { + fmt++; + str = str_original_begin; + } else { + *p_fmt = ufbxi_match_skip(fmt, false) + 1; + return false; + } + } else { + if (!ok) { + str = str_begin; + } + } + + fmt_begin = fmt; + count = 0; + } +} + +static ufbxi_noinline bool ufbxi_match(const ufbx_string *str, const char *fmt) +{ + const char *ptr = str->data, *end = str->data + str->length; + if (ufbxi_match_imp(&ptr, end, &fmt)) { + return ptr == end; + } else { + return false; + } +} + +static ufbxi_noinline bool ufbxi_is_format(const char *data, size_t size, ufbx_file_format format) +{ + ufbx_string line, buf = { data, size }; + + if (format == UFBX_FILE_FORMAT_FBX) { + if (size >= UFBXI_BINARY_MAGIC_SIZE && !memcmp(data, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) { + return true; + } + + while (ufbxi_next_line(&line, &buf, true)) { + if (ufbxi_match(&line, ";\\s*FBX\\s*\\d+\\.\\d+\\.\\d+\\s*project\\s+file")) return true; + if (ufbxi_match(&line, "FBXHeaderExtension:.*")) return true; + } + } else if (format == UFBX_FILE_FORMAT_OBJ) { + while (ufbxi_next_line(&line, &buf, true)) { + const char *pattern = + "(vn?\\s+\\F|vt)\\s+\\F\\s+\\F.*" "|" + "f\\s+[\\-/0-9]+\\s+[\\-/0-9]+\\s*[\\-/0-9]+.*" "|" + "(usemtl|mtllib)\\s+\\S.*"; + if (ufbxi_match(&line, pattern)) return true; + } + } else if (format == UFBX_FILE_FORMAT_MTL) { + while (ufbxi_next_line(&line, &buf, true)) { + const char *pattern = + "newmtl\\s+\\S.*"; + if (ufbxi_match(&line, pattern)) return true; + } + } else { + ufbxi_unreachable("Unhandled format"); + } + + return false; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_determine_format(ufbxi_context *uc) +{ + ufbx_file_format format = uc->opts.file_format; + + if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_content) { + ufbxi_pause_progress(uc); + + size_t lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD; + while (format == UFBX_FILE_FORMAT_UNKNOWN && lookahead <= uc->opts.file_format_lookahead) { + if (lookahead > uc->data_size) { + if (uc->eof) break; + ufbxi_check(ufbxi_refill(uc, lookahead, false)); + } + + size_t data_size = ufbxi_min_sz(lookahead, uc->data_size); + ufbxi_check_msg(data_size > 0, "Empty file"); + + for (uint32_t fmt = UFBX_FILE_FORMAT_FBX; fmt < UFBX_FILE_FORMAT_COUNT; fmt++) { + if (ufbxi_is_format(uc->data, data_size, (ufbx_file_format)fmt)) { + format = (ufbx_file_format)fmt; + break; + } + } + + if (lookahead >= uc->opts.file_format_lookahead) { + break; + } else if (lookahead < SIZE_MAX / 2) { + lookahead = ufbxi_min_sz(lookahead * 2, uc->opts.file_format_lookahead); + } else { + lookahead = SIZE_MAX; + } + } + + ufbxi_check(ufbxi_resume_progress(uc)); + } + + if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_extension) { + if (uc->opts.filename.length > 0) { + ufbx_string extension = uc->opts.filename; + for (size_t i = extension.length; i > 0; i--) { + if (extension.data[i - 1] == '.') { + extension.data += i - 1; + extension.length -= i - 1; + break; + } + } + + if (ufbxi_match(&extension, "\\c\\.fbx")) { + format = UFBX_FILE_FORMAT_FBX; + } else if (ufbxi_match(&extension, "\\c\\.obj")) { + format = UFBX_FILE_FORMAT_OBJ; + } else if (ufbxi_match(&extension, "\\c\\.mtl")) { + format = UFBX_FILE_FORMAT_MTL; + } + } + } + + ufbxi_check_msg(format != UFBX_FILE_FORMAT_UNKNOWN, "Unrecognized file format"); + uc->scene.metadata.file_format = format; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_begin_parse(ufbxi_context *uc) +{ + const char *header = ufbxi_peek_bytes(uc, UFBXI_BINARY_HEADER_SIZE); + ufbxi_check(header); + + // If the file starts with the binary magic parse it as binary, otherwise + // treat it as an ASCII file. + if (!memcmp(header, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) { + + // The byte after the magic indicates endianness + char endian = header[UFBXI_BINARY_MAGIC_SIZE + 0]; + uc->file_big_endian = endian != 0; + + // Read the version directly from the header + const char *version_word = header + UFBXI_BINARY_MAGIC_SIZE + 1; + if (uc->file_big_endian) { + version_word = ufbxi_swap_endian(uc, version_word, 1, 4); + ufbxi_check(version_word); + } + uc->version = ufbxi_read_u32(version_word); + + // This is quite probably an FBX file.. + uc->sure_fbx = true; + ufbxi_consume_bytes(uc, UFBXI_BINARY_HEADER_SIZE); + + } else { + uc->from_ascii = true; + + // Use the current read buffer as the initial parse buffer + memset(&uc->ascii, 0, sizeof(uc->ascii)); + uc->ascii.src = uc->data; + uc->ascii.src_yield = uc->data + uc->yield_size; + uc->ascii.src_end = uc->data + uc->data_size + uc->yield_size; + + // Initialize the first token + ufbxi_check(ufbxi_ascii_next_token(uc, &uc->ascii.token)); + + // Default to version 7400 if not found in header + if (uc->version > 0) { + uc->sure_fbx = true; + } else { + if (!uc->opts.strict) uc->version = 7400; + ufbxi_check_msg(uc->version > 0, "Not an FBX file"); + } + } + + return 1; +} + +ufbxi_nodiscard static int ufbxi_parse_toplevel_child_imp(ufbxi_context *uc, ufbxi_parse_state state, ufbxi_buf *buf, bool *p_end) +{ + if (uc->from_ascii) { + ufbxi_check(ufbxi_ascii_parse_node(uc, 0, state, p_end, buf, true)); + } else { + ufbxi_check(ufbxi_binary_parse_node(uc, 0, state, p_end, buf, true)); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel(ufbxi_context *uc, const char *name) +{ + ufbxi_for(ufbxi_node, node, uc->top_nodes, uc->top_nodes_len) { + if (node->name == name) { + uc->top_node = node; + uc->top_child_index = 0; + return 1; + } + } + + // Reached end and not found in cache + if (uc->parsed_to_end) { + uc->top_node = NULL; + uc->top_child_index = 0; + return 1; + } + + for (;;) { + // Parse the next top-level node + bool end = false; + if (uc->from_ascii) { + ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false)); + } else { + ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false)); + } + + // Top-level node not found + if (end) { + uc->top_node = NULL; + uc->top_child_index = 0; + uc->parsed_to_end = true; + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_retain_toplevel(uc, NULL)); + } + + // Not needed anymore + ufbxi_buf_free(&uc->tmp_parse); + + return 1; + } + + uc->top_nodes_len++; + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->top_nodes, &uc->top_nodes_cap, uc->top_nodes_len)); + ufbxi_node *node = &uc->top_nodes[uc->top_nodes_len - 1]; + ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, node); + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_retain_toplevel(uc, node)); + } + + // Return if we parsed the right one + if (node->name == name) { + uc->top_node = node; + uc->top_child_index = SIZE_MAX; + return 1; + } + + // If not we need to parse all the children of the node for later + uint32_t num_children = 0; + ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, node->name); + if (uc->has_next_child) { + for (;;) { + ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, &uc->tmp, &end)); + if (end) break; + num_children++; + } + } + + node->num_children = num_children; + node->children = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_node, num_children); + ufbxi_check(node->children); + + if (uc->opts.retain_dom) { + for (size_t i = 0; i < num_children; i++) { + ufbxi_check(ufbxi_retain_toplevel_child(uc, &node->children[i])); + } + } + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel_child(ufbxi_context *uc, ufbxi_node **p_node, ufbxi_buf *tmp_buf) +{ + // Top-level node not found + if (!uc->top_node) { + *p_node = NULL; + return 1; + } + + if (uc->top_child_index == SIZE_MAX) { + // Parse children on demand + if (!tmp_buf) { + ufbxi_buf_clear(&uc->tmp_parse); + } + bool end = false; + ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, uc->top_node->name); + ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, tmp_buf ? tmp_buf : &uc->tmp_parse, &end)); + if (end) { + *p_node = NULL; + } else { + // Parse to either reused `uc->top_child` or push if retaining to `tmp_buf`. + ufbxi_node *dst = &uc->top_child; + if (tmp_buf) { + dst = ufbxi_push_zero(tmp_buf, ufbxi_node, 1); + ufbxi_check(dst); + } + + ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, dst); + *p_node = dst; + + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_retain_toplevel_child(uc, dst)); + } + } + } else { + // Iterate already parsed nodes + size_t child_index = uc->top_child_index; + if (child_index == uc->top_node->num_children) { + *p_node = NULL; + } else { + uc->top_child_index++; + *p_node = &uc->top_node->children[child_index]; + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_legacy_toplevel(ufbxi_context *uc) +{ + ufbx_assert(uc->top_nodes_len == 0); + + bool end = false; + if (uc->from_ascii) { + ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true)); + } else { + ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true)); + } + + // Top-level node not found + if (end) { + uc->top_node = NULL; + uc->top_child_index = 0; + uc->parsed_to_end = true; + return 1; + } + + ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, &uc->legacy_node); + uc->top_child_index = 0; + uc->top_node = &uc->legacy_node; + + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_retain_toplevel(uc, &uc->legacy_node)); + } + + return 1; +} + +// -- Setup + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_strings(ufbxi_context *uc) +{ +#if defined(UFBX_REGRESSION) + ufbx_string reg_prev = ufbx_empty_string; +#endif + + // Push all the global 'ufbxi_*' strings into the pool without copying them + // This allows us to compare name pointers to the global values + ufbxi_for(const ufbx_string, str, ufbxi_strings, ufbxi_arraycount(ufbxi_strings)) { +#if defined(UFBX_REGRESSION) + ufbx_assert(strlen(str->data) == str->length); + ufbx_assert(ufbxi_str_less(reg_prev, *str)); + reg_prev = *str; +#endif + ufbxi_check(ufbxi_push_string_imp(&uc->string_pool, str->data, str->length, NULL, false, true)); + } + + return 1; +} + +typedef struct { + const char *name; + ufbx_prop_type type; +} ufbxi_prop_type_name; + +static const ufbxi_prop_type_name ufbxi_prop_type_names[] = { + { "Boolean", UFBX_PROP_BOOLEAN }, + { "bool", UFBX_PROP_BOOLEAN }, + { "Bool", UFBX_PROP_BOOLEAN }, + { "Integer", UFBX_PROP_INTEGER }, + { "int", UFBX_PROP_INTEGER }, + { "enum", UFBX_PROP_INTEGER }, + { "Enum", UFBX_PROP_INTEGER }, + { "Visibility", UFBX_PROP_INTEGER }, + { "Visibility Inheritance", UFBX_PROP_INTEGER }, + { "KTime", UFBX_PROP_INTEGER }, + { "Number", UFBX_PROP_NUMBER }, + { "double", UFBX_PROP_NUMBER }, + { "Real", UFBX_PROP_NUMBER }, + { "Float", UFBX_PROP_NUMBER }, + { "Intensity", UFBX_PROP_NUMBER }, + { "Vector", UFBX_PROP_VECTOR }, + { "Vector3D", UFBX_PROP_VECTOR }, + { "Color", UFBX_PROP_COLOR }, + { "ColorAndAlpha", UFBX_PROP_COLOR_WITH_ALPHA }, + { "ColorRGB", UFBX_PROP_COLOR }, + { "String", UFBX_PROP_STRING }, + { "KString", UFBX_PROP_STRING }, + { "object", UFBX_PROP_STRING }, + { "DateTime", UFBX_PROP_DATE_TIME }, + { "Lcl Translation", UFBX_PROP_TRANSLATION }, + { "Lcl Rotation", UFBX_PROP_ROTATION }, + { "Lcl Scaling", UFBX_PROP_SCALING }, + { "Distance", UFBX_PROP_DISTANCE }, + { "Compound", UFBX_PROP_COMPOUND }, + { "Blob", UFBX_PROP_BLOB }, + { "Reference", UFBX_PROP_REFERENCE }, +}; + +static ufbx_prop_type ufbxi_get_prop_type(ufbxi_context *uc, const char *name) +{ + uint32_t hash = ufbxi_hash_ptr(name); + ufbxi_prop_type_name *entry = ufbxi_map_find(&uc->prop_type_map, ufbxi_prop_type_name, hash, &name); + if (entry) { + return entry->type; + } + return UFBX_PROP_UNKNOWN; +} + +static ufbxi_noinline ufbx_prop *ufbxi_find_prop_with_key(const ufbx_props *props, const char *name, uint32_t key) +{ + do { + ufbx_prop *prop_data = props->props.data; + size_t begin = 0; + size_t end = props->props.count; + while (end - begin >= 16) { + size_t mid = (begin + end) >> 1; + const ufbx_prop *p = &prop_data[mid]; + if (p->_internal_key < key) { + begin = mid + 1; + } else { + end = mid; + } + } + + end = props->props.count; + for (; begin < end; begin++) { + const ufbx_prop *p = &prop_data[begin]; + if (p->_internal_key > key) break; + if (p->name.data == name && (p->flags & UFBX_PROP_FLAG_NO_VALUE) == 0) { + return (ufbx_prop*)p; + } + } + + props = props->defaults; + } while (props); + + return NULL; +} + +typedef struct { + const char *key; + ufbx_texture_file *file; +} ufbxi_texture_file_entry; + +#define ufbxi_find_prop(props, name) ufbxi_find_prop_with_key((props), (name), \ + ((uint32_t)(uint8_t)name[0] << 24u) | ((uint32_t)(uint8_t)name[1] << 16u) | \ + ((uint32_t)(uint8_t)name[2] << 8u) | (uint32_t)(uint8_t)name[3]) + +static ufbxi_forceinline ufbx_real ufbxi_find_real(const ufbx_props *props, const char *name, ufbx_real def) +{ + ufbx_prop *prop = ufbxi_find_prop(props, name); + if (prop) { + return prop->value_real; + } else { + return def; + } +} + +static ufbxi_forceinline ufbx_vec3 ufbxi_find_vec3(const ufbx_props *props, const char *name, ufbx_real def_x, ufbx_real def_y, ufbx_real def_z) +{ + ufbx_prop *prop = ufbxi_find_prop(props, name); + if (prop) { + return prop->value_vec3; + } else { + ufbx_vec3 def = { def_x, def_y, def_z }; + return def; + } +} + +static ufbxi_forceinline int64_t ufbxi_find_int(const ufbx_props *props, const char *name, int64_t def) +{ + ufbx_prop *prop = ufbxi_find_prop(props, name); + if (prop) { + return prop->value_int; + } else { + return def; + } +} + +static ufbxi_forceinline int64_t ufbxi_find_enum(const ufbx_props *props, const char *name, int64_t def, int64_t max_value) +{ + ufbx_prop *prop = ufbxi_find_prop(props, name); + if (prop) { + int64_t value = prop->value_int; + if (value >= 0 && value <= max_value) { + return value; + } else { + return def; + } + } else { + return def; + } +} + +ufbxi_noinline static bool ufbxi_matrix_all_zero(const ufbx_matrix *matrix) +{ + for (size_t i = 0; i < 12; i++) { + if (matrix->v[i] != 0.0f) return false; + } + return true; +} + +static ufbxi_forceinline bool ufbxi_is_vec3_zero(ufbx_vec3 v) +{ + return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0); +} + +static ufbxi_forceinline bool ufbxi_is_vec4_zero(ufbx_vec4 v) +{ + return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0); +} + +static ufbxi_forceinline bool ufbxi_is_vec3_one(ufbx_vec3 v) +{ + return (v.x == 1.0) & (v.y == 1.0) & (v.z == 1.0); +} + +static ufbxi_forceinline bool ufbxi_is_quat_identity(ufbx_quat v) +{ + return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0) & (v.w == 1.0); +} + +static ufbxi_noinline bool ufbxi_is_transform_identity(const ufbx_transform *t) +{ + return (bool)((int)ufbxi_is_vec3_zero(t->translation) & (int)ufbxi_is_quat_identity(t->rotation) & (int)ufbxi_is_vec3_one(t->scale)); +} + +static ufbxi_forceinline uint32_t ufbxi_get_name_key(const char *name, size_t len) +{ + uint32_t key = 0; + if (len >= 4) { + key = (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16 + | (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3]; + } else { + for (size_t i = 0; i < 4; i++) { + key <<= 8; + if (i < len) key |= (uint8_t)name[i]; + } + } + return key; +} + +static ufbxi_forceinline uint32_t ufbxi_get_name_key_c(const char *name) +{ + if (name[0] == '\0') return 0; + if (name[1] == '\0') return (uint32_t)(uint8_t)name[0]<<24; + if (name[2] == '\0') return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16; + return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16 + | (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3]; +} + +static ufbxi_forceinline bool ufbxi_name_key_less(ufbx_prop *prop, const char *data, size_t name_len, uint32_t key) +{ + if (prop->_internal_key < key) return true; + if (prop->_internal_key > key) return false; + + size_t prop_len = prop->name.length; + size_t len = ufbxi_min_sz(prop_len, name_len); + int cmp = memcmp(prop->name.data, data, len); + if (cmp != 0) return cmp < 0; + return prop_len < name_len; +} + +static const char *const ufbxi_node_prop_names[] = { + "AxisLen", + "DefaultAttributeIndex", + "Freeze", + "GeometricRotation", + "GeometricScaling", + "GeometricTranslation", + "InheritType", + "LODBox", + "Lcl Rotation", + "Lcl Scaling", + "Lcl Translation", + "LookAtProperty", + "MaxDampRangeX", + "MaxDampRangeY", + "MaxDampRangeZ", + "MaxDampStrengthX", + "MaxDampStrengthY", + "MaxDampStrengthZ", + "MinDampRangeX", + "MinDampRangeY", + "MinDampRangeZ", + "MinDampStrengthX", + "MinDampStrengthY", + "MinDampStrengthZ", + "NegativePercentShapeSupport", + "PostRotation", + "PreRotation", + "PreferedAngleX", + "PreferedAngleY", + "PreferedAngleZ", + "QuaternionInterpolate", + "RotationActive", + "RotationMax", + "RotationMaxX", + "RotationMaxY", + "RotationMaxZ", + "RotationMin", + "RotationMinX", + "RotationMinY", + "RotationMinZ", + "RotationOffset", + "RotationOrder", + "RotationPivot", + "RotationSpaceForLimitOnly", + "RotationStiffnessX", + "RotationStiffnessY", + "RotationStiffnessZ", + "ScalingActive", + "ScalingMax", + "ScalingMaxX", + "ScalingMaxY", + "ScalingMaxZ", + "ScalingMin", + "ScalingMinX", + "ScalingMinY", + "ScalingMinZ", + "ScalingOffset", + "ScalingPivot", + "Show", + "TranslationActive", + "TranslationMax", + "TranslationMaxX", + "TranslationMaxY", + "TranslationMaxZ", + "TranslationMin", + "TranslationMinX", + "TranslationMinY", + "TranslationMinZ", + "UpVectorProperty", + "Visibility Inheritance", + "Visibility", + "notes", +}; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_init_node_prop_names(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_map_grow(&uc->node_prop_set, const char*, ufbxi_arraycount(ufbxi_node_prop_names))); + for (size_t i = 0; i < ufbxi_arraycount(ufbxi_node_prop_names); i++) { + const char *name = ufbxi_node_prop_names[i]; + const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name, strlen(name), NULL, false, true); + ufbxi_check(pooled); + uint32_t hash = ufbxi_hash_ptr(pooled); + const char **entry = ufbxi_map_insert(&uc->node_prop_set, const char*, hash, &pooled); + ufbxi_check(entry); + *entry = pooled; + } + + return 1; +} + +static bool ufbxi_is_node_property_name(ufbxi_context *uc, const char *name) +{ + // You need to call `ufbxi_init_node_prop_names()` before calling this + ufbx_assert(uc->node_prop_set.size > 0); + + uint32_t hash = ufbxi_hash_ptr(name); + const char **entry = ufbxi_map_find(&uc->node_prop_set, const char*, hash, &name); + return entry != NULL; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_maps(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_map_grow(&uc->prop_type_map, ufbxi_prop_type_name, ufbxi_arraycount(ufbxi_prop_type_names))); + ufbxi_for(const ufbxi_prop_type_name, name, ufbxi_prop_type_names, ufbxi_arraycount(ufbxi_prop_type_names)) { + const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name->name, strlen(name->name), NULL, false, true); + ufbxi_check(pooled); + uint32_t hash = ufbxi_hash_ptr(pooled); + ufbxi_prop_type_name *entry = ufbxi_map_insert(&uc->prop_type_map, ufbxi_prop_type_name, hash, &pooled); + ufbxi_check(entry); + entry->type = name->type; + entry->name = pooled; + } + + return 1; +} + +// -- Reading the parsed data + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_embedded_blob(ufbxi_context *uc, ufbx_blob *dst_blob, ufbxi_node *node) +{ + if (!node) return 1; + + ufbxi_value_array *content_arr = ufbxi_get_array(node, 'C'); + if (content_arr && content_arr->size > 0) { + ufbx_string content; + size_t num_parts = content_arr->size; + ufbx_string *parts = (ufbx_string*)content_arr->data; + + if (num_parts == 1 && !uc->from_ascii) { + content = parts[0]; + } else { + size_t total_size = 0; + ufbxi_for(ufbx_string, part, parts, num_parts) { + total_size += part->length; + } + char *dst = ufbxi_push(&uc->result, char, total_size); + ufbxi_check(dst); + content.data = dst; + content.length = total_size; + ufbxi_for(ufbx_string, part, parts, num_parts) { + memcpy(dst, part->data, part->length); + dst += part->length; + } + } + + dst_blob->data = content.data; + dst_blob->size = content.length; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_property(ufbxi_context *uc, ufbxi_node *node, ufbx_prop *prop, int version) +{ + const char *type_str = NULL, *subtype_str = NULL; + ufbxi_check(ufbxi_get_val2(node, "SC", &prop->name, (char**)&type_str)); + uint32_t val_ix = 2; + if (version == 70) { + ufbxi_check(ufbxi_get_val_at(node, val_ix++, 'C', (char**)&subtype_str)); + } + + uint32_t flags = 0; + prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); + + ufbx_string flags_str; + if (ufbxi_get_val_at(node, val_ix++, 'S', &flags_str)) { + for (size_t i = 0; i < flags_str.length; i++) { + char next = i + 1 < flags_str.length ? flags_str.data[i + 1] : '0'; + switch (flags_str.data[i]) { + case 'A': flags |= UFBX_PROP_FLAG_ANIMATABLE; break; + case 'U': flags |= UFBX_PROP_FLAG_USER_DEFINED; break; + case 'H': flags |= UFBX_PROP_FLAG_HIDDEN; break; + case 'L': flags |= ((uint32_t)(next - '0') & 0xf) << 4; break; // UFBX_PROP_FLAG_LOCK_* + case 'M': flags |= ((uint32_t)(next - '0') & 0xf) << 8; break; // UFBX_PROP_FLAG_MUTE_* + default: break; // Ignore unknown flags + } + } + } + + prop->type = ufbxi_get_prop_type(uc, type_str); + if (prop->type == UFBX_PROP_UNKNOWN && subtype_str) { + prop->type = ufbxi_get_prop_type(uc, subtype_str); + } + + if (ufbxi_get_val_at(node, val_ix, 'L', &prop->value_int)) { + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT; + } + + size_t real_ix; + for (real_ix = 0; real_ix < 4; real_ix++) { + if (!ufbxi_get_val_at(node, val_ix + real_ix, 'R', &prop->value_real_arr[real_ix])) break; + } + if (real_ix > 0) { + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (real_ix - 1); + } + + // Skip one value forward in case the current value is not a string, as some properties + // contain mixed numbers and strings. Currenltly known cases: + // Lod Distance: P: "Thresholds|Level0", "Distance", "", "",64, "cm" + // User Enum: P: "User_Enum", "Enum", "", "A+U",1, "ValueA~ValueB~ValueC" + if (ufbxi_get_val_type(node, val_ix) != UFBXI_VALUE_STRING) { + val_ix++; + } + + if (ufbxi_get_val_at(node, val_ix, 'S', &prop->value_str)) { + if (prop->value_str.length > 0) { + ufbxi_ignore(ufbxi_get_val_at(node, val_ix, 'b', &prop->value_blob)); + } + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR; + } else { + prop->value_str = ufbx_empty_string; + } + + // Very unlikely, seems to only exist in some "non standard" FBX files + if (node->num_children > 0) { + ufbxi_node *binary = ufbxi_find_child(node, ufbxi_BinaryData); + ufbxi_check(ufbxi_read_embedded_blob(uc, &prop->value_blob, binary)); + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_BLOB; + } + + prop->flags = (ufbx_prop_flags)flags; + + return 1; +} + +static ufbxi_forceinline bool ufbxi_prop_less(ufbx_prop *a, ufbx_prop *b) +{ + if (a->_internal_key < b->_internal_key) return true; + if (a->_internal_key > b->_internal_key) return false; + return strcmp(a->name.data, b->name.data) < 0; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_properties(ufbxi_context *uc, ufbx_prop *props, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_prop))); + ufbxi_macro_stable_sort(ufbx_prop, 32, props, uc->tmp_arr, count, ( ufbxi_prop_less(a, b) )); + return 1; +} + +ufbxi_noinline static void ufbxi_deduplicate_properties(ufbx_prop_list *list) +{ + if (list->count >= 2) { + ufbx_prop *ps = list->data; + size_t dst = 0, src = 0, end = list->count; + while (src < end) { + if (src + 1 < end && ps[src].name.data == ps[src + 1].name.data) { + src++; + } else if (dst != src) { + ps[dst++] = ps[src++]; + } else { + dst++; src++; + } + } + list->count = dst; + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_properties(ufbxi_context *uc, ufbxi_node *parent, ufbx_props *props) +{ + props->defaults = NULL; + + int version = 70; + ufbxi_node *node = ufbxi_find_child(parent, ufbxi_Properties70); + if (!node) { + node = ufbxi_find_child(parent, ufbxi_Properties60); + if (!node) { + // No properties found, not an error + props->props.data = NULL; + props->props.count = 0; + return 1; + } + version = 60; + } + + props->props.data = ufbxi_push_zero(&uc->result, ufbx_prop, node->num_children); + props->props.count = node->num_children; + ufbxi_check(props->props.data); + + for (size_t i = 0; i < props->props.count; i++) { + ufbxi_check(ufbxi_read_property(uc, &node->children[i], &props->props.data[i], version)); + } + + ufbxi_check(ufbxi_sort_properties(uc, props->props.data, props->props.count)); + ufbxi_deduplicate_properties(&props->props); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_thumbnail(ufbxi_context *uc, ufbxi_node *node, ufbx_thumbnail *thumbnail) +{ + ufbxi_check(ufbxi_read_properties(uc, node, &thumbnail->props)); + + int64_t custom_width = ufbx_find_int(&thumbnail->props, "CustomWidth", 0); + int64_t custom_height = ufbx_find_int(&thumbnail->props, "CustomHeight", 0); + + int32_t format; + ufbxi_node *format_node = ufbxi_find_child_strcmp(node, "Format"); + if (format_node && ufbxi_get_val1(format_node, "I", &format)) { + if (format >= 0 && format + 1 < UFBX_THUMBNAIL_FORMAT_COUNT) { + thumbnail->format = (ufbx_thumbnail_format)(format + 1); + } + } + + int32_t size; + if (ufbxi_find_val1(node, ufbxi_Size, "I", &size)) { + if (size > 0) { + thumbnail->width = (uint32_t)size; + thumbnail->height = (uint32_t)size; + } else if (size < 0 && custom_width > 0 && custom_height > 0) { + thumbnail->width = (uint32_t)custom_width; + thumbnail->height = (uint32_t)custom_height; + } + } + + ufbxi_value_array *data_arr = ufbxi_find_array(node, ufbxi_ImageData, 'c'); + if (data_arr) { + thumbnail->data.data = data_arr->data; + thumbnail->data.size = data_arr->size; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_scene_info(ufbxi_context *uc, ufbxi_node *node) +{ + ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.metadata.scene_props)); + + ufbxi_node *thumbnail = ufbxi_find_child(node, ufbxi_Thumbnail); + if (thumbnail) { + ufbxi_check(ufbxi_read_thumbnail(uc, thumbnail, &uc->scene.metadata.thumbnail)); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_header_extension(ufbxi_context *uc) +{ + bool has_tc_definition = false; + int32_t tc_definition = 0; + int32_t header_version = 0; + + for (;;) { + ufbxi_node *child; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL)); + if (!child) break; + + if (child->name == ufbxi_Creator) { + ufbxi_ignore(ufbxi_get_val1(child, "S", &uc->scene.metadata.creator)); + } + + if (uc->version < 6000 && child->name == ufbxi_FBXVersion) { + int32_t version; + if (ufbxi_get_val1(child, "I", &version)) { + if (version > 0 && version < 6000 && (uint32_t)version > uc->version) { + uc->version = (uint32_t)version; + } + } + } + + if (child->name == ufbxi_FBXHeaderVersion) { + ufbxi_ignore(ufbxi_get_val1(child, "I", &header_version)); + } + + if (child->name == ufbxi_OtherFlags) { + if (ufbxi_find_val1(child, ufbxi_TCDefinition, "I", &tc_definition)) { + has_tc_definition = true; + } + } + + if (child->name == ufbxi_SceneInfo) { + ufbxi_check(ufbxi_read_scene_info(uc, child)); + } + + } + + // FBX 8000 will change the KTime units and the new units are opt-in currently via `TCDefinition`. + // `TCDefinition` seems be accounted in all versions, as long as `FBXHeaderVersion >= 1004`. + // The old KTime units are specified as the value `127` and all other values seem to use the new definition. + bool use_v7_ktime = uc->version < 8000; + if (header_version >= 1004 && has_tc_definition) { + use_v7_ktime = tc_definition == 127; + } + + uc->ktime_sec = use_v7_ktime ? 46186158000 : 141120000; + uc->ktime_sec_double = (double)uc->ktime_sec; + + return 1; +} + +static bool ufbxi_match_version_string(const char *fmt, ufbx_string str, uint32_t *p_version) +{ + size_t num_ix = 0; + size_t pos = 0; + while (*fmt) { + char c = *fmt++; + if (c >= 'a' && c <= 'z') { + if (pos >= str.length) return false; + char s = str.data[pos]; + if (s != c && (int)s + (int)('a' - 'A') != (int)c) return false; + pos++; + } else if (c == ' ') { + while (pos < str.length) { + char s = str.data[pos]; + if (s != ' ' && s != '\t') break; + pos++; + } + } else if (c == '-') { + while (pos < str.length) { + char s = str.data[pos]; + if (s == '-') break; + pos++; + } + if (pos >= str.length) return false; + pos++; + } else if (c == '/' || c == '.' || c == '(' || c == ')') { + if (pos >= str.length) return false; + if (str.data[pos] != c) return false; + pos++; + } else if (c == '?') { + uint32_t num = 0; + size_t len = 0; + while (pos < str.length) { + char s = str.data[pos]; + if (!(s >= '0' && s <= '9')) break; + num = num*10 + (uint32_t)(s - '0'); + pos++; + len++; + } + if (len == 0) return false; + p_version[num_ix++] = num; + } else { + ufbxi_unreachable("Unhandled match character"); + } + } + + return true; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_match_exporter(ufbxi_context *uc) +{ + ufbx_string creator = uc->scene.metadata.creator; + uint32_t version[3] = { 0 }; + if (ufbxi_match_version_string("blender-- ?.?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_BLENDER_BINARY; + uc->exporter_version = ufbx_pack_version(version[0], version[1], version[2]); + } else if (ufbxi_match_version_string("blender- ?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_BLENDER_BINARY; + uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); + } else if (ufbxi_match_version_string("blender version ?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_BLENDER_ASCII; + uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); + } else if (ufbxi_match_version_string("fbx sdk/fbx plugins version ?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_FBX_SDK; + uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); + } else if (ufbxi_match_version_string("fbx sdk/fbx plugins build ?", creator, version)) { + uc->exporter = UFBX_EXPORTER_FBX_SDK; + uc->exporter_version = ufbx_pack_version(version[0]/10000u, version[0]/100u%100u, version[0]%100u); + } else if (ufbxi_match_version_string("motionbuilder version ?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_MOTION_BUILDER; + uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); + } else if (ufbxi_match_version_string("motionbuilder/mocap/online version ?.?", creator, version)) { + uc->exporter = UFBX_EXPORTER_MOTION_BUILDER; + uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); + } + + uc->scene.metadata.exporter = uc->exporter; + uc->scene.metadata.exporter_version = uc->exporter_version; + + // Un-detect the exporter in `ufbxi_context` to disable special cases + if (uc->opts.disable_quirks) { + uc->exporter = UFBX_EXPORTER_UNKNOWN; + uc->exporter_version = 0; + } + + if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { + uc->blender_full_weights = true; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_document(ufbxi_context *uc) +{ + bool found_root_id = 0; + + for (;;) { + ufbxi_node *child; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL)); + if (!child) break; + + if (child->name == ufbxi_Document && !found_root_id) { + // Post-7000: Try to find the first document node and root ID. + // TODO: Multiple documents / roots? + if (ufbxi_find_val1(child, ufbxi_RootNode, "L", &uc->root_id)) { + found_root_id = true; + } + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_definitions(ufbxi_context *uc) +{ + for (;;) { + ufbxi_node *object; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &object, NULL)); + if (!object) break; + + if (object->name != ufbxi_ObjectType) continue; + + ufbxi_template *tmpl = ufbxi_push_zero(&uc->tmp_stack, ufbxi_template, 1); + uc->num_templates++; + ufbxi_check(tmpl); + ufbxi_check(ufbxi_get_val1(object, "C", (char**)&tmpl->type)); + + // Pre-7000 FBX versions don't have property templates, they just have + // the object counts by themselves. + ufbxi_node *props = ufbxi_find_child(object, ufbxi_PropertyTemplate); + if (props) { + ufbxi_check(ufbxi_get_val1(props, "S", &tmpl->sub_type)); + + // Remove the "Fbx" prefix from sub-types, remember to re-intern! + if (tmpl->sub_type.length > 3 && !strncmp(tmpl->sub_type.data, "Fbx", 3)) { + tmpl->sub_type.data += 3; + tmpl->sub_type.length -= 3; + + // HACK: LOD groups use LODGroup for Template, LodGroup for Object? + if (tmpl->sub_type.length == 8 && !memcmp(tmpl->sub_type.data, "LODGroup", 8)) { + tmpl->sub_type.data = "LodGroup"; + } + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tmpl->sub_type, false)); + } + + ufbxi_check(ufbxi_read_properties(uc, props, &tmpl->props)); + } + } + + // TODO: Preserve only the `props` part of the templates + uc->templates = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbxi_template, uc->num_templates); + ufbxi_check(uc->templates); + + return 1; +} + +ufbxi_nodiscard static ufbx_props *ufbxi_find_template(ufbxi_context *uc, const char *name, const char *sub_type) +{ + // TODO: Binary search + ufbxi_for(ufbxi_template, tmpl, uc->templates, uc->num_templates) { + if (tmpl->type == name) { + + // Check that sub_type matches unless the type is Material, Model, AnimationStack, AnimationLayer. + // Those match to all sub-types. + if (tmpl->type != ufbxi_Material && tmpl->type != ufbxi_Model + && tmpl->type != ufbxi_AnimationStack && tmpl->type != ufbxi_AnimationLayer) { + if (tmpl->sub_type.data != sub_type) { + return NULL; + } + } + + if (tmpl->props.props.count > 0) { + return &tmpl->props; + } else { + return NULL; + } + } + } + return NULL; +} + +// Name ID categories +#if defined(UFBX_REGRESSION) + #define UFBXI_MAXIMUM_FAST_POINTER_ID UINT64_C(0x100) +#else + #define UFBXI_MAXIMUM_FAST_POINTER_ID UINT64_C(0x4000000000000000) +#endif +#define UFBXI_POINTER_ID_START UINT64_C(0x8000000000000000) +#define UFBXI_SYNTHETIC_ID_START (UFBXI_POINTER_ID_START + UFBXI_MAXIMUM_FAST_POINTER_ID) + +static ufbxi_forceinline uint64_t ufbxi_push_synthetic_id(ufbxi_context *uc) +{ + return ++uc->synthetic_id_counter; +} + +static ufbxi_noinline uint64_t ufbxi_synthetic_id_from_ptr_id(ufbxi_context *uc, uintptr_t ptr, uint64_t id) +{ + ufbxi_ptr_id ptr_id = { ptr, id }; + uint32_t hash = ufbxi_hash_ptr_id(ptr_id); + ufbxi_ptr_fbx_id_entry *entry = ufbxi_map_find(&uc->ptr_fbx_id_map, ufbxi_ptr_fbx_id_entry, hash, &ptr_id); + + if (!entry) { + entry = ufbxi_map_insert(&uc->ptr_fbx_id_map, ufbxi_ptr_fbx_id_entry, hash, &ptr_id); + ufbxi_check_return(entry, 0); + entry->ptr_id = ptr_id; + entry->fbx_id = ufbxi_push_synthetic_id(uc); + } + + return entry->fbx_id; +} + +static ufbxi_forceinline uint64_t ufbxi_synthetic_id_from_string(ufbxi_context *uc, const char *str) +{ + uintptr_t uptr = (uintptr_t)str; + if (uptr < (UINTPTR_MAX < UFBXI_MAXIMUM_FAST_POINTER_ID ? UINTPTR_MAX : UFBXI_MAXIMUM_FAST_POINTER_ID)) { + return (uint64_t)uptr; + } else { + return ufbxi_synthetic_id_from_ptr_id(uc, uptr, 0); + } +} + +ufbxi_nodiscard ufbxi_forceinline static int ufbxi_validate_fbx_id(ufbxi_context *uc, uint64_t *p_fbx_id) +{ + uint64_t fbx_id = *p_fbx_id; + if (fbx_id >= UFBXI_POINTER_ID_START) { + fbx_id = ufbxi_synthetic_id_from_ptr_id(uc, 0, fbx_id); + ufbxi_check(fbx_id); + *p_fbx_id = fbx_id; + } + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_split_type_and_name(ufbxi_context *uc, ufbx_string type_and_name, ufbx_string *type, ufbx_string *name) +{ + // Name and type are packed in a single property as Type::Name (in ASCII) + // or Name\x00\x01Type (in binary) + const char *sep = uc->from_ascii ? "::" : "\x00\x01"; + size_t type_end = 2; + for (; type_end <= type_and_name.length; type_end++) { + const char *ch = type_and_name.data + type_end - 2; + if (ch[0] == sep[0] && ch[1] == sep[1]) break; + } + + // ???: ASCII and binary store type and name in different order + if (type_end <= type_and_name.length) { + if (uc->from_ascii) { + name->data = type_and_name.data + type_end; + name->length = type_and_name.length - type_end; + type->data = type_and_name.data; + type->length = type_end - 2; + } else { + name->data = type_and_name.data; + name->length = type_end - 2; + type->data = type_and_name.data + type_end; + type->length = type_and_name.length - type_end; + } + } else { + *name = type_and_name; + type->data = ufbxi_empty_char; + type->length = 0; + } + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, type, false)); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, name, false)); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_id(ufbxi_context *uc, uint64_t fbx_id, uint32_t element_id) +{ + uint32_t hash = ufbxi_hash64(fbx_id); + ufbxi_fbx_id_entry *entry = ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); + + if (!entry) { + entry = ufbxi_map_insert(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); + ufbxi_check(entry); + entry->fbx_id = fbx_id; + entry->element_id = element_id; + entry->user_id = 0; + } else { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_DUPLICATE_OBJECT_ID, "Duplicate object ID")); + } + + return 1; +} + +static ufbxi_noinline ufbxi_fbx_id_entry *ufbxi_find_fbx_id(ufbxi_context *uc, uint64_t fbx_id) +{ + uint32_t hash = ufbxi_hash64(fbx_id); + return ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); +} + +static ufbxi_forceinline bool ufbxi_fbx_id_exists(ufbxi_context *uc, uint64_t fbx_id) +{ + return ufbxi_find_fbx_id(uc, fbx_id) != NULL; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_attr(ufbxi_context *uc, uint64_t fbx_id, uint64_t attrib_fbx_id) +{ + uint32_t hash = ufbxi_hash64(fbx_id); + ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id); + // TODO: Strict / warn about duplicate objects + + if (!entry) { + entry = ufbxi_map_insert(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id); + ufbxi_check(entry); + entry->node_fbx_id = fbx_id; + entry->attr_fbx_id = attrib_fbx_id; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbxi_context *uc, ufbxi_element_info *info, size_t size, ufbx_element_type type) +{ + size_t aligned_size = (size + 7u) & ~0x7u; + + uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items; + uint32_t element_id = uc->num_elements++; + + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, &info->fbx_id), NULL); + uc->tmp_element_byte_offset += aligned_size; + + ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8); + ufbxi_check_return(elem, NULL); + elem->type = type; + elem->element_id = element_id; + elem->typed_id = typed_id; + elem->name = info->name; + elem->props = info->props; + elem->dom_node = info->dom_node; + + if (uc->p_element_id) { + *uc->p_element_id = element_id; + } + + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); + + ufbxi_check_return(ufbxi_insert_fbx_id(uc, info->fbx_id, element_id), NULL); + + return elem; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element_size(ufbxi_context *uc, uint64_t *p_fbx_id, ufbxi_node *node, const char *name, size_t size, ufbx_element_type type) +{ + size_t aligned_size = (size + 7u) & ~0x7u; + + uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items; + uint32_t element_id = uc->num_elements++; + + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); + uc->tmp_element_byte_offset += aligned_size; + + ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8); + ufbxi_check_return(elem, NULL); + elem->type = type; + elem->element_id = element_id; + elem->typed_id = typed_id; + elem->dom_node = ufbxi_get_dom_node(uc, node); + if (name) { + elem->name.data = name; + elem->name.length = strlen(name); + } + + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); + + *p_fbx_id = ufbxi_push_synthetic_id(uc); + + ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, p_fbx_id), NULL); + ufbxi_check_return(ufbxi_insert_fbx_id(uc, *p_fbx_id, element_id), NULL); + + return elem; +} + +#define ufbxi_push_element(uc, info, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_element_size((uc), (info), sizeof(type_name), (type_enum))) +#define ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_synthetic_element_size((uc), (p_fbx_id), (node), (name), sizeof(type_name), (type_enum))) + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_oo(ufbxi_context *uc, uint64_t src, uint64_t dst) +{ + ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); + ufbxi_check(conn); + conn->src = src; + conn->dst = dst; + conn->src_prop = conn->dst_prop = ufbx_empty_string; + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_op(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string prop) +{ + ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); + ufbxi_check(conn); + conn->src = src; + conn->dst = dst; + conn->src_prop = ufbx_empty_string; + conn->dst_prop = prop; + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_pp(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string src_prop, ufbx_string dst_prop) +{ + ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); + ufbxi_check(conn); + conn->src = src; + conn->dst = dst; + conn->src_prop = src_prop; + conn->dst_prop = dst_prop; + return 1; +} + +ufbxi_noinline static void ufbxi_init_synthetic_int_prop(ufbx_prop *dst, const char *name, int64_t value, ufbx_prop_type type) +{ + dst->type = type; + dst->name.data = name; + dst->name.length = strlen(name); + dst->value_real = (ufbx_real)value; + dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_INT); + dst->value_int = value; + dst->value_str.data = ufbxi_empty_char; + + ufbxi_dev_assert(dst->name.length >= 4); + dst->_internal_key = ufbxi_get_name_key(name, 4); +} + +ufbxi_noinline static void ufbxi_init_synthetic_real_prop(ufbx_prop *dst, const char *name, ufbx_real value, ufbx_prop_type type) +{ + dst->type = type; + dst->name.data = name; + dst->name.length = strlen(name); + dst->value_real = value; + dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL); + dst->value_int = (int64_t)value; + dst->value_str.data = ufbxi_empty_char; + + ufbxi_dev_assert(dst->name.length >= 4); + dst->_internal_key = ufbxi_get_name_key(name, 4); +} + +ufbxi_noinline static void ufbxi_init_synthetic_vec3_prop(ufbx_prop *dst, const char *name, const ufbx_vec3 *value, ufbx_prop_type type) +{ + dst->type = type; + dst->name.data = name; + dst->name.length = strlen(name); + dst->value_vec3 = *value; + dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_VEC3); + dst->value_int = ufbxi_f64_to_i64(dst->value_real); + dst->value_str.data = ufbxi_empty_char; + + ufbxi_dev_assert(dst->name.length >= 4); + dst->_internal_key = ufbxi_get_name_key(name, 4); +} + +ufbxi_noinline static void ufbxi_set_own_prop_vec3_uniform(ufbx_props *props, const char *name, ufbx_real value) +{ + ufbx_props local_props = *props; + local_props.defaults = NULL; + ufbx_prop *prop = ufbx_find_prop(&local_props, name); + if (prop) { + prop->value_vec4.x = value; + prop->value_vec4.y = value; + prop->value_vec4.z = value; + prop->value_vec4.w = 0.0f; + prop->value_int = (int64_t)value; + } +} + +typedef struct { + uint32_t geometry_helper_id; + uint32_t scale_helper_id; +} ufbxi_node_extra; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_geometry_transform_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id) +{ + ufbx_vec3 geo_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 geo_rotation = ufbxi_find_vec3(&node->props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 geo_scaling = ufbxi_find_vec3(&node->props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f); + if (!ufbxi_is_vec3_zero(geo_translation) || !ufbxi_is_vec3_zero(geo_rotation) || !ufbxi_is_vec3_one(geo_scaling)) { + + uint64_t geo_fbx_id; + ufbx_node *geo_node = ufbxi_push_synthetic_element(uc, &geo_fbx_id, NULL, uc->opts.geometry_transform_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(geo_node); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &geo_node->element.element_id)); + geo_node->element.dom_node = node->element.dom_node; + + ufbx_prop *props = ufbxi_push_zero(&uc->result, ufbx_prop, 3); + ufbxi_check(props); + ufbxi_init_synthetic_vec3_prop(&props[0], ufbxi_Lcl_Rotation, &geo_rotation, UFBX_PROP_ROTATION); + ufbxi_init_synthetic_vec3_prop(&props[1], ufbxi_Lcl_Scaling, &geo_scaling, UFBX_PROP_SCALING); + ufbxi_init_synthetic_vec3_prop(&props[2], ufbxi_Lcl_Translation, &geo_translation, UFBX_PROP_TRANSLATION); + + geo_node->props.props.data = props; + geo_node->props.props.count = 3; + + node->has_geometry_transform = true; + geo_node->is_geometry_transform_helper = true; + + ufbxi_check(ufbxi_connect_oo(uc, geo_fbx_id, node_fbx_id)); + uc->has_geometry_transform_nodes = true; + + ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element_id, ufbxi_node_extra); + ufbxi_check(extra); + extra->geometry_helper_id = geo_node->element_id; + } + + return 1; +} + +typedef struct { + const char *name; + ufbx_vec3 default_value; +} ufbxi_scale_helper_prop; + +static const ufbxi_scale_helper_prop ufbxi_scale_helper_props[] = { + { ufbxi_GeometricRotation, { 0.0f, 0.0f, 0.0f } }, + { ufbxi_GeometricScaling, { 1.0f, 1.0f, 1.0f } }, + { ufbxi_GeometricTranslation, { 0.0f, 0.0f, 0.0f } }, + { ufbxi_Lcl_Scaling, { 1.0f, 1.0f, 1.0f } }, +}; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_scale_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id) +{ + uint64_t scale_fbx_id; + ufbx_node *scale_node = ufbxi_push_synthetic_element(uc, &scale_fbx_id, NULL, uc->opts.scale_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(scale_node); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &scale_node->element.element_id)); + scale_node->element.dom_node = node->element.dom_node; + + node->scale_helper = scale_node; + scale_node->is_scale_helper = true; + + ufbxi_check(ufbxi_connect_oo(uc, scale_fbx_id, node_fbx_id)); + uc->has_scale_helper_nodes = true; + + ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element.element_id, ufbxi_node_extra); + ufbxi_check(extra); + extra->scale_helper_id = scale_node->element_id; + + size_t max_props = ufbxi_arraycount(ufbxi_scale_helper_props); + ufbx_prop *helper_props = ufbxi_push(&uc->result, ufbx_prop, max_props); + ufbxi_check(helper_props); + + size_t num_props = 0; + ufbx_props props_copy = node->props; + props_copy.defaults = NULL; + for (size_t i = 0; i < max_props; i++) { + const ufbxi_scale_helper_prop *hp = &ufbxi_scale_helper_props[i]; + ufbx_prop *src_prop = ufbxi_find_prop(&props_copy, hp->name); + if (!src_prop) continue; + + helper_props[num_props++] = *src_prop; + src_prop->value_vec3 = hp->default_value; + src_prop->value_int = (int64_t)src_prop->value_vec3.x; + } + + scale_node->props.props.data = helper_props; + scale_node->props.props.count = num_props; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_model(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + (void)node; + ufbx_node *elem_node = ufbxi_push_element(uc, info, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(elem_node); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id)); + + int64_t inherit_type = ufbxi_find_int(&elem_node->props, ufbxi_InheritType, -1); + switch (inherit_type) { + case 0: // RrSs + elem_node->original_inherit_mode = UFBX_INHERIT_MODE_COMPONENTWISE_SCALE; + break; + case 2: // Rrs + elem_node->original_inherit_mode = UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE; + break; + default: break; + } + + if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) { + elem_node->inherit_mode = elem_node->original_inherit_mode; + } else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_IGNORE) { + elem_node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL; + elem_node->inherit_mode = UFBX_INHERIT_MODE_NORMAL; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_element(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, size_t size, ufbx_element_type type) +{ + (void)node; + ufbx_element *elem = ufbxi_push_element_size(uc, info, size, type); + ufbxi_check(elem); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_unknown(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *element, ufbx_string type, ufbx_string sub_type, const char *node_name) +{ + (void)node; + ufbx_unknown *unknown = ufbxi_push_element(uc, element, ufbx_unknown, UFBX_ELEMENT_UNKNOWN); + ufbxi_check(unknown); + unknown->type = type; + unknown->sub_type = sub_type; + unknown->super_type.data = node_name; + unknown->super_type.length = strlen(node_name); + + // `type`, `sub_type` and `node_name` are raw strings so they may need to be sanitized. + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->type, false)); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->sub_type, false)); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->super_type, false)); + + return 1; +} + +typedef struct { + ufbx_vertex_vec3 elem; + uint32_t index; +} ufbxi_tangent_layer; + +static ufbx_real ufbxi_zero_element[8] = { 0 }; + +// Sentinel pointers used for zero/sequential index buffers +static const uint32_t ufbxi_sentinel_index_zero[1] = { 100000000 }; +static const uint32_t ufbxi_sentinel_index_consecutive[1] = { 123456789 }; + +ufbxi_noinline static int ufbxi_fix_index(ufbxi_context *uc, uint32_t *p_dst, uint32_t index, size_t one_past_max_val) +{ + switch (uc->opts.index_error_handling) { + case UFBX_INDEX_ERROR_HANDLING_CLAMP: + ufbxi_check(one_past_max_val > 0); + ufbxi_check(one_past_max_val <= UINT32_MAX); + *p_dst = (uint32_t)one_past_max_val - 1; + ufbxi_check(ufbxi_warnf(UFBX_WARNING_INDEX_CLAMPED, "Clamped index")); + break; + case UFBX_INDEX_ERROR_HANDLING_NO_INDEX: + *p_dst = UFBX_NO_INDEX; + break; + case UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING: + ufbxi_fmt_err_info(&uc->error, "%u (max %u)", index, one_past_max_val ? (one_past_max_val - 1) : 0); + ufbxi_fail_msg("UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING", "Bad index"); + case UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE: + *p_dst = index; + break; + default: + ufbxi_unreachable("Unhandled index_error_handling"); + return 0; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_check_indices(ufbxi_context *uc, uint32_t **p_dst, uint32_t *indices, bool owns_indices, size_t num_indices, size_t num_indexers, size_t num_elems) +{ + // If the indices are truncated extend them with `UFBX_NO_INDEX`, the following normalization pass + // will handle them the same way as other out-of-bounds indices. + if (num_indices < num_indexers) { + uint32_t *new_indices = ufbxi_push(&uc->result, uint32_t, num_indexers); + ufbxi_check(new_indices); + + memcpy(new_indices, indices, sizeof(uint32_t) * num_indices); + for (size_t i = num_indices; i < num_indexers; i++) { + new_indices[i] = UFBX_NO_INDEX; + } + + indices = new_indices; + num_indices = num_indexers; + owns_indices = true; + } + + // Normalize out-of-bounds indices to `invalid_index` + for (size_t i = 0; i < num_indices; i++) { + uint32_t ix = indices[i]; + if (ix >= num_elems) { + // If the indices refer to an external buffer we need to + // allocate a separate buffer for them + if (!owns_indices) { + indices = ufbxi_push_copy(&uc->result, uint32_t, num_indices, indices); + ufbxi_check(indices); + owns_indices = true; + } + ufbxi_check(ufbxi_fix_index(uc, &indices[i], ix, num_elems)); + } + } + + *p_dst = indices; + + return 1; +} + +ufbx_static_assert(vertex_real_size, sizeof(ufbx_vertex_real) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec2_size, sizeof(ufbx_vertex_vec2) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec3_size, sizeof(ufbx_vertex_vec3) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec4_size, sizeof(ufbx_vertex_vec4) == sizeof(ufbx_vertex_attrib)); + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_warn_polygon_mapping(ufbxi_context *uc, const char *data_name, const char *mapping) +{ + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_POLYGON_MAPPING, "Ignoring geometry '%s' with bad mapping mode '%s'", data_name, mapping)); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_context *uc, ufbx_mesh *mesh, ufbxi_node *node, + ufbx_vertex_attrib *attrib, const char *data_name, const char *index_name, const char *w_name, char data_type, size_t num_components) +{ + ufbx_real **p_dst_data = (ufbx_real**)&attrib->values.data; + + ufbxi_value_array *data = ufbxi_find_array(node, data_name, data_type); + ufbxi_value_array *indices = ufbxi_find_array(node, index_name, 'i'); + + if (!uc->opts.strict) { + if (!data) return 1; + } + + ufbxi_check(data); + ufbxi_check(data->size % num_components == 0); + + size_t num_elems = data->size / num_components; + + // HACK: If there's no elements at all keep the attribute as NULL + // TODO: Strict mode for this? + if (num_elems == 0) { + return 1; + } + + ufbxi_check(num_elems > 0 && num_elems < INT32_MAX); + + attrib->exists = true; + attrib->indices.count = mesh->num_indices; + + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_MappingInformationType, "C", (char**)&mapping)); + + attrib->values.count = num_elems ? num_elems : 1; + + // Data array is always used as-is, if empty set the data to a global + // zero buffer so invalid zero index can point to some valid data. + // The zero data is offset by 4 elements to accommodate for invalid index (-1) + if (num_elems > 0) { + *p_dst_data = (ufbx_real*)data->data; + } else { + *p_dst_data = ufbxi_zero_element + 4; + } + + // HACK: Some old exporters seem to use ByPolygon to mean ByPolygonVertex, + // it should be quite safe to remap this + if (mapping == ufbxi_ByPolygon) { + size_t num_indices = indices ? indices->size : num_elems; + if (num_indices == mesh->num_indices) { + mapping = ufbxi_ByPolygonVertex; + } + } + + if (indices) { + size_t num_indices = indices->size; + uint32_t *index_data = (uint32_t*)indices->data; + + if (mapping == ufbxi_ByPolygonVertex) { + + // Indexed by polygon vertex: We can use the provided indices directly. + ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, num_indices, mesh->num_indices, num_elems)); + + } else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) { + + // Indexed by vertex: Follow through the position index mapping to get the final indices. + uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); + ufbxi_check(new_index_data); + + uint32_t *vert_ix = mesh->vertex_indices.data; + for (size_t i = 0; i < mesh->num_indices; i++) { + uint32_t ix = vert_ix[i]; + if (ix < num_indices) { + new_index_data[i] = index_data[ix]; + } else { + ufbxi_check(ufbxi_fix_index(uc, &new_index_data[i], ix, num_elems)); + } + } + + ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); + attrib->unique_per_vertex = true; + + } else if (mapping == ufbxi_ByPolygon) { + + // Indexed by polygon: Generate new indices based on polygons + uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); + ufbxi_check(new_index_data); + + size_t num_faces = mesh->num_faces; + for (size_t face_ix = 0; face_ix < num_faces; face_ix++) { + ufbx_face face = mesh->faces.data[face_ix]; + uint32_t index = UFBX_NO_INDEX; + if (face_ix < num_indices) { + index = index_data[face_ix]; + } + if (index >= num_elems) { + ufbxi_check(ufbxi_fix_index(uc, &index, index, num_elems)); + } + for (size_t i = 0; i < face.num_indices; i++) { + new_index_data[face.index_begin + i] = index; + } + } + + attrib->indices.data = new_index_data; + + } else if (mapping == ufbxi_AllSame) { + + // Indexed by all same: ??? This could be possibly used for making + // holes with invalid indices, but that seems really fringe. + // Just use the shared zero index buffer for this. + uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices); + attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero; + attrib->unique_per_vertex = true; + + } else { + memset(attrib, 0, sizeof(ufbx_vertex_attrib)); + ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); + return 1; + } + + } else { + + if (mapping == ufbxi_ByPolygonVertex) { + + // Direct by polygon index: Use shared consecutive array if there's enough + // elements, otherwise use a unique truncated consecutive index array. + if (num_elems >= mesh->num_indices) { + uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices); + attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; + } else { + uint32_t *index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); + ufbxi_check(index_data); + for (size_t i = 0; i < mesh->num_indices; i++) { + index_data[i] = (uint32_t)i; + } + ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); + } + + } else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) { + + // Direct by vertex: We can re-use the position indices.. + ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, mesh->vertex_position.indices.data, false, mesh->num_indices, mesh->num_indices, num_elems)); + attrib->unique_per_vertex = true; + + } else if (mapping == ufbxi_ByPolygon) { + + // Direct by polygon: Generate new indices based on polygons + uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); + ufbxi_check(new_index_data); + + uint32_t num_faces = (uint32_t)mesh->num_faces; + for (uint32_t face_ix = 0; face_ix < num_faces; face_ix++) { + ufbx_face face = mesh->faces.data[face_ix]; + for (size_t i = 0; i < face.num_indices; i++) { + new_index_data[face.index_begin + i] = face_ix; + } + } + + ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); + + } else if (mapping == ufbxi_AllSame) { + + // Direct by all same: This cannot fail as the index list is just zero. + uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices); + attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero; + attrib->unique_per_vertex = true; + + } else { + memset(attrib, 0, sizeof(ufbx_vertex_attrib)); + ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); + return 1; + } + } + + if (uc->opts.retain_vertex_attrib_w && w_name) { + ufbxi_value_array *w_data = ufbxi_find_array(node, w_name, 'r'); + if (w_data) { + if (w_data->size == num_elems) { + attrib->values_w.count = w_data->size; + attrib->values_w.data = (ufbx_real*)w_data->data; + } else { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, "Bad W array size %s=%zu, %s=%zu", + w_name, w_data->size, data_name, num_elems)); + } + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_truncated_array(ufbxi_context *uc, void *p_data, size_t *p_count, ufbxi_node *node, const char *name, char fmt, size_t size) +{ + ufbxi_value_array *arr = ufbxi_find_array(node, name, fmt); + if (!arr) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_GEOMETRY_DATA, "Missing geometry data: %s", name)); + return 1; + } + + *p_count = size; + + void *data = arr->data; + if (arr->size < size) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_TRUNCATED_ARRAY, "Truncated array: %s", name)); + + size_t elem_size = ufbxi_array_type_size(fmt); + void *new_data = ufbxi_push_size(&uc->result, elem_size, size); + ufbxi_check(new_data); + memcpy(new_data, data, arr->size * elem_size); + // Extend the array with the last element if possible + if (arr->size > 0) { + char *first_elem = (char*)data + (arr->size - 1) * elem_size; + for (size_t i = arr->size; i < size; i++) { + memcpy((char*)new_data + i * elem_size, first_elem, elem_size); + } + } else { + memset(new_data, 0, size * elem_size); + } + data = new_data; + } + + *(void**)p_data = data; + return 1; +} + +ufbxi_noinline static bool ufbxi_uv_set_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_uv_set *a = (const ufbx_uv_set *)va, *b = (const ufbx_uv_set *)vb; + return a->index < b->index; +} + +ufbxi_noinline static bool ufbxi_color_set_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_color_set *a = (const ufbx_color_set *)va, *b = (const ufbx_color_set *)vb; + return a->index < b->index; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_uv_sets(ufbxi_context *uc, ufbx_uv_set *sets, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_uv_set))); + ufbxi_stable_sort(sizeof(ufbx_uv_set), 32, sets, uc->tmp_arr, count, &ufbxi_uv_set_less, NULL); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_color_sets(ufbxi_context *uc, ufbx_color_set *sets, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_color_set))); + ufbxi_stable_sort(sizeof(ufbx_color_set), 32, sets, uc->tmp_arr, count, &ufbxi_color_set_less, NULL); + return 1; +} + +typedef struct ufbxi_blend_offset { + uint32_t vertex; + ufbx_vec3 position_offset; + ufbx_vec3 normal_offset; +} ufbxi_blend_offset; + +static ufbxi_noinline bool ufbxi_blend_offset_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_blend_offset *a = (const ufbxi_blend_offset*)va, *b = (const ufbxi_blend_offset*)vb; + return a->vertex < b->vertex; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_offsets(ufbxi_context *uc, ufbxi_blend_offset *offsets, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_blend_offset))); + ufbxi_stable_sort(sizeof(ufbxi_blend_offset), 16, offsets, uc->tmp_arr, count, &ufbxi_blend_offset_less, NULL); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_shape(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); + ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_Indexes); + ufbxi_node *node_normals = ufbxi_find_child(node, ufbxi_Normals); + if (!node_vertices || !node_indices) return 1; + + ufbx_blend_shape *shape = ufbxi_push_element(uc, info, ufbx_blend_shape, UFBX_ELEMENT_BLEND_SHAPE); + ufbxi_check(shape); + + if (uc->opts.ignore_geometry) return 1; + + ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); + ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i'); + + ufbxi_check(vertices && indices); + ufbxi_check(vertices->size % 3 == 0); + ufbxi_check(indices->size == vertices->size / 3); + + size_t num_offsets = indices->size; + uint32_t *vertex_indices = (uint32_t*)indices->data; + + shape->num_offsets = num_offsets; + shape->position_offsets.data = (ufbx_vec3*)vertices->data; + shape->offset_vertices.data = vertex_indices; + shape->position_offsets.count = num_offsets; + shape->offset_vertices.count = num_offsets; + + if (node_normals) { + ufbxi_value_array *normals = ufbxi_get_array(node_normals, 'r'); + ufbxi_check(normals && normals->size == vertices->size); + shape->normal_offsets.data = (ufbx_vec3*)normals->data; + shape->normal_offsets.count = num_offsets; + } + + // Sort the blend shape vertices only if absolutely necessary + bool sorted = true; + for (size_t i = 1; i < num_offsets; i++) { + if (vertex_indices[i - 1] > vertex_indices[i]) { + sorted = false; + break; + } + } + + if (!sorted) { + ufbxi_blend_offset *offsets = ufbxi_push(&uc->tmp_stack, ufbxi_blend_offset, num_offsets); + ufbxi_check(offsets); + + for (size_t i = 0; i < num_offsets; i++) { + offsets[i].vertex = shape->offset_vertices.data[i]; + offsets[i].position_offset = shape->position_offsets.data[i]; + if (node_normals) offsets[i].normal_offset = shape->normal_offsets.data[i]; + } + + ufbxi_check(ufbxi_sort_blend_offsets(uc, offsets, num_offsets)); + + for (size_t i = 0; i < num_offsets; i++) { + shape->offset_vertices.data[i] = offsets[i].vertex; + shape->position_offsets.data[i] = offsets[i].position_offset; + if (node_normals) shape->normal_offsets.data[i] = offsets[i].normal_offset; + } + ufbxi_pop(&uc->tmp_stack, ufbxi_blend_offset, num_offsets, NULL); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_blend_shapes(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_blend_deformer *deformer = NULL; + uint64_t deformer_fbx_id = 0; + + ufbxi_for (ufbxi_node, n, node->children, node->num_children) { + if (n->name != ufbxi_Shape) continue; + + ufbx_string name; + ufbxi_check(ufbxi_get_val1(n, "S", &name)); + + if (deformer == NULL) { + deformer = ufbxi_push_synthetic_element(uc, &deformer_fbx_id, n, name.data, ufbx_blend_deformer, UFBX_ELEMENT_BLEND_DEFORMER); + ufbxi_check(deformer); + ufbxi_check(ufbxi_connect_oo(uc, deformer_fbx_id, info->fbx_id)); + } + + uint64_t channel_fbx_id = 0; + ufbx_blend_channel *channel = ufbxi_push_synthetic_element(uc, &channel_fbx_id, n, name.data, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL); + ufbxi_check(channel); + + ufbx_real_list weight_list = { NULL, 0 }; + ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &weight_list)); + + size_t num_shape_props = 1; + ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props); + ufbxi_check(shape_props); + shape_props[0].name.data = ufbxi_DeformPercent; + shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1; + shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent); + shape_props[0].type = UFBX_PROP_NUMBER; + shape_props[0].value_real = (ufbx_real)0.0; + shape_props[0].value_str = ufbx_empty_string; + shape_props[0].value_blob = ufbx_empty_blob; + + ufbx_prop *self_prop = ufbx_find_prop_len(&info->props, name.data, name.length); + if (self_prop && (self_prop->type == UFBX_PROP_NUMBER || self_prop->type == UFBX_PROP_INTEGER)) { + shape_props[0].value_real = self_prop->value_real; + ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name)); + } else if (uc->version < 6000) { + ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name)); + } + + channel->name = name; + channel->props.props.data = shape_props; + channel->props.props.count = num_shape_props; + + ufbxi_element_info shape_info = { 0 }; + + shape_info.fbx_id = ufbxi_push_synthetic_id(uc); + shape_info.name = name; + shape_info.dom_node = ufbxi_get_dom_node(uc, n); + + ufbxi_check(ufbxi_read_shape(uc, n, &shape_info)); + + ufbxi_check(ufbxi_connect_oo(uc, channel_fbx_id, deformer_fbx_id)); + ufbxi_check(ufbxi_connect_oo(uc, shape_info.fbx_id, channel_fbx_id)); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_process_indices(ufbxi_context *uc, ufbx_mesh *mesh, uint32_t *index_data) +{ + // Count the number of faces and allocate the index list + // Indices less than zero (~actual_index) ends a polygon + size_t num_total_faces = 0; + ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) { + num_total_faces += ((int32_t)*p_ix < 0) ? 1u : 0u; + } + mesh->faces.data = ufbxi_push(&uc->result, ufbx_face, num_total_faces); + ufbxi_check(mesh->faces.data); + + size_t num_triangles = 0; + size_t max_face_triangles = 0; + size_t num_bad_faces[3] = { 0 }; + + ufbx_face *dst_face = mesh->faces.data; + uint32_t *p_face_begin = index_data; + ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) { + uint32_t ix = *p_ix; + // Un-negate final indices of polygons + if ((int32_t)ix < 0) { + ix = ~ix; + *p_ix = ix; + uint32_t num_indices = (uint32_t)((p_ix - p_face_begin) + 1); + dst_face->index_begin = (uint32_t)(p_face_begin - index_data); + dst_face->num_indices = num_indices; + if (num_indices >= 3) { + num_triangles += num_indices - 2; + max_face_triangles = ufbxi_max_sz(max_face_triangles, num_indices - 2); + } else { + num_bad_faces[num_indices]++; + } + dst_face++; + p_face_begin = p_ix + 1; + } + ufbxi_check((size_t)ix < mesh->num_vertices); + } + + mesh->vertex_position.indices.data = index_data; + mesh->num_faces = ufbxi_to_size(dst_face - mesh->faces.data); + mesh->faces.count = mesh->num_faces; + mesh->num_triangles = num_triangles; + mesh->max_face_triangles = max_face_triangles; + mesh->num_empty_faces = num_bad_faces[0]; + mesh->num_point_faces = num_bad_faces[1]; + mesh->num_line_faces = num_bad_faces[2]; + + mesh->vertex_first_index.count = mesh->num_vertices; + mesh->vertex_first_index.data = ufbxi_push(&uc->result, uint32_t, mesh->num_vertices); + ufbxi_check(mesh->vertex_first_index.data); + + ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) { + *p_vx_ix = UFBX_NO_INDEX; + } + + { + size_t num_indices = mesh->num_indices; + size_t num_vertices = mesh->num_vertices; + uint32_t *vertex_indices = mesh->vertex_indices.data; + uint32_t *vertex_first_index = mesh->vertex_first_index.data; + for (size_t ix = 0; ix < num_indices; ix++) { + uint32_t vx = vertex_indices[ix]; + if (vx < num_vertices) { + if (vertex_first_index[vx] == UFBX_NO_INDEX) { + vertex_first_index[vx] = (uint32_t)ix; + } + } else { + ufbxi_check(ufbxi_fix_index(uc, &vertex_indices[ix], vx, mesh->num_vertices)); + } + } + } + + // HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero + // index buffer for face materials.. + uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces); + uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_faces); + + return 1; +} + +ufbxi_noinline static void ufbxi_patch_mesh_reals(ufbx_mesh *mesh) +{ + mesh->vertex_position.value_reals = 3; + mesh->vertex_normal.value_reals = 3; + mesh->vertex_uv.value_reals = 2; + mesh->vertex_tangent.value_reals = 3; + mesh->vertex_bitangent.value_reals = 3; + mesh->vertex_color.value_reals = 4; + mesh->vertex_crease.value_reals = 1; + mesh->skinned_position.value_reals = 3; + mesh->skinned_normal.value_reals = 3; + + ufbxi_nounroll ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + set->vertex_uv.value_reals = 2; + set->vertex_tangent.value_reals = 3; + set->vertex_bitangent.value_reals = 3; + } + + ufbxi_nounroll ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { + set->vertex_color.value_reals = 4; + } +} + +typedef struct { + uint32_t id, index; +} ufbxi_id_group; + +static bool ufbxi_less_int32(void *user, const void *va, const void *vb) +{ + (void)user; + const int32_t a = *(const int32_t*)va, b = *(const int32_t*)vb; + return a < b; +} + +ufbx_static_assert(mesh_mat_point_faces, offsetof(ufbx_mesh_part, num_point_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 1 * sizeof(size_t)); +ufbx_static_assert(mesh_mat_line_faces, offsetof(ufbx_mesh_part, num_line_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 2 * sizeof(size_t)); +static ufbxi_forceinline void ufbxi_mesh_part_add_face(ufbx_mesh_part *part, uint32_t num_indices) +{ + part->num_faces++; + if (num_indices >= 3) { + part->num_triangles += num_indices - 2; + } else { + // `num_empty/point/line_faces` are consecutive, see static asserts above. + // cppcheck-suppress objectIndex + (&part->num_empty_faces)[num_indices]++; + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_assign_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, size_t *p_consecutive_indices, bool retain_parts) +{ + size_t num_faces = mesh->num_faces; + ufbxi_check_err(error, num_faces > 0); + ufbxi_check_err(error, num_faces < UINT32_MAX); + ufbxi_check_err(error, mesh->face_group.count == num_faces); + + uint32_t *ids = ufbxi_push(buf, uint32_t, num_faces); + ufbxi_check_err(error, ids); + + uint32_t num_ids = 0; + + ufbxi_id_group seen_ids[1 << UFBXI_FACE_GROUP_HASH_BITS]; + memset(seen_ids, 0, sizeof(seen_ids)); + + uint32_t seed = 2654435769u; + uint32_t rehash_threshold = 256; + + // Loosely deduplicate group IDs + ufbxi_for_list(uint32_t, p_id, mesh->face_group) { + uint32_t id = *p_id; + uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS); + if (seen_ids[id_hash].id != id || seen_ids[id_hash].index == 0) { + seen_ids[id_hash].id = id; + if (++seen_ids[id_hash].index > rehash_threshold) { + seed *= seed; + rehash_threshold *= 2; + } + ids[num_ids++] = id; + } + } + + // Sort and deduplicate remaining IDs + ufbxi_unstable_sort(ids, num_ids, sizeof(uint32_t), &ufbxi_less_int32, NULL); + + size_t num_groups = 0; + for (size_t i = 0; i < num_ids; ) { + uint32_t id = ids[i]; + ids[num_groups++] = id; + do { i++; } while (i < num_ids && ids[i] == id); + } + + // Allocate group info structs + ufbx_face_group *groups = ufbxi_push_zero(buf, ufbx_face_group, num_groups); + ufbxi_check_err(error, groups); + for (size_t i = 0; i < num_groups; i++) { + groups[i].id = (int32_t)ids[i]; + groups[i].name.data = ufbxi_empty_char; + } + + mesh->face_groups.data = groups; + mesh->face_groups.count = num_groups; + + ufbx_mesh_part *parts = NULL; + if (retain_parts) { + parts = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups); + ufbxi_check_err(error, parts); + mesh->face_group_parts.data = parts; + mesh->face_group_parts.count = num_groups; + } + + // Optimization: Use `consecutive_indices` for a single group + if (p_consecutive_indices && num_groups == 1) { + memset(mesh->face_group.data, 0, sizeof(uint32_t) * num_faces); + + if (parts) { + parts[0].face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; + parts[0].face_indices.count = num_faces; + parts[0].num_empty_faces = mesh->num_empty_faces; + parts[0].num_point_faces = mesh->num_point_faces; + parts[0].num_line_faces = mesh->num_line_faces; + parts[0].num_faces = num_faces; + parts[0].num_triangles = mesh->num_triangles; + } + + *p_consecutive_indices = ufbxi_max_sz(*p_consecutive_indices, num_faces); + return 1; + } + + memset(seen_ids, 0, sizeof(seen_ids)); + + // Count faces and triangles per group and reassign IDs + const ufbx_face *p_face = mesh->faces.data; + ufbxi_for_list(uint32_t, p_id, mesh->face_group) { + uint32_t id = *p_id; + uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS); + + uint32_t num_indices = p_face->num_indices; + + size_t index; + if (seen_ids[id_hash].id == id && seen_ids[id_hash].index > 0) { + index = seen_ids[id_hash].index - 1; + *p_id = (uint32_t)index; + } else { + int32_t signed_id = (int32_t)id; + index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_face_group, 8, &index, groups, 0, num_groups, ( a->id < signed_id ), ( a->id == signed_id )); + ufbx_assert(index < num_groups); + seen_ids[id_hash].id = id; + seen_ids[id_hash].index = (uint32_t)index + 1; + } + + if (parts) { + ufbxi_mesh_part_add_face(&parts[index], num_indices); + } + + *p_id = (uint32_t)index; + p_face++; + } + + if (!parts) return 1; + + // Subdivide `ids` for per-group `face_indices` + uint32_t *face_indices = ids; + uint32_t part_index = 0; + ufbxi_for(ufbx_mesh_part, part, parts, num_groups) { + part->index = part_index++; + part->face_indices.data = face_indices; + face_indices += part->num_faces; + } + ufbx_assert(face_indices == ids + num_faces); + + // Collect per-group faces + uint32_t face_index = 0; + ufbxi_for_list(uint32_t, p_id, mesh->face_group) { + ufbx_mesh_part *part = &parts[*p_id]; + part->face_indices.data[part->face_indices.count++] = face_index++; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_update_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, bool need_copy) +{ + size_t num_faces = mesh->faces.count; + size_t num_groups = mesh->face_group_parts.count; + if (num_groups == 0) return 1; + + if (need_copy) { + mesh->face_group_parts.data = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups); + ufbxi_check_err(error, mesh->face_group_parts.data); + } + + uint32_t *face_indices = ufbxi_push(buf, uint32_t, num_faces); + ufbxi_check_err(error, face_indices); + + ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { + ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]]; + ufbxi_mesh_part_add_face(part, mesh->faces.data[i].num_indices); + } + + uint32_t part_index = 0; + ufbxi_for_list(ufbx_mesh_part, part, mesh->face_group_parts) { + part->index = part_index++; + part->face_indices.data = face_indices; + part->face_indices.count = 0; + face_indices += part->num_faces; + } + + ufbxi_nounroll for (uint32_t i = 0; i < num_faces; i++) { + ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]]; + part->face_indices.data[part->face_indices.count++] = i; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH); + ufbxi_check(mesh); + + // In up to version 7100 FBX files blend shapes are contained within the same geometry node + if (uc->version <= 7100) { + ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info)); + } + + ufbxi_patch_mesh_reals(mesh); + + // Sometimes there are empty meshes in FBX files? + // TODO: Should these be included in output? option? strict mode? + ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); + ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex); + if (!node_vertices) return 1; + + if (uc->opts.ignore_geometry) return 1; + + ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); + ufbxi_value_array *indices = node_indices ? ufbxi_get_array(node_indices, 'i') : NULL; + ufbxi_value_array *edge_indices = ufbxi_find_array(node, ufbxi_Edges, 'i'); + ufbxi_check(vertices); + ufbxi_check(!node_indices || indices); // If node_indices exists, it must be an array + ufbxi_check(vertices->size % 3 == 0); + + mesh->num_vertices = vertices->size / 3; + mesh->num_indices = indices ? indices->size : 0; + + uint32_t *index_data = indices ? (uint32_t*)indices->data : NULL; + + // Duplicate `index_data` for modification if we retain DOM + if (uc->opts.retain_dom) { + index_data = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, index_data); + ufbxi_check(index_data); + } + + mesh->vertices.data = (ufbx_vec3*)vertices->data; + mesh->vertices.count = mesh->num_vertices; + mesh->vertex_indices.data = index_data; + mesh->vertex_indices.count = mesh->num_indices; + + mesh->vertex_position.exists = true; + mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data; + mesh->vertex_position.values.count = mesh->num_vertices; + mesh->vertex_position.indices.data = index_data; + mesh->vertex_position.indices.count = mesh->num_indices; + mesh->vertex_position.unique_per_vertex = true; + + // Check/make sure that the last index is negated (last of polygon) + if (mesh->num_indices > 0) { + if ((int32_t)index_data[mesh->num_indices - 1] >= 0) { + if (uc->opts.strict) ufbxi_fail("Non-negated last index"); + index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1]; + } + } + + // Read edges before un-negating the indices + if (edge_indices) { + size_t num_edges = edge_indices->size; + ufbx_edge *edges = ufbxi_push(&uc->result, ufbx_edge, num_edges); + ufbxi_check(edges); + + size_t dst_ix = 0; + + // Edges are represented using a single index into PolygonVertexIndex. + // The edge is between two consecutive vertices in the polygon. + uint32_t *edge_data = (uint32_t*)edge_indices->data; + for (size_t i = 0; i < num_edges; i++) { + uint32_t index_ix = edge_data[i]; + if (index_ix >= mesh->num_indices) { + if (uc->opts.strict) ufbxi_fail("Edge index out of bounds"); + continue; + } + edges[dst_ix].a = index_ix; + if ((int32_t)index_data[index_ix] < 0) { + // Previous index is the last one of this polygon, rewind to first index. + while (index_ix > 0 && (int32_t)index_data[index_ix - 1] >= 0) { + index_ix--; + } + } else { + // Connect to the next index in the same polygon + index_ix++; + } + ufbxi_check(index_ix < mesh->num_indices); + edges[dst_ix].b = index_ix; + dst_ix++; + } + + mesh->edges.data = edges; + mesh->edges.count = dst_ix; + mesh->num_edges = mesh->edges.count; + } + + ufbxi_check(ufbxi_process_indices(uc, mesh, index_data)); + + // Count the number of UV/color sets + size_t num_uv = 0, num_color = 0, num_bitangents = 0, num_tangents = 0; + ufbxi_for (ufbxi_node, n, node->children, node->num_children) { + if (n->name == ufbxi_LayerElementUV) num_uv++; + if (n->name == ufbxi_LayerElementColor) num_color++; + if (n->name == ufbxi_LayerElementBinormal) num_bitangents++; + if (n->name == ufbxi_LayerElementTangent) num_tangents++; + } + + size_t num_textures = 0; + + ufbxi_tangent_layer *bitangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_bitangents); + ufbxi_tangent_layer *tangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_tangents); + ufbxi_check(bitangents); + ufbxi_check(tangents); + + mesh->uv_sets.data = ufbxi_push_zero(&uc->result, ufbx_uv_set, num_uv); + mesh->color_sets.data = ufbxi_push_zero(&uc->result, ufbx_color_set, num_color); + ufbxi_check(mesh->uv_sets.data); + ufbxi_check(mesh->color_sets.data); + + size_t num_bitangents_read = 0, num_tangents_read = 0; + ufbxi_for (ufbxi_node, n, node->children, node->num_children) { + if (n->name[0] != 'L') continue; // All names start with 'LayerElement*' + + if (n->name == ufbxi_LayerElementNormal) { + if (mesh->vertex_normal.exists) continue; + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_normal, + ufbxi_Normals, ufbxi_NormalsIndex, ufbxi_NormalsW, 'r', 3)); + } else if (n->name == ufbxi_LayerElementBinormal) { + ufbxi_tangent_layer *layer = &bitangents[num_bitangents_read++]; + + ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index)); + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem, + ufbxi_Binormals, ufbxi_BinormalsIndex, ufbxi_BinormalsW, 'r', 3)); + if (!layer->elem.exists) num_bitangents_read--; + + } else if (n->name == ufbxi_LayerElementTangent) { + ufbxi_tangent_layer *layer = &tangents[num_tangents_read++]; + + ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index)); + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem, + ufbxi_Tangents, ufbxi_TangentsIndex, ufbxi_TangentsW, 'r', 3)); + if (!layer->elem.exists) num_tangents_read--; + + } else if (n->name == ufbxi_LayerElementUV) { + ufbx_uv_set *set = &mesh->uv_sets.data[mesh->uv_sets.count++]; + + ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index)); + if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) { + set->name = ufbx_empty_string; + } + + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_uv, + ufbxi_UV, ufbxi_UVIndex, NULL, 'r', 2)); + if (!set->vertex_uv.exists) mesh->uv_sets.count--; + + } else if (n->name == ufbxi_LayerElementColor) { + ufbx_color_set *set = &mesh->color_sets.data[mesh->color_sets.count++]; + + ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index)); + if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) { + set->name = ufbx_empty_string; + } + + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_color, + ufbxi_Colors, ufbxi_ColorIndex, NULL, 'r', 4)); + if (!set->vertex_color.exists) mesh->color_sets.count--; + + } else if (n->name == ufbxi_LayerElementVertexCrease) { + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_crease, + ufbxi_VertexCrease, ufbxi_VertexCreaseIndex, NULL, 'r', 1)); + } else if (n->name == ufbxi_LayerElementEdgeCrease) { + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByEdge) { + if (mesh->edge_crease.count) continue; + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_crease.data, &mesh->edge_crease.count, n, ufbxi_EdgeCrease, 'r', mesh->num_edges)); + } else { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_EdgeCrease, mapping)); + } + } else if (n->name == ufbxi_LayerElementSmoothing) { + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByEdge) { + if (mesh->edge_smoothing.count) continue; + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_smoothing.data, &mesh->edge_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_edges)); + } else if (mapping == ufbxi_ByPolygon) { + if (mesh->face_smoothing.count) continue; + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_smoothing.data, &mesh->face_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_faces)); + } else { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Smoothing, mapping)); + } + } else if (n->name == ufbxi_LayerElementVisibility) { + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByEdge) { + if (mesh->edge_visibility.count) continue; + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_visibility.data, &mesh->edge_visibility.count, n, ufbxi_Visibility, 'b', mesh->num_edges)); + } else { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Visibility, mapping)); + } + } else if (n->name == ufbxi_LayerElementMaterial) { + if (mesh->face_material.count) continue; + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByPolygon) { + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, n, ufbxi_Materials, 'i', mesh->num_faces)); + } else if (mapping == ufbxi_AllSame) { + ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_Materials, 'i'); + ufbxi_check(arr && arr->size >= 1); + uint32_t material = *(uint32_t*)arr->data; + mesh->face_material.count = mesh->num_faces; + if (material == 0) { + mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; + } else { + mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces); + ufbxi_check(mesh->face_material.data); + ufbxi_for_list(uint32_t, p_mat, mesh->face_material) { + *p_mat = material; + } + } + } else { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Materials, mapping)); + } + } else if (n->name == ufbxi_LayerElementPolygonGroup) { + if (mesh->face_group.count) continue; + const char *mapping = NULL; + ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByPolygon) { + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_group.data, &mesh->face_group.count, n, ufbxi_PolygonGroup, 'i', mesh->num_faces)); + } + } else if (n->name == ufbxi_LayerElementHole) { + if (mesh->face_group.count) continue; + const char *mapping = NULL; + ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + if (mapping == ufbxi_ByPolygon) { + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_hole.data, &mesh->face_hole.count, n, ufbxi_Hole, 'b', mesh->num_faces)); + } + } else if (!strncmp(n->name, "LayerElement", 12)) { + + // Make sure the name has no internal zero bytes + ufbxi_check(!memchr(n->name, '\0', n->name_len)); + + // What?! 6x00 stores textures in mesh geometry, eg. "LayerElementTexture", + // "LayerElementDiffuseFactorTextures", "LayerElementEmissive_Textures"... + ufbx_string prop_name = ufbx_empty_string; + if (n->name_len > 20 && !strcmp(n->name + n->name_len - 8, "Textures")) { + prop_name.data = n->name + 12; + prop_name.length = (size_t)n->name_len - 20; + if (prop_name.data[prop_name.length - 1] == '_') { + prop_name.length -= 1; + } + } else if (!strcmp(n->name, "LayerElementTexture")) { + prop_name.data = "Diffuse"; + prop_name.length = 7; + } + + if (prop_name.length > 0) { + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop_name, false)); + const char *mapping = NULL; + if (ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)) { + ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_TextureId, 'i'); + + ufbxi_tmp_mesh_texture *tex = ufbxi_push_zero(&uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, 1); + ufbxi_check(tex); + if (arr) { + tex->face_texture = (uint32_t*)arr->data; + tex->num_faces = arr->size; + } + tex->prop_name = prop_name; + tex->all_same = (mapping == ufbxi_AllSame); + num_textures++; + } + } + } + } + + // Always use a default zero material, this will be removed if no materials are found + if (!mesh->face_material.count) { + uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces); + mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; + mesh->face_material.count = mesh->num_faces; + } + + if (uc->opts.strict) { + ufbxi_check(mesh->uv_sets.count == num_uv); + ufbxi_check(mesh->color_sets.count == num_color); + ufbxi_check(num_bitangents_read == num_bitangents); + ufbxi_check(num_tangents_read == num_tangents); + } + + // Connect bitangents/tangents to UV sets + ufbxi_for (ufbxi_node, n, node->children, node->num_children) { + if (n->name != ufbxi_Layer) continue; + ufbx_uv_set *uv_set = NULL; + ufbxi_tangent_layer *bitangent_layer = NULL; + ufbxi_tangent_layer *tangent_layer = NULL; + + ufbxi_for (ufbxi_node, c, n->children, n->num_children) { + uint32_t index; + const char *type; + if (c->name != ufbxi_LayerElement) continue; + if (!ufbxi_find_val1(c, ufbxi_TypedIndex, "I", &index)) continue; + if (!ufbxi_find_val1(c, ufbxi_Type, "C", (char**)&type)) continue; + + if (type == ufbxi_LayerElementUV) { + ufbxi_for(ufbx_uv_set, set, mesh->uv_sets.data, mesh->uv_sets.count) { + if (set->index == index) { + uv_set = set; + break; + } + } + } else if (type == ufbxi_LayerElementBinormal) { + ufbxi_for(ufbxi_tangent_layer, layer, bitangents, num_bitangents_read) { + if (layer->index == index) { + bitangent_layer = layer; + break; + } + } + } else if (type == ufbxi_LayerElementTangent) { + ufbxi_for(ufbxi_tangent_layer, layer, tangents, num_tangents_read) { + if (layer->index == index) { + tangent_layer = layer; + break; + } + } + } + } + + if (uv_set) { + if (bitangent_layer) { + uv_set->vertex_bitangent = bitangent_layer->elem; + } + if (tangent_layer) { + uv_set->vertex_tangent = tangent_layer->elem; + } + } + } + + mesh->skinned_is_local = true; + mesh->skinned_position = mesh->vertex_position; + mesh->skinned_normal = mesh->vertex_normal; + + ufbxi_patch_mesh_reals(mesh); + + if (mesh->face_group.count > 0 && mesh->face_groups.count == 0) { + ufbxi_check(ufbxi_assign_face_groups(&uc->result, &uc->error, mesh, &uc->max_consecutive_indices, uc->retain_mesh_parts)); + } + + // Sort UV and color sets by set index + ufbxi_check(ufbxi_sort_uv_sets(uc, mesh->uv_sets.data, mesh->uv_sets.count)); + ufbxi_check(ufbxi_sort_color_sets(uc, mesh->color_sets.data, mesh->color_sets.count)); + + if (num_textures > 0) { + ufbxi_mesh_extra *extra = ufbxi_push_element_extra(uc, mesh->element.element_id, ufbxi_mesh_extra); + ufbxi_check(extra); + extra->texture_count = num_textures; + extra->texture_arr = ufbxi_push_pop(&uc->tmp, &uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, num_textures); + ufbxi_check(extra->texture_arr); + } + + // Subdivision + + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_PreviewDivisionLevels, "I", &mesh->subdivision_preview_levels)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RenderDivisionLevels, "I", &mesh->subdivision_render_levels)); + + int32_t smoothness, boundary; + if (ufbxi_find_val1(node, ufbxi_Smoothness, "I", &smoothness)) { + if (smoothness >= 0 && smoothness <= UFBX_SUBDIVISION_DISPLAY_SMOOTH) { + mesh->subdivision_display_mode = (ufbx_subdivision_display_mode)smoothness; + } + } + if (ufbxi_find_val1(node, ufbxi_BoundaryRule, "I", &boundary)) { + if (boundary >= 0 && boundary <= UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS - 1) { + mesh->subdivision_boundary = (ufbx_subdivision_boundary)(boundary + 1); + } + } + + return 1; +} + +ufbxi_noinline static ufbx_nurbs_topology ufbxi_read_nurbs_topology(const char *form) +{ + if (!strcmp(form, "Open")) { + return UFBX_NURBS_TOPOLOGY_OPEN; + } else if (!strcmp(form, "Closed")) { + return UFBX_NURBS_TOPOLOGY_CLOSED; + } else if (!strcmp(form, "Periodic")) { + return UFBX_NURBS_TOPOLOGY_PERIODIC; + } + return UFBX_NURBS_TOPOLOGY_OPEN; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_nurbs_curve *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_curve, UFBX_ELEMENT_NURBS_CURVE); + ufbxi_check(nurbs); + + int32_t dimension = 3; + + const char *form = NULL; + ufbxi_check(ufbxi_find_val1(node, ufbxi_Order, "I", &nurbs->basis.order)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Dimension, "I", &dimension)); + ufbxi_check(ufbxi_find_val1(node, ufbxi_Form, "C", (char**)&form)); + nurbs->basis.topology = ufbxi_read_nurbs_topology(form); + nurbs->basis.is_2d = dimension == 2; + + if (!uc->opts.ignore_geometry) { + ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); + ufbxi_value_array *knot = ufbxi_find_array(node, ufbxi_KnotVector, 'r'); + ufbxi_check(points); + ufbxi_check(knot); + ufbxi_check(points->size % 4 == 0); + + nurbs->control_points.count = points->size / 4; + nurbs->control_points.data = (ufbx_vec4*)points->data; + nurbs->basis.knot_vector.data = (ufbx_real*)knot->data; + nurbs->basis.knot_vector.count = knot->size; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_surface(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_nurbs_surface *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_surface, UFBX_ELEMENT_NURBS_SURFACE); + ufbxi_check(nurbs); + + const char *form_u = NULL, *form_v = NULL; + size_t dimension_u = 0, dimension_v = 0; + int32_t step_u = 0, step_v = 0; + ufbxi_check(ufbxi_find_val2(node, ufbxi_NurbsSurfaceOrder, "II", &nurbs->basis_u.order, &nurbs->basis_v.order)); + ufbxi_check(ufbxi_find_val2(node, ufbxi_Dimensions, "ZZ", &dimension_u, &dimension_v)); + ufbxi_check(ufbxi_find_val2(node, ufbxi_Step, "II", &step_u, &step_v)); + ufbxi_check(ufbxi_find_val2(node, ufbxi_Form, "CC", (char**)&form_u, (char**)&form_v)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FlipNormals, "B", &nurbs->flip_normals)); + nurbs->basis_u.topology = ufbxi_read_nurbs_topology(form_u); + nurbs->basis_v.topology = ufbxi_read_nurbs_topology(form_v); + nurbs->num_control_points_u = dimension_u; + nurbs->num_control_points_v = dimension_v; + nurbs->span_subdivision_u = step_u > 0 ? (uint32_t)step_u : 4u; + nurbs->span_subdivision_v = step_v > 0 ? (uint32_t)step_v : 4u; + + if (!uc->opts.ignore_geometry) { + ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); + ufbxi_value_array *knot_u = ufbxi_find_array(node, ufbxi_KnotVectorU, 'r'); + ufbxi_value_array *knot_v = ufbxi_find_array(node, ufbxi_KnotVectorV, 'r'); + ufbxi_check(points); + ufbxi_check(knot_u); + ufbxi_check(knot_v); + ufbxi_check(points->size % 4 == 0); + ufbxi_check(points->size / 4 == (size_t)dimension_u * (size_t)dimension_v); + + nurbs->control_points.count = points->size / 4; + nurbs->control_points.data = (ufbx_vec4*)points->data; + nurbs->basis_u.knot_vector.data = (ufbx_real*)knot_u->data; + nurbs->basis_u.knot_vector.count = knot_u->size; + nurbs->basis_v.knot_vector.data = (ufbx_real*)knot_v->data; + nurbs->basis_v.knot_vector.count = knot_v->size; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_line(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_line_curve *line = ufbxi_push_element(uc, info, ufbx_line_curve, UFBX_ELEMENT_LINE_CURVE); + ufbxi_check(line); + + if (!uc->opts.ignore_geometry) { + ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); + ufbxi_value_array *points_index = ufbxi_find_array(node, ufbxi_PointsIndex, 'i'); + ufbxi_check(points); + ufbxi_check(points_index); + ufbxi_check(points->size % 3 == 0); + + if (points->size > 0) { + line->control_points.count = points->size / 3; + line->control_points.data = (ufbx_vec3*)points->data; + line->point_indices.count = points_index->size; + line->point_indices.data = (uint32_t*)points_index->data; + + ufbxi_check(line->control_points.count < INT32_MAX); + + // Count end points + size_t num_segments = 1; + if (line->point_indices.count > 0) { + for (size_t i = 0; i < line->point_indices.count - 1; i++) { + uint32_t ix = line->point_indices.data[i]; + num_segments += (int32_t)ix < 0 ? 1u : 0u; + } + } + + size_t prev_end = 0; + line->segments.data = ufbxi_push(&uc->result, ufbx_line_segment, num_segments); + ufbxi_check(line->segments.data); + for (size_t i = 0; i < line->point_indices.count; i++) { + uint32_t ix = line->point_indices.data[i]; + if ((int32_t)ix < 0) { + ix = ~ix; + if (i + 1 < line->point_indices.count) { + ufbx_line_segment *segment = &line->segments.data[line->segments.count++]; + segment->index_begin = (uint32_t)prev_end; + segment->num_indices = (uint32_t)(i - prev_end); + prev_end = i; + } + } + + if (ix < line->control_points.count) { + line->point_indices.data[i] = ix; + } else { + ufbxi_check(ufbxi_fix_index(uc, &line->point_indices.data[i], ix, line->control_points.count)); + } + } + + ufbx_line_segment *segment = &line->segments.data[line->segments.count++]; + segment->index_begin = (uint32_t)prev_end; + segment->num_indices = (uint32_t)ufbxi_to_size(line->point_indices.count - prev_end); + ufbx_assert(line->segments.count == num_segments); + } + } + + return 1; +} + +ufbxi_noinline static void ufbxi_read_transform_matrix(ufbx_matrix *m, ufbx_real *data) +{ + m->m00 = data[ 0]; m->m10 = data[ 1]; m->m20 = data[ 2]; + m->m01 = data[ 4]; m->m11 = data[ 5]; m->m21 = data[ 6]; + m->m02 = data[ 8]; m->m12 = data[ 9]; m->m22 = data[10]; + m->m03 = data[12]; m->m13 = data[13]; m->m23 = data[14]; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_bone(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type) +{ + (void)node; + + ufbx_bone *bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE); + ufbxi_check(bone); + + if (sub_type == ufbxi_Root) { + bone->is_root = true; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_marker(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type, ufbx_marker_type type) +{ + (void)node; + (void)sub_type; + + ufbx_marker *marker = ufbxi_push_element(uc, info, ufbx_marker, UFBX_ELEMENT_MARKER); + ufbxi_check(marker); + + marker->type = type; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_skin_deformer *skin = ufbxi_push_element(uc, info, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER); + ufbxi_check(skin); + + const char *skinning_type = NULL; + if (ufbxi_find_val1(node, ufbxi_SkinningType, "C", (char**)&skinning_type)) { + if (!strcmp(skinning_type, "Rigid")) { + skin->skinning_method = UFBX_SKINNING_METHOD_RIGID; + } else if (!strcmp(skinning_type, "Linear")) { + skin->skinning_method = UFBX_SKINNING_METHOD_LINEAR; + } else if (!strcmp(skinning_type, "DualQuaternion")) { + skin->skinning_method = UFBX_SKINNING_METHOD_DUAL_QUATERNION; + } else if (!strcmp(skinning_type, "Blend")) { + skin->skinning_method = UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR; + } + } + + ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); + ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_BlendWeights, 'r'); + if (indices && weights) { + // TODO strict: ufbxi_check(indices->size == weights->size); + skin->num_dq_weights = ufbxi_min_sz(indices->size, weights->size); + skin->dq_vertices.data = (uint32_t*)indices->data; + skin->dq_weights.data = (ufbx_real*)weights->data; + skin->dq_vertices.count = skin->num_dq_weights; + skin->dq_weights.count = skin->num_dq_weights; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin_cluster(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_skin_cluster *cluster = ufbxi_push_element(uc, info, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER); + ufbxi_check(cluster); + + ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); + ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r'); + + if (indices && weights) { + ufbxi_check(indices->size == weights->size); + cluster->num_weights = indices->size; + cluster->vertices.data = (uint32_t*)indices->data; + cluster->weights.data = (ufbx_real*)weights->data; + cluster->vertices.count = cluster->num_weights; + cluster->weights.count = cluster->num_weights; + } + + ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r'); + ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r'); + if (transform && transform_link) { + ufbxi_check(transform->size >= 16); + ufbxi_check(transform_link->size >= 16); + + ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data); + ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_blend_channel(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_blend_channel *channel = ufbxi_push_element(uc, info, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL); + ufbxi_check(channel); + + ufbx_real_list list = { NULL, 0 }; + ufbxi_value_array *full_weights = ufbxi_find_array(node, ufbxi_FullWeights, 'r'); + if (full_weights) { + list.data = (ufbx_real*)full_weights->data; + list.count = full_weights->size; + } + ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &list)); + + // Blender saves blend shapes with DeformPercent as a field, not a property. + // However, the animations are mapped to the DeformPercent property. + ufbxi_node *deform_percent = ufbxi_find_child(node, ufbxi_DeformPercent); + if (channel->props.props.count == 0 && deform_percent) { + size_t num_shape_props = 1; + ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props); + ufbxi_check(shape_props); + shape_props[0].name.data = ufbxi_DeformPercent; + shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1; + shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent); + shape_props[0].type = UFBX_PROP_NUMBER; + shape_props[0].value_str = ufbx_empty_string; + shape_props[0].value_real = 100.0f; + ufbxi_ignore(ufbxi_get_val1(deform_percent, "R", &shape_props[0].value_real)); + channel->props.props.data = shape_props; + channel->props.props.count = num_shape_props; + } + + return 1; +} + +typedef enum { + UFBXI_KEY_INTERPOLATION_CONSTANT = 0x2, + UFBXI_KEY_INTERPOLATION_LINEAR = 0x4, + UFBXI_KEY_INTERPOLATION_CUBIC = 0x8, + UFBXI_KEY_TANGENT_AUTO = 0x100, + UFBXI_KEY_TANGENT_TCB = 0x200, + UFBXI_KEY_TANGENT_USER = 0x400, + UFBXI_KEY_TANGENT_BROKEN = 0x800, + UFBXI_KEY_CONSTANT_NEXT = 0x100, + UFBXI_KEY_CLAMP = 0x1000, + UFBXI_KEY_TIME_INDEPENDENT = 0x2000, + UFBXI_KEY_CLAMP_PROGRESSIVE = 0x4000, + UFBXI_KEY_WEIGHTED_RIGHT = 0x1000000, + UFBXI_KEY_WEIGHTED_NEXT_LEFT = 0x2000000, + UFBXI_KEY_VELOCITY_RIGHT = 0x10000000, + UFBXI_KEY_VELOCITY_NEXT_LEFT = 0x20000000, +} ufbxi_key_flags; + +static ufbxi_noinline float ufbxi_solve_auto_tangent(ufbxi_context *uc, double prev_time, double time, double next_time, ufbx_real prev_value, ufbx_real value, ufbx_real next_value, float weight_left, float weight_right, float auto_bias, uint32_t flags) +{ + // Clamp tangent to zero if near either left or right key + if (flags & UFBXI_KEY_CLAMP) { + if (ufbx_fmin(ufbx_fabs(prev_value - value), ufbx_fabs(next_value - value)) <= uc->opts.key_clamp_threshold) { + return 0.0f; + } + } + + // Time-independent: Set the initial slope to be the difference between the two keyframes. + double slope = (next_value - prev_value) / (next_time - prev_time); + + // Non-time-independent tangents seem to blend between left/right tangent and the total difference. + if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { + double slope_left = (value - prev_value) / (time - prev_time); + double slope_right = (next_value - value) / (next_time - time); + double delta = (time - prev_time) / (next_time - prev_time); + slope = slope * 0.5 + (slope_left * (1.0 - delta) + slope_right * delta) * 0.5; + + double bias_weight = ufbx_fabs(auto_bias) / 100.0; + if (bias_weight > 0.0001) { + double bias_target = auto_bias > 0.0 ? slope_right : slope_left; + double bias_delta = bias_target - slope; + slope = slope * (1.0 - bias_weight) + bias_target * bias_weight; + + // Auto bias larger than 500 (positive or negative) adds an absolute + // value to the slope, determined by `((bias-500) / 100)^2 * 40`. + double abs_bias_weight = bias_weight - 5.0; + if (abs_bias_weight > 0.0) { + double bias_sign = ufbx_fabs(bias_delta) > 0.00001 ? bias_delta : auto_bias; + bias_sign = bias_sign > 0.0 ? 1.0 : -1.0; + slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; + } + } + } + + // Prevent overshooting by clamping the slope in case either + // tangent goes above/below the endpoints. + if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) { + // Split the slope to sign and a non-negative absolute value + double slope_sign = slope >= 0.0 ? 1.0 : -1.0; + double abs_slope = slope_sign * slope; + + // Find limits for the absolute value of the slope + double range_left = weight_left * (time - prev_time); + double range_right = weight_right * (next_time - time); + double max_left = range_left > 0.0 ? slope_sign * (value - prev_value) / range_left : 0.0; + double max_right = range_right > 0.0 ? slope_sign * (next_value - value) / range_right : 0.0; + + // Clamp negative values and NaNs to zero + if (!(max_left > 0.0)) max_left = 0.0; + if (!(max_right > 0.0)) max_right = 0.0; + + // Clamp the absolute slope from both sides + if (abs_slope > max_left) abs_slope = max_left; + if (abs_slope > max_right) abs_slope = max_right; + + slope = (slope_sign * abs_slope); + } + + return (float)slope; +} + +static float ufbxi_solve_auto_tangent_left(ufbxi_context *uc, double prev_time, double time, ufbx_real prev_value, ufbx_real value, float weight_left, float auto_bias, uint32_t flags) +{ + (void)weight_left; + if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f; + if (flags & UFBXI_KEY_CLAMP) { + if (ufbx_fabs(prev_value - value) <= uc->opts.key_clamp_threshold) { + return 0.0f; + } + } + + double slope = (value - prev_value) / (time - prev_time); + + if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { + double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0; + if (abs_bias_weight > 0.0) { + double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0; + slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; + } + } + + return (float)slope; +} + +static float ufbxi_solve_auto_tangent_right(ufbxi_context *uc, double time, double next_time, ufbx_real value, ufbx_real next_value, float weight_right, float auto_bias, uint32_t flags) +{ + (void)weight_right; + if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f; + if (flags & UFBXI_KEY_CLAMP) { + if (ufbx_fabs(next_value - value) <= uc->opts.key_clamp_threshold) { + return 0.0f; + } + } + + double slope = (next_value - value) / (next_time - time); + + if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { + double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0; + if (abs_bias_weight > 0.0) { + double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0; + slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; + } + } + + return (float)slope; +} + +static void ufbxi_solve_tcb(float *p_slope_left, float *p_slope_right, double tension, double continuity, double bias, double slope_left, double slope_right, bool edge) +{ + double factor = edge ? 1.0 : 0.5; + double d00 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 - continuity); + double d01 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 + continuity); + double d10 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 + continuity); + double d11 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 - continuity); + + *p_slope_left = (float)(d00 * slope_left + d01 * slope_right); + *p_slope_right = (float)(d10 * slope_left + d11 * slope_right); +} + +ufbxi_noinline static void ufbxi_read_extrapolation(ufbx_extrapolation *p_extrapolation, ufbxi_node *node, const char *name) +{ + ufbxi_node *child = ufbxi_find_child(node, name); + ufbx_extrapolation_mode mode = UFBX_EXTRAPOLATION_CONSTANT; + int32_t repeat_count = -1; + + if (child) { + int32_t mode_ch; + if (ufbxi_find_val1(child, ufbxi_Type, "I", &mode_ch)) { + + switch (mode_ch) { + case 'A': mode = UFBX_EXTRAPOLATION_REPEAT_RELATIVE; break; + case 'C': mode = UFBX_EXTRAPOLATION_CONSTANT; break; + case 'K': mode = UFBX_EXTRAPOLATION_SLOPE; break; + case 'M': mode = UFBX_EXTRAPOLATION_MIRROR; break; + case 'R': mode = UFBX_EXTRAPOLATION_REPEAT; break; + default: /* Unknown */ break; + } + if (ufbxi_find_val1(child, ufbxi_Repetition, "I", &repeat_count)) { + if (repeat_count < 0) { + repeat_count = -1; + } + } + } + } + + p_extrapolation->mode = mode; + p_extrapolation->repeat_count = repeat_count; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_animation_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_anim_curve *curve = ufbxi_push_element(uc, info, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE); + ufbxi_check(curve); + + ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); + ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); + + if (uc->opts.ignore_animation) return 1; + + ufbxi_value_array *times, *values, *attr_flags, *attrs, *refs; + ufbxi_check(times = ufbxi_find_array(node, ufbxi_KeyTime, 'l')); + ufbxi_check(values = ufbxi_find_array(node, ufbxi_KeyValueFloat, 'r')); + ufbxi_check(attr_flags = ufbxi_find_array(node, ufbxi_KeyAttrFlags, 'i')); + ufbxi_check(attrs = ufbxi_find_array(node, ufbxi_KeyAttrDataFloat, '?')); + ufbxi_check(refs = ufbxi_find_array(node, ufbxi_KeyAttrRefCount, 'i')); + + // Time and value arrays that define the keyframes should be parallel + ufbxi_check(times->size == values->size); + + // Flags and attributes are run-length encoded where KeyAttrRefCount (refs) + // is an array that describes how many times to repeat a given flag/attribute. + // Attributes consist of 4 32-bit floating point values per key. + ufbxi_check(attr_flags->size == refs->size); + ufbxi_check(attrs->size == refs->size * 4u); + + size_t num_keys = times->size; + ufbx_keyframe *keys = ufbxi_push(&uc->result, ufbx_keyframe, num_keys); + ufbxi_check(keys); + + curve->keyframes.data = keys; + curve->keyframes.count = num_keys; + + int64_t *p_time = (int64_t*)times->data; + ufbx_real *p_value = (ufbx_real*)values->data; + int32_t *p_flag = (int32_t*)attr_flags->data; + float *p_attr = (float*)attrs->data; + int32_t *p_ref = (int32_t*)refs->data, *p_ref_end = p_ref + refs->size; + + // The previous key defines the weight/slope of the left tangent + float slope_left = 0.0f; + float weight_left = 0.333333f; + // float velocity_left = 0.0f; + + double prev_time = 0.0; + double next_time = 0.0; + + int32_t refs_left = 0; + if (num_keys > 0) { + next_time = (double)p_time[0] / uc->ktime_sec_double; + if (p_ref < p_ref_end) refs_left = *p_ref; + } + + for (size_t i = 0; i < num_keys; i++) { + ufbx_keyframe *key = &keys[i]; + ufbxi_check(refs_left > 0); + + ufbx_real value = *p_value; + if (i == 0) { + curve->min_value = value; + curve->max_value = value; + } else { + curve->min_value = ufbxi_min_real(curve->min_value, value); + curve->max_value = ufbxi_max_real(curve->max_value, value); + } + + key->time = next_time; + key->value = value; + + if (i + 1 < num_keys) { + next_time = (double)p_time[1] / uc->ktime_sec_double; + } + + uint32_t flags = (uint32_t)*p_flag; + + float slope_right = p_attr[0]; + float weight_right = 0.333333f; + //float velocity_right = 0.0f; + float next_slope_left = p_attr[1]; + float next_weight_left = 0.333333f; + // float next_velocity_left = 0.0f; + + if ((flags & (UFBXI_KEY_WEIGHTED_RIGHT|UFBXI_KEY_WEIGHTED_NEXT_LEFT)) != 0) { + // At least one of the tangents is weighted. The weights are encoded as + // two 0.4 _decimal_ fixed point values that are packed into 32 bits and + // interpreted as a 32-bit float. + uint32_t packed_weights; + memcpy(&packed_weights, &p_attr[2], sizeof(uint32_t)); + + if (flags & UFBXI_KEY_WEIGHTED_RIGHT) { + // Right tangent is weighted + weight_right = (float)(packed_weights & 0xffff) * 0.0001f; + } + + if (flags & UFBXI_KEY_WEIGHTED_NEXT_LEFT) { + // Next left tangent is weighted + next_weight_left = (float)(packed_weights >> 16) * 0.0001f; + } + } +#if 0 + if ((flags & (UFBXI_KEY_VELOCITY_RIGHT|UFBXI_KEY_VELOCITY_NEXT_LEFT)) != 0) { + // Velocities are encoded in the same way as weights, see above. + uint32_t packed_velocities; + memcpy(&packed_velocities, &p_attr[3], sizeof(uint32_t)); + + if (flags & UFBXI_KEY_VELOCITY_RIGHT) { + // Right tangent has velocity + velocity_right = (float)(int16_t)(packed_velocities & 0xffff) * 0.0001f; + } + + if (flags & UFBXI_KEY_VELOCITY_NEXT_LEFT) { + // Next left tangent has velocity + next_velocity_left = (float)(int16_t)(packed_velocities >> 16) * 0.0001f; + } + } +#endif + + if (flags & UFBXI_KEY_INTERPOLATION_CONSTANT) { + // Constant interpolation: Set cubic tangents to flat. + + if (flags & UFBXI_KEY_CONSTANT_NEXT) { + // Take constant value from next key + key->interpolation = UFBX_INTERPOLATION_CONSTANT_NEXT; + + } else { + // Take constant value from the previous key + key->interpolation = UFBX_INTERPOLATION_CONSTANT_PREV; + } + + weight_right = next_weight_left = 0.333333f; + slope_right = next_slope_left = 0.0f; + + } else if (flags & UFBXI_KEY_INTERPOLATION_CUBIC) { + // Cubic interpolation + key->interpolation = UFBX_INTERPOLATION_CUBIC; + + if (flags & UFBXI_KEY_TANGENT_TCB) { + double tcb_slope_left = 0.0; + double tcb_slope_right = 0.0; + bool tcb_edge = false; + if (i > 0 && key->time > prev_time) { + tcb_slope_left = (key->value - p_value[-1]) / (key->time - prev_time); + } else { + tcb_edge = true; + } + if (i + 1 < num_keys && next_time > key->time) { + tcb_slope_right = (p_value[1] - key->value) / (next_time - key->time); + } else { + tcb_edge = true; + } + + ufbxi_solve_tcb(&slope_left, &slope_right, p_attr[0], p_attr[1], p_attr[2], tcb_slope_left, tcb_slope_right, tcb_edge); + + // TODO: How to handle these? + next_slope_left = 0.0f; + next_weight_left = 0.333333f; + // next_velocity_left = 0.0f; + } else if (flags & UFBXI_KEY_TANGENT_USER) { + // User tangents + + if (flags & UFBXI_KEY_TANGENT_BROKEN) { + // Broken tangents: No need to modify slopes + } else { + // Unified tangents: Use right slope for both sides + // TODO: ??? slope_left = slope_right; + } + + } else { + // TODO: Auto break (0x800) + + if (i > 0 && i + 1 < num_keys && key->time > prev_time && next_time > key->time) { + if (ufbx_fabs(slope_left + slope_right) <= 0.0001f) { + slope_left = slope_right = ufbxi_solve_auto_tangent(uc, + prev_time, key->time, next_time, + p_value[-1], key->value, p_value[1], + weight_left, weight_right, slope_right, flags); + } else { + slope_left = ufbxi_solve_auto_tangent(uc, + prev_time, key->time, next_time, + p_value[-1], key->value, p_value[1], + weight_left, weight_right, -slope_left, flags); + slope_right = ufbxi_solve_auto_tangent(uc, + prev_time, key->time, next_time, + p_value[-1], key->value, p_value[1], + weight_left, weight_right, slope_right, flags); + } + } else if (i > 0 && key->time > prev_time) { + slope_left = slope_right = ufbxi_solve_auto_tangent_left(uc, + prev_time, key->time, + p_value[-1], key->value, + weight_left, -slope_left, flags); + } else if (i + 1 < num_keys && next_time > key->time) { + slope_left = slope_right = ufbxi_solve_auto_tangent_right(uc, + key->time, next_time, + key->value, p_value[1], + weight_right, slope_right, flags); + } else { + // Only / invalid keyframe: Set both slopes to zero + slope_left = slope_right = 0.0f; + } + + + // ??? Looks like at least MotionBuilder adjusts weight and auto bias to + // implement velocity and the velocity information in the file is purely + // for UI (?) If auto bias is not accounted for the velocity computation + // below results in the correct tangents, but with auto bias the velocity + // seems to be accounted for twice resulting in incorrect values... +#if 0 + if (weight_left >= UFBX_EPSILON) { + slope_left *= (float)(1.0 - ufbx_fmin(velocity_left / weight_left, 1.0)); + } + if (weight_right >= UFBX_EPSILON) { + slope_right *= (float)(1.0 - ufbx_fmin(velocity_right / weight_right, 1.0)); + } +#endif + } + + } else { + // Linear or unknown interpolation: Set cubic tangents to match + // the linear interpolation with weights of 1/3. + key->interpolation = UFBX_INTERPOLATION_LINEAR; + + weight_right = 0.333333f; + next_weight_left = 0.333333f; + + if (next_time > key->time) { + double delta_time = next_time - key->time; + if (delta_time > 0.0) { + double slope = (p_value[1] - key->value) / delta_time; + slope_right = next_slope_left = (float)slope; + } else { + slope_right = next_slope_left = 0.0f; + } + } else { + slope_right = next_slope_left = 0.0f; + } + } + + // Set the tangents based on weights (dx relative to the time difference + // between the previous/next key) and slope (simply d = slope * dx) + if (key->time > prev_time) { + double delta = key->time - prev_time; + key->left.dx = (float)(weight_left * delta); + key->left.dy = key->left.dx * slope_left; + } else { + key->left.dx = 0.0f; + key->left.dy = 0.0f; + } + + if (next_time > key->time) { + double delta = next_time - key->time; + key->right.dx = (float)(weight_right * delta); + key->right.dy = key->right.dx * slope_right; + } else { + key->right.dx = 0.0f; + key->right.dy = 0.0f; + } + + slope_left = next_slope_left; + weight_left = next_weight_left; + // velocity_left = next_velocity_left; + prev_time = key->time; + + // Decrement attribute refcount and potentially move to the next one. + if (--refs_left == 0) { + p_flag++; + p_attr += 4; + p_ref++; + if (p_ref < p_ref_end) refs_left = *p_ref; + } + p_time++; + p_value++; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_material(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_material *material = ufbxi_push_element(uc, info, ufbx_material, UFBX_ELEMENT_MATERIAL); + ufbxi_check(material); + + if (!ufbxi_find_val1(node, ufbxi_ShadingModel, "S", &material->shading_model_name)) { + material->shading_model_name = ufbx_empty_string; + } + + material->shader_prop_prefix = ufbx_empty_string; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE); + ufbxi_check(texture); + + texture->type = UFBX_TEXTURE_FILE; + + texture->filename = ufbx_empty_string; + texture->absolute_filename = ufbx_empty_string; + texture->relative_filename = ufbx_empty_string; + + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &texture->absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &texture->absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &texture->relative_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &texture->relative_filename)); + + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &texture->raw_absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &texture->raw_absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &texture->raw_relative_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &texture->raw_relative_filename)); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_layered_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE); + ufbxi_check(texture); + + texture->type = UFBX_TEXTURE_LAYERED; + + texture->filename = ufbx_empty_string; + texture->absolute_filename = ufbx_empty_string; + texture->relative_filename = ufbx_empty_string; + + ufbxi_texture_extra *extra = ufbxi_push_element_extra(uc, texture->element.element_id, ufbxi_texture_extra); + ufbxi_check(extra); + + ufbxi_value_array *alphas = ufbxi_find_array(node, ufbxi_Alphas, 'r'); + if (alphas) { + extra->alphas = (ufbx_real*)alphas->data; + extra->num_alphas = alphas->size; + } + + ufbxi_value_array *blend_modes = ufbxi_find_array(node, ufbxi_BlendModes, 'i'); + if (blend_modes) { + extra->blend_modes = (int32_t*)blend_modes->data; + extra->num_blend_modes = blend_modes->size; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_video(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_video *video = ufbxi_push_element(uc, info, ufbx_video, UFBX_ELEMENT_VIDEO); + ufbxi_check(video); + + video->filename = ufbx_empty_string; + video->absolute_filename = ufbx_empty_string; + video->relative_filename = ufbx_empty_string; + + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &video->absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &video->absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &video->relative_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &video->relative_filename)); + + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &video->raw_absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &video->raw_absolute_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &video->raw_relative_filename)); + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &video->raw_relative_filename)); + + ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content); + ufbxi_check(ufbxi_read_embedded_blob(uc, &video->content, content_node)); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_anim_stack(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + (void)node; + + ufbx_anim_stack *stack = ufbxi_push_element(uc, info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); + ufbxi_check(stack); + + uint32_t hash = ufbxi_hash_ptr(info->name.data); + ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data); + if (!entry) { + entry = ufbxi_map_insert(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data); + ufbxi_check(entry); + entry->name = info->name.data; + entry->stack = stack; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_pose(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type) +{ + ufbx_pose *pose = ufbxi_push_element(uc, info, ufbx_pose, UFBX_ELEMENT_POSE); + ufbxi_check(pose); + + // TODO: What are the actual other types? + pose->is_bind_pose = sub_type == ufbxi_BindPose; + + size_t num_bones = 0; + ufbxi_for(ufbxi_node, n, node->children, node->num_children) { + if (n->name != ufbxi_PoseNode) continue; + + // Bones are linked with FBX names/IDs bypassing the connection system (!?) + uint64_t fbx_id = 0; + if (uc->version < 7000) { + char *name = NULL; + if (!ufbxi_find_val1(n, ufbxi_Node, "c", &name)) continue; + fbx_id = ufbxi_synthetic_id_from_string(uc, name); + ufbxi_check(fbx_id); + } else { + if (!ufbxi_find_val1(n, ufbxi_Node, "L", &fbx_id)) continue; + ufbxi_check(ufbxi_validate_fbx_id(uc, &fbx_id)); + } + + ufbxi_value_array *matrix = ufbxi_find_array(n, ufbxi_Matrix, 'r'); + if (!matrix) continue; + ufbxi_check(matrix->size >= 16); + + ufbxi_tmp_bone_pose *tmp_pose = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_bone_pose, 1); + ufbxi_check(tmp_pose); + + num_bones++; + tmp_pose->bone_fbx_id = fbx_id; + ufbxi_read_transform_matrix(&tmp_pose->bone_to_world, (ufbx_real*)matrix->data); + } + + // HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer + pose->bone_poses.count = num_bones; + pose->bone_poses.data = (ufbx_bone_pose*)ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_bone_pose, num_bones); + ufbxi_check(pose->bone_poses.data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_shader_prop_bindings(ufbxi_context *uc, ufbx_shader_prop_binding *bindings, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_shader_prop_binding))); + ufbxi_macro_stable_sort(ufbx_shader_prop_binding, 32, bindings, uc->tmp_arr, count, + ( ufbxi_str_less(a->shader_prop, b->shader_prop) ) ); + return 1; +} + + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_binding_table(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_shader_binding *bindings = ufbxi_push_element(uc, info, ufbx_shader_binding, UFBX_ELEMENT_SHADER_BINDING); + ufbxi_check(bindings); + + size_t num_entries = 0; + ufbxi_for (ufbxi_node, n, node->children, node->num_children) { + if (n->name != ufbxi_Entry) continue; + + ufbx_string src, dst; + const char *src_type = NULL, *dst_type = NULL; + if (!ufbxi_get_val4(n, "SCSC", &src, (char**)&src_type, &dst, (char**)&dst_type)) { + continue; + } + + if (src_type == ufbxi_FbxPropertyEntry && dst_type == ufbxi_FbxSemanticEntry) { + ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1); + ufbxi_check(bind); + bind->material_prop = src; + bind->shader_prop = dst; + num_entries++; + } else if (src_type == ufbxi_FbxSemanticEntry && dst_type == ufbxi_FbxPropertyEntry) { + ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1); + ufbxi_check(bind); + bind->material_prop = dst; + bind->shader_prop = src; + num_entries++; + } + } + + bindings->prop_bindings.count = num_entries; + bindings->prop_bindings.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_shader_prop_binding, num_entries); + ufbxi_check(bindings->prop_bindings.data); + + ufbxi_check(ufbxi_sort_shader_prop_bindings(uc, bindings->prop_bindings.data, bindings->prop_bindings.count)); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_set(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + (void)node; + + ufbx_selection_set *set = ufbxi_push_element(uc, info, ufbx_selection_set, UFBX_ELEMENT_SELECTION_SET); + ufbxi_check(set); + + return 1; +} + +ufbxi_noinline static void ufbxi_find_uint32_list(ufbx_uint32_list *dst, ufbxi_node *node, const char *name) +{ + ufbxi_value_array *arr = ufbxi_find_array(node, name, 'i'); + if (arr) { + dst->data = (uint32_t*)arr->data; + dst->count = arr->size; + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_selection_node *sel = ufbxi_push_element(uc, info, ufbx_selection_node, UFBX_ELEMENT_SELECTION_NODE); + ufbxi_check(sel); + + int32_t in_set = 0; + if (ufbxi_find_val1(node, ufbxi_IsTheNodeInSet, "I", &in_set) && in_set) { + sel->include_node = true; + } + + ufbxi_find_uint32_list(&sel->vertices, node, ufbxi_VertexIndexArray); + ufbxi_find_uint32_list(&sel->edges, node, ufbxi_EdgeIndexArray); + ufbxi_find_uint32_list(&sel->faces, node, ufbxi_PolygonIndexArray); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_character(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + (void)node; + + ufbx_character *character = ufbxi_push_element(uc, info, ufbx_character, UFBX_ELEMENT_CHARACTER); + ufbxi_check(character); + + // TODO: There's some extremely cursed all-caps data in characters + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_audio_clip(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_audio_clip *audio = ufbxi_push_element(uc, info, ufbx_audio_clip, UFBX_ELEMENT_AUDIO_CLIP); + ufbxi_check(audio); + + audio->filename = ufbx_empty_string; + audio->absolute_filename = ufbx_empty_string; + audio->relative_filename = ufbx_empty_string; + + ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content); + ufbxi_check(ufbxi_read_embedded_blob(uc, &audio->content, content_node)); + + return 1; +} + +typedef struct { + ufbx_constraint_type type; + const char *name; +} ufbxi_constraint_type; + +static const ufbxi_constraint_type ufbxi_constraint_types[] = { + { UFBX_CONSTRAINT_AIM, "Aim" }, + { UFBX_CONSTRAINT_PARENT, "Parent-Child" }, + { UFBX_CONSTRAINT_POSITION, "Position From Positions" }, + { UFBX_CONSTRAINT_ROTATION, "Rotation From Rotations" }, + { UFBX_CONSTRAINT_SCALE, "Scale From Scales" }, + { UFBX_CONSTRAINT_SINGLE_CHAIN_IK, "Single Chain IK" }, +}; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_constraint(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + (void)node; + + ufbx_constraint *constraint = ufbxi_push_element(uc, info, ufbx_constraint, UFBX_ELEMENT_CONSTRAINT); + ufbxi_check(constraint); + + if (!ufbxi_find_val1(node, ufbxi_Type, "S", &constraint->type_name)) { + constraint->type_name = ufbx_empty_string; + } + + ufbxi_for(const ufbxi_constraint_type, ctype, ufbxi_constraint_types, ufbxi_arraycount(ufbxi_constraint_types)) { + if (!strcmp(constraint->type_name.data, ctype->name)) { + constraint->type = ctype->type; + break; + } + } + + // TODO: There's some extremely cursed all-caps data in characters + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_attribute(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, ufbx_string type_str, const char *sub_type, const char *super_type) +{ + if ((sub_type == ufbxi_empty_char || sub_type == ufbxi_Model) && type_str.data == ufbxi_Model) { + // Plain model + return 1; + } + + ufbxi_element_info attrib_info = *info; + + attrib_info.fbx_id = ufbxi_push_synthetic_id(uc); + + // Use type and name from NodeAttributeName if it exists *uniquely* + ufbx_string type_and_name; + if (ufbxi_find_val1(node, ufbxi_NodeAttributeName, "s", &type_and_name)) { + ufbx_string attrib_type_str, attrib_name_str; + ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &attrib_type_str, &attrib_name_str)); + if (attrib_name_str.length > 0) { + attrib_info.name = attrib_name_str; + uint64_t attrib_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); + ufbxi_check(attrib_id); + if (info->fbx_id != attrib_id && !ufbxi_fbx_id_exists(uc, attrib_id)) { + attrib_info.fbx_id = attrib_id; + } + } + } + + // 6x00: Link the node to the node attribute so property connections can be + // redirected from connections if necessary. + ufbxi_check(ufbxi_insert_fbx_attr(uc, info->fbx_id, attrib_info.fbx_id)); + + // Split properties between the node and the attribute. + // Consider all user properties as node properties. + ufbx_prop *ps = info->props.props.data; + size_t dst = 0, src = 0, end = info->props.props.count; + while (src < end) { + if (!ufbxi_is_node_property_name(uc, ps[src].name.data) && (ps[src].flags & UFBX_PROP_FLAG_USER_DEFINED) == 0) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, 1, &ps[src])); + src++; + } else if (dst != src) { + ps[dst++] = ps[src++]; + } else { + dst++; src++; + } + } + attrib_info.props.props.count = end - dst; + attrib_info.props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, attrib_info.props.props.count); + ufbxi_check(attrib_info.props.props.data); + info->props.props.count = dst; + + if (sub_type == ufbxi_Mesh) { + ufbxi_check(ufbxi_read_mesh(uc, node, &attrib_info)); + } else if (sub_type == ufbxi_Light) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT)); + } else if (sub_type == ufbxi_Camera) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA)); + } else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) { + ufbxi_check(ufbxi_read_bone(uc, node, &attrib_info, sub_type)); + } else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY)); + } else if (sub_type == ufbxi_NurbsCurve) { + if (!ufbxi_find_child(node, ufbxi_KnotVector)) return 1; + ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &attrib_info)); + } else if (sub_type == ufbxi_NurbsSurface) { + if (!ufbxi_find_child(node, ufbxi_KnotVectorU)) return 1; + if (!ufbxi_find_child(node, ufbxi_KnotVectorV)) return 1; + ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &attrib_info)); + } else if (sub_type == ufbxi_Line) { + if (!ufbxi_find_child(node, ufbxi_Points)) return 1; + if (!ufbxi_find_child(node, ufbxi_PointsIndex)) return 1; + ufbxi_check(ufbxi_read_line(uc, node, &attrib_info)); + } else if (sub_type == ufbxi_TrimNurbsSurface) { + if (!ufbxi_find_child(node, ufbxi_Layer)) return 1; + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE)); + } else if (sub_type == ufbxi_Boundary) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY)); + } else if (sub_type == ufbxi_CameraStereo) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA)); + } else if (sub_type == ufbxi_CameraSwitcher) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER)); + } else if (sub_type == ufbxi_FKEffector) { + ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_FK_EFFECTOR)); + } else if (sub_type == ufbxi_IKEffector) { + ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_IK_EFFECTOR)); + } else if (sub_type == ufbxi_LodGroup) { + ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP)); + } else { + ufbx_string sub_type_str = { sub_type, strlen(sub_type) }; + ufbxi_check(ufbxi_read_unknown(uc, node, &attrib_info, type_str, sub_type_str, super_type)); + } + + ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info->fbx_id)); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_global_settings(ufbxi_context *uc, ufbxi_node *node) +{ + ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.settings.props)); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_object(ufbxi_context *uc, ufbxi_node *node) +{ + ufbxi_element_info info = { 0 }; + info.dom_node = ufbxi_get_dom_node(uc, node); + + if (node->name == ufbxi_GlobalSettings) { + ufbxi_check(ufbxi_read_global_settings(uc, node)); + return 1; + } + + ufbx_string type_and_name, sub_type_str; + + // Failing to parse the object properties is not an error since + // there's some weird objects mixed in every now and then. + // FBX version 7000 and up uses 64-bit unique IDs per object, + // older FBX versions just use name/type pairs, which we can + // use as IDs since all strings are interned into a string pool. + if (uc->version >= 7000) { + if (!ufbxi_get_val3(node, "Lss", &info.fbx_id, &type_and_name, &sub_type_str)) return 1; + ufbxi_check(ufbxi_validate_fbx_id(uc, &info.fbx_id)); + } else { + if (!ufbxi_get_val2(node, "ss", &type_and_name, &sub_type_str)) return 1; + info.fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); + ufbxi_check(info.fbx_id); + } + + // Remove the "Fbx" prefix from sub-types, remember to re-intern! + if (sub_type_str.length > 3 && !memcmp(sub_type_str.data, "Fbx", 3)) { + sub_type_str.data += 3; + sub_type_str.length -= 3; + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &sub_type_str, false)); + } + + ufbx_string type_str; + ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type_str, &info.name)); + + const char *name = node->name, *sub_type = sub_type_str.data; + ufbxi_check(ufbxi_read_properties(uc, node, &info.props)); + info.props.defaults = ufbxi_find_template(uc, name, sub_type); + + if (name == ufbxi_Model) { + if (uc->version < 7000) { + ufbxi_check(ufbxi_read_synthetic_attribute(uc, node, &info, type_str, sub_type, name)); + } + ufbxi_check(ufbxi_read_model(uc, node, &info)); + } else if (name == ufbxi_NodeAttribute) { + if (sub_type == ufbxi_Light) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT)); + } else if (sub_type == ufbxi_Camera) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA)); + } else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) { + ufbxi_check(ufbxi_read_bone(uc, node, &info, sub_type)); + } else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY)); + } else if (sub_type == ufbxi_CameraStereo) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA)); + } else if (sub_type == ufbxi_CameraSwitcher) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER)); + } else if (sub_type == ufbxi_FKEffector) { + ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_FK_EFFECTOR)); + } else if (sub_type == ufbxi_IKEffector) { + ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_IK_EFFECTOR)); + } else if (sub_type == ufbxi_LodGroup) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP)); + } else { + ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); + } + } else if (name == ufbxi_Geometry) { + if (sub_type == ufbxi_Mesh) { + ufbxi_check(ufbxi_read_mesh(uc, node, &info)); + } else if (sub_type == ufbxi_Shape) { + ufbxi_check(ufbxi_read_shape(uc, node, &info)); + } else if (sub_type == ufbxi_NurbsCurve) { + ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &info)); + } else if (sub_type == ufbxi_NurbsSurface) { + ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &info)); + } else if (sub_type == ufbxi_Line) { + ufbxi_check(ufbxi_read_line(uc, node, &info)); + } else if (sub_type == ufbxi_TrimNurbsSurface) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE)); + } else if (sub_type == ufbxi_Boundary) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY)); + } else { + ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); + } + } else if (name == ufbxi_Deformer) { + if (sub_type == ufbxi_Skin) { + ufbxi_check(ufbxi_read_skin(uc, node, &info)); + } else if (sub_type == ufbxi_Cluster) { + ufbxi_check(ufbxi_read_skin_cluster(uc, node, &info)); + } else if (sub_type == ufbxi_BlendShape) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_blend_deformer), UFBX_ELEMENT_BLEND_DEFORMER)); + } else if (sub_type == ufbxi_BlendShapeChannel) { + ufbxi_check(ufbxi_read_blend_channel(uc, node, &info)); + } else if (sub_type == ufbxi_VertexCacheDeformer) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_deformer), UFBX_ELEMENT_CACHE_DEFORMER)); + } else { + ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); + } + } else if (name == ufbxi_Material) { + ufbxi_check(ufbxi_read_material(uc, node, &info)); + } else if (name == ufbxi_Texture) { + ufbxi_check(ufbxi_read_texture(uc, node, &info)); + } else if (name == ufbxi_LayeredTexture) { + ufbxi_check(ufbxi_read_layered_texture(uc, node, &info)); + } else if (name == ufbxi_Video) { + ufbxi_check(ufbxi_read_video(uc, node, &info)); + } else if (name == ufbxi_AnimationStack) { + ufbxi_check(ufbxi_read_anim_stack(uc, node, &info)); + } else if (name == ufbxi_AnimationLayer) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_layer), UFBX_ELEMENT_ANIM_LAYER)); + } else if (name == ufbxi_AnimationCurveNode) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_value), UFBX_ELEMENT_ANIM_VALUE)); + } else if (name == ufbxi_AnimationCurve) { + ufbxi_check(ufbxi_read_animation_curve(uc, node, &info)); + } else if (name == ufbxi_Pose) { + ufbxi_check(ufbxi_read_pose(uc, node, &info, sub_type)); + } else if (name == ufbxi_Implementation) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_shader), UFBX_ELEMENT_SHADER)); + } else if (name == ufbxi_BindingTable) { + ufbxi_check(ufbxi_read_binding_table(uc, node, &info)); + } else if (name == ufbxi_Collection) { + if (sub_type == ufbxi_SelectionSet) { + ufbxi_check(ufbxi_read_selection_set(uc, node, &info)); + } + } else if (name == ufbxi_CollectionExclusive) { + if (sub_type == ufbxi_DisplayLayer) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_display_layer), UFBX_ELEMENT_DISPLAY_LAYER)); + } + } else if (name == ufbxi_SelectionNode) { + ufbxi_check(ufbxi_read_selection_node(uc, node, &info)); + } else if (name == ufbxi_Constraint) { + if (sub_type == ufbxi_Character) { + ufbxi_check(ufbxi_read_character(uc, node, &info)); + } else { + ufbxi_check(ufbxi_read_constraint(uc, node, &info)); + } + } else if (name == ufbxi_SceneInfo) { + ufbxi_check(ufbxi_read_scene_info(uc, node)); + } else if (name == ufbxi_Cache) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_file), UFBX_ELEMENT_CACHE_FILE)); + } else if (name == ufbxi_ObjectMetaData) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_metadata_object), UFBX_ELEMENT_METADATA_OBJECT)); + } else if (name == ufbxi_AudioLayer) { + ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_audio_layer), UFBX_ELEMENT_AUDIO_LAYER)); + } else if (name == ufbxi_Audio) { + ufbxi_check(ufbxi_read_audio_clip(uc, node, &info)); + } else { + ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects(ufbxi_context *uc) +{ + for (;;) { + // Push a deferred element ID for tagging warnings + uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1); + ufbxi_check(uc->p_element_id); + *uc->p_element_id = UFBX_NO_INDEX; + uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items; + + ufbxi_node *node; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); + if (!node) break; + + ufbxi_check(ufbxi_read_object(uc, node)); + + uc->warnings.deferred_element_id_plus_one = 0; + uc->p_element_id = NULL; + } + + return 1; +} + +typedef struct { + ufbxi_node **nodes; + size_t num_nodes; + uint32_t task_index; +} ufbxi_object_batch; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_context *uc) +{ + uc->parse_threaded = true; + + bool parsed_to_end = false; + ufbxi_object_batch batches[UFBX_THREAD_GROUP_COUNT]; // ufbxi_uninit + memset(batches, 0, sizeof(batches)); + + size_t empty_count = 0; + size_t batch_index = 0; + while (empty_count < UFBX_THREAD_GROUP_COUNT) { + ufbxi_object_batch *batch = &batches[batch_index]; + + ufbxi_check(ufbxi_thread_pool_wait_group(&uc->thread_pool)); + + if (batch->num_nodes > 0) { + ufbxi_for_ptr(ufbxi_node, p_node, batch->nodes, batch->num_nodes) { + ufbxi_buf_clear(&uc->tmp_parse); + + // Push a deferred element ID for tagging warnings + uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1); + ufbxi_check(uc->p_element_id); + *uc->p_element_id = UFBX_NO_INDEX; + uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items; + + ufbxi_check(ufbxi_read_object(uc, *p_node)); + + uc->warnings.deferred_element_id_plus_one = 0; + uc->p_element_id = NULL; + } + batch->num_nodes = 0; + } + + ufbxi_buf *tmp_buf = &uc->tmp_thread_parse[batch_index]; + + // ASCII data may be in `tmp_buf`, so copy it to safety in case + if (uc->ascii.src_buf == tmp_buf) { + ufbxi_ascii *ua = &uc->ascii; + size_t size = ufbxi_to_size(ua->src_end - ua->src); + if (uc->read_buffer_size < size) { + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, size)); + } + memcpy(uc->read_buffer, ua->src, size); + uc->data = uc->data_begin = ua->src = uc->read_buffer; + ua->src_end = uc->read_buffer + size; + ua->src_is_retained = false; + ua->src_buf = NULL; + if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) { + ua->src_yield = ua->src_end; + } else { + ua->src_yield = ua->src + uc->progress_interval; + } + uc->data = ua->src; + } + + ufbxi_buf_clear(tmp_buf); + + if (!parsed_to_end) { + size_t num_nodes = 0; + uint32_t task_start = uc->thread_pool.start_index; + uint32_t max_tasks = uc->thread_pool.num_tasks / UFBX_THREAD_GROUP_COUNT; + max_tasks = ufbxi_min32(max_tasks, ufbxi_thread_pool_available_tasks(&uc->thread_pool)); + size_t max_memory = uc->opts.thread_opts.memory_limit / UFBX_THREAD_GROUP_COUNT; + + for (;;) { + ufbxi_node *node = NULL; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, tmp_buf)); + if (!node) { + parsed_to_end = true; + break; + } + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbxi_node*, 1, &node)); + num_nodes++; + + uint32_t num_tasks = uc->thread_pool.start_index - task_start; + if (num_tasks >= max_tasks) break; + + size_t memory_used = tmp_buf->pushed_size + tmp_buf->pos; + if (memory_used >= max_memory) break; + } + + batch->num_nodes = num_nodes; + batch->nodes = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node*, num_nodes); + ufbxi_check(batch->nodes); + batch->task_index = uc->thread_pool.start_index; + + } + + // Not safe to refer to this buffer anymore + uc->ascii.src_is_retained = false; + + ufbxi_thread_pool_flush_group(&uc->thread_pool); + + if (batch->num_nodes == 0) { + empty_count += 1; + } + + batch_index = (batch_index + 1) % UFBX_THREAD_GROUP_COUNT; + } + + ufbxi_check(ufbxi_thread_pool_wait_all(&uc->thread_pool)); + + uc->parse_threaded = false; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_connections(ufbxi_context *uc) +{ + // Read the connections to the list first + for (;;) { + ufbxi_node *node; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); + if (!node) break; + + char *type; + + uint64_t src_id = 0, dst_id = 0; + ufbx_string src_prop = ufbx_empty_string, dst_prop = ufbx_empty_string; + + if (uc->version < 7000) { + char *src_name = NULL, *dst_name = NULL; + // Pre-7000 versions use Type::Name pairs as identifiers + + if (!ufbxi_get_val1(node, "c", &type)) continue; + + if (type == ufbxi_OO) { + if (!ufbxi_get_val3(node, "_cc", NULL, &src_name, &dst_name)) continue; + } else if (type == ufbxi_OP) { + if (!ufbxi_get_val4(node, "_ccs", NULL, &src_name, &dst_name, &dst_prop)) continue; + } else if (type == ufbxi_PO) { + if (!ufbxi_get_val4(node, "_csc", NULL, &src_name, &src_prop, &dst_name)) continue; + } else if (type == ufbxi_PP) { + if (!ufbxi_get_val5(node, "_cscs", NULL, &src_name, &src_prop, &dst_name, &dst_prop)) continue; + } else { + // TODO: Strict mode? + continue; + } + + if (src_prop.length > 0) { + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &src_prop, false)); + } + if (dst_prop.length > 0) { + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst_prop, false)); + } + + src_id = ufbxi_synthetic_id_from_string(uc, src_name); + dst_id = ufbxi_synthetic_id_from_string(uc, dst_name); + ufbxi_check(src_id && dst_id); + + } else { + // Post-7000 versions use proper unique 64-bit IDs + + if (!ufbxi_get_val1(node, "C", &type)) continue; + + if (type == ufbxi_OO) { + if (!ufbxi_get_val3(node, "_LL", NULL, &src_id, &dst_id)) continue; + } else if (type == ufbxi_OP) { + if (!ufbxi_get_val4(node, "_LLS", NULL, &src_id, &dst_id, &dst_prop)) continue; + } else if (type == ufbxi_PO) { + if (!ufbxi_get_val4(node, "_LSL", NULL, &src_id, &src_prop, &dst_id)) continue; + } else if (type == ufbxi_PP) { + if (!ufbxi_get_val5(node, "_LSLS", NULL, &src_id, &src_prop, &dst_id, &dst_prop)) continue; + } else { + // TODO: Strict mode? + continue; + } + + ufbxi_check(ufbxi_validate_fbx_id(uc, &src_id)); + ufbxi_check(ufbxi_validate_fbx_id(uc, &dst_id)); + } + + ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); + ufbxi_check(conn); + conn->src = src_id; + conn->dst = dst_id; + conn->src_prop = src_prop; + conn->dst_prop = dst_prop; + } + + return 1; +} + +// -- Pre-7000 "Take" based animation + +ufbxi_forceinline static char ufbxi_double_to_char(double value) +{ + if (value >= 0.0 && value <= 127.0) { + return (char)(int)value; + } else { + return 0; + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_anim_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t value_fbx_id, const char *name, ufbx_real *p_default) +{ + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Default, "R", p_default)); + + // Find the key array, early return with success if not found as we may have only a default + ufbxi_value_array *keys = ufbxi_find_array(node, ufbxi_Key, 'd'); + if (!keys) return 1; + + uint64_t curve_fbx_id = 0; + ufbx_anim_curve *curve = ufbxi_push_synthetic_element(uc, &curve_fbx_id, node, name, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE); + ufbxi_check(curve); + + ufbxi_check(ufbxi_connect_op(uc, curve_fbx_id, value_fbx_id, curve->name)); + + ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); + ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); + + if (uc->opts.ignore_animation) return 1; + + size_t num_keys = 0; + ufbxi_check(ufbxi_find_val1(node, ufbxi_KeyCount, "Z", &num_keys)); + curve->keyframes.data = ufbxi_push(&uc->result, ufbx_keyframe, num_keys); + curve->keyframes.count = num_keys; + ufbxi_check(curve->keyframes.data); + + float slope_left = 0.0f; + float weight_left = 0.333333f; + + double next_time = 0.0; + double next_value = 0.0; + double prev_time = 0.0; + + // The pre-7000 keyframe data is stored as a _heterogenous_ array containing 64-bit integers, + // floating point values, and _bare characters_. We cast all values to double and interpret them. + double *data = (double*)keys->data, *data_end = data + keys->size; + + if (num_keys > 0) { + ufbxi_check(data_end - data >= 2); + next_time = data[0] / uc->ktime_sec_double; + next_value = data[1]; + } + + for (size_t i = 0; i < num_keys; i++) { + ufbx_keyframe *key = &curve->keyframes.data[i]; + + if (i == 0) { + curve->min_value = (ufbx_real)next_value; + curve->max_value = (ufbx_real)next_value; + } else { + curve->min_value = ufbxi_min_real(curve->min_value, (ufbx_real)next_value); + curve->max_value = ufbxi_max_real(curve->max_value, (ufbx_real)next_value); + } + + // First three values: Time, Value, InterpolationMode + ufbxi_check(data_end - data >= 3); + key->time = next_time; + key->value = (ufbx_real)next_value; + char mode = ufbxi_double_to_char(data[2]); + data += 3; + + float slope_right = 0.0f; + float weight_right = 0.333333f; + float next_slope_left = 0.0f; + float next_weight_left = 0.333333f; + bool auto_slope = false; + + if (mode == 'U') { + // Cubic interpolation + key->interpolation = UFBX_INTERPOLATION_CUBIC; + + ufbxi_check(data_end - data >= 1); + char slope_mode = ufbxi_double_to_char(data[0]); + data += 1; + + size_t num_weights = 1; + if (slope_mode == 's' || slope_mode == 'b') { + // Slope mode 's'/'b' (standard? broken?) always have two explicit slopes + // TODO: `b` might actually be some kind of TCB curve + ufbxi_check(data_end - data >= 2); + slope_right = (float)data[0]; + next_slope_left = (float)data[1]; + data += 2; + } else if (slope_mode == 'a') { + // Parameterless slope mode 'a' seems to appear in baked animations. Let's just assume + // automatic tangents for now as they're the least likely to break with + // objectionable artifacts. We need to defer the automatic tangent resolve + // until we have read the next time/value. + // TODO: Solve what this is more throroughly + auto_slope = true; + if (uc->version == 5000) { + num_weights = 0; + } + } else if (slope_mode == 'p') { + // TODO: What is this mode? It seems to have negative values sometimes? + // Also it seems to have _two_ trailing weights values, currently observed: + // `n,n` and `a,X,Y,n`... + // Ignore unknown values for now + ufbxi_check(data_end - data >= 2); + data += 2; + num_weights = 2; + } else if (slope_mode == 't') { + // TODO: What is this mode? It seems that it does not have any weights and the + // third value seems _tiny_ (around 1e-30?) + ufbxi_check(data_end - data >= 3); + data += 3; + num_weights = 0; + } else { + ufbxi_fail("Unknown slope mode"); + } + + for (; num_weights > 0; num_weights--) { + ufbxi_check(data_end - data >= 1); + char weight_mode = ufbxi_double_to_char(data[0]); + data += 1; + + if (weight_mode == 'n') { + // Automatic weights (0.3333...) + } else if (weight_mode == 'a') { + // Manual weights: RightWeight, NextLeftWeight + ufbxi_check(data_end - data >= 2); + weight_right = (float)data[0]; + next_weight_left = (float)data[1]; + data += 2; + } else if (weight_mode == 'l') { + // Next left tangent is weighted + ufbxi_check(data_end - data >= 1); + next_weight_left = (float)data[0]; + data += 1; + } else if (weight_mode == 'r') { + // Right tangent is weighted + ufbxi_check(data_end - data >= 1); + weight_right = (float)data[0]; + data += 1; + } else if (weight_mode == 'c') { + // TODO: What is this mode? At least it has no parameters so let's + // just assume automatic weights for the time being (0.3333...) + } else { + ufbxi_fail("Unknown weight mode"); + } + } + + } else if (mode == 'L') { + // Linear interpolation: No parameters + key->interpolation = UFBX_INTERPOLATION_LINEAR; + } else if (mode == 'C') { + // Constant interpolation: Single parameter (use prev/next) + ufbxi_check(data_end - data >= 1); + key->interpolation = ufbxi_double_to_char(data[0]) == 'n' ? UFBX_INTERPOLATION_CONSTANT_NEXT : UFBX_INTERPOLATION_CONSTANT_PREV; + data += 1; + } else { + ufbxi_fail("Unknown key mode"); + } + + // Retrieve next key and value + if (i + 1 < num_keys) { + ufbxi_check(data_end - data >= 2); + next_time = data[0] / uc->ktime_sec_double; + next_value = data[1]; + } + + if (auto_slope) { + if (i > 0) { + slope_left = slope_right = ufbxi_solve_auto_tangent(uc, + prev_time, key->time, next_time, + key[-1].value, key->value, (ufbx_real)next_value, + weight_left, weight_right, 0.0f, UFBXI_KEY_CLAMP_PROGRESSIVE|UFBXI_KEY_TIME_INDEPENDENT); + } else { + slope_left = slope_right = 0.0f; + } + } + + // Set up linear cubic tangents if necessary + if (key->interpolation == UFBX_INTERPOLATION_LINEAR) { + if (next_time > key->time) { + double slope = (next_value - key->value) / (next_time - key->time); + slope_right = next_slope_left = (float)slope; + } else { + slope_right = next_slope_left = 0.0f; + } + } + + if (key->time > prev_time) { + double delta = key->time - prev_time; + key->left.dx = (float)(weight_left * delta); + key->left.dy = key->left.dx * slope_left; + } else { + key->left.dx = 0.0f; + key->left.dy = 0.0f; + } + + if (next_time > key->time) { + double delta = next_time - key->time; + key->right.dx = (float)(weight_right * delta); + key->right.dy = key->right.dx * slope_right; + } else { + key->right.dx = 0.0f; + key->right.dy = 0.0f; + } + + slope_left = next_slope_left; + weight_left = next_weight_left; + prev_time = key->time; + } + + ufbxi_check(data == data_end); + + return 1; +} + +// Recursion limited as it is further called only for `name="T"/"R"/"S"` and +// cannot enter the `name=="Transform"` branch. +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_prop_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name) + ufbxi_recursive_function(int, ufbxi_read_take_prop_channel, (uc, node, target_fbx_id, layer_fbx_id, name), 2, + (ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name)) +{ + if (name.data == ufbxi_Transform) { + // Pre-7000 have transform keyframes in a deeply nested structure, + // flatten it to make it resemble post-7000 structure a bit closer: + // old: Model: { Channel: "Transform" { Channel: "T" { Channel "X": { ... } } } } + // new: Model: { Channel: "Lcl Translation" { Channel "X": { ... } } } + + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + if (child->name != ufbxi_Channel) continue; + + const char *old_name = NULL; + ufbxi_check(ufbxi_get_val1(child, "C", (char**)&old_name)); + + ufbx_string new_name; + if (old_name == ufbxi_T) { new_name.data = ufbxi_Lcl_Translation; new_name.length = sizeof(ufbxi_Lcl_Translation) - 1; } + else if (old_name == ufbxi_R) { new_name.data = ufbxi_Lcl_Rotation; new_name.length = sizeof(ufbxi_Lcl_Rotation) - 1; } + else if (old_name == ufbxi_S) { new_name.data = ufbxi_Lcl_Scaling; new_name.length = sizeof(ufbxi_Lcl_Scaling) - 1; } + else { + continue; + } + + // Read child as a top-level property channel + ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, new_name)); + } + + } else { + + // Pre-6000 FBX files store blend shape keys with a " (Shape)" suffix + if (uc->version < 6000) { + const char *const suffix = " (Shape)"; + size_t suffix_len = strlen(suffix); + if (name.length > suffix_len && !memcmp(name.data + name.length - suffix_len, suffix, suffix_len)) { + name.length -= suffix_len; + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false)); + } + } + + // Find 1-3 channel nodes that contain a `Key:` node + ufbxi_node *channel_nodes[3] = { 0 }; + const char *channel_names[3] = { 0 }; + size_t num_channel_nodes = 0; + + if (ufbxi_find_child(node, ufbxi_Key) || ufbxi_find_child(node, ufbxi_Default)) { + // Channel has only a single curve + channel_nodes[0] = node; + channel_names[0] = name.data; + num_channel_nodes = 1; + } else { + // Channel is a compound of multiple curves + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + if (child->name != ufbxi_Channel) continue; + if (!ufbxi_find_child(child, ufbxi_Key) && !ufbxi_find_child(child, ufbxi_Default)) continue; + if (!ufbxi_get_val1(child, "C", (char**)&channel_names[num_channel_nodes])) continue; + channel_nodes[num_channel_nodes] = child; + if (++num_channel_nodes == 3) break; + } + } + + // Early return: No valid channels found, not an error + if (num_channel_nodes == 0) return 1; + + uint64_t value_fbx_id = 0; + ufbx_anim_value *value = ufbxi_push_synthetic_element(uc, &value_fbx_id, node, name.data, ufbx_anim_value, UFBX_ELEMENT_ANIM_VALUE); + + // Add a "virtual" connection between the animated property and the layer/target + ufbxi_check(ufbxi_connect_oo(uc, value_fbx_id, layer_fbx_id)); + ufbxi_check(ufbxi_connect_op(uc, value_fbx_id, target_fbx_id, name)); + + for (size_t i = 0; i < num_channel_nodes; i++) { + ufbxi_check(ufbxi_read_take_anim_channel(uc, channel_nodes[i], value_fbx_id, channel_names[i], &value->default_value.v[i])); + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_object(ufbxi_context *uc, ufbxi_node *node, uint64_t layer_fbx_id) +{ + // Takes are used only in pre-7000 FBX versions so objects are identified + // by their unique Type::Name pair that we use as unique IDs through the + // pooled interned string pointers. + const char *type_and_name = NULL; + ufbxi_check(ufbxi_get_val1(node, "c", (char**)&type_and_name)); + uint64_t target_fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name); + ufbxi_check(target_fbx_id); + + // Add all suitable Channels as animated properties + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + ufbx_string name; + if (child->name != ufbxi_Channel) continue; + if (!ufbxi_get_val1(child, "S", &name)) continue; + + ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, name)); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take(ufbxi_context *uc, ufbxi_node *node) +{ + ufbx_prop tmp_props[4]; + uint32_t num_props = 0; + memset(tmp_props, 0, sizeof(tmp_props)); + + int64_t start = 0, stop = 0; + if (ufbxi_find_val2(node, ufbxi_LocalTime, "LL", &start, &stop)) { + ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStart, start, UFBX_PROP_INTEGER); + ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStop, stop, UFBX_PROP_INTEGER); + } + if (ufbxi_find_val2(node, ufbxi_ReferenceTime, "LL", &start, &stop)) { + ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStart, start, UFBX_PROP_INTEGER); + ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStop, stop, UFBX_PROP_INTEGER); + } + + const char *name; + ufbxi_check(ufbxi_get_val1(node, "C", (char**)&name)); + + // Hack: For post-7000 files we are only interested in the animation times + // for fallback in case the information is missing in the stacks. + if (uc->version >= 7000) { + uint32_t hash = ufbxi_hash_ptr(name); + ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &name); + + if (entry) { + ufbx_anim_stack *stack = entry->stack; + if (stack->props.props.count == 0) { + stack->props.props.count = num_props; + stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(stack->props.props.data); + } + } + + return 1; + } + + uint64_t stack_fbx_id = 0, layer_fbx_id = 0; + + // Treat the Take as a post-7000 version animation stack and layer. + ufbx_anim_stack *stack = ufbxi_push_synthetic_element(uc, &stack_fbx_id, node, name, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); + ufbxi_check(stack); + + stack->props.props.count = num_props; + stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(stack->props.props.data); + + ufbx_anim_layer *layer = ufbxi_push_synthetic_element(uc, &layer_fbx_id, node, ufbxi_BaseLayer, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER); + ufbxi_check(layer); + + ufbxi_check(ufbxi_connect_oo(uc, layer_fbx_id, stack_fbx_id)); + + // Read all properties of objects included in the take + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + // TODO: Do some object types have another name? + if (child->name != ufbxi_Model) continue; + + ufbxi_check(ufbxi_read_take_object(uc, child, layer_fbx_id)); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_takes(ufbxi_context *uc) +{ + for (;;) { + ufbxi_node *node; + ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); + if (!node) break; + + if (node->name == ufbxi_Take) { + ufbxi_check(ufbxi_read_take(uc, node)); + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_settings(ufbxi_context *uc, ufbxi_node *node) +{ + if (uc->read_legacy_settings) return 1; + uc->read_legacy_settings = true; + + ufbx_prop tmp_props[2]; + uint32_t num_props = 0; + memset(tmp_props, 0, sizeof(tmp_props)); + + ufbxi_node *frame_rate = ufbxi_find_child_strcmp(node, "FrameRate"); + if (frame_rate) { + double fps = 0.0; + if (!ufbxi_get_val1(frame_rate, "D", &fps)) { + ufbx_string str; + if (ufbxi_get_val1(frame_rate, "S", &str)) { + char *end; + double val = ufbxi_parse_double(str.data, str.length, &end, uc->double_parse_flags); + if (end == str.data + str.length) { + fps = val; + } + } + } + if (fps > 0.0) { + ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_CustomFrameRate, (ufbx_real)fps, UFBX_PROP_NUMBER); + ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_TimeMode, UFBX_TIME_MODE_CUSTOM, UFBX_PROP_INTEGER); + } + } + + if (num_props > 0) { + ufbx_props *props = &uc->scene.settings.props; + size_t num_existing = props->props.count; + + size_t new_count = num_props + num_existing; + ufbx_prop *new_props = ufbxi_push(&uc->result, ufbx_prop, new_count); + ufbxi_check(new_props); + + memcpy(new_props, tmp_props, num_props * sizeof(ufbx_prop)); + if (num_existing > 0) { + memcpy(new_props + num_props, props->props.data, num_existing * sizeof(ufbx_prop)); + } + + ufbxi_check(ufbxi_sort_properties(uc, new_props, new_count)); + props->props.data = new_props; + props->props.count = new_count; + ufbxi_deduplicate_properties(&props->props); + + ufbxi_check(uc->scene.settings.props.props.data); + } + + return 1; +} + +ufbxi_noinline static ufbx_matrix ufbxi_unscaled_transform_to_matrix(const ufbx_transform *t) +{ + ufbx_transform transform = *t; + transform.scale.x = 1.0f; + transform.scale.y = 1.0f; + transform.scale.z = 1.0f; + return ufbx_transform_to_matrix(&transform); +} + +ufbxi_noinline static void ufbxi_setup_root_node(ufbxi_context *uc, ufbx_node *root) +{ + if (uc->opts.use_root_transform) { + root->local_transform = uc->opts.root_transform; + root->node_to_parent = ufbx_transform_to_matrix(&uc->opts.root_transform); + } else { + root->local_transform = ufbx_identity_transform; + root->node_to_parent = ufbx_identity_matrix; + } + root->is_root = true; +} + +static ufbxi_forceinline bool ufbxi_supports_version(uint32_t version) +{ + return version >= 3000 && version <= 7700; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_root(ufbxi_context *uc) +{ + // FBXHeaderExtension: Some metadata (optional) + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_FBXHeaderExtension)); + ufbxi_check(ufbxi_read_header_extension(uc)); + + // The ASCII exporter version is stored in top-level + if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Creator)); + if (uc->top_node) { + ufbxi_ignore(ufbxi_get_val1(uc->top_node, "S", &uc->scene.metadata.creator)); + } + } + + // Resolve the exporter before continuing + ufbxi_check(ufbxi_match_exporter(uc)); + if (uc->version < 7000) { + ufbxi_check(ufbxi_init_node_prop_names(uc)); + } + // Don't allow changing version from this point onwards + uc->ascii.found_version = true; + + // Document: Read root ID + if (uc->version >= 7000) { + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Documents)); + ufbxi_check(ufbxi_read_document(uc)); + } else { + // Pre-7000: Root node has a specific type-name pair "Model::Scene" + // (or reversed in binary). Use the interned name as ID as usual. + const char *root_name = uc->from_ascii ? "Model::Scene" : "Scene\x00\x01Model"; + root_name = ufbxi_push_string_imp(&uc->string_pool, root_name, 12, NULL, false, true); + ufbxi_check(root_name); + uc->root_id = ufbxi_synthetic_id_from_string(uc, root_name); + ufbxi_check(uc->root_id); + } + + // Add a nameless root node with the root ID + { + ufbxi_element_info root_info = { uc->root_id }; + root_info.name = ufbx_empty_string; + ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(root); + ufbxi_setup_root_node(uc, root); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); + } + + // Definitions: Object type counts and property templates (optional) + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Definitions)); + ufbxi_check(ufbxi_read_definitions(uc)); + + // Objects: Actual scene data + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Objects)); + if (!uc->sure_fbx) { + // If the file is a bit iffy about being a real FBX file reject it if + // even the objects are not found. + ufbxi_check_msg(uc->top_node, "Not an FBX file"); + } + if (uc->thread_pool.enabled) { + ufbxi_check(ufbxi_read_objects_threaded(uc)); + } else { + ufbxi_check(ufbxi_read_objects(uc)); + } + + // Connections: Relationships between nodes + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Connections)); + ufbxi_check(ufbxi_read_connections(uc)); + + // Takes: Pre-7000 animation data + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Takes)); + ufbxi_check(ufbxi_read_takes(uc)); + + // Check if there's a top-level GlobalSettings that we skimmed over + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_GlobalSettings)); + if (uc->top_node) { + ufbxi_check(ufbxi_read_global_settings(uc, uc->top_node)); + } + + // Version5: Pre-6000 settings + ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Version5)); + if (uc->top_node) { + ufbxi_node *settings = ufbxi_find_child_strcmp(uc->top_node, "Settings"); + if (settings) { + ufbxi_check(ufbxi_read_legacy_settings(uc, settings)); + } + } + + // Force parsing all the nodes by parsing a toplevel that cannot be found + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_parse_toplevel(uc, NULL)); + } + + return 1; +} + +typedef struct { + const char *prop_name; + ufbx_prop_type prop_type; + const char *node_name; + const char *node_fmt; +} ufbxi_legacy_prop; + +// Must be alphabetically sorted! +static const ufbxi_legacy_prop ufbxi_legacy_light_props[] = { + { ufbxi_CastLight, UFBX_PROP_BOOLEAN, ufbxi_CastLight, "L" }, + { ufbxi_CastShadows, UFBX_PROP_BOOLEAN, ufbxi_CastShadows, "L" }, + { ufbxi_Color, UFBX_PROP_COLOR, ufbxi_Color, "RRR" }, + { ufbxi_ConeAngle, UFBX_PROP_NUMBER, ufbxi_ConeAngle, "R" }, + { ufbxi_HotSpot, UFBX_PROP_NUMBER, ufbxi_HotSpot, "R" }, + { ufbxi_Intensity, UFBX_PROP_NUMBER, ufbxi_Intensity, "R" }, + { ufbxi_LightType, UFBX_PROP_INTEGER, ufbxi_LightType, "L" }, +}; + +// Must be alphabetically sorted! +static const ufbxi_legacy_prop ufbxi_legacy_camera_props[] = { + { ufbxi_ApertureMode, UFBX_PROP_INTEGER, ufbxi_ApertureMode, "L" }, + { ufbxi_AspectH, UFBX_PROP_NUMBER, ufbxi_AspectH, "R" }, + { ufbxi_AspectRatioMode, UFBX_PROP_INTEGER, "AspectType", "L" }, + { ufbxi_AspectW, UFBX_PROP_NUMBER, ufbxi_AspectW, "R" }, + { ufbxi_FieldOfView, UFBX_PROP_NUMBER, "Aperture", "R" }, + { ufbxi_FieldOfViewX, UFBX_PROP_NUMBER, "FieldOfViewXProperty", "R" }, + { ufbxi_FieldOfViewY, UFBX_PROP_NUMBER, "FieldOfViewYProperty", "R" }, + { ufbxi_FilmHeight, UFBX_PROP_NUMBER, "CameraAperture", "_R" }, + { ufbxi_FilmSqueezeRatio, UFBX_PROP_NUMBER, "SqueezeRatio", "R" }, + { ufbxi_FilmWidth, UFBX_PROP_NUMBER, "CameraAperture", "R_" }, + { ufbxi_FocalLength, UFBX_PROP_NUMBER, ufbxi_FocalLength, "R" }, +}; + +// Must be alphabetically sorted! +static const ufbxi_legacy_prop ufbxi_legacy_bone_props[] = { + { ufbxi_Size, UFBX_PROP_NUMBER, ufbxi_Size, "R" }, +}; + +// Must be alphabetically sorted! +static const ufbxi_legacy_prop ufbxi_legacy_material_props[] = { + { ufbxi_AmbientColor, UFBX_PROP_COLOR, "Ambient", "RRR" }, + { ufbxi_DiffuseColor, UFBX_PROP_COLOR, "Diffuse", "RRR" }, + { ufbxi_EmissiveColor, UFBX_PROP_COLOR, "Emissive", "RRR" }, + { ufbxi_ShadingModel, UFBX_PROP_COLOR, ufbxi_ShadingModel, "S" }, + { ufbxi_Shininess, UFBX_PROP_NUMBER, "Shininess", "R" }, + { ufbxi_SpecularColor, UFBX_PROP_COLOR, "Specular", "RRR" }, +}; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_prop(ufbxi_node *node, ufbx_prop *prop, const ufbxi_legacy_prop *legacy_prop) +{ + size_t value_ix = 0; + uint32_t flags = 0; + + const char *fmt = legacy_prop->node_fmt; + for (size_t fmt_ix = 0; fmt[fmt_ix]; fmt_ix++) { + char c = fmt[fmt_ix]; + switch (c) { + case 'L': + ufbx_assert(value_ix == 0); + if (!ufbxi_get_val_at(node, fmt_ix, 'L', &prop->value_int)) return 0; + prop->value_real = (ufbx_real)prop->value_int; + prop->value_real_arr[1] = 0.0f; + prop->value_real_arr[2] = 0.0f; + prop->value_real_arr[3] = 0.0f; + prop->value_str = ufbx_empty_string; + prop->value_blob = ufbx_empty_blob; + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT; + value_ix++; + break; + case 'R': + ufbx_assert(value_ix < 4); + if (!ufbxi_get_val_at(node, fmt_ix, 'R', &prop->value_real_arr[value_ix])) return 0; + if (value_ix == 0) { + prop->value_int = ufbxi_f64_to_i64(prop->value_real); + prop->value_real_arr[1] = 0.0f; + prop->value_real_arr[2] = 0.0f; + prop->value_real_arr[3] = 0.0f; + prop->value_str = ufbx_empty_string; + prop->value_blob = ufbx_empty_blob; + } + flags &= ~(uint32_t)(UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_VEC2|UFBX_PROP_FLAG_VALUE_VEC3|UFBX_PROP_FLAG_VALUE_VEC4); + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << value_ix; + value_ix++; + break; + case 'S': + ufbx_assert(value_ix == 0); + if (!ufbxi_get_val_at(node, fmt_ix, 'S', &prop->value_str)) return 0; + if (prop->value_str.length > 0) { + int found = ufbxi_get_val_at(node, fmt_ix, 'b', &prop->value_blob); + ufbxi_ignore(found); + ufbx_assert(found); + } else { + prop->value_blob = ufbx_empty_blob; + } + prop->value_real = 0.0f; + prop->value_real_arr[1] = 0.0f; + prop->value_real_arr[2] = 0.0f; + prop->value_real_arr[3] = 0.0f; + prop->value_int = 0; + flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR; + value_ix++; + break; + case '_': + break; + default: + ufbxi_unreachable("Unhandled legacy fmt"); + } + } + + prop->flags = (ufbx_prop_flags)flags; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_read_legacy_props(ufbxi_node *node, ufbx_prop *props, const ufbxi_legacy_prop *legacy_props, size_t num_legacy) +{ + size_t num_props = 0; + for (size_t legacy_ix = 0; legacy_ix < num_legacy; legacy_ix++) { + const ufbxi_legacy_prop *legacy_prop = &legacy_props[legacy_ix]; + ufbx_prop *prop = &props[num_props]; + + ufbxi_node *n = ufbxi_find_child_strcmp(node, legacy_prop->node_name); + if (!n) continue; + if (!ufbxi_read_legacy_prop(n, prop, legacy_prop)) continue; + + prop->name.data = legacy_prop->prop_name; + prop->name.length = strlen(legacy_prop->prop_name); + prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); + prop->flags = (ufbx_prop_flags)0; + prop->type = legacy_prop->prop_type; + num_props++; + } + + return num_props; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_material(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name) +{ + ufbx_material *ufbxi_restrict material = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_material, UFBX_ELEMENT_MATERIAL); + ufbxi_check(material); + + ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_material_props)]; + size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_material_props, ufbxi_arraycount(ufbxi_legacy_material_props)); + + material->shading_model_name = ufbx_empty_string; + material->props.props.count = num_props; + material->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(material->props.props.data); + + material->shader_prop_prefix = ufbx_empty_string; + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_link(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name) +{ + ufbx_skin_cluster *ufbxi_restrict cluster = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER); + ufbxi_check(cluster); + + // TODO: Merge with ufbxi_read_skin_cluster(), at least partially? + ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); + ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r'); + + if (indices && weights) { + ufbxi_check(indices->size == weights->size); + cluster->num_weights = indices->size; + cluster->vertices.data = (uint32_t*)indices->data; + cluster->weights.data = (ufbx_real*)weights->data; + cluster->vertices.count = cluster->num_weights; + cluster->weights.count = cluster->num_weights; + } + + ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r'); + ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r'); + if (transform && transform_link) { + ufbxi_check(transform->size >= 16); + ufbxi_check(transform_link->size >= 16); + + ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data); + ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_light(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_light *ufbxi_restrict light = ufbxi_push_element(uc, info, ufbx_light, UFBX_ELEMENT_LIGHT); + ufbxi_check(light); + + ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_light_props)]; + size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_light_props, ufbxi_arraycount(ufbxi_legacy_light_props)); + + light->props.props.count = num_props; + light->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(light->props.props.data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_camera(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_camera *ufbxi_restrict camera = ufbxi_push_element(uc, info, ufbx_camera, UFBX_ELEMENT_CAMERA); + ufbxi_check(camera); + + ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_camera_props)]; + size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_camera_props, ufbxi_arraycount(ufbxi_legacy_camera_props)); + + camera->props.props.count = num_props; + camera->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(camera->props.props.data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_limb_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + ufbx_bone *ufbxi_restrict bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE); + ufbxi_check(bone); + + ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_bone_props)]; + size_t num_props = 0; + + ufbxi_node *prop_node = ufbxi_find_child_strcmp(node, "Properties"); + if (prop_node) { + num_props = ufbxi_read_legacy_props(prop_node, tmp_props, ufbxi_legacy_bone_props, ufbxi_arraycount(ufbxi_legacy_bone_props)); + } + + bone->props.props.count = num_props; + bone->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); + ufbxi_check(bone->props.props.data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) +{ + // Only read polygon meshes, ignore eg. NURBS without error + ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); + ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex); + if (!node_vertices || !node_indices) return 1; + + ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH); + ufbxi_check(mesh); + + ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info)); + + ufbxi_patch_mesh_reals(mesh); + + if (uc->opts.ignore_geometry) return 1; + + ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); + ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i'); + ufbxi_check(vertices && indices); + ufbxi_check(vertices->size % 3 == 0); + + mesh->num_vertices = vertices->size / 3; + mesh->num_indices = indices->size; + + uint32_t *index_data = (uint32_t*)indices->data; + + // Duplicate `index_data` for modification if we retain DOM + if (uc->opts.retain_dom) { + index_data = ufbxi_push_copy(&uc->result, uint32_t, indices->size, index_data); + ufbxi_check(index_data); + } + + mesh->vertices.data = (ufbx_vec3*)vertices->data; + mesh->vertex_indices.data = index_data; + mesh->vertices.count = mesh->num_vertices; + mesh->vertex_indices.count = mesh->num_indices; + + mesh->vertex_position.exists = true; + mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data; + mesh->vertex_position.values.count = mesh->num_vertices; + mesh->vertex_position.indices.data = index_data; + mesh->vertex_position.indices.count = mesh->num_indices; + mesh->vertex_position.unique_per_vertex = true; + + // Check/make sure that the last index is negated (last of polygon) + if (mesh->num_indices > 0) { + if ((int32_t)index_data[mesh->num_indices - 1] >= 0) { + if (uc->opts.strict) ufbxi_fail("Non-negated last index"); + index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1]; + } + } + + ufbxi_check(ufbxi_process_indices(uc, mesh, index_data)); + + // Normals are either per-vertex or per-index in legacy FBX files? + // If the version is 5000 prefer per-vertex, otherwise per-index... + ufbxi_value_array *normals = ufbxi_find_array(node, ufbxi_Normals, 'r'); + if (normals) { + size_t num_normals = normals->size / 3; + bool per_vertex = num_normals == mesh->num_vertices; + bool per_index = num_normals == mesh->num_indices; + if (per_vertex && (!per_index || uc->version == 5000)) { + mesh->vertex_normal.exists = true; + mesh->vertex_normal.values.count = num_normals; + mesh->vertex_normal.indices.count = mesh->num_indices; + mesh->vertex_normal.unique_per_vertex = true; + mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data; + mesh->vertex_normal.indices.data = mesh->vertex_indices.data; + } else if (per_index) { + uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices); + mesh->vertex_normal.exists = true; + mesh->vertex_normal.values.count = num_normals; + mesh->vertex_normal.indices.count = mesh->num_indices; + mesh->vertex_normal.unique_per_vertex = false; + mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data; + mesh->vertex_normal.indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; + } + } + + // Optional UV values are stored pretty much like a modern vertex element + ufbxi_node *uv_info = ufbxi_find_child(node, ufbxi_GeometryUVInfo); + if (uv_info) { + ufbx_uv_set *set = ufbxi_push_zero(&uc->result, ufbx_uv_set, 1); + ufbxi_check(set); + set->index = 0; + set->name.data = ufbxi_empty_char; + ufbxi_check(ufbxi_read_vertex_element(uc, mesh, uv_info, (ufbx_vertex_attrib*)&set->vertex_uv, + ufbxi_TextureUV, ufbxi_TextureUVVerticeIndex, NULL, 'r', 2)); + + mesh->uv_sets.data = set; + mesh->uv_sets.count = 1; + mesh->vertex_uv = set->vertex_uv; + } + + // Material indices + { + const char *mapping = NULL; + ufbxi_check(ufbxi_find_val1(node, ufbxi_MaterialAssignation, "C", (char**)&mapping)); + if (mapping == ufbxi_ByPolygon) { + ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, node, ufbxi_Materials, 'i', mesh->num_faces)); + } else if (mapping == ufbxi_AllSame) { + ufbxi_value_array *arr = ufbxi_find_array(node, ufbxi_Materials, 'i'); + uint32_t material = 0; + if (arr && arr->size >= 1) { + material = ((uint32_t*)arr->data)[0]; + } + + mesh->face_material.count = mesh->num_faces; + if (material == 0) { + mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; + } else { + mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces); + ufbxi_check(mesh->face_material.data); + ufbxi_for_list(uint32_t, p_mat, mesh->face_material) { + *p_mat = material; + } + } + } + } + + uint64_t skin_fbx_id = 0; + ufbx_skin_deformer *skin = NULL; + + // Materials, Skin Clusters + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + if (child->name == ufbxi_Material) { + uint64_t fbx_id = 0; + ufbx_string type_and_name, type, name; + ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name)); + ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); + ufbxi_check(ufbxi_read_legacy_material(uc, child, &fbx_id, name.data)); + ufbxi_check(ufbxi_connect_oo(uc, fbx_id, info->fbx_id)); + } else if (child->name == ufbxi_Link) { + uint64_t fbx_id = 0; + ufbx_string type_and_name, type, name; + ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name)); + ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); + ufbxi_check(ufbxi_read_legacy_link(uc, child, &fbx_id, name.data)); + + uint64_t node_fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); + ufbxi_check(node_fbx_id); + ufbxi_check(ufbxi_connect_oo(uc, node_fbx_id, fbx_id)); + if (!skin) { + skin = ufbxi_push_synthetic_element(uc, &skin_fbx_id, NULL, info->name.data, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER); + ufbxi_check(skin); + ufbxi_check(ufbxi_connect_oo(uc, skin_fbx_id, info->fbx_id)); + } + ufbxi_check(ufbxi_connect_oo(uc, fbx_id, skin_fbx_id)); + } + } + + mesh->skinned_is_local = true; + mesh->skinned_position = mesh->vertex_position; + mesh->skinned_normal = mesh->vertex_normal; + + ufbxi_patch_mesh_reals(mesh); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_media(ufbxi_context *uc, ufbxi_node *node) +{ + ufbxi_node *videos = ufbxi_find_child(node, ufbxi_Video); + if (videos) { + ufbxi_for(ufbxi_node, child, videos->children, videos->num_children) { + ufbxi_element_info video_info = { 0 }; + ufbxi_check(ufbxi_get_val1(child, "S", &video_info.name)); + video_info.fbx_id = ufbxi_push_synthetic_id(uc); + video_info.dom_node = ufbxi_get_dom_node(uc, node); + + ufbxi_check(ufbxi_read_video(uc, child, &video_info)); + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_model(ufbxi_context *uc, ufbxi_node *node) +{ + ufbx_string type_and_name, type, name; + ufbxi_check(ufbxi_get_val1(node, "s", &type_and_name)); + ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); + + ufbxi_element_info info = { 0 }; + info.fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); + ufbxi_check(info.fbx_id); + info.name = name; + info.dom_node = ufbxi_get_dom_node(uc, node); + + ufbx_node *elem_node = ufbxi_push_element(uc, &info, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(elem_node); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id)); + + ufbxi_element_info attrib_info = { 0 }; + attrib_info.fbx_id = ufbxi_push_synthetic_id(uc); + attrib_info.name = name; + attrib_info.dom_node = info.dom_node; + + // If we make unused connections it doesn't matter.. + ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info.fbx_id)); + + const char *attrib_type = ufbxi_empty_char; + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Type, "C", (char**)&attrib_type)); + + bool has_attrib = true; + if (attrib_type == ufbxi_Light) { + ufbxi_check(ufbxi_read_legacy_light(uc, node, &attrib_info)); + } else if (attrib_type == ufbxi_Camera) { + ufbxi_check(ufbxi_read_legacy_camera(uc, node, &attrib_info)); + } else if (attrib_type == ufbxi_LimbNode) { + ufbxi_check(ufbxi_read_legacy_limb_node(uc, node, &attrib_info)); + } else if (ufbxi_find_child(node, ufbxi_Vertices)) { + ufbxi_check(ufbxi_read_legacy_mesh(uc, node, &attrib_info)); + } else { + has_attrib = false; + } + + // Mark the node as having an attribute so property connections can be forwarded + if (has_attrib) { + ufbxi_check(ufbxi_insert_fbx_attr(uc, info.fbx_id, attrib_info.fbx_id)); + } + + // Children are represented as an array of strings + ufbxi_value_array *children = ufbxi_find_array(node, ufbxi_Children, 's'); + if (children) { + ufbx_string *names = (ufbx_string*)children->data; + for (size_t i = 0; i < children->size; i++) { + uint64_t child_fbx_id = ufbxi_synthetic_id_from_string(uc, names[i].data); + ufbxi_check(child_fbx_id); + ufbxi_check(ufbxi_connect_oo(uc, child_fbx_id, info.fbx_id)); + } + } + + // Non-take animation channels + ufbxi_for(ufbxi_node, child, node->children, node->num_children) { + if (child->name == ufbxi_Channel) { + ufbx_string channel_name; + if (ufbxi_get_val1(child, "S", &channel_name)) { + if (uc->legacy_implicit_anim_layer_id == 0) { + // Defer creation so we won't be the first animation stack.. + uc->legacy_implicit_anim_layer_id = ufbxi_push_synthetic_id(uc); + } + ufbxi_check(ufbxi_read_take_prop_channel(uc, child, info.fbx_id, uc->legacy_implicit_anim_layer_id, channel_name)); + } + } + } + + return 1; +} + +// Read a pre-6000 FBX file where everything is stored at the root level +ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_legacy_root(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_init_node_prop_names(uc)); + + // Some legacy FBX files have an `Fbx_Root` node that could be used as the + // root node. However no other formats have root node with transforms so it + // might be better to leave it as-is and create an empty one. + { + ufbx_node *root = ufbxi_push_synthetic_element(uc, &uc->root_id, NULL, ufbxi_empty_char, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(root); + ufbxi_setup_root_node(uc, root); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); + } + + // NOTE: `ufbxi_read_header_extension()` is optional so use default KTime definition + uc->ktime_sec = 46186158000; + uc->ktime_sec_double = (double)uc->ktime_sec; + + for (;;) { + ufbxi_check(ufbxi_parse_legacy_toplevel(uc)); + if (!uc->top_node) break; + + ufbxi_node *node = uc->top_node; + if (node->name == ufbxi_FBXHeaderExtension) { + ufbxi_check(ufbxi_read_header_extension(uc)); + } else if (node->name == ufbxi_Media) { + ufbxi_check(ufbxi_read_legacy_media(uc, node)); + } else if (node->name == ufbxi_Takes) { + ufbxi_check(ufbxi_read_takes(uc)); + } else if (node->name == ufbxi_Model) { + ufbxi_check(ufbxi_read_legacy_model(uc, node)); + } else if (!strcmp(node->name, "Settings")) { + ufbxi_check(ufbxi_read_legacy_settings(uc, node)); + } + } + + if (uc->opts.retain_dom) { + ufbxi_check(ufbxi_retain_toplevel(uc, NULL)); + } + + // Create the implicit animation stack if necessary + if (uc->legacy_implicit_anim_layer_id) { + ufbxi_element_info layer_info = { 0 }; + layer_info.fbx_id = uc->legacy_implicit_anim_layer_id; + layer_info.name.data = "(internal)"; + layer_info.name.length = strlen(layer_info.name.data); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &layer_info.name, true)); + ufbx_anim_layer *layer = ufbxi_push_element(uc, &layer_info, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER); + ufbxi_check(layer); + + ufbxi_element_info stack_info = layer_info; + stack_info.fbx_id = ufbxi_push_synthetic_id(uc); + ufbx_anim_stack *stack = ufbxi_push_element(uc, &stack_info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); + ufbxi_check(stack); + + ufbxi_check(ufbxi_connect_oo(uc, layer_info.fbx_id, stack_info.fbx_id)); + } + + return 1; +} + +// Filename manipulation + +ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_trim_delimiters(ufbxi_context *uc, const char *data, size_t length) +{ + for (; length > 0; length--) { + char c = data[length - 1]; + bool is_separator = c == '/' || c == uc->opts.path_separator; + if (is_separator) { + length--; + break; + } + } + return length; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_init_file_paths(ufbxi_context *uc) +{ + if (uc->opts.filename.length > 0) { + uc->scene.metadata.filename = uc->opts.filename; + } else if (uc->opts.raw_filename.size > 0) { + uc->scene.metadata.filename.data = (const char*)uc->opts.raw_filename.data; + uc->scene.metadata.filename.length = uc->opts.raw_filename.size; + } + + if (uc->opts.raw_filename.size > 0) { + uc->scene.metadata.raw_filename = uc->opts.raw_filename; + } else if (uc->opts.filename.length > 0) { + uc->scene.metadata.raw_filename.data = uc->opts.filename.data; + uc->scene.metadata.raw_filename.size = uc->opts.filename.length; + } + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.filename, false)); + ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_filename, true)); + + uc->scene.metadata.relative_root.data = uc->scene.metadata.filename.data; + uc->scene.metadata.relative_root.length = ufbxi_trim_delimiters(uc, uc->scene.metadata.filename.data, uc->scene.metadata.filename.length); + + uc->scene.metadata.raw_relative_root.data = uc->scene.metadata.raw_filename.data; + uc->scene.metadata.raw_relative_root.size = ufbxi_trim_delimiters(uc, (const char*)uc->scene.metadata.raw_filename.data, uc->scene.metadata.raw_filename.size); + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.relative_root, false)); + ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_relative_root, true)); + + return 1; +} + +typedef union { + ufbx_string str; + ufbx_blob blob; +} ufbxi_strblob; + +static ufbxi_noinline void ufbxi_strblob_set(ufbxi_strblob *dst, const char *data, size_t length, bool raw) +{ + if (raw) { + dst->blob.data = data; + dst->blob.size = length; + } else { + dst->str.data = length == 0 ? ufbxi_empty_char : data; + dst->str.length = length; + } +} + +static ufbxi_forceinline const char *ufbxi_strblob_data(const ufbxi_strblob *strblob, bool raw) +{ + return raw ? (const char*)strblob->blob.data : strblob->str.data; +} + +static ufbxi_forceinline size_t ufbxi_strblob_length(const ufbxi_strblob *strblob, bool raw) +{ + return raw ? strblob->blob.size : strblob->str.length; +} + +ufbxi_nodiscard ufbxi_noinline static bool ufbxi_is_absolute_path(const char *path, size_t length) +{ + if (length > 0 && (path[0] == '/' || path[0] == '\\')) { + return true; + } else if (length > 2 && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { + return true; + } + return false; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_src, bool raw) +{ + const char *src = ufbxi_strblob_data(p_src, raw); + size_t src_length = ufbxi_strblob_length(p_src, raw); + + // Skip leading directory separators and early return if the relative path is empty + while (src_length > 0 && (src[0] == '/' || src[0] == '\\')) { + src++; + src_length--; + } + if (src_length == 0) { + ufbxi_strblob_set(p_dst, NULL, 0, raw); + return 1; + } + + const char *prefix_data; + size_t prefix_length; + if (raw) { + prefix_data = (const char*)uc->scene.metadata.raw_relative_root.data; + prefix_length = uc->scene.metadata.raw_relative_root.size; + } else { + prefix_data = (const char*)uc->scene.metadata.relative_root.data; + prefix_length = uc->scene.metadata.relative_root.length; + } + + // Retain absolute paths + if (ufbxi_is_absolute_path(src, src_length)) { + prefix_length = 0; + } + + // Undo directories from `prefix` for every `..` + while (prefix_length > 0 && src_length >= 3 && src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) { + size_t part_start = prefix_length; + while (part_start > 0 && !(prefix_data[part_start - 1] == '/' || prefix_data[part_start - 1] == '\\')) { + part_start--; + } + size_t part_len = prefix_length - part_start; + + if (part_len == 2 && prefix_data[part_start] == '.' && prefix_data[part_start + 1] == '.') { + // Prefix itself ends in `..`, cannot cancel out a leading `../` + break; + } + + // Eat the leading '/' before the part segment + prefix_length = part_start > 0 ? part_start - 1 : 0; + + if (part_len == 1 && prefix_data[part_start] == '.') { + // Single '.' -> remove and continue without cancelling out a leading `../` + continue; + } + + src += 3; + src_length -= 3; + } + + size_t result_cap = prefix_length + src_length + 1; + char *result = ufbxi_push(&uc->tmp_stack, char, result_cap); + ufbxi_check(result); + char *ptr = result; + + // Copy prefix and suffix converting separators in the process + if (prefix_length > 0) { + memcpy(ptr, prefix_data, prefix_length); + ptr[prefix_length] = uc->opts.path_separator; + ptr += prefix_length + 1; + } + for (size_t i = 0; i < src_length; i++) { + char c = src[i]; + if (c == '/' || c == '\\') { + c = uc->opts.path_separator; + } + *ptr++ = c; + } + + // Intern the string and pop the temporary buffer + ufbx_string dst = { result, ufbxi_to_size(ptr - result) }; + ufbx_assert(dst.length <= result_cap); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst, raw)); + ufbxi_pop(&uc->tmp_stack, char, result_cap, NULL); + + ufbxi_strblob_set(p_dst, dst.data, dst.length, raw); + + return 1; +} + +// Open file utility + +static ufbxi_noinline bool ufbxi_open_file(const ufbx_open_file_cb *cb, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_blob *original_filename, ufbxi_allocator *ator, ufbx_open_file_type type) +{ + if (!cb || !cb->fn) return false; + + ufbx_open_file_info info; // ufbxi_uninit + info.context = (uintptr_t)ator; + if (original_filename) { + info.original_filename = *original_filename; + } else { + info.original_filename.data = path; + info.original_filename.size = path_len; + } + info.type = type; + + return cb->fn(cb->user, stream, path, path_len, &info); +} + +#define ufbxi_patch_zero(dst, src) do { \ + ufbx_assert((dst) == 0 || (dst) == (src)); \ + (dst) = (src); \ + } while (0) + +static void ufbxi_update_vertex_first_index(ufbx_mesh *mesh) +{ + ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) { + *p_vx_ix = UFBX_NO_INDEX; + } + + uint32_t num_vertices = (uint32_t)mesh->num_vertices; + for (size_t ix = 0; ix < mesh->num_indices; ix++) { + uint32_t vx = mesh->vertex_indices.data[ix]; + if (vx < num_vertices && mesh->vertex_first_index.data[vx] == UFBX_NO_INDEX) { + mesh->vertex_first_index.data[vx] = (uint32_t)ix; + } + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh) +{ + if (mesh->vertices.count == 0) { + mesh->vertices = mesh->vertex_position.values; + } + if (mesh->vertex_indices.count == 0) { + mesh->vertex_indices = mesh->vertex_position.indices; + } + + ufbxi_patch_zero(mesh->num_vertices, mesh->vertices.count); + ufbxi_patch_zero(mesh->num_indices, mesh->vertex_indices.count); + ufbxi_patch_zero(mesh->num_faces, mesh->faces.count); + + if (mesh->num_triangles == 0 || mesh->max_face_triangles == 0) { + size_t num_triangles = 0; + size_t max_face_triangles = 0; + size_t num_bad_faces[3] = { 0 }; + ufbxi_nounroll ufbxi_for_list(ufbx_face, face, mesh->faces) { + if (face->num_indices >= 3) { + size_t tris = face->num_indices - 2; + num_triangles += tris; + max_face_triangles = ufbxi_max_sz(max_face_triangles, tris); + } else { + num_bad_faces[face->num_indices]++; + } + } + + ufbxi_patch_zero(mesh->num_triangles, num_triangles); + ufbxi_patch_zero(mesh->max_face_triangles, max_face_triangles); + ufbxi_patch_zero(mesh->num_empty_faces, num_bad_faces[0]); + ufbxi_patch_zero(mesh->num_point_faces, num_bad_faces[1]); + ufbxi_patch_zero(mesh->num_line_faces, num_bad_faces[2]); + } + + if (!mesh->skinned_position.exists) { + mesh->skinned_is_local = true; + mesh->skinned_position = mesh->vertex_position; + mesh->skinned_normal = mesh->vertex_normal; + } + + if (mesh->vertex_first_index.count == 0) { + mesh->vertex_first_index.count = mesh->num_vertices; + mesh->vertex_first_index.data = ufbxi_push(buf, uint32_t, mesh->num_vertices); + ufbxi_check_err(error, mesh->vertex_first_index.data); + ufbxi_update_vertex_first_index(mesh); + } + + if (mesh->uv_sets.count == 0 && mesh->vertex_uv.exists) { + ufbx_uv_set *uv_set = ufbxi_push_zero(buf, ufbx_uv_set, 1); + ufbxi_check_err(error, uv_set); + + uv_set->name.data = ufbxi_empty_char; + uv_set->vertex_uv = mesh->vertex_uv; + uv_set->vertex_tangent = mesh->vertex_tangent; + uv_set->vertex_bitangent = mesh->vertex_bitangent; + + mesh->uv_sets.data = uv_set; + mesh->uv_sets.count = 1; + } + + if (mesh->color_sets.count == 0 && mesh->vertex_color.exists) { + ufbx_color_set *color_set = ufbxi_push_zero(buf, ufbx_color_set, 1); + ufbxi_check_err(error, color_set); + + color_set->name.data = ufbxi_empty_char; + color_set->vertex_color = mesh->vertex_color; + + mesh->color_sets.data = color_set; + mesh->color_sets.count = 1; + } + + ufbxi_patch_mesh_reals(mesh); + + return 1; +} + +// -- .obj file + +#if UFBXI_FEATURE_FORMAT_OBJ + +static const uint8_t ufbxi_obj_attrib_stride[] = { + 3, 2, 3, 4, +}; + +ufbx_static_assert(obj_attrib_strides, ufbxi_arraycount(ufbxi_obj_attrib_stride) == UFBXI_OBJ_NUM_ATTRIBS_EXT); + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_props(ufbxi_context *uc, ufbx_prop_list *dst, size_t count) +{ + ufbx_prop_list props; // ufbxi_uninit + props.count = count; + props.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_props, ufbx_prop, count); + ufbxi_check(props.data); + + ufbxi_for_list(ufbx_prop, prop, props) { + prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); + if (prop->value_str.length == 0) { + prop->value_str.data = ufbxi_empty_char; + } + if (!prop->value_int) { + prop->value_int = ufbxi_f64_to_i64(prop->value_real); + } + if (prop->value_blob.size == 0 && prop->value_str.length > 0) { + prop->value_blob.data = prop->value_str.data; + prop->value_blob.size = prop->value_str.length; + } + } + + if (props.count > 1) { + ufbxi_check(ufbxi_sort_properties(uc, props.data, props.count)); + ufbxi_deduplicate_properties(&props); + } + + *dst = props; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_push_mesh(ufbxi_context *uc) +{ + ufbxi_obj_mesh *mesh = ufbxi_push_zero(&uc->obj.tmp_meshes, ufbxi_obj_mesh, 1); + ufbxi_check(mesh); + uc->obj.mesh = mesh; + + ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) { + mesh->vertex_range[i].min_ix = UINT64_MAX; + } + + const char *name = ""; + if (uc->opts.obj_split_groups && uc->obj.group.length > 0) { + name = uc->obj.group.data; + } else if (!uc->opts.obj_merge_objects && uc->obj.object.length > 0) { + name = uc->obj.object.data; + } else if (!uc->opts.obj_merge_groups && uc->obj.group.length > 0) { + name = uc->obj.group.data; + } + + mesh->fbx_node = ufbxi_push_synthetic_element(uc, &mesh->fbx_node_id, NULL, name, ufbx_node, UFBX_ELEMENT_NODE); + mesh->fbx_mesh = ufbxi_push_synthetic_element(uc, &mesh->fbx_mesh_id, NULL, name, ufbx_mesh, UFBX_ELEMENT_MESH); + ufbxi_check(mesh->fbx_node && mesh->fbx_mesh); + + mesh->fbx_mesh->vertex_position.unique_per_vertex = true; + + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &mesh->fbx_node->element_id)); + + uc->obj.face_material = UFBX_NO_INDEX; + uc->obj.face_group = 0; + uc->obj.face_group_dirty = true; + uc->obj.material_dirty = true; + + ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_mesh_id, mesh->fbx_node_id)); + ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_node_id, 0)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_mesh(ufbxi_context *uc) +{ + if (!uc->obj.mesh) return 1; + + size_t num_props = uc->obj.tmp_props.num_items; + ufbxi_check(ufbxi_obj_pop_props(uc, &uc->obj.mesh->fbx_mesh->props.props, num_props)); + + size_t num_groups = uc->obj.tmp_face_group_infos.num_items; + ufbx_face_group *groups = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group_infos, ufbx_face_group, num_groups); + ufbxi_check(groups); + + uc->obj.mesh->fbx_mesh->face_groups.data = groups; + uc->obj.mesh->fbx_mesh->face_groups.count = num_groups; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_init(ufbxi_context *uc) +{ + uc->from_ascii = true; + uc->obj.initialized = true; + + + ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) { + uc->obj.tmp_vertices[i].ator = &uc->ator_tmp; + uc->obj.tmp_indices[i].ator = &uc->ator_tmp; + } + uc->obj.tmp_color_valid.ator = &uc->ator_tmp; + uc->obj.tmp_faces.ator = &uc->ator_tmp; + uc->obj.tmp_face_material.ator = &uc->ator_tmp; + uc->obj.tmp_face_smoothing.ator = &uc->ator_tmp; + uc->obj.tmp_face_group.ator = &uc->ator_tmp; + uc->obj.tmp_face_group_infos.ator = &uc->ator_tmp; + uc->obj.tmp_meshes.ator = &uc->ator_tmp; + uc->obj.tmp_props.ator = &uc->ator_tmp; + + // .obj parsing does its own yield logic + uc->data_size += uc->yield_size; + + uc->obj.object.data = ufbxi_empty_char; + uc->obj.group.data = ufbxi_empty_char; + + ufbxi_map_init(&uc->obj.group_map, &uc->ator_tmp, ufbxi_map_cmp_const_char_ptr, NULL); + + // Add a nameless root node with the root ID + { + ufbxi_element_info root_info = { uc->root_id }; + root_info.name = ufbx_empty_string; + ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE); + ufbxi_check(root); + ufbxi_setup_root_node(uc, root); + ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); + } + + return 1; +} + +static ufbxi_noinline void ufbxi_obj_free(ufbxi_context *uc) +{ + if (!uc->obj.initialized) return; + + ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) { + ufbxi_buf_free(&uc->obj.tmp_vertices[i]); + ufbxi_buf_free(&uc->obj.tmp_indices[i]); + } + ufbxi_buf_free(&uc->obj.tmp_color_valid); + ufbxi_buf_free(&uc->obj.tmp_faces); + ufbxi_buf_free(&uc->obj.tmp_face_material); + ufbxi_buf_free(&uc->obj.tmp_face_smoothing); + ufbxi_buf_free(&uc->obj.tmp_face_group); + ufbxi_buf_free(&uc->obj.tmp_face_group_infos); + ufbxi_buf_free(&uc->obj.tmp_meshes); + ufbxi_buf_free(&uc->obj.tmp_props); + + ufbxi_map_free(&uc->obj.group_map); + + ufbxi_free(&uc->ator_tmp, ufbx_string, uc->obj.tokens, uc->obj.tokens_cap); + ufbxi_free(&uc->ator_tmp, ufbx_material*, uc->obj.tmp_materials, uc->obj.tmp_materials_cap); +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_read_line(ufbxi_context *uc) +{ + ufbxi_dev_assert(!uc->obj.eof); + + size_t offset = 0; + + for (;;) { + const char *begin = ufbxi_add_ptr(uc->data, offset); + const char *end = begin ? (const char*)memchr(begin, '\n', uc->data_size - offset) : NULL; + if (!end) { + if (uc->eof) { + offset = uc->data_size; + uc->obj.eof = true; + break; + } else { + size_t new_cap = ufbxi_max_sz(1, uc->data_size * 2); + ufbxi_check(ufbxi_refill(uc, new_cap, false)); + continue; + } + } + + offset += ufbxi_to_size(end - begin) + 1; + + // Handle line continuations + const char *esc = end; + if (esc > begin && esc[-1] == '\r') esc--; + if (esc > begin && esc[-1] == '\\') { + continue; + } + + break; + } + + size_t line_len = offset; + + uc->obj.line.data = uc->data; + uc->obj.line.length = line_len; + uc->data += line_len; + uc->data_size -= line_len; + + uc->obj.read_progress += line_len; + if (uc->obj.read_progress >= uc->progress_interval) { + ufbxi_check(ufbxi_report_progress(uc)); + uc->obj.read_progress %= uc->progress_interval; + } + + if (uc->obj.eof) { + char *new_data = ufbxi_push(&uc->tmp, char, line_len + 1); + ufbxi_check(new_data); + memcpy(new_data, uc->obj.line.data, line_len); + new_data[line_len] = '\n'; + uc->obj.line.data = new_data; + uc->obj.line.length++; + } + + return 1; +} + +static ufbxi_noinline ufbx_string ufbxi_obj_span_token(ufbxi_context *uc, size_t start_token, size_t end_token) +{ + ufbx_assert(start_token < uc->obj.num_tokens); + end_token = ufbxi_min_sz(end_token, uc->obj.num_tokens - 1); + + ufbx_assert(start_token <= end_token); + ufbx_string start = uc->obj.tokens[start_token]; + ufbx_string end = uc->obj.tokens[end_token]; + size_t num_between = ufbxi_to_size(end.data - start.data); + + ufbx_string result; + result.data = start.data; + result.length = num_between + end.length; + return result; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize(ufbxi_context *uc) +{ + const char *ptr = uc->obj.line.data, *end = ptr + uc->obj.line.length; + uc->obj.num_tokens = 0; + + for (;;) { + char c; + + // Skip whitespace + for (;;) { + c = *ptr; + if (c == ' ' || c == '\t' || c == '\r') { + ptr++; + continue; + } + + // Treat line continuations as whitespace + if (c == '\\') { + const char *p = ptr + 1; + if (*p == '\r') p++; + if (*p == '\n' && p < end - 1) { + ptr = p + 1; + continue; + } + } + + break; + } + + c = *ptr; + if (c == '\n') break; + if (c == '#' && uc->obj.num_tokens > 0) break; + + size_t index = uc->obj.num_tokens++; + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tokens, &uc->obj.tokens_cap, index + 1)); + + ufbx_string *tok = &uc->obj.tokens[index]; + tok->data = ptr; + + // Treat comment start as a single token + if (c == '#') { + ptr++; + tok->length = 1; + continue; + } + + for (;;) { + c = *++ptr; + + if (ufbxi_is_space(c)) { + break; + } + + if (c == '\\') { + const char *p = ptr + 1; + if (*p == '\r') p++; + if (*p == '\n' && p < end - 1) { + break; + } + } + } + + tok->length = ufbxi_to_size(ptr - tok->data); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize_line(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_obj_read_line(uc)); + ufbxi_check(ufbxi_obj_tokenize(uc)); + return 1; +} + +static ufbxi_noinline int ufbxi_obj_parse_vertex(ufbxi_context *uc, ufbxi_obj_attrib attrib, size_t offset) +{ + if (uc->opts.ignore_geometry) return 1; + + ufbxi_buf *dst = &uc->obj.tmp_vertices[attrib]; + size_t num_values = ufbxi_obj_attrib_stride[attrib]; + uc->obj.vertex_count[attrib]++; + + size_t read_values = num_values; + if (attrib == UFBXI_OBJ_ATTRIB_COLOR) { + if (offset + read_values > uc->obj.num_tokens) { + read_values = 3; + } + } + ufbxi_check(offset + read_values <= uc->obj.num_tokens); + + uint32_t parse_flags = uc->double_parse_flags; + ufbx_real *vals = ufbxi_push_fast(dst, ufbx_real, num_values); + ufbxi_check(vals); + for (size_t i = 0; i < read_values; i++) { + ufbx_string str = uc->obj.tokens[offset + i]; + char *end; // ufbxi_uninit + double val = ufbxi_parse_double(str.data, str.length, &end, parse_flags); + ufbxi_check(end == str.data + str.length); + vals[i] = (ufbx_real)val; + } + + if (read_values < num_values) { + ufbx_assert(read_values + 1 == num_values); + ufbx_assert(attrib == UFBXI_OBJ_ATTRIB_COLOR); + vals[read_values] = 1.0f; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_index(ufbxi_context *uc, ufbx_string *s, uint32_t attrib) +{ + const char *ptr = s->data, *end = ptr + s->length; + + bool negative = false; + if (*ptr == '-') { + negative = true; + ptr++; + } + + // As .obj indices are never zero we can detect missing indices + // by simply not writing to it. + uint64_t index = 0; + for (; ptr != end; ptr++) { + char c = *ptr; + if (c >= '0' && c <= '9') { + ufbxi_check(index < UINT64_MAX / 10 - 10); + index = index * 10 + (uint64_t)(c - '0'); + } else if (c == '/') { + ptr++; + break; + } + } + + if (negative) { + size_t count = uc->obj.vertex_count[attrib]; + index = index <= count ? count - index : UINT64_MAX; + } else { + // Corrects to zero based indices and wraps 0 to UINT64_MAX (missing) + index -= 1; + } + + ufbxi_obj_fast_indices *fast_indices = &uc->obj.fast_indices[attrib]; + if (fast_indices->num_left == 0) { + size_t num_push = 128; + uint64_t *dst = ufbxi_push(&uc->obj.tmp_indices[attrib], uint64_t, num_push); + ufbxi_check(dst); + uc->obj.fast_indices[attrib].indices = dst; + uc->obj.fast_indices[attrib].num_left = num_push; + } + + *fast_indices->indices++ = index; + fast_indices->num_left--; + + ufbxi_obj_mesh *mesh = uc->obj.mesh; + + if (index != UINT64_MAX) { + ufbxi_obj_index_range *range = &mesh->vertex_range[attrib]; + range->min_ix = ufbxi_min64(range->min_ix, index); + range->max_ix = ufbxi_max64(range->max_ix, index); + } + + s->data = ptr; + s->length = ufbxi_to_size(end - ptr); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_indices(ufbxi_context *uc, size_t token_begin, size_t num_tokens) +{ + bool flush_mesh = false; + if (uc->obj.object_dirty) { + if (!uc->opts.obj_merge_objects) { + flush_mesh = true; + } + uc->obj.object_dirty = false; + } + + if (uc->obj.group_dirty) { + if (((uc->obj.object.length == 0 || uc->opts.obj_merge_objects) && !uc->opts.obj_merge_groups) || uc->opts.obj_split_groups) { + flush_mesh = true; + } + uc->obj.group_dirty = false; + uc->obj.face_group_dirty = true; + } + + if (!uc->obj.mesh || flush_mesh) { + ufbxi_check(ufbxi_obj_flush_mesh(uc)); + ufbxi_check(ufbxi_obj_push_mesh(uc)); + } + ufbxi_obj_mesh *mesh = uc->obj.mesh; + + if (uc->obj.material_dirty) { + if (uc->obj.usemtl_fbx_id != 0) { + ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id); + ufbx_assert(entry); + if (mesh->usemtl_base == 0 || entry->user_id < mesh->usemtl_base) { + ufbxi_check(ufbxi_connect_oo(uc, uc->obj.usemtl_fbx_id, mesh->fbx_node_id)); + + uint32_t index = ++uc->obj.usemtl_index; + ufbxi_check(index < UINT32_MAX); + entry->user_id = index; + + if (mesh->usemtl_base == 0) { + mesh->usemtl_base = index; + } + uc->obj.face_material = index - mesh->usemtl_base; + } + uc->obj.face_material = entry->user_id - mesh->usemtl_base; + } else { + uc->obj.face_material = UFBX_NO_INDEX; + } + } + + // EARLY RETURN: Rest of the function should only be related to geometry! + if (uc->opts.ignore_geometry) return 1; + + if (num_tokens == 0 && !uc->opts.allow_empty_faces) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_EMPTY_FACE_REMOVED, "Empty face has been removed")); + return 1; + } + + if (uc->obj.face_group_dirty) { + ufbx_string name = ufbx_empty_string; + if (uc->obj.group.length > 0 && (uc->obj.object.length > 0 || uc->opts.obj_merge_groups) && !uc->opts.obj_split_groups) { + name = uc->obj.group; + } + + uint32_t hash = ufbxi_hash_ptr(name.data); + ufbxi_obj_group_entry *entry = ufbxi_map_find(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data); + if (!entry) { + entry = ufbxi_map_insert(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data); + ufbxi_check(entry); + entry->name = name.data; + entry->mesh_id = 0; + entry->local_id = 0; + } + + uint32_t mesh_id = mesh->fbx_mesh->element_id; + if (entry->mesh_id != mesh_id) { + uint32_t id = mesh->num_groups++; + entry->mesh_id = mesh_id; + entry->local_id = id; + + ufbx_face_group *group = ufbxi_push_zero(&uc->obj.tmp_face_group_infos, ufbx_face_group, 1); + ufbxi_check(group); + group->id = 0; + group->name = name; + } + + uc->obj.face_group = entry->local_id; + + if (!uc->obj.has_face_group) { + uc->obj.has_face_group = true; + ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_group, uint32_t, uc->obj.tmp_faces.num_items)); + } + + uc->obj.face_group_dirty = false; + } + + size_t num_indices = num_tokens; + ufbxi_check(UINT32_MAX - mesh->num_indices >= num_indices); + + ufbx_face *face = ufbxi_push_fast(&uc->obj.tmp_faces, ufbx_face, 1); + ufbxi_check(face); + + face->index_begin = (uint32_t)mesh->num_indices; + face->num_indices = (uint32_t)num_indices; + + mesh->num_faces++; + mesh->num_indices += num_indices; + + uint32_t *p_face_mat = ufbxi_push_fast(&uc->obj.tmp_face_material, uint32_t, 1); + ufbxi_check(p_face_mat); + *p_face_mat = uc->obj.face_material; + + if (uc->obj.has_face_smoothing) { + bool *p_face_smooth = ufbxi_push_fast(&uc->obj.tmp_face_smoothing, bool, 1); + ufbxi_check(p_face_smooth); + *p_face_smooth = uc->obj.face_smoothing; + } + + if (uc->obj.has_face_group) { + uint32_t *p_face_group = ufbxi_push_fast(&uc->obj.tmp_face_group, uint32_t, 1); + ufbxi_check(p_face_group); + *p_face_group = uc->obj.face_group; + } + + for (size_t ix = 0; ix < num_indices; ix++) { + ufbx_string tok = uc->obj.tokens[token_begin + ix]; + for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { + ufbxi_check(ufbxi_obj_parse_index(uc, &tok, attrib)); + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_multi_indices(ufbxi_context *uc, size_t window) +{ + for (size_t begin = 1; begin + window <= uc->obj.num_tokens; begin++) { + ufbxi_check(ufbxi_obj_parse_indices(uc, begin, window)); + } + return 1; +} + +static ufbxi_noinline uint32_t ufbxi_parse_hex(const char *digits, size_t length) +{ + uint32_t value = 0; + + for (size_t i = 0; i < length; i++) { + char c = digits[i]; + uint32_t v = 0; + if (c >= '0' && c <= '9') { + v = (uint32_t)(c - '0'); + } else if (c >= 'A' && c <= 'F') { + v = (uint32_t)(c - 'A') + 10; + } else if (c >= 'a' && c <= 'f') { + v = (uint32_t)(c - 'a') + 10; + } + value = (value << 4) | v; + } + + return value; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_comment(ufbxi_context *uc) +{ + if (uc->obj.num_tokens >= 3 && ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("MRGB"))) { + size_t num_color = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR]; + + // Pop standard vertex colors and replace them with MRGB colors + if (num_color > uc->obj.mrgb_vertex_count) { + size_t num_pop = num_color - uc->obj.mrgb_vertex_count; + ufbxi_pop(&uc->obj.tmp_color_valid, bool, num_pop, NULL); + ufbxi_pop(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pop * 4, NULL); + uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] -= num_pop; + } + + ufbx_string mrgb = uc->obj.tokens[2]; + for (size_t i = 0; i + 8 <= mrgb.length; i += 8) { + ufbx_real *p_rgba = ufbxi_push(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, 4); + bool *p_valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1); + ufbxi_check(p_rgba && p_valid); + *p_valid = true; + + uint32_t hex = ufbxi_parse_hex(mrgb.data + i, 8); + p_rgba[0] = (ufbx_real)((hex >> 16u) & 0xff) / 255.0f; + p_rgba[1] = (ufbx_real)((hex >> 8u) & 0xff) / 255.0f; + p_rgba[2] = (ufbx_real)((hex >> 0u) & 0xff) / 255.0f; + p_rgba[3] = (ufbx_real)((hex >> 24u) & 0xff) / 255.0f; + } + + uc->obj.has_vertex_color = true; + } + + if (!uc->opts.disable_quirks) { + if (ufbxi_match(&uc->obj.line, "\\s*#\\s*File exported by ZBrush.*")) { + if (!uc->obj.mesh) { + uc->opts.obj_merge_groups = true; + } + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_material(ufbxi_context *uc) +{ + uc->obj.material_dirty = true; + + // Allow empty `usemtl` lines to specify "no material". + if (uc->obj.num_tokens < 2) { + uc->obj.usemtl_fbx_id = 0; + return 1; + } + + ufbx_string name = ufbxi_obj_span_token(uc, 1, SIZE_MAX); + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false)); + + uint64_t fbx_id = ufbxi_synthetic_id_from_string(uc, name.data); + ufbxi_check(fbx_id); + + ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id); + + uc->obj.usemtl_fbx_id = fbx_id; + + if (!entry) { + ufbxi_element_info info = { 0 }; + info.fbx_id = fbx_id; + info.name = name; + + ufbx_material *material = ufbxi_push_element(uc, &info, ufbx_material, UFBX_ELEMENT_MATERIAL); + ufbxi_check(material); + + material->shader_type = UFBX_SHADER_WAVEFRONT_MTL; + material->shading_model_name.data = ufbxi_empty_char; + material->shader_prop_prefix.data = ufbxi_empty_char; + + size_t id = material->element_id; + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tmp_materials, &uc->obj.tmp_materials_cap, id + 1)); + uc->obj.tmp_materials[id] = material; + } + + return 1; +} + +#define ufbxi_obj_cmd1(a) ((uint32_t)(a)<<24u) +#define ufbxi_obj_cmd2(a,b) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16) +#define ufbxi_obj_cmd3(a,b,c) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u) + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_vertices(ufbxi_context *uc, ufbx_real_list *dst, uint32_t attrib, uint64_t min_index) +{ + size_t stride = ufbxi_obj_attrib_stride[attrib]; + ufbxi_check(min_index < uc->obj.tmp_vertices[attrib].num_items / stride); + + size_t count = uc->obj.tmp_vertices[attrib].num_items - (size_t)min_index * stride; + ufbx_real *data = ufbxi_push(&uc->result, ufbx_real, count + 4); + ufbxi_check(data); + + data[0] = 0.0f; + data[1] = 0.0f; + data[2] = 0.0f; + data[3] = 0.0f; + data += 4; + + ufbxi_pop(&uc->obj.tmp_vertices[attrib], ufbx_real, count, data); + + dst->data = data; + dst->count = count; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_setup_attrib(ufbxi_context *uc, ufbxi_obj_mesh *mesh, uint64_t *tmp_indices, + ufbx_vertex_attrib *dst, const ufbx_real_list *p_data, uint32_t attrib, bool non_disjoint, bool required) +{ + ufbx_real_list data = *p_data; + + size_t num_indices = mesh->num_indices; + size_t stride = ufbxi_obj_attrib_stride[attrib]; + size_t num_values = data.count / stride; + + uint64_t mesh_min_ix = mesh->vertex_range[attrib].min_ix; + if (num_indices == 0 || num_values == 0 || mesh_min_ix == UINT64_MAX) { + ufbxi_check(num_indices == 0 || !required); + + // Pop indices without copying if the attribute is not used + ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, NULL); + return 1; + } + + uint64_t min_index = non_disjoint ? 0 : mesh_min_ix; + + ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, tmp_indices); + + uint32_t *dst_indices = ufbxi_push(&uc->result, uint32_t, num_indices); + ufbxi_check(dst_indices); + + dst->exists = true; + + dst->values.data = data.data; + dst->values.count = num_values; + + dst->indices.data = dst_indices; + dst->indices.count = num_indices; + + ufbxi_nounroll for (size_t i = 0; i < num_indices; i++) { + uint64_t ix = tmp_indices[i]; + if (ix != UINT64_MAX) { + ix -= min_index; + ufbxi_check(ix < UINT32_MAX); + } + if (ix < num_values) { + dst_indices[i] = (uint32_t)ix; + } else { + ufbxi_check(ufbxi_fix_index(uc, &dst_indices[i], (uint32_t)ix, num_values)); + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pad_colors(ufbxi_context *uc, size_t num_vertices) +{ + if (uc->opts.ignore_geometry) return 1; + + size_t num_colors = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR]; + if (num_vertices > num_colors) { + size_t num_pad = num_vertices - num_colors; + ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pad * 4)); + ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_color_valid, bool, num_pad)); + uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] += num_pad; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_meshes(ufbxi_context *uc) +{ + size_t num_meshes = uc->obj.tmp_meshes.num_items; + ufbxi_obj_mesh *meshes = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_meshes, ufbxi_obj_mesh, num_meshes); + ufbxi_check(meshes); + + if (uc->obj.has_vertex_color) { + ufbxi_check(ufbxi_obj_pad_colors(uc, uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION])); + } + + // Pop unused fast indices + for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) { + ufbxi_pop(&uc->obj.tmp_indices[i], uint64_t, uc->obj.fast_indices[i].num_left, NULL); + } + + // Check if the file has disjoint vertices + bool non_disjoint[UFBXI_OBJ_NUM_ATTRIBS] = { 0 }; + uint64_t next_min[UFBXI_OBJ_NUM_ATTRIBS] = { 0 }; + ufbx_real_list vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT] = { 0 }; + bool *color_valid = NULL; + + size_t max_indices = 0; + + for (size_t i = 0; i < num_meshes; i++) { + ufbxi_obj_mesh *mesh = &meshes[i]; + max_indices = ufbxi_max_sz(max_indices, mesh->num_indices); + ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { + ufbxi_obj_index_range range = mesh->vertex_range[attrib]; + if (range.min_ix > range.max_ix) continue; + if (range.min_ix < next_min[attrib]) { + non_disjoint[attrib] = true; + } + next_min[attrib] = range.max_ix + 1; + } + } + + uint64_t *tmp_indices = ufbxi_push(&uc->tmp, uint64_t, max_indices); + ufbxi_check(tmp_indices); + + ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { + if (!non_disjoint[attrib]) continue; + ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, 0)); + } + if (uc->obj.has_vertex_color && non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) { + ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, 0)); + color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4); + ufbxi_check(color_valid); + } + + for (size_t i = num_meshes; i > 0; i--) { + ufbxi_obj_mesh *mesh = &meshes[i - 1]; + + ufbx_mesh *fbx_mesh = mesh->fbx_mesh; + + size_t num_faces = mesh->num_faces; + + if (!uc->opts.ignore_geometry) { + ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { + if (non_disjoint[attrib]) continue; + uint64_t min_ix = mesh->vertex_range[attrib].min_ix; + if (min_ix < UINT64_MAX) { + ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, min_ix)); + } + } + if (uc->obj.has_vertex_color && !non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) { + uint64_t min_ix = mesh->vertex_range[UFBXI_OBJ_ATTRIB_POSITION].min_ix; + ufbxi_check(min_ix < UINT64_MAX); + ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, min_ix)); + color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4); + ufbxi_check(color_valid); + } + + fbx_mesh->faces.count = num_faces; + fbx_mesh->face_material.count = num_faces; + + fbx_mesh->faces.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_faces, ufbx_face, num_faces); + fbx_mesh->face_material.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_material, uint32_t, num_faces); + + ufbxi_check(fbx_mesh->faces.data); + ufbxi_check(fbx_mesh->face_material.data); + + if (uc->obj.has_face_smoothing) { + fbx_mesh->face_smoothing.count = num_faces; + fbx_mesh->face_smoothing.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_smoothing, bool, num_faces); + ufbxi_check(fbx_mesh->face_smoothing.data); + } + + if (uc->obj.has_face_group) { + if (mesh->num_groups > 1) { + fbx_mesh->face_group.count = num_faces; + fbx_mesh->face_group.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group, uint32_t, num_faces); + ufbxi_check(fbx_mesh->face_group.data); + } else { + ufbxi_pop(&uc->obj.tmp_face_group, uint32_t, num_faces, NULL); + } + } + + ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_position, + &vertices[UFBXI_OBJ_ATTRIB_POSITION], UFBXI_OBJ_ATTRIB_POSITION, non_disjoint[UFBXI_OBJ_ATTRIB_POSITION], true)); + + ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_uv, + &vertices[UFBXI_OBJ_ATTRIB_UV], UFBXI_OBJ_ATTRIB_UV, non_disjoint[UFBXI_OBJ_ATTRIB_UV], false)); + + ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_normal, + &vertices[UFBXI_OBJ_ATTRIB_NORMAL], UFBXI_OBJ_ATTRIB_NORMAL, non_disjoint[UFBXI_OBJ_ATTRIB_NORMAL], false)); + + if (uc->obj.has_vertex_color) { + ufbx_assert(color_valid); + bool has_color = false; + bool all_valid = true; + size_t max_index = fbx_mesh->vertex_position.values.count; + ufbxi_for_list(uint32_t, p_ix, fbx_mesh->vertex_position.indices) { + if (*p_ix < max_index) { + if (color_valid[*p_ix]) { + has_color = true; + } else { + all_valid = false; + } + } + } + + if (has_color) { + fbx_mesh->vertex_color.exists = true; + fbx_mesh->vertex_color.values.data = (ufbx_vec4*)vertices[UFBXI_OBJ_ATTRIB_COLOR].data; + fbx_mesh->vertex_color.values.count = vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4; + fbx_mesh->vertex_color.indices = fbx_mesh->vertex_position.indices; + fbx_mesh->vertex_color.unique_per_vertex = true; + + if (!all_valid) { + uint32_t *indices = fbx_mesh->vertex_color.indices.data; + indices = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, indices); + ufbxi_check(indices); + + size_t num_values = fbx_mesh->vertex_color.values.count; + ufbxi_for(uint32_t, p_ix, indices, mesh->num_indices) { + if (*p_ix >= num_values || !color_valid[*p_ix]) { + ufbxi_check(ufbxi_fix_index(uc, p_ix, *p_ix, num_values)); + } + } + + fbx_mesh->vertex_color.indices.data = indices; + } + } + } + } + + ufbxi_check(ufbxi_finalize_mesh(&uc->result, &uc->error, fbx_mesh)); + + if (uc->retain_mesh_parts) { + fbx_mesh->face_group_parts.count = mesh->num_groups; + fbx_mesh->face_group_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, mesh->num_groups); + ufbxi_check(fbx_mesh->face_group_parts.data); + } + + if (mesh->num_groups > 1) { + ufbxi_check(ufbxi_update_face_groups(&uc->result, &uc->error, fbx_mesh, false)); + } else if (mesh->num_groups == 1) { + fbx_mesh->face_group.data = (uint32_t*)ufbxi_sentinel_index_zero; + fbx_mesh->face_group.count = num_faces; + // NOTE: Consecutive and zero indices are always allocated so we can skip doing it here, + // see HACK(consecutiv-faces).. + if (fbx_mesh->face_group_parts.count > 0) { + ufbx_mesh_part *part = &fbx_mesh->face_group_parts.data[0]; + part->num_faces = fbx_mesh->num_faces; + part->num_faces = num_faces; + part->num_empty_faces = fbx_mesh->num_empty_faces; + part->num_point_faces = fbx_mesh->num_point_faces; + part->num_line_faces = fbx_mesh->num_line_faces; + part->num_triangles = fbx_mesh->num_triangles; + part->face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; + part->face_indices.count = num_faces; + } + } + + // HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero + // index buffer for face materials.. + uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, num_faces); + uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, num_faces); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_file(ufbxi_context *uc) +{ + while (!uc->obj.eof) { + ufbxi_check(ufbxi_obj_tokenize_line(uc)); + size_t num_tokens = uc->obj.num_tokens; + if (num_tokens == 0) continue; + + ufbx_string cmd = uc->obj.tokens[0]; + uint32_t key = ufbxi_get_name_key(cmd.data, cmd.length); + if (key == ufbxi_obj_cmd1('v')) { + ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_POSITION, 1)); + if (num_tokens >= 7) { + size_t num_vertices = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION]; + uc->obj.has_vertex_color = true; + ufbxi_check(ufbxi_obj_pad_colors(uc, num_vertices - 1)); + if (uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] < num_vertices) { + ufbx_assert(uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] == num_vertices - 1); + ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_COLOR, 4)); + bool *valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1); + ufbxi_check(valid); + *valid = true; + } + } + } else if (key == ufbxi_obj_cmd2('v','t')) { + ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_UV, 1)); + } else if (key == ufbxi_obj_cmd2('v','n')) { + ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_NORMAL, 1)); + } else if (key == ufbxi_obj_cmd1('f')) { + ufbxi_check(ufbxi_obj_parse_indices(uc, 1, uc->obj.num_tokens - 1)); + } else if (key == ufbxi_obj_cmd1('p')) { + ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 1)); + } else if (key == ufbxi_obj_cmd1('l')) { + ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 2)); + } else if (key == ufbxi_obj_cmd1('s')) { + if (num_tokens >= 2) { + uc->obj.has_face_smoothing = true; + uc->obj.face_smoothing = !ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("off")); + + // Fill in previously missed face smoothing data + if (uc->obj.tmp_face_smoothing.num_items == 0 && uc->obj.tmp_faces.num_items > 0) { + ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_smoothing, bool, uc->obj.tmp_faces.num_items)); + } + } + } else if (key == ufbxi_obj_cmd1('o')) { + if (num_tokens >= 2) { + uc->obj.object = ufbxi_obj_span_token(uc, 1, SIZE_MAX); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.object, false)); + uc->obj.object_dirty = true; + } + } else if (key == ufbxi_obj_cmd1('g')) { + if (num_tokens >= 2) { + uc->obj.group = ufbxi_obj_span_token(uc, 1, SIZE_MAX); + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.group, false)); + uc->obj.group_dirty = true; + } else { + uc->obj.group = ufbx_empty_string; + uc->obj.group_dirty = true; + } + } else if (key == ufbxi_obj_cmd1('#')) { + ufbxi_check(ufbxi_obj_parse_comment(uc)); + } else if (ufbxi_str_equal(cmd, ufbxi_str_c("mtllib"))) { + ufbxi_check(uc->obj.num_tokens >= 2); + ufbx_string lib = ufbxi_obj_span_token(uc, 1, SIZE_MAX); + lib.data = ufbxi_push_copy(&uc->tmp, char, lib.length + 1, lib.data); + ufbxi_check(lib.data); + uc->obj.mtllib_relative_path.data = lib.data; + uc->obj.mtllib_relative_path.size = lib.length; + } else if (ufbxi_str_equal(cmd, ufbxi_str_c("usemtl"))) { + ufbxi_check(ufbxi_obj_parse_material(uc)); + } else if (!uc->opts.disable_quirks && key == 0) { + // ZBrush exporter seems to end the files with '\0', sometimes.. + } else { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, "Unknown .obj directive, skipped line")); + } + } + + ufbxi_check(ufbxi_obj_flush_mesh(uc)); + ufbxi_check(ufbxi_obj_pop_meshes(uc)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_material(ufbxi_context *uc) +{ + if (uc->obj.usemtl_fbx_id == 0) return 1; + + ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id); + ufbx_assert(entry); + ufbx_material *material = uc->obj.tmp_materials[entry->element_id]; + + size_t num_props = uc->obj.tmp_props.num_items; + ufbxi_check(ufbxi_obj_pop_props(uc, &material->props.props, num_props)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_prop(ufbxi_context *uc, ufbx_string name, size_t start, bool include_rest, size_t *p_next) +{ + if (start >= uc->obj.num_tokens) { + if (p_next) { + *p_next = start; + } + return 1; + } + + ufbx_prop *prop = ufbxi_push_zero(&uc->obj.tmp_props, ufbx_prop, 1); + ufbxi_check(prop); + prop->name = name; + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->name, false)); + + uint32_t flags = UFBX_PROP_FLAG_VALUE_STR; + + size_t num_reals = 0; + for (; num_reals < 4; num_reals++) { + if (start + num_reals >= uc->obj.num_tokens) break; + ufbx_string tok = uc->obj.tokens[start + num_reals]; + + char *end; // ufbxi_uninit + double val = ufbxi_parse_double(tok.data, tok.length, &end, uc->double_parse_flags); + if (end != tok.data + tok.length) break; + + prop->value_real_arr[num_reals] = (ufbx_real)val; + if (num_reals == 0) { + prop->value_int = ufbxi_f64_to_i64(val); + flags |= UFBX_PROP_FLAG_VALUE_INT; + } + } + + size_t num_args = 0; + if (!include_rest) { + for (; start + num_args < uc->obj.num_tokens - 1; num_args++) { + if (ufbxi_match(&uc->obj.tokens[start + num_args], "-[A-Za-z][\\-A-Za-z0-9_]*")) break; + } + } + + if (num_args > 0 || include_rest) { + ufbx_string span = ufbxi_obj_span_token(uc, start, include_rest ? SIZE_MAX : start + num_args - 1); + prop->value_str = span; + prop->value_blob.data = span.data; + prop->value_blob.size = span.length; + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->value_str, false)); + ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &prop->value_blob, true)); + } else { + prop->value_str.data = ufbxi_empty_char; + } + + if (num_reals > 0) { + flags = (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (num_reals - 1); + } else { + if (!strcmp(prop->value_str.data, "on")) { + prop->value_int = 1; + prop->value_real = 1.0f; + flags |= UFBX_PROP_FLAG_VALUE_INT; + } else if (!strcmp(prop->value_str.data, "off")) { + prop->value_int = 0; + prop->value_real = 0.0f; + flags |= UFBX_PROP_FLAG_VALUE_INT; + } + } + + prop->flags = (ufbx_prop_flags)flags; + + if (p_next) { + *p_next = start + num_args; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl_map(ufbxi_context *uc, size_t prefix_len) +{ + if (uc->obj.num_tokens < 2) return 1; + + size_t num_props = 1; + ufbxi_check(ufbxi_obj_parse_prop(uc, ufbxi_str_c("obj|args"), 1, true, NULL)); + + size_t start = 1; + for (; start + 1 < uc->obj.num_tokens; ) { + ufbx_string tok = uc->obj.tokens[start]; + if (ufbxi_match(&tok, "-[A-Za-z][\\-A-Za-z0-9_]*")) { + tok.data += 1; + tok.length -= 1; + ufbxi_check(ufbxi_obj_parse_prop(uc, tok, start + 1, false, &start)); + num_props++; + } else { + break; + } + } + + ufbx_string tex_str = ufbxi_obj_span_token(uc, start, SIZE_MAX); + ufbx_blob tex_raw = { tex_str.data, tex_str.length }; + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tex_str, false)); + ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &tex_raw, true)); + + uint64_t fbx_id = 0; + ufbx_texture *texture = ufbxi_push_synthetic_element(uc, &fbx_id, NULL, "", ufbx_texture, UFBX_ELEMENT_TEXTURE); + ufbxi_check(texture); + + texture->filename.data = ufbxi_empty_char; + texture->absolute_filename.data = ufbxi_empty_char; + texture->uv_set.data = ufbxi_empty_char; + + texture->relative_filename = tex_str; + texture->raw_relative_filename = tex_raw; + + ufbxi_check(ufbxi_obj_pop_props(uc, &texture->props.props, num_props)); + + ufbx_string prop = uc->obj.tokens[0]; + ufbx_assert(prop.length >= prefix_len); + prop.data += prefix_len; + prop.length -= prefix_len; + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop, false)); + + if (uc->obj.usemtl_fbx_id != 0) { + ufbxi_check(ufbxi_connect_op(uc, fbx_id, uc->obj.usemtl_fbx_id, prop)); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl(ufbxi_context *uc) +{ + uc->obj.mesh = NULL; + uc->obj.usemtl_fbx_id = 0; + + while (!uc->obj.eof) { + ufbxi_check(ufbxi_obj_tokenize_line(uc)); + size_t num_tokens = uc->obj.num_tokens; + if (num_tokens == 0) continue; + + ufbx_string cmd = uc->obj.tokens[0]; + if (ufbxi_str_equal(cmd, ufbxi_str_c("newmtl"))) { + // HACK: Reuse mesh material parsing, but don't allow for empty material name + ufbxi_check(uc->obj.num_tokens >= 2); + ufbxi_check(ufbxi_obj_flush_material(uc)); + ufbxi_check(ufbxi_obj_parse_material(uc)); + } else if (cmd.length > 4 && !memcmp(cmd.data, "map_", 4)) { + ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 4)); + } else if (cmd.length == 4 && (!memcmp(cmd.data, "bump", 4) || !memcmp(cmd.data, "disp", 4) || !memcmp(cmd.data, "norm", 4))) { + ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 0)); + } else if (cmd.length == 1 && cmd.data[0] == '#') { + // Implement .mtl magic comment handling here if necessary + } else { + ufbxi_check(ufbxi_obj_parse_prop(uc, uc->obj.tokens[0], 1, true, NULL)); + } + } + + ufbxi_check(ufbxi_obj_flush_material(uc)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load_mtl(ufbxi_context *uc) +{ + // HACK: Reset everything and switch to loading the .mtl file globally + if (uc->close_fn) { + uc->close_fn(uc->read_user); + } + + uc->read_fn = NULL; + uc->close_fn = NULL; + uc->read_user = NULL; + uc->data_begin = NULL; + uc->data = NULL; + uc->data_size = 0; + uc->yield_size = 0; + uc->eof = false; + uc->obj.eof = false; + + if (uc->opts.obj_mtl_data.size > 0) { + uc->data_begin = uc->data = (const char*)uc->opts.obj_mtl_data.data; + uc->data_size = uc->opts.obj_mtl_data.size; + ufbxi_check(ufbxi_obj_parse_mtl(uc)); + return 1; + } + + ufbx_stream stream = { 0 }; + bool has_stream = false; + bool needs_stream = false; + ufbx_blob stream_path = { 0 }; + + if (uc->opts.open_file_cb.fn) { + if (uc->opts.obj_mtl_path.length > 0) { + has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->opts.obj_mtl_path.data, uc->opts.obj_mtl_path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); + stream_path.data = uc->opts.obj_mtl_path.data; + stream_path.size = uc->opts.obj_mtl_path.length; + needs_stream = true; + if (!has_stream) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", uc->opts.obj_mtl_path.data)); + } + } + + if (!has_stream && uc->opts.load_external_files && uc->obj.mtllib_relative_path.size > 0) { + ufbx_blob dst; // ufbxi_uninit + ufbxi_check(ufbxi_resolve_relative_filename(uc, (ufbxi_strblob*)&dst, (const ufbxi_strblob*)&uc->obj.mtllib_relative_path, true)); + has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, (const char*)dst.data, dst.size, &uc->obj.mtllib_relative_path, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); + stream_path = uc->obj.mtllib_relative_path; + needs_stream = true; + if (!has_stream) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", dst.data)); + } + } + + ufbx_string path = uc->scene.metadata.filename; + if (!has_stream && uc->opts.load_external_files && uc->opts.obj_search_mtl_by_filename && path.length > 4) { + ufbx_string ext = { path.data + path.length - 4, 4 }; + if (ufbxi_match(&ext, "\\c.obj")) { + ufbxi_analysis_assert(path.length < SIZE_MAX - 1); + char *copy = ufbxi_push_copy(&uc->tmp, char, path.length + 1, path.data); + ufbxi_check(copy); + copy[path.length - 3] = copy[path.length - 3] == 'O' ? 'M' : 'm'; + copy[path.length - 2] = copy[path.length - 2] == 'B' ? 'T' : 't'; + copy[path.length - 1] = copy[path.length - 1] == 'J' ? 'L' : 'l'; + has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, copy, path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); + if (has_stream) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_IMPLICIT_MTL, "Opened .mtl file derived from .obj filename: %s", copy)); + } + } + } + } + + if (has_stream) { + // Adopt `stream` to ufbx read callbacks + uc->read_fn = stream.read_fn; + uc->close_fn = stream.close_fn; + uc->read_user = stream.user; + + int ok = ufbxi_obj_parse_mtl(uc); + + if (uc->close_fn) { + uc->close_fn(uc->read_user); + } + uc->read_fn = NULL; + uc->close_fn = NULL; + uc->read_user = NULL; + + ufbxi_check(ok); + } else if (needs_stream && !uc->opts.ignore_missing_external_files) { + ufbxi_set_err_info(&uc->error, (const char*)stream_path.data, stream_path.size); + ufbxi_fail_msg("ufbxi_obj_load_mtl()", "External file not found"); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_obj_init(uc)); + ufbxi_check(ufbxi_obj_parse_file(uc)); + ufbxi_check(ufbxi_init_file_paths(uc)); + ufbxi_check(ufbxi_obj_load_mtl(uc)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_mtl_load(ufbxi_context *uc) +{ + ufbxi_check(ufbxi_obj_init(uc)); + ufbxi_check(ufbxi_init_file_paths(uc)); + ufbxi_check(ufbxi_obj_parse_mtl(uc)); + + return 1; +} + +#else +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_obj_load(ufbxi_context *uc) +{ + ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ"); + ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled"); +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_mtl_load(ufbxi_context *uc) +{ + ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ"); + ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled"); +} + +static ufbxi_forceinline void ufbxi_obj_free(ufbxi_context *uc) +{ +} +#endif + +// -- Scene pre-processing + +typedef struct { + ufbx_element *src, *dst; +} ufbxi_pre_connection; + +typedef struct { + bool has_constant_scale; + bool has_recursive_scale_helper; + bool has_skin_deformer; + ufbx_vec3 constant_scale; + uint32_t element_id; + uint32_t first_child; + uint32_t next_child; + uint32_t parent; +} ufbxi_pre_node; + +typedef struct { + bool has_skin_deformer; +} ufbxi_pre_mesh; + +typedef struct { + bool has_constant_value; + ufbx_vec3 constant_value; +} ufbxi_pre_anim_value; + +static bool ufbxi_pivot_nonzero(ufbx_vec3 offset) +{ + // TODO: Expose this as a setting? + const double epsilon = 0.0009765625; + return ufbx_fabs(offset.x) >= epsilon || ufbx_fabs(offset.y) >= epsilon || ufbx_fabs(offset.z) >= epsilon; +} + +static ufbx_real ufbxi_pivot_div(ufbx_real offset, ufbx_real initial_scale) +{ + const double epsilon = 0.0078125; + if (ufbx_fabs(initial_scale) >= epsilon) { + return offset / initial_scale; + } else { + return offset; + } +} + +// Called between parsing and `ufbxi_finalize_scene()`. +// This is a very messy function reminiscent of the _old_ ufbx, where we do +// multiple passes over connections without having a proper scene graph. +// This, however gives us the advantage of allowing us to modify elements +// and connections. We can, for example, add new helper nodes and redirect +// animated properties from source nodes to the helpers. The rest of ufbx +// will treat these as if they were a part of the source file. +ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context *uc) +{ + bool required = false; + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES || uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) required = true; + if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_HELPER_NODES || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) required = true; + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT || uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) required = true; +#if defined(UFBX_REGRESSION) + required = true; +#endif + + if (!required) return 1; + + uint32_t num_elements = uc->num_elements; + size_t num_nodes = uc->tmp_node_ids.num_items; + ufbx_element **elements = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_ptrs, ufbx_element*, num_elements); + ufbxi_check(elements); + + size_t num_connections = uc->tmp_connections.num_items; + ufbxi_tmp_connection *tmp_connections = ufbxi_push_peek(&uc->tmp_parse, &uc->tmp_connections, ufbxi_tmp_connection, num_connections); + ufbxi_check(tmp_connections); + + ufbxi_pre_connection *pre_connections = ufbxi_push(&uc->tmp_parse, ufbxi_pre_connection, num_connections); + ufbxi_check(pre_connections); + + uint32_t *instance_counts = ufbxi_push_zero(&uc->tmp_parse, uint32_t, num_elements); + ufbxi_check(instance_counts); + + bool *modify_not_supported = ufbxi_push_zero(&uc->tmp_parse, bool, num_elements); + ufbxi_check(modify_not_supported); + + ufbx_element_type *node_attrib_type = ufbxi_push_zero(&uc->tmp_parse, ufbx_element_type, num_nodes); + ufbxi_check(node_attrib_type); + + bool *has_unscaled_children = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes); + ufbxi_check(has_unscaled_children); + + bool *has_scale_animation = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes); + ufbxi_check(has_scale_animation); + + ufbxi_pre_node *pre_nodes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_node, num_nodes); + ufbxi_check(pre_nodes); + + size_t num_meshes = uc->tmp_typed_element_offsets[UFBX_ELEMENT_MESH].num_items; + ufbxi_pre_mesh *pre_meshes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_mesh, num_meshes); + ufbxi_check(pre_meshes); + + size_t num_anim_values = uc->tmp_typed_element_offsets[UFBX_ELEMENT_ANIM_VALUE].num_items; + ufbxi_pre_anim_value *pre_anim_values = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_anim_value, num_anim_values); + ufbxi_check(pre_anim_values); + + uint64_t *fbx_ids = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_fbx_ids, uint64_t, num_elements); + ufbxi_check(fbx_ids); + + // TODO + const ufbx_real scale_epsilon = 0.001f; + const ufbx_real pivot_epsilon = 0.001f; + const ufbx_real compensate_epsilon = 0.01f; + + for (size_t i = 0; i < num_elements; i++) { + ufbx_element *element = elements[i]; + uint32_t id = element->typed_id; + + if (element->type == UFBX_ELEMENT_NODE) { + ufbxi_pre_node *pre_node = &pre_nodes[id]; + pre_node->has_constant_scale = true; + pre_node->constant_scale = ufbxi_find_vec3(&element->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); + pre_node->element_id = element->element_id; + pre_node->first_child = ~0u; + pre_node->next_child = ~0u; + pre_node->parent = ~0u; + } if (element->type == UFBX_ELEMENT_ANIM_VALUE) { + ufbxi_pre_anim_value *pre_value = &pre_anim_values[id]; + pre_value->has_constant_value = true; + pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_X, UFBX_NAN); + pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_d_X, pre_value->constant_value.x); + pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_Y, UFBX_NAN); + pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_d_Y, pre_value->constant_value.y); + pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_Z, UFBX_NAN); + pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_d_Z, pre_value->constant_value.z); + } + } + + for (size_t i = 0; i < num_connections; i++) { + ufbxi_tmp_connection *tmp = &tmp_connections[i]; + ufbxi_pre_connection *pre = &pre_connections[i]; + + ufbxi_fbx_id_entry *src_entry = ufbxi_find_fbx_id(uc, tmp->src); + ufbxi_fbx_id_entry *dst_entry = ufbxi_find_fbx_id(uc, tmp->dst); + + ufbx_element *src = src_entry ? elements[src_entry->element_id] : NULL; + ufbx_element *dst = dst_entry ? elements[dst_entry->element_id] : NULL; + pre->src = src; + pre->dst = dst; + if (!src || !dst) continue; + + if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) { + // Count number of instances of each attribute + if (dst->type == UFBX_ELEMENT_NODE) { + ufbx_node *dst_node = (ufbx_node*)dst; + + if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { + uint32_t count = ++instance_counts[src->element_id]; + node_attrib_type[dst->typed_id] = count == 1 ? src->type : UFBX_ELEMENT_UNKNOWN; + + // These must match what can be trasnsformed in `ufbxi_modify_geometry()` + switch (src->type) { + case UFBX_ELEMENT_MESH: + case UFBX_ELEMENT_LINE_CURVE: + case UFBX_ELEMENT_NURBS_CURVE: + case UFBX_ELEMENT_NURBS_SURFACE: + break; // Nop, supported + default: + modify_not_supported[dst->element_id] = true; + break; + } + } + + if (src->type == UFBX_ELEMENT_NODE) { + ufbx_node *src_node = (ufbx_node*)src; + ufbxi_pre_node *pre_dst = &pre_nodes[dst_node->typed_id]; + ufbxi_pre_node *pre_src = &pre_nodes[src_node->typed_id]; + + // Remember parent and add children into a linked list + if (pre_src->parent == ~0u) { + pre_src->parent = dst_node->typed_id; + pre_src->next_child = pre_dst->first_child; + pre_dst->first_child = src_node->typed_id; + } + + if (uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_PRESERVE) { + if (!dst_node->is_root && src_node->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) { + has_unscaled_children[dst->typed_id] = true; + } + } + } + } else if (dst->type == UFBX_ELEMENT_MESH) { + if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) { + ufbxi_pre_mesh *pre_mesh = &pre_meshes[dst->typed_id]; + pre_mesh->has_skin_deformer = true; + } + } + } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { + const char *dst_prop = tmp->dst_prop.data; + if (dst->type == UFBX_ELEMENT_ANIM_VALUE && src->type == UFBX_ELEMENT_ANIM_CURVE) { + ufbx_anim_curve *src_curve = (ufbx_anim_curve*)src; + uint32_t index = 0; + if (dst_prop == ufbxi_Y || dst_prop == ufbxi_d_Y) { + index = 1; + } else if (dst_prop == ufbxi_Z || dst_prop == ufbxi_d_Z) { + index = 2; + } + + ufbxi_pre_anim_value *pre_value = &pre_anim_values[dst->typed_id]; + if (src_curve->max_value - src_curve->min_value >= scale_epsilon) { + pre_value->has_constant_value = false; + } else { + ufbx_real constant_value = (src_curve->min_value + src_curve->max_value) * 0.5f; + if (ufbx_isnan(pre_value->constant_value.v[index])) { + pre_value->constant_value.v[index] = constant_value; + } + if ((ufbx_real)ufbx_fabs(pre_value->constant_value.v[index] - constant_value) > scale_epsilon) { + pre_value->has_constant_value = false; + } + } + } + } + } + + for (size_t i = 0; i < num_connections; i++) { + ufbxi_tmp_connection *tmp = &tmp_connections[i]; + ufbxi_pre_connection *pre = &pre_connections[i]; + ufbx_element *src = pre->src, *dst = pre->dst; + if (!src || !dst) continue; + + if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) { + // Count maximum number of instanced attributes in a node + if (dst->type == UFBX_ELEMENT_NODE) { + if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { + instance_counts[dst->element_id] = ufbxi_max32(instance_counts[dst->element_id], instance_counts[src->element_id]); + if (src->type == UFBX_ELEMENT_MESH) { + ufbxi_pre_mesh *pre_mesh = &pre_meshes[src->typed_id]; + if (pre_mesh->has_skin_deformer) { + pre_nodes[dst->typed_id].has_skin_deformer = true; + } + } + } else if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) { + pre_nodes[dst->typed_id].has_skin_deformer = true; + } + } + } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { + if (dst->type == UFBX_ELEMENT_NODE) { + if (src->type == UFBX_ELEMENT_ANIM_VALUE) { + if (tmp->dst_prop.data == ufbxi_Lcl_Scaling) { + ufbxi_pre_node *pre_node = &pre_nodes[dst->typed_id]; + if (pre_node->has_constant_scale) { + ufbxi_pre_anim_value *pre_value = &pre_anim_values[src->typed_id]; + if (!pre_value->has_constant_value) { + pre_node->has_constant_scale = false; + } else { + ufbx_real error = 0.0f; + error += (ufbx_real)ufbx_fabs(pre_value->constant_value.x - pre_node->constant_scale.x); + error += (ufbx_real)ufbx_fabs(pre_value->constant_value.y - pre_node->constant_scale.y); + error += (ufbx_real)ufbx_fabs(pre_value->constant_value.z - pre_node->constant_scale.z); + if (error >= scale_epsilon) { + pre_node->has_constant_scale = false; + } + } + } + } + } + } + } + } + + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT || uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { + for (size_t i = 0; i < num_nodes; i++) { + ufbxi_pre_node *pre_node = &pre_nodes[i]; + ufbx_node *node = (ufbx_node*)elements[pre_node->element_id]; + + ufbx_vec3 rotation_pivot = ufbxi_find_vec3(&node->props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling_pivot = ufbxi_find_vec3(&node->props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling_offset = ufbxi_find_vec3(&node->props, ufbxi_ScalingOffset, 0.0f, 0.0f, 0.0f); + + bool should_modify_pivot = false; + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { + should_modify_pivot = !ufbxi_is_vec3_zero(rotation_pivot); + } else if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { + should_modify_pivot = ufbxi_pivot_nonzero(rotation_pivot) || ufbxi_pivot_nonzero(scaling_pivot) || ufbxi_pivot_nonzero(scaling_offset); + } + + if (should_modify_pivot) { + bool skip_geometry_transform = false; + bool can_modify_geometry_transform = true; + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { + if (node_attrib_type[node->typed_id] == UFBX_ELEMENT_EMPTY) { + if (!uc->opts.pivot_handling_retain_empties) { + skip_geometry_transform = true; + } else { + can_modify_geometry_transform = false; + } + } + } + + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) { + if (instance_counts[node->element_id] > 1 || modify_not_supported[node->element_id]) { + can_modify_geometry_transform = false; + } + } + // Currently, geometry transform messes up skinning + if (pre_node->has_skin_deformer) { + can_modify_geometry_transform = false; + } + + bool can_modify_pivot = true; + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { + ufbx_real err = 0.0f; + err += (ufbx_real)ufbx_fabs(rotation_pivot.x - scaling_pivot.x); + err += (ufbx_real)ufbx_fabs(rotation_pivot.y - scaling_pivot.y); + err += (ufbx_real)ufbx_fabs(rotation_pivot.z - scaling_pivot.z); + if (err > pivot_epsilon) { + can_modify_pivot = false; + } + } + + if (can_modify_pivot && (can_modify_geometry_transform || skip_geometry_transform)) { + ufbx_vec3 geometric_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); + + ufbx_vec3 child_offset = { 0.0f }; + ufbx_prop *new_props = NULL; + size_t num_props = node->props.props.count; + size_t new_prop_count = num_props; + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { + ufbx_assert(!skip_geometry_transform); // not supporeted in legacy mode + child_offset = ufbxi_neg3(rotation_pivot); + geometric_translation = ufbxi_add3(geometric_translation, child_offset); + + new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 3); + ufbxi_check(new_props); + memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop)); + + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR); + } else if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { + // We can eliminate the post-rotation translation and move it to the geometry/children as follows. + // Let Z be the initial value of S in the transform (aka `initial_scale`): + // + // (Rp-1+Soff+Sp) + S * (Sp-1) + // S * (Sp-1 + (Rp-1+Soff+Sp)/S) + // S * (Sp-1 + (Rp-1+Soff+Sp)/S - (Rp-1+Soff+Sp)/Z + (Rp-1+Soff+Sp)/Z) + // + // (Rp-1 + Soff + Sp) + S * (-(Rp-1 + Soff + Sp)/Z + (Sp-1 + (Rp-1 + Soff + Sp)/Z)) + // ^-scaled_offset--^ ^-unscaled_offset--^ ^-unscaled_offset--^ + // ^---------------- 0, when S=Z ----------------^ ^------- child_offset ------^ + // + // We need to be careful when doing this in case any component of Z is 0. Fortunately, + // the above holds for all `Z != 0`, it will just result in non-zero translation in the parent. + ufbx_vec3 initial_scale = ufbxi_find_vec3(&node->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); + ufbx_vec3 scaled_offset = ufbxi_sub3(ufbxi_add3(scaling_offset, scaling_pivot), rotation_pivot); + ufbx_vec3 unscaled_offset; + unscaled_offset.x = ufbxi_pivot_div(scaled_offset.x, initial_scale.x); + unscaled_offset.y = ufbxi_pivot_div(scaled_offset.y, initial_scale.y); + unscaled_offset.z = ufbxi_pivot_div(scaled_offset.z, initial_scale.z); + + // Convert `scaled_offset + S*unscaled_offset` to FBX scaling pivot and offset. + ufbx_vec3 new_scaling_pivot = unscaled_offset; + ufbx_vec3 new_scaling_offset = ufbxi_sub3(scaled_offset, new_scaling_pivot); + child_offset = ufbxi_sub3(unscaled_offset, scaling_pivot); + + new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 4); + ufbxi_check(new_props); + memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop)); + + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingPivot, &new_scaling_pivot, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingOffset, &new_scaling_offset, UFBX_PROP_VECTOR); + if (!skip_geometry_transform) { + geometric_translation = ufbxi_add3(geometric_translation, child_offset); + ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR); + } + } + + node->props.props.data = new_props; + node->props.props.count = new_prop_count; + ufbxi_check(ufbxi_sort_properties(uc, node->props.props.data, node->props.props.count)); + ufbxi_deduplicate_properties(&node->props.props); + + node->adjust_pre_translation = ufbxi_add3(node->adjust_pre_translation, rotation_pivot); + node->has_adjust_transform = true; + uint32_t ix = pre_node->first_child; + while (ix != ~0u) { + ufbxi_pre_node *pre_child = &pre_nodes[ix]; + ufbx_node *child = (ufbx_node*)elements[pre_child->element_id]; + + child->adjust_pre_translation = ufbxi_add3(child->adjust_pre_translation, child_offset); + child->has_adjust_transform = true; + + ix = pre_child->next_child; + } + } + } + } + } + + for (size_t i = 0; i < num_elements; i++) { + ufbx_element *element = elements[i]; + uint64_t fbx_id = fbx_ids[i]; + + if (element->type == UFBX_ELEMENT_NODE) { + ufbx_node *node = (ufbx_node*)element; + bool requires_helper_node = false; + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES) { + requires_helper_node = true; + } else if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) { + // Setup a geometry transform helper for nodes that have instanced attributes + requires_helper_node = instance_counts[i] > 1 || modify_not_supported[i]; + } + if (requires_helper_node) { + ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, node, fbx_id)); + } + } + } + + for (size_t i = 0; i < num_elements; i++) { + ufbx_element *element = elements[i]; + uint64_t fbx_id = fbx_ids[i]; + + if (element->type == UFBX_ELEMENT_NODE) { + ufbx_node *node = (ufbx_node*)element; + if (has_unscaled_children[node->typed_id] && !node->scale_helper) { + ufbxi_pre_node *pre_node = &pre_nodes[node->typed_id]; + ufbx_real ref = uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE + ? pre_node->constant_scale.x : (ufbx_real)1.0f; + ufbx_vec3 scale = pre_node->constant_scale; + ufbx_real dx = (ufbx_real)ufbx_fabs(scale.x - ref); + ufbx_real dy = (ufbx_real)ufbx_fabs(scale.y - ref); + ufbx_real dz = (ufbx_real)ufbx_fabs(scale.z - ref); + if ((dx + dy + dz >= scale_epsilon || !pre_node->has_constant_scale || (ufbx_real)ufbx_fabs(scale.x) <= compensate_epsilon) + && uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) { + ufbxi_check(ufbxi_setup_scale_helper(uc, node, fbx_id)); + + // If we added a geometry transform helper that may scale further helpers + // recursively for all child nodes using `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE` + // This is guaranteed to terminate as `ufbxi_pre_node` may only have one parent, + // meaning any cycles must contain `node` itself. + uint32_t ix = pre_node->first_child; + while (ix != ~0u && ix != node->typed_id) { + ufbxi_pre_node *pre_child = &pre_nodes[ix]; + ufbx_node *child = (ufbx_node*)elements[pre_child->element_id]; + + if (pre_child->parent != node->typed_id || child->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) { + if (!pre_child->has_recursive_scale_helper && child->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) { + pre_child->has_recursive_scale_helper = true; + + uint64_t child_fbx_id = fbx_ids[pre_child->element_id]; + ufbxi_check(ufbxi_setup_scale_helper(uc, child, child_fbx_id)); + child->is_scale_compensate_parent = false; + + // Traverse to children if any + if (pre_child->first_child != ~0u) { + ix = pre_child->first_child; + continue; + } + } + } + + // Move to next child, popping parents until we find one + while (pre_child->next_child == ~0u) { + ix = pre_child->parent; + if (ix == node->typed_id) break; + pre_child = &pre_nodes[ix]; + } + if (ix != node->typed_id) { + ix = pre_child->next_child; + } + } + + } else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) { + if ((ufbx_real)ufbx_fabs(scale.x - 1.0f) >= scale_epsilon) { + node->is_scale_compensate_parent = true; + } + } + } + } + } + + return 1; +} + +// -- Scene processing + +static ufbxi_noinline ufbx_element *ufbxi_find_element_by_fbx_id(ufbxi_context *uc, uint64_t fbx_id) +{ + ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id); + if (entry) { + return uc->scene.elements.data[entry->element_id]; + } + return NULL; +} + +ufbxi_forceinline static bool ufbxi_cmp_name_element_less(const ufbx_name_element *a, const ufbx_name_element *b) +{ + if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; + int cmp = strcmp(a->name.data, b->name.data); + if (cmp != 0) return cmp < 0; + return a->type < b->type; +} + +ufbxi_forceinline static bool ufbxi_cmp_name_element_less_ref(const ufbx_name_element *a, ufbx_string name, ufbx_element_type type, uint32_t key) +{ + if (a->_internal_key != key) return a->_internal_key < key; + int cmp = ufbxi_str_cmp(a->name, name); + if (cmp != 0) return cmp < 0; + return a->type < type; +} + +ufbxi_forceinline static bool ufbxi_cmp_prop_less_ref(const ufbx_prop *a, ufbx_string name, uint32_t key) +{ + if (a->_internal_key != key) return a->_internal_key < key; + return ufbxi_str_less(a->name, name); +} + +ufbxi_forceinline static bool ufbxi_cmp_prop_less_concat(const ufbx_prop *a, const ufbx_string *parts, size_t num_parts, uint32_t key) +{ + if (a->_internal_key != key) return a->_internal_key < key; + return ufbxi_concat_str_cmp(&a->name, parts, num_parts) < 0; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_name_elements(ufbxi_context *uc, ufbx_name_element *name_elems, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_name_element))); + ufbxi_macro_stable_sort(ufbx_name_element, 32, name_elems, uc->tmp_arr, count, + ( ufbxi_cmp_name_element_less(a, b) ) ); + return 1; +} + +ufbxi_noinline static bool ufbxi_cmp_node_less(ufbx_node *a, ufbx_node *b) +{ + if (a->node_depth != b->node_depth) return a->node_depth < b->node_depth; + if (a->parent && b->parent) { + uint32_t a_pid = a->parent->element.element_id, b_pid = b->parent->element.element_id; + if (a_pid != b_pid) return a_pid < b_pid; + } else { + ufbx_assert(a->parent == NULL && b->parent == NULL); + } + if (a->is_geometry_transform_helper != b->is_geometry_transform_helper) { + // Sort geometry transform helpers always before rest of the children. + return (unsigned)a->is_geometry_transform_helper > (unsigned)b->is_geometry_transform_helper; + } + if (a->is_scale_helper != b->is_scale_helper) { + // Sort scale helpers after geometry transform helpers. + return (unsigned)a->is_scale_helper > (unsigned)b->is_scale_helper; + } + return a->element.element_id < b->element.element_id; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_node_ptrs(ufbxi_context *uc, ufbx_node **nodes, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_node*))); + ufbxi_macro_stable_sort(ufbx_node*, 32, nodes, uc->tmp_arr, count, + ( ufbxi_cmp_node_less(*a, *b) ) ); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_cmp_tmp_material_texture_less(const ufbxi_tmp_material_texture *a, const ufbxi_tmp_material_texture *b) +{ + if (a->material_id != b->material_id) return a->material_id < b->material_id; + if (a->texture_id != b->texture_id) return a->texture_id < b->texture_id; + return ufbxi_str_less(a->prop_name, b->prop_name); +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_tmp_material_textures(ufbxi_context *uc, ufbxi_tmp_material_texture *mat_texs, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_tmp_material_texture))); + ufbxi_macro_stable_sort(ufbxi_tmp_material_texture, 32, mat_texs, uc->tmp_arr, count, + ( ufbxi_cmp_tmp_material_texture_less(a, b) )); + return 1; +} + +// We need to be able to assume no padding! +ufbx_static_assert(connection_size, sizeof(ufbx_connection) == sizeof(ufbx_element*)*2 + sizeof(ufbx_string)*2); + +ufbxi_forceinline static bool ufbxi_cmp_connection_less(ufbx_connection *a, ufbx_connection *b, size_t index) +{ + ufbx_element *a_elem = (&a->src)[index], *b_elem = (&b->src)[index]; + if (a_elem != b_elem) return a_elem < b_elem; + int cmp = strcmp((&a->src_prop)[index].data, (&b->src_prop)[index].data); + if (cmp != 0) return cmp < 0; + cmp = strcmp((&a->src_prop)[index ^ 1].data, (&b->src_prop)[index ^ 1].data); + return cmp < 0; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_connections(ufbxi_context *uc, ufbx_connection *connections, size_t count, size_t index) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_connection))); + ufbxi_macro_stable_sort(ufbx_connection, 32, connections, uc->tmp_arr, count, ( ufbxi_cmp_connection_less(a, b, index) )); + return 1; +} + +static uint64_t ufbxi_find_attribute_fbx_id(ufbxi_context *uc, uint64_t node_fbx_id) +{ + uint32_t hash = ufbxi_hash64(node_fbx_id); + ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &node_fbx_id); + if (entry) { + return entry->attr_fbx_id; + } + return node_fbx_id; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_connections(ufbxi_context *uc) +{ + size_t num_connections = uc->tmp_connections.num_items; + ufbxi_tmp_connection *tmp_connections = ufbxi_push_pop(&uc->tmp, &uc->tmp_connections, ufbxi_tmp_connection, num_connections); + ufbxi_buf_free(&uc->tmp_connections); + ufbxi_check(tmp_connections); + + // NOTE: We truncate this array in case not all connections are resolved + uc->scene.connections_src.data = ufbxi_push(&uc->result, ufbx_connection, num_connections); + ufbxi_check(uc->scene.connections_src.data); + + // HACK: Translate property connections from node to attribute if the property name is not included + // in the known node properties and is not a property of the node. + if (uc->version > 0 && uc->version < 7000) { + ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) { + if (tmp_conn->src_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->src_prop.data)) { + ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src); + if (!src || !ufbx_find_prop_len(&src->props, tmp_conn->src_prop.data, tmp_conn->src_prop.length)) { + tmp_conn->src = ufbxi_find_attribute_fbx_id(uc, tmp_conn->src); + } + } + if (tmp_conn->dst_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->dst_prop.data)) { + ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst); + if (!dst || !ufbx_find_prop_len(&dst->props, tmp_conn->dst_prop.data, tmp_conn->dst_prop.length)) { + tmp_conn->dst = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); + } + } + } + } + + ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) { + ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src); + ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst); + if (!src || !dst) continue; + + if (!uc->opts.disable_quirks) { + // Some exporters connect arbitrary non-nodes to root breaking further code, ignore those connections here! + if (dst->type == UFBX_ELEMENT_NODE && src->type != UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_root) { + ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, src->element_id, "Non-node element connected to root")); + continue; + } + } + + // Remap connections to geometry transform helpers if necessary, see `ufbxi_setup_geometry_transform_helper()` for how these are setup. + if (uc->has_geometry_transform_nodes) { + if (dst->type == UFBX_ELEMENT_NODE && src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { + ufbx_node *node = (ufbx_node*)dst; + if (node->has_geometry_transform) { + ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id); + ufbx_assert(extra); + dst = uc->scene.elements.data[extra->geometry_helper_id]; + ufbx_assert(dst->type == UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_geometry_transform_helper); + } + } + } + + // Remap connections to scale helpers if necessary, see `ufbxi_setup_scale_helper()` for how these are setup. + if (uc->has_scale_helper_nodes) { + if (dst->type == UFBX_ELEMENT_NODE) { + ufbx_node *dst_node = (ufbx_node*)dst; + if (dst_node->scale_helper) { + if (src->type == UFBX_ELEMENT_NODE) { + ufbx_node *src_node = (ufbx_node*)src; + if (!src_node->is_scale_helper && src_node->original_inherit_mode == UFBX_INHERIT_MODE_NORMAL) { + dst = &dst_node->scale_helper->element; + } + } else if (src->type == UFBX_ELEMENT_ANIM_VALUE) { + if (tmp_conn->dst_prop.data == ufbxi_Lcl_Scaling) { + dst = &dst_node->scale_helper->element; + } + } else { + dst = &dst_node->scale_helper->element; + } + } + } else if (src->type == UFBX_ELEMENT_NODE) { + ufbx_node *src_node = (ufbx_node*)src; + if (src_node->scale_helper) { + if (dst->type == UFBX_ELEMENT_SKIN_CLUSTER) { + src = &src_node->scale_helper->element; + } + } + } + } + + // Translate deformers to point to the geometry in 6100, we don't need to worry about + // blend shapes here as they're always connected synthetically in older files. + if (uc->version > 0 && uc->version < 7000 && dst->type == UFBX_ELEMENT_NODE) { + if (src->type == UFBX_ELEMENT_SKIN_DEFORMER || src->type == UFBX_ELEMENT_CACHE_DEFORMER) { + uint64_t dst_id = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); + ufbx_element *dst_elem = ufbxi_find_element_by_fbx_id(uc, dst_id); + if (dst_elem) { + dst = dst_elem; + } + } + } + + ufbx_connection *conn = &uc->scene.connections_src.data[uc->scene.connections_src.count++]; + conn->src = src; + conn->dst = dst; + conn->src_prop = tmp_conn->src_prop; + conn->dst_prop = tmp_conn->dst_prop; + } + + uc->scene.connections_dst.count = uc->scene.connections_src.count; + uc->scene.connections_dst.data = ufbxi_push_copy(&uc->result, ufbx_connection, + uc->scene.connections_src.count, uc->scene.connections_src.data); + ufbxi_check(uc->scene.connections_dst.data); + + ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_src.data, uc->scene.connections_src.count, 0)); + ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_dst.data, uc->scene.connections_dst.count, 1)); + + // We don't need the temporary connections at this point anymore + ufbxi_buf_free(&uc->tmp_connections); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_connections_to_elements(ufbxi_context *uc) +{ + ufbx_connection *conn_src = uc->scene.connections_src.data; + ufbx_connection *conn_src_end = ufbxi_add_ptr(conn_src, uc->scene.connections_src.count); + ufbx_connection *conn_dst = uc->scene.connections_dst.data; + ufbx_connection *conn_dst_end = ufbxi_add_ptr(conn_dst, uc->scene.connections_dst.count); + + ufbxi_for_ptr(ufbx_element, p_elem, uc->scene.elements.data, uc->scene.elements.count) { + ufbx_element *elem = *p_elem; + uint32_t id = elem->element_id; + + while (conn_src < conn_src_end && conn_src->src->element_id < id) conn_src++; + while (conn_dst < conn_dst_end && conn_dst->dst->element_id < id) conn_dst++; + ufbx_connection *src_end = conn_src, *dst_end = conn_dst; + + while (src_end < conn_src_end && src_end->src->element_id == id) src_end++; + while (dst_end < conn_dst_end && dst_end->dst->element_id == id) dst_end++; + + elem->connections_src.data = conn_src; + elem->connections_src.count = ufbxi_to_size(src_end - conn_src); + elem->connections_dst.data = conn_dst; + elem->connections_dst.count = ufbxi_to_size(dst_end - conn_dst); + + // Setup animated properties + // TODO: It seems we're invalidating a lot of properties here actually, maybe they + // should be initially pushed to `tmp` instead of result if this happens so much.. + { + ufbx_prop *prop = elem->props.props.data, *prop_end = ufbxi_add_ptr(prop, elem->props.props.count); + ufbx_prop *copy_start = prop; + bool needs_copy = false; + size_t num_animated = 0, num_synthetic = 0; + + for (;;) { + // Scan to the next animation connection + for (; conn_dst < dst_end; conn_dst++) { + if (conn_dst->dst_prop.length == 0) continue; + if (conn_dst->src_prop.length > 0) break; + if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) break; + } + + ufbx_string name = ufbx_empty_string; + if (conn_dst < dst_end) { + name = conn_dst->dst_prop; + } + if (name.length == 0) break; + + // NOTE: "Animated" properties also include connected ones as we need + // to resolve them during evaluation + num_animated++; + + ufbx_anim_value *anim_value = NULL; + uint32_t flags = 0; + for (; conn_dst < dst_end && conn_dst->dst_prop.data == name.data; conn_dst++) { + if (conn_dst->src_prop.length > 0) { + flags |= UFBX_PROP_FLAG_CONNECTED; + } else if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) { + anim_value = (ufbx_anim_value*)conn_dst->src; + flags |= UFBX_PROP_FLAG_ANIMATED; + } + } + + uint32_t key = ufbxi_get_name_key(name.data, name.length); + while (prop != prop_end && ufbxi_name_key_less(prop, name.data, name.length, key)) prop++; + + if (prop != prop_end && prop->name.data == name.data) { + prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags | flags); + } else { + // Animated property that is not in the element property list + // Copy the preceding properties to the stack, then push a + // synthetic property for the animated property. + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop - copy_start), copy_start)); + copy_start = prop; + needs_copy = true; + + // Let's hope we can find the property in the defaults at least + ufbx_prop anim_def_prop; + ufbx_prop *def_prop = NULL; + if (elem->props.defaults) { + def_prop = ufbxi_find_prop_with_key(elem->props.defaults, name.data, key); + } else if (anim_value) { + memset(&anim_def_prop, 0, sizeof(anim_def_prop)); + // Hack a couple of common types + ufbx_prop_type type = UFBX_PROP_UNKNOWN; + if (name.data == ufbxi_Lcl_Translation) type = UFBX_PROP_TRANSLATION; + else if (name.data == ufbxi_Lcl_Rotation) type = UFBX_PROP_ROTATION; + else if (name.data == ufbxi_Lcl_Scaling) { + type = UFBX_PROP_SCALING; + anim_def_prop.value_vec3.x = 1.0f; + anim_def_prop.value_vec3.y = 1.0f; + anim_def_prop.value_vec3.z = 1.0f; + } + // Property values are only defined in anim_props on legacy files + if (uc->version < 6000) { + anim_def_prop.value_vec3 = anim_value->default_value; + } + anim_def_prop.type = type; + def_prop = &anim_def_prop; + } else { + flags |= UFBX_PROP_FLAG_NO_VALUE; + } + + ufbx_prop *new_prop = ufbxi_push_zero(&uc->tmp_stack, ufbx_prop, 1); + ufbxi_check(new_prop); + if (def_prop) *new_prop = *def_prop; + flags |= (uint32_t)new_prop->flags; + new_prop->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_ANIMATABLE | UFBX_PROP_FLAG_SYNTHETIC | flags); + new_prop->name = name; + new_prop->_internal_key = key; + new_prop->value_str = ufbx_empty_string; + new_prop->value_blob = ufbx_empty_blob; + num_synthetic++; + } + } + + // Copy the properties if necessary + if (needs_copy) { + size_t num_new_props = elem->props.props.count + num_synthetic; + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop_end - copy_start), copy_start)); + elem->props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, num_new_props); + ufbxi_check(elem->props.props.data); + elem->props.props.count = num_new_props; + } + elem->props.num_animated = num_animated; + } + + conn_src = src_end; + conn_dst = dst_end; + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_linearize_nodes(ufbxi_context *uc) +{ + size_t num_nodes = uc->tmp_node_ids.num_items; + uint32_t *node_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_node_ids, uint32_t, num_nodes); + ufbxi_buf_free(&uc->tmp_node_ids); + ufbxi_check(node_ids); + + ufbx_node **node_ptrs = ufbxi_push(&uc->tmp_stack, ufbx_node*, num_nodes); + ufbxi_check(node_ptrs); + + // Fetch the node pointers + for (size_t i = 0; i < num_nodes; i++) { + node_ptrs[i] = (ufbx_node*)uc->scene.elements.data[node_ids[i]]; + ufbx_assert(node_ptrs[i]->element.type == UFBX_ELEMENT_NODE); + } + + uc->scene.root_node = node_ptrs[0]; + + size_t *node_offsets = ufbxi_push_pop(&uc->tmp_stack, &uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, num_nodes); + ufbxi_check(node_offsets); + + // Hook up the parent nodes, we'll assume that there's no cycles at this point + ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) { + ufbx_node *node = *p_node; + + // Pre-6000 files don't have any explicit root connections so they must always + // be connected to the root.. + if (node->parent == NULL && !(uc->opts.allow_nodes_out_of_root && uc->version >= 6000)) { + if (node != uc->scene.root_node) { + node->parent = uc->scene.root_node; + } + } + + ufbxi_for_list(ufbx_connection, conn, node->element.connections_dst) { + if (conn->src_prop.length > 0 || conn->dst_prop.length > 0) continue; + if (conn->src->type != UFBX_ELEMENT_NODE) continue; + ((ufbx_node*)conn->src)->parent = node; + } + } + + // Count the parent depths and child amounts + ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) { + ufbx_node *node = *p_node; + uint32_t depth = 0; + + for (ufbx_node *p = node->parent; p; p = p->parent) { + depth += p->node_depth + 1; + if (p->node_depth > 0) break; + ufbxi_check_msg(depth <= num_nodes, "Cyclic node hierarchy"); + } + + if (uc->opts.node_depth_limit > 0) { + ufbxi_check_msg(depth <= uc->opts.node_depth_limit, "Node depth limit exceeded"); + } + node->node_depth = depth; + + // Second pass to cache the depths to avoid O(n^2) + for (ufbx_node *p = node->parent; p; p = p->parent) { + if (--depth <= p->node_depth) break; + p->node_depth = depth; + } + } + + ufbxi_check(ufbxi_sort_node_ptrs(uc, node_ptrs, num_nodes)); + + for (uint32_t i = 0; i < num_nodes; i++) { + size_t *p_offset = ufbxi_push(&uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, 1); + ufbxi_check(p_offset); + ufbx_node *node = node_ptrs[i]; + + uint32_t original_id = node->element.typed_id; + node->element.typed_id = i; + *p_offset = node_offsets[original_id]; + } + + // Pop the temporary arrays + ufbxi_pop(&uc->tmp_stack, size_t, num_nodes, NULL); + ufbxi_pop(&uc->tmp_stack, ufbx_node*, num_nodes, NULL); + + return 1; +} + + +ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_dst_connections(ufbx_element *element, const char *prop) +{ + if (!prop) prop = ufbxi_empty_char; + + size_t begin = element->connections_dst.count, end = begin; + + ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin, + element->connections_dst.data, 0, element->connections_dst.count, + (strcmp(a->dst_prop.data, prop) < 0), + (a->dst_prop.data == prop && a->src_prop.length == 0)); + + ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end, + element->connections_dst.data, begin, element->connections_dst.count, + (a->dst_prop.data == prop && a->src_prop.length == 0)); + + ufbx_connection_list result = { element->connections_dst.data + begin, end - begin }; + return result; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_src_connections(ufbx_element *element, const char *prop) +{ + if (!prop) prop = ufbxi_empty_char; + + size_t begin = element->connections_src.count, end = begin; + + ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin, + element->connections_src.data, 0, element->connections_src.count, + (strcmp(a->src_prop.data, prop) < 0), + (a->src_prop.data == prop && a->dst_prop.length == 0)); + + ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end, + element->connections_src.data, begin, element->connections_src.count, + (a->src_prop.data == prop && a->dst_prop.length == 0)); + + ufbx_connection_list result = { element->connections_src.data + begin, end - begin }; + return result; +} + +ufbxi_nodiscard static ufbx_element *ufbxi_get_element_node(ufbx_element *element) +{ + if (!element) return NULL; + if (element->type == UFBX_ELEMENT_NODE) { + ufbx_node *node = (ufbx_node*)element; + if (node->is_geometry_transform_helper) return (ufbx_element*)node->parent; + return NULL; + } else { + return element->instances.count > 0 ? &element->instances.data[0]->element : NULL; + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_dst_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type src_type) +{ + size_t num_elements = 0; + + do { + ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->src->type == src_type) { + if (ignore_duplicates) { + uint32_t element_id = conn->src->element_id; + if (uc->tmp_element_flag[element_id]) { + ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id)); + continue; + } + uc->tmp_element_flag[element_id] = 1; + } + ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); + ufbxi_check(p_elem); + *p_elem = conn->src; + num_elements++; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + ufbx_element_list *list = (ufbx_element_list*)p_dst_list; + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements); + list->count = num_elements; + ufbxi_check(list->data); + + if (ignore_duplicates) { + ufbxi_for_ptr_list(ufbx_element, p_elem, *list) { + uc->tmp_element_flag[(*p_elem)->element_id] = 0; + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_src_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type dst_type) +{ + size_t num_elements = 0; + + do { + ufbx_connection_list conns = ufbxi_find_src_connections(element, prop); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->dst->type == dst_type) { + if (ignore_duplicates) { + uint32_t element_id = conn->dst->element_id; + if (uc->tmp_element_flag[element_id]) { + ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id)); + continue; + } + uc->tmp_element_flag[element_id] = 1; + } + ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); + ufbxi_check(p_elem); + *p_elem = conn->dst; + num_elements++; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + ufbx_element_list *list = (ufbx_element_list*)p_dst_list; + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements); + list->count = num_elements; + ufbxi_check(list->data); + + if (ignore_duplicates) { + ufbxi_for_ptr_list(ufbx_element, p_elem, *list) { + uc->tmp_element_flag[(*p_elem)->element_id] = 0; + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_dst_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type src_type) +{ + do { + ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->src->type == src_type) { + return conn->src; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + return NULL; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_src_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type dst_type) +{ + do { + ufbx_connection_list conns = ufbxi_find_src_connections(element, prop); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->dst->type == dst_type) { + return conn->dst; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + return NULL; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_textures(ufbxi_context *uc, ufbx_material_texture_list *list, ufbx_element *element, bool search_node) +{ + size_t num_textures = 0; + + do { + ufbxi_for_list(ufbx_connection, conn, element->connections_dst) { + if (conn->src_prop.length > 0) continue; + if (conn->src->type == UFBX_ELEMENT_TEXTURE) { + ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1); + ufbxi_check(tex); + tex->shader_prop = tex->material_prop = conn->dst_prop; + tex->texture = (ufbx_texture*)conn->src; + num_textures++; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures); + list->count = num_textures; + ufbxi_check(list->data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_mesh_materials(ufbxi_context *uc, ufbx_material_list *list, ufbx_element *element, bool search_node) +{ + size_t num_materials = 0; + + do { + ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->src->type == UFBX_ELEMENT_MATERIAL) { + ufbx_material *mat = (ufbx_material*)conn->src; + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_material*, 1, &mat)); + num_materials++; + } + } + + if (num_materials > 0) break; + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material*, num_materials); + list->count = num_materials; + ufbxi_check(list->data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_deformers(ufbxi_context *uc, ufbx_element_list *list, ufbx_element *element, bool search_node) +{ + size_t num_deformers = 0; + + do { + ufbxi_for_list(ufbx_connection, conn, element->connections_dst) { + if (conn->src_prop.length > 0) continue; + ufbx_element_type type = conn->src->type; + if (type == UFBX_ELEMENT_SKIN_DEFORMER || type == UFBX_ELEMENT_BLEND_DEFORMER || type == UFBX_ELEMENT_CACHE_DEFORMER) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &conn->src)); + num_deformers++; + } + } + } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); + + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_deformers); + list->count = num_deformers; + ufbxi_check(list->data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe_list *list, ufbx_element *element) +{ + size_t num_keyframes = 0; + + ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->src->type == UFBX_ELEMENT_BLEND_SHAPE) { + ufbx_blend_keyframe key = { (ufbx_blend_shape*)conn->src }; + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_blend_keyframe, 1, &key)); + num_keyframes++; + } + } + + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_blend_keyframe, num_keyframes); + list->count = num_keyframes; + ufbxi_check(list->data); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_texture_layers(ufbxi_context *uc, ufbx_texture_layer_list *list, ufbx_element *element) +{ + size_t num_layers = 0; + + ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); + ufbxi_for_list(ufbx_connection, conn, conns) { + if (conn->src->type == UFBX_ELEMENT_TEXTURE) { + ufbx_texture *texture = (ufbx_texture*)conn->src; + ufbx_texture_layer layer = { texture }; + layer.alpha = ufbxi_find_real(&texture->props, ufbxi_Texture_alpha, 1.0f); + layer.blend_mode = (ufbx_blend_mode)ufbxi_find_enum(&texture->props, ufbxi_BlendMode, UFBX_BLEND_REPLACE, UFBX_BLEND_OVERLAY); + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture_layer, 1, &layer)); + num_layers++; + } + } + + list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_texture_layer, num_layers); + list->count = num_layers; + ufbxi_check(list->data); + + return 1; +} + +static ufbxi_forceinline bool ufbxi_prop_connection_less(const ufbx_connection *a, const char *prop) +{ + int cmp = strcmp(a->dst_prop.data, prop); + if (cmp != 0) return cmp < 0; + return a->src_prop.length == 0; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_connection *ufbxi_find_prop_connection(const ufbx_element *element, const char *prop) +{ + if (!prop) prop = ufbxi_empty_char; + + size_t index = SIZE_MAX; + + ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &index, + element->connections_dst.data, 0, element->connections_dst.count, + (ufbxi_prop_connection_less(a, prop)), + (a->dst_prop.data == prop && a->src_prop.length > 0)); + + return index < SIZE_MAX ? &element->connections_dst.data[index] : NULL; +} + +ufbxi_forceinline static void ufbxi_patch_index_pointer(ufbxi_context *uc, uint32_t **p_index) +{ + if (*p_index == ufbxi_sentinel_index_zero) { + *p_index = uc->zero_indices; + } else if (*p_index == ufbxi_sentinel_index_consecutive) { + *p_index = uc->consecutive_indices; + } +} + +ufbxi_nodiscard static bool ufbxi_cmp_anim_prop_less(const ufbx_anim_prop *a, const ufbx_anim_prop *b) +{ + if (a->element != b->element) return a->element < b->element; + if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; + return ufbxi_str_less(a->prop_name, b->prop_name); +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_anim_props(ufbxi_context *uc, ufbx_anim_prop *aprops, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_anim_prop))); + ufbxi_macro_stable_sort(ufbx_anim_prop, 32, aprops, uc->tmp_arr, count, ( ufbxi_cmp_anim_prop_less(a, b) )); + return 1; +} + +ufbxi_noinline static bool ufbxi_material_texture_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_material_texture *a = (const ufbx_material_texture*)va, *b = (const ufbx_material_texture*)vb; + return ufbxi_str_less(a->material_prop, b->material_prop); +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_material_textures(ufbxi_context *uc, ufbx_material_texture *textures, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_material_texture))); + ufbxi_stable_sort(sizeof(ufbx_material_texture), 32, textures, uc->tmp_arr, count, &ufbxi_material_texture_less, NULL); + return 1; +} + +static ufbxi_noinline bool ufbxi_bone_pose_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_bone_pose *a = (const ufbx_bone_pose *)va, *b = (const ufbx_bone_pose *)vb; + return a->bone_node->typed_id < b->bone_node->typed_id; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_anim_prop *ufbxi_find_anim_prop_start(ufbx_anim_layer *layer, const ufbx_element *element) +{ + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &index, layer->anim_props.data, 0, layer->anim_props.count, + (a->element < element), (a->element == element)); + return index != SIZE_MAX ? &layer->anim_props.data[index] : NULL; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_bone_poses(ufbxi_context *uc, ufbx_pose *pose) +{ + size_t count = pose->bone_poses.count; + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, pose->bone_poses.count * sizeof(ufbx_bone_pose))); + ufbxi_stable_sort(sizeof(ufbx_bone_pose), 16, pose->bone_poses.data, uc->tmp_arr, count, &ufbxi_bone_pose_less, NULL); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_skin_weights(ufbxi_context *uc, ufbx_skin_deformer *skin) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, skin->max_weights_per_vertex * sizeof(ufbx_skin_weight))); + + for (size_t i = 0; i < skin->vertices.count; i++) { + ufbx_skin_vertex v = skin->vertices.data[i]; + ufbxi_macro_stable_sort(ufbx_skin_weight, 32, skin->weights.data + v.weight_begin, uc->tmp_arr, v.num_weights, + ( a->weight > b->weight )); + } + + return 1; +} + +ufbxi_noinline static bool ufbxi_blend_keyframe_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_blend_keyframe *a = (const ufbx_blend_keyframe*)va, *b = (const ufbx_blend_keyframe*)vb; + return a->target_weight < b->target_weight; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe *keyframes, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_blend_keyframe))); + ufbxi_stable_sort(sizeof(ufbx_blend_keyframe), 32, keyframes, uc->tmp_arr, count, &ufbxi_blend_keyframe_less, NULL); + return 1; +} + +// Material tables + +typedef void (*ufbxi_mat_transform_fn)(ufbx_vec4 *a); + +static void ufbxi_mat_transform_invert_x(ufbx_vec4 *v) { v->x = 1.0f - v->x; } +static void ufbxi_mat_transform_unknown_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; } +static void ufbxi_mat_transform_blender_opacity(ufbx_vec4 *v) { v->x = 1.0f - v->x; } +static void ufbxi_mat_transform_blender_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; } + +typedef enum { + UFBXI_MAT_TRANSFORM_IDENTITY, + UFBXI_MAT_TRANSFORM_INVERT_X, + UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, + UFBXI_MAT_TRANSFORM_BLENDER_OPACITY, + UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, + + UFBXI_MAT_TRANSFORM_COUNT, +} ufbxi_mat_transform; + +typedef enum { + // Set `value_vec4.w` (usually alpha) to 1.0 if not defined by the property + UFBXI_SHADER_MAPPING_DEFAULT_W_1 = 0x1, + // Widen values to RGB if only a single value is present. + UFBXI_SHADER_MAPPING_WIDEN_TO_RGB = 0x2, + // Multiply the existing value. + UFBXI_SHADER_MAPPING_MULTIPLY_VALUE = 0x4, +} ufbxi_shader_mapping_flag; + +typedef enum { + // Invert the feature flag + UFBXI_SHADER_FEATURE_INVERTED = 0x1, + // Enable the feature if the given property exists + UFBXI_SHADER_FEATURE_IF_EXISTS = 0x2, + // Enable the feature if the given property has a texture + UFBXI_SHADER_FEATURE_IF_TEXTURE = 0x4, + // Enable if the feature is in [0.5, 1.5], (ie. 2 won't enable this feature) + UFBXI_SHADER_FEATURE_IF_AROUND_1 = 0x8, + + UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE = UFBXI_SHADER_FEATURE_IF_EXISTS|UFBXI_SHADER_FEATURE_IF_TEXTURE, +} ufbxi_shader_feature_flag; + +static const ufbxi_mat_transform_fn ufbxi_mat_transform_fns[] = { + NULL, + &ufbxi_mat_transform_invert_x, + &ufbxi_mat_transform_unknown_shininess, + &ufbxi_mat_transform_blender_opacity, + &ufbxi_mat_transform_blender_shininess, +}; + +ufbx_static_assert(transform_count, ufbxi_arraycount(ufbxi_mat_transform_fns) == UFBXI_MAT_TRANSFORM_COUNT); + +typedef struct { + uint8_t index; // < `ufbx_material_(fbx|pbr)_map` + uint8_t flags; // < Combination of `ufbxi_shader_mapping_flag` + uint8_t transform; // < `ufbxi_mat_transform` + uint8_t prop_len; // < Length of `prop` not including NULL terminator + const char *prop; // < Name of FBX material property or shader mapping +} ufbxi_shader_mapping; + +typedef struct { + const ufbxi_shader_mapping *data; + size_t count; + const ufbxi_shader_mapping *features; + size_t feature_count; + uint32_t default_features; + ufbx_string texture_prefix; + ufbx_string texture_suffix; + ufbx_string texture_enabled_prefix; + ufbx_string texture_enabled_suffix; +} ufbxi_shader_mapping_list; + +#define ufbxi_mat_string(str) sizeof(str) - 1, str + +static const ufbxi_shader_mapping ufbxi_base_fbx_mapping[] = { + { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, + { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, + { UFBX_MATERIAL_FBX_DIFFUSE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, + { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, + { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") }, + { UFBX_MATERIAL_FBX_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") }, + { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Shininess") }, + { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("ShininessExponent") }, + { UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Reflection") }, + { UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("ReflectionColor") }, + { UFBX_MATERIAL_FBX_REFLECTION_FACTOR, 0, 0, ufbxi_mat_string("ReflectionFactor") }, + { UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, + { UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, + { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, + { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, + { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, + { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, + { UFBX_MATERIAL_FBX_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, + { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Ambient") }, + { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("AmbientColor") }, + { UFBX_MATERIAL_FBX_AMBIENT_FACTOR, 0, 0, ufbxi_mat_string("AmbientFactor") }, + { UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, + { UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("Bump") }, + { UFBX_MATERIAL_FBX_BUMP_FACTOR, 0, 0, ufbxi_mat_string("BumpFactor") }, + { UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("Displacement") }, + { UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("DisplacementFactor") }, + { UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT, 0, 0, ufbxi_mat_string("VectorDisplacement") }, + { UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("VectorDisplacementFactor") }, +}; + +static const ufbxi_shader_mapping ufbxi_obj_fbx_mapping[] = { + { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ka") }, + { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") }, + { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") }, + { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") }, + { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Ns") }, + { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, UFBXI_MAT_TRANSFORM_INVERT_X, ufbxi_mat_string("d") }, + { UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, + { UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("disp") }, + { UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("bump") }, +}; + +static const ufbxi_shader_mapping ufbxi_fbx_lambert_shader_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, +}; + +static const ufbxi_shader_mapping ufbxi_fbx_phong_shader_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Shininess") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("ShininessExponent") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, +}; + +static const ufbxi_shader_mapping ufbxi_osl_standard_shader_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuse_roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_IOR") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_anisotropy") }, + { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specular_rotation") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmission_extra_roughness") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_scale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_anisotropy") }, + { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheen_color") }, + { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheen_roughness") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, + { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, + { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_IOR") }, + { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_anisotropy") }, + { UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coat_rotation") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal") }, + { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") }, + { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") }, + { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") }, + { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_IOR") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") }, + { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") }, +}; + +static const ufbxi_shader_mapping ufbxi_osl_standard_shader_features[] = { + { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") }, +}; + +static const ufbxi_shader_mapping ufbxi_arnold_shader_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specularRoughness") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuseRoughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specularColor") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specularIOR") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specularAnisotropy") }, + { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specularRotation") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmissionColor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmissionDepth") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmissionScatter") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmissionScatterAnisotropy") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmissionDispersion") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmissionExtraRoughness") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurfaceColor") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurfaceRadius") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurfaceScale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurfaceAnisotropy") }, + { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheenColor") }, + { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheenRoughness") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, + { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coatColor") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coatRoughness") }, + { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coatIOR") }, + { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coatAnisotropy") }, + { UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coatRotation") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coatNormal") }, + { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thinFilmThickness") }, + { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thinFilmIOR") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissionColor") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, + { UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE, 0, 0, ufbxi_mat_string("indirectDiffuse") }, + { UFBX_MATERIAL_PBR_INDIRECT_SPECULAR, 0, 0, ufbxi_mat_string("indirectSpecular") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") }, + { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") }, + { UFBX_MATERIAL_PBR_MATTE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("aiMatteColor") }, + { UFBX_MATERIAL_PBR_MATTE_FACTOR, 0, 0, ufbxi_mat_string("aiMatteColorA") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY, 0, 0, ufbxi_mat_string("dielectricPriority") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV, 0, 0, ufbxi_mat_string("transmitAovs") }, +}; + +static const ufbxi_shader_mapping ufbxi_arnold_shader_features[] = { + { UFBX_MATERIAL_FEATURE_MATTE, 0, 0, ufbxi_mat_string("aiEnableMatte") }, + { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thinWalled") }, + { UFBX_MATERIAL_FEATURE_CAUSTICS, 0, 0, ufbxi_mat_string("caustics") }, + { UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, 0, 0, ufbxi_mat_string("internalReflections") }, + { UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, 0, 0, ufbxi_mat_string("exitToBackground") }, +}; + +static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_rough") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("reflectivity") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("refl_color") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("anisotropy") }, + { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("aniso_angle") }, + { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisoangle") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("trans_ior") }, // NOTE: Not a typo, IOR is same for transparency/specular + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transparency") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("trans_color") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("trans_depth") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_rough") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_roughness") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("scattering") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_color") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_scatter_color") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("sss_depth") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("sss_scale") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coating") }, + { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_rough") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, + { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("clearcoat_bump_map_amt") }, + { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") }, + { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("cutout") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") }, +}; + +static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_features[] = { + { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") }, + { UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("material_mode") }, + { UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("material_mode") }, + { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, UFBXI_SHADER_FEATURE_INVERTED, 0, ufbxi_mat_string("trans_roughness_lock") }, + { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("roughness_inv") }, + { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("trans_roughness_inv") }, + { UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("coat_roughness_inv") }, +}; + +static const ufbxi_shader_mapping ufbxi_gltf_material_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|baseColor") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("main|roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("main|metalness") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("main|normal") }, + { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("main|ambientOcclusion") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emission") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emissionColor") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("main|Alpha") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("extension|clearcoat") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|clearcoatRoughness") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("extension|clearcoatNormal") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|sheenColor") }, + { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|sheenRoughness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|Specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularcolor") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularColor") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("extension|transmission") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("extension|indexOfRefraction") }, +}; + +static const ufbxi_shader_mapping ufbxi_openpbr_material_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("base_diffuse_roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("base_metalness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular_weight") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_roughness_anisotropy") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_ior") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission_weight") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion_scale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface_weight") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius_scale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_radius") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_scatter_anisotropy") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat_weight") }, + { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, + { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_roughness_anisotropy") }, + { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal_map") }, + { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("fuzz_weight") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("fuzz_color") }, + { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("fuzz_roughness") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission_weight") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, UFBXI_SHADER_MAPPING_MULTIPLY_VALUE, 0, ufbxi_mat_string("emission_luminance") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") }, + { UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, 0, 0, ufbxi_mat_string("thin_film_weight") }, + { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") }, + { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_ior") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump_map_amt") }, + { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("geometry_tangent_map") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("geometry_opacity") }, +}; + +static const ufbxi_shader_mapping ufbxi_openpbr_material_features[] = { + { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("geometry_thin_walled") }, +}; + +static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_metal_rough_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Roughness_Map") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, + { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, +}; + +static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("glossiness") }, + { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, +}; + +static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_features[] = { + { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBXI_SHADER_FEATURE_IF_AROUND_1, 0, ufbxi_mat_string("useGlossiness") }, +}; + +static const ufbxi_shader_mapping ufbxi_gltf_material_features[] = { + { UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, 0, 0, ufbxi_mat_string("main|DoubleSided") }, + { UFBX_MATERIAL_FEATURE_SHEEN, 0, 0, ufbxi_mat_string("extension|enableSheen") }, + { UFBX_MATERIAL_FEATURE_COAT, 0, 0, ufbxi_mat_string("extension|enableClearCoat") }, + { UFBX_MATERIAL_FEATURE_TRANSMISSION, 0, 0, ufbxi_mat_string("extension|enableTransmission") }, + { UFBX_MATERIAL_FEATURE_IOR, 0, 0, ufbxi_mat_string("extension|enableIndexOfRefraction") }, + { UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("extension|enableSpecular") }, + { UFBX_MATERIAL_FEATURE_UNLIT, 0, 0, ufbxi_mat_string("extension|unlit") }, +}; + +// NOTE: These are just the names used by the standard PBS "preset". +// In _theory_ we could walk ShaderGraph but that's a bit out of scope for ufbx. +static const ufbxi_shader_mapping ufbxi_shaderfx_graph_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("color") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metallic") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normal") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emissive_intensity") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissive") }, + { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, +}; + +static const ufbxi_shader_mapping ufbxi_blender_phong_shader_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, UFBXI_MAT_TRANSFORM_BLENDER_OPACITY, ufbxi_mat_string("TransparencyFactor") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("Shininess") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("ShininessExponent") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("ReflectionFactor") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, +}; + +static const ufbxi_shader_mapping ufbxi_obj_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Ns") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Pr") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("Ni") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("Pm") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("d") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Tf") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("disp") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ps") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("Pc") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("Pcr") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("aniso") }, + { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisor") }, +}; + +static const ufbxi_shader_mapping ufbxi_obj_features[] = { + { UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pr") }, + { UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") }, + { UFBX_MATERIAL_FEATURE_SHEEN, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ps") }, + { UFBX_MATERIAL_FEATURE_COAT, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pc") }, + { UFBX_MATERIAL_FEATURE_METALNESS, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") }, + { UFBX_MATERIAL_FEATURE_IOR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ni") }, + { UFBX_MATERIAL_FEATURE_OPACITY, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("d") }, + { UFBX_MATERIAL_FEATURE_TRANSMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Tf") }, + { UFBX_MATERIAL_FEATURE_EMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ke") }, +}; + +enum { + UFBXI_MAT_PBR = 1 << UFBX_MATERIAL_FEATURE_PBR, + UFBXI_MAT_METALNESS = 1 << UFBX_MATERIAL_FEATURE_METALNESS, + UFBXI_MAT_DIFFUSE = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE, + UFBXI_MAT_SPECULAR = 1 << UFBX_MATERIAL_FEATURE_SPECULAR, + UFBXI_MAT_EMISSION = 1 << UFBX_MATERIAL_FEATURE_EMISSION, + UFBXI_MAT_COAT = 1 << UFBX_MATERIAL_FEATURE_COAT, + UFBXI_MAT_SHEEN = 1 << UFBX_MATERIAL_FEATURE_SHEEN, + UFBXI_MAT_TRANSMISSION = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION, + UFBXI_MAT_OPACITY = 1 << UFBX_MATERIAL_FEATURE_OPACITY, + UFBXI_MAT_AMBIENT_OCCLUSION = 1 << UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION, + UFBXI_MAT_MATTE = 1 << UFBX_MATERIAL_FEATURE_MATTE, + UFBXI_MAT_UNLIT = 1 << UFBX_MATERIAL_FEATURE_UNLIT, + UFBXI_MAT_IOR = 1 << UFBX_MATERIAL_FEATURE_IOR, + UFBXI_MAT_DIFFUSE_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, + UFBXI_MAT_TRANSMISSION_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, + UFBXI_MAT_THIN_WALLED = 1 << UFBX_MATERIAL_FEATURE_THIN_WALLED, + UFBXI_MAT_CAUSTICS = 1 << UFBX_MATERIAL_FEATURE_CAUSTICS, + UFBXI_MAT_EXIT_TO_BACKGROUND = 1 << UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, + UFBXI_MAT_INTERNAL_REFLECTIONS = 1 << UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, + UFBXI_MAT_DOUBLE_SIDED = 1 << UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, +}; + +static const ufbxi_shader_mapping_list ufbxi_shader_pbr_mappings[] = { + { // UFBX_SHADER_UNKNOWN + ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping), + NULL, 0, + (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), + }, + { // UFBX_SHADER_FBX_LAMBERT + ufbxi_fbx_lambert_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_lambert_shader_pbr_mapping), + NULL, 0, + (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), + }, + { // UFBX_SHADER_FBX_PHONG + ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping), + NULL, 0, + (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), + }, + { // UFBX_SHADER_OSL_STANDARD_SURFACE + ufbxi_osl_standard_shader_pbr_mapping, ufbxi_arraycount(ufbxi_osl_standard_shader_pbr_mapping), + ufbxi_osl_standard_shader_features, ufbxi_arraycount(ufbxi_osl_standard_shader_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT + | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), + }, + { // UFBX_SHADER_ARNOLD_STANDARD_SURFACE + ufbxi_arnold_shader_pbr_mapping, ufbxi_arraycount(ufbxi_arnold_shader_pbr_mapping), + ufbxi_arnold_shader_features, ufbxi_arraycount(ufbxi_arnold_shader_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT + | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), + }, + { // UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL + ufbxi_3ds_max_physical_material_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_physical_material_pbr_mapping), + ufbxi_3ds_max_physical_material_features, ufbxi_arraycount(ufbxi_3ds_max_physical_material_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_COAT + | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR), + { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix + { NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH + ufbxi_3ds_max_pbr_metal_rough_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_metal_rough_pbr_mapping), + ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY), + { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix + { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS + ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping), + ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_SPECULAR | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY), + { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix + { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_GLTF_MATERIAL + ufbxi_gltf_material_pbr_mapping, ufbxi_arraycount(ufbxi_gltf_material_pbr_mapping), + ufbxi_gltf_material_features, ufbxi_arraycount(ufbxi_gltf_material_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_AMBIENT_OCCLUSION), + { NULL, 0 }, ufbxi_string_literal("Map"), // texture_prefix/suffix + { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_OPENPBR_MATERIAL + ufbxi_openpbr_material_pbr_mapping, ufbxi_arraycount(ufbxi_openpbr_material_pbr_mapping), + ufbxi_openpbr_material_features, ufbxi_arraycount(ufbxi_openpbr_material_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT + | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), + { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix + { NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_SHADERFX_GRAPH + ufbxi_shaderfx_graph_pbr_mapping, ufbxi_arraycount(ufbxi_shaderfx_graph_pbr_mapping), + NULL, 0, + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_AMBIENT_OCCLUSION), + ufbxi_string_literal("TEX_"), ufbxi_string_literal("_map"), // texture_prefix/suffix + ufbxi_string_literal("use_"), ufbxi_string_literal("_map"), // texture_enabled_prefix/suffix + }, + { // UFBX_SHADER_BLENDER_PHONG + ufbxi_blender_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_blender_phong_shader_pbr_mapping), + NULL, 0, + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION), + }, + { // UFBX_SHADER_WAVEFRONT_MTL + ufbxi_obj_pbr_mapping, ufbxi_arraycount(ufbxi_obj_pbr_mapping), + ufbxi_obj_features, ufbxi_arraycount(ufbxi_obj_features), + (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR), + }, +}; + +ufbx_static_assert(shader_pbr_mapping_list, ufbxi_arraycount(ufbxi_shader_pbr_mappings) == UFBX_SHADER_TYPE_COUNT); + +enum { + UFBXI_MAPPING_FETCH_VALUE = 0x1, + UFBXI_MAPPING_FETCH_TEXTURE = 0x2, + UFBXI_MAPPING_FETCH_TEXTURE_ENABLED = 0x4, + UFBXI_MAPPING_FETCH_FEATURE = 0x8, +}; + +ufbxi_noinline static void ufbxi_fetch_mapping_maps(ufbx_material *material, ufbx_material_map *maps, ufbx_material_feature_info *features, + ufbx_shader *shader, const ufbxi_shader_mapping *mappings, size_t count, ufbx_string prefix, ufbx_string prefix2, ufbx_string suffix, uint32_t flags) +{ + char combined_name[512]; + ufbx_shader_prop_binding identity_binding; + + ufbxi_for(const ufbxi_shader_mapping, mapping, mappings, count) { + ufbx_string prop_name = { mapping->prop, mapping->prop_len }; + if (prefix.length > 0 || prefix2.length > 0 || suffix.length > 0) { + if (prop_name.length + prefix.length + prefix2.length + suffix.length <= sizeof(combined_name)) { + char *dst = combined_name; + + if (prefix.length > 0) { + memcpy(dst, prefix.data, prefix.length); + dst += prefix.length; + } + if (prefix2.length > 0) { + memcpy(dst, prefix2.data, prefix2.length); + dst += prefix2.length; + } + if (prop_name.length > 0) { + memcpy(dst, prop_name.data, prop_name.length); + dst += prop_name.length; + } + if (suffix.length > 0) { + memcpy(dst, suffix.data, suffix.length); + dst += suffix.length; + } + + prop_name.data = combined_name; + prop_name.length = ufbxi_to_size(dst - combined_name); + } + } + + ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, prop_name.data, prop_name.length); + if (bindings.count == 0) { + identity_binding.material_prop = prop_name; + identity_binding.shader_prop = ufbx_empty_string; + bindings.data = &identity_binding; + bindings.count = 1; + } + + uint32_t mapping_flags = mapping->flags; + ufbxi_for_list(ufbx_shader_prop_binding, binding, bindings) { + ufbx_string name = binding->material_prop; + + ufbx_prop *prop = ufbx_find_prop_len(&material->props, name.data, name.length); + if (flags & UFBXI_MAPPING_FETCH_FEATURE) { + ufbx_material_feature_info *feature = &features[mapping->index]; + if (prop && prop->type != UFBX_PROP_REFERENCE) { + feature->enabled = prop->value_int != 0; + feature->is_explicit = true; + if (mapping_flags & UFBXI_SHADER_FEATURE_IF_AROUND_1) { + feature->enabled = (prop->value_real >= 0.5f && prop->value_real <= 1.5f); + } + if (mapping_flags & UFBXI_SHADER_FEATURE_INVERTED) { + feature->enabled = !feature->enabled; + } + if (mapping_flags & UFBXI_SHADER_FEATURE_IF_EXISTS) { + feature->enabled = true; + } + } + if (mapping_flags & UFBXI_SHADER_FEATURE_IF_TEXTURE) { + ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length); + if (texture) { + feature->enabled = true; + } + } + continue; + } + + ufbx_material_map *map = &maps[mapping->index]; + + if (flags & UFBXI_MAPPING_FETCH_VALUE) { + if (prop && prop->type != UFBX_PROP_REFERENCE) { + if ((mapping->flags & UFBXI_SHADER_MAPPING_MULTIPLY_VALUE) != 0) { + map->value_vec4.x *= prop->value_vec4.x; + map->value_int = ufbxi_f64_to_i64(map->value_vec4.x); + } else { + map->value_vec4 = prop->value_vec4; + map->value_int = prop->value_int; + } + map->has_value = true; + if (mapping->transform) { + ufbxi_mat_transform_fn transform_fn = ufbxi_mat_transform_fns[mapping->transform]; + transform_fn(&map->value_vec4); + } + + uint32_t prop_flags = (uint32_t)prop->flags; + if ((mapping->flags & UFBXI_SHADER_MAPPING_DEFAULT_W_1) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) == 0) { + map->value_vec4.w = 1.0f; + } + if ((mapping->flags & UFBXI_SHADER_MAPPING_WIDEN_TO_RGB) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) { + map->value_vec3.y = map->value_vec3.x; + map->value_vec3.z = map->value_vec3.x; + } + if ((prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) { + map->value_components = 1; + } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC2) != 0) { + map->value_components = 2; + } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC3) != 0) { + map->value_components = 3; + } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) != 0) { + map->value_components = 4; + } else { + map->value_components = 0; + } + } + } + + if (flags & UFBXI_MAPPING_FETCH_TEXTURE) { + ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length); + if (texture) { + map->texture = texture; + map->texture_enabled = true; + } + } + + if (flags & UFBXI_MAPPING_FETCH_TEXTURE_ENABLED) { + if (prop) { + map->texture_enabled = prop->value_int != 0; + } + } + } + } +} + +ufbxi_noinline static void ufbxi_update_factor(ufbx_material_map *factor_map, ufbx_material_map *color_map) +{ + if (!factor_map->has_value) { + if (color_map->has_value && !ufbxi_is_vec4_zero(color_map->value_vec4)) { + factor_map->value_real = 1.0f; + factor_map->value_int = 1; + } else { + factor_map->value_real = 0.0f; + factor_map->value_int = 0; + } + } +} + +// Some material modes have toggleable roughness/glossiness mode, we read it initially +// always as roughness and if a matching feature such as `roughness_as_glossiness` is set +// we transfer the data into the glossiness and invert the roughness. +typedef struct { + uint8_t feature; + uint8_t roughness_map; + uint8_t glossiness_map; +} ufbxi_glossiness_remap; + +static const ufbxi_glossiness_remap ufbxi_glossiness_remaps[] = { + { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_ROUGHNESS, UFBX_MATERIAL_PBR_GLOSSINESS }, + { UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_COAT_ROUGHNESS, UFBX_MATERIAL_PBR_COAT_GLOSSINESS }, + { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS }, +}; + +ufbxi_noinline static void ufbxi_fetch_maps(ufbx_scene *scene, ufbx_material *material) +{ + (void)scene; + + ufbx_shader *shader = material->shader; + ufbx_assert((uint32_t)material->shader_type < UFBX_SHADER_TYPE_COUNT); + + memset(&material->fbx, 0, sizeof(material->fbx)); + memset(&material->pbr, 0, sizeof(material->pbr)); + memset(&material->features, 0, sizeof(material->features)); + + const ufbxi_shader_mapping *base_mapping = ufbxi_base_fbx_mapping; + size_t num_base_mapping = ufbxi_arraycount(ufbxi_base_fbx_mapping); + + if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ || scene->metadata.file_format == UFBX_FILE_FORMAT_MTL) { + base_mapping = ufbxi_obj_fbx_mapping; + num_base_mapping = ufbxi_arraycount(ufbxi_obj_fbx_mapping); + } + + ufbxi_fetch_mapping_maps(material, material->fbx.maps, NULL, NULL, + base_mapping, num_base_mapping, + ufbx_empty_string, ufbx_empty_string, ufbx_empty_string, + UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE); + + ufbxi_shader_mapping_list list = ufbxi_shader_pbr_mappings[material->shader_type]; + + for (uint32_t i = 0; i < UFBX_MATERIAL_FEATURE_COUNT; i++) { + if ((list.default_features & (1u << i)) != 0) { + material->features.features[i].enabled = true; + } + } + + ufbx_string prefix = ufbx_empty_string; + if (!shader) { + prefix = material->shader_prop_prefix; + } + + if (list.texture_prefix.length > 0 || list.texture_suffix.length > 0) { + ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, + list.data, list.count, prefix, list.texture_prefix, list.texture_suffix, + UFBXI_MAPPING_FETCH_TEXTURE); + } + + ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, + list.data, list.count, prefix, ufbx_empty_string, ufbx_empty_string, + UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE); + + if (list.texture_enabled_prefix.length > 0 || list.texture_enabled_suffix.length > 0) { + ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, + list.data, list.count, prefix, list.texture_enabled_prefix, list.texture_enabled_suffix, + UFBXI_MAPPING_FETCH_TEXTURE_ENABLED); + } + + ufbxi_fetch_mapping_maps(material, NULL, material->features.features, shader, + list.features, list.feature_count, prefix, ufbx_empty_string, ufbx_empty_string, + UFBXI_MAPPING_FETCH_FEATURE); + + ufbxi_update_factor(&material->fbx.diffuse_factor, &material->fbx.diffuse_color); + ufbxi_update_factor(&material->fbx.specular_factor, &material->fbx.specular_color); + ufbxi_update_factor(&material->fbx.reflection_factor, &material->fbx.reflection_color); + ufbxi_update_factor(&material->fbx.transparency_factor, &material->fbx.transparency_color); + ufbxi_update_factor(&material->fbx.emission_factor, &material->fbx.emission_color); + ufbxi_update_factor(&material->fbx.ambient_factor, &material->fbx.ambient_color); + + ufbxi_update_factor(&material->pbr.base_factor, &material->pbr.base_color); + ufbxi_update_factor(&material->pbr.specular_factor, &material->pbr.specular_color); + ufbxi_update_factor(&material->pbr.emission_factor, &material->pbr.emission_color); + ufbxi_update_factor(&material->pbr.sheen_factor, &material->pbr.sheen_color); + ufbxi_update_factor(&material->pbr.thin_film_factor, &material->pbr.thin_film_thickness); + ufbxi_update_factor(&material->pbr.transmission_factor, &material->pbr.transmission_color); + + // Patch transmission roughness if only extra roughness is defined + if (!material->pbr.transmission_roughness.has_value && material->pbr.roughness.has_value && material->pbr.transmission_extra_roughness.has_value) { + material->pbr.transmission_roughness.value_real = material->pbr.roughness.value_real + material->pbr.transmission_extra_roughness.value_real; + } + + // Map roughness to glossiness and vice versa + ufbxi_for(const ufbxi_glossiness_remap, remap, ufbxi_glossiness_remaps, ufbxi_arraycount(ufbxi_glossiness_remaps)) { + ufbx_material_map *roughness = &material->pbr.maps[remap->roughness_map]; + ufbx_material_map *glossiness = &material->pbr.maps[remap->glossiness_map]; + if (material->features.features[remap->feature].enabled) { + *glossiness = *roughness; + memset(roughness, 0, sizeof(ufbx_material_map)); + if (glossiness->has_value) { + roughness->value_real = 1.0f - glossiness->value_real; + } + } else { + if (roughness->has_value) { + glossiness->value_real = 1.0f - roughness->value_real; + } + } + } +} + +typedef enum { + UFBXI_CONSTRAINT_PROP_NODE, + UFBXI_CONSTRAINT_PROP_IK_EFFECTOR, + UFBXI_CONSTRAINT_PROP_IK_END_NODE, + UFBXI_CONSTRAINT_PROP_AIM_UP, + UFBXI_CONSTRAINT_PROP_TARGET, +} ufbxi_constraint_prop_type; + +typedef struct { + ufbxi_constraint_prop_type type; + const char *name; +} ufbxi_constraint_prop; + +static const ufbxi_constraint_prop ufbxi_constraint_props[] = { + { UFBXI_CONSTRAINT_PROP_NODE, "Constrained Object" }, + { UFBXI_CONSTRAINT_PROP_NODE, "Constrained object (Child)" }, + { UFBXI_CONSTRAINT_PROP_NODE, "First Joint" }, + { UFBXI_CONSTRAINT_PROP_TARGET, "Source" }, + { UFBXI_CONSTRAINT_PROP_TARGET, "Source (Parent)" }, + { UFBXI_CONSTRAINT_PROP_TARGET, "Aim At Object" }, + { UFBXI_CONSTRAINT_PROP_TARGET, "Pole Vector Object" }, + { UFBXI_CONSTRAINT_PROP_IK_EFFECTOR, "Effector" }, + { UFBXI_CONSTRAINT_PROP_IK_END_NODE, "End Joint" }, + { UFBXI_CONSTRAINT_PROP_AIM_UP, "World Up Object" }, +}; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_constraint_prop(ufbxi_context *uc, ufbx_constraint *constraint, ufbx_node *node, const char *prop) +{ + ufbxi_for(const ufbxi_constraint_prop, cprop, ufbxi_constraint_props, ufbxi_arraycount(ufbxi_constraint_props)) { + if (strcmp(cprop->name, prop) != 0) continue; + switch (cprop->type) { + case UFBXI_CONSTRAINT_PROP_NODE: constraint->node = node; break; + case UFBXI_CONSTRAINT_PROP_IK_EFFECTOR: constraint->ik_effector = node; break; + case UFBXI_CONSTRAINT_PROP_IK_END_NODE: constraint->ik_end_node = node; break; + case UFBXI_CONSTRAINT_PROP_AIM_UP: constraint->aim_up_node = node; break; + case UFBXI_CONSTRAINT_PROP_TARGET: { + ufbx_constraint_target *target = ufbxi_push_zero(&uc->tmp_stack, ufbx_constraint_target, 1); + ufbxi_check(target); + target->node = node; + target->weight = 1.0f; + target->transform = ufbx_identity_transform; + } break; + default: + ufbxi_unreachable("Unexpected constraint prop"); + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_nurbs_basis(ufbxi_context *uc, ufbx_nurbs_basis *basis) +{ + if (basis->topology == UFBX_NURBS_TOPOLOGY_CLOSED) { + basis->num_wrap_control_points = 1; + } else if (basis->topology == UFBX_NURBS_TOPOLOGY_PERIODIC) { + basis->num_wrap_control_points = basis->order - 1; + } else { + basis->num_wrap_control_points = 0; + } + + if (basis->order > 1) { + size_t degree = basis->order - 1; + ufbx_real_list knots = basis->knot_vector; + if (knots.count >= 2*degree + 1) { + basis->t_min = knots.data[degree]; + basis->t_max = knots.data[knots.count - degree - 1]; + + size_t max_spans = knots.count - 2*degree; + ufbx_real *spans = ufbxi_push(&uc->result, ufbx_real, max_spans); + ufbxi_check(spans); + + ufbx_real prev = -UFBX_INFINITY; + size_t num_spans = 0; + for (size_t i = 0; i < max_spans; i++) { + ufbx_real t = knots.data[degree + i]; + if (t != prev) { + spans[num_spans++] = t; + prev = t; + } + } + + basis->spans.data = spans; + basis->spans.count = num_spans; + basis->valid = true; + for (size_t i = 1; i < knots.count; i++) { + if (knots.data[i - 1] > knots.data[i]) { + basis->valid = false; + break; + } + } + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_lod_group(ufbxi_context *uc, ufbx_lod_group *lod) +{ + size_t num_levels = 0; + for (size_t i = 0; i < lod->instances.count; i++) { + num_levels = ufbxi_max_sz(num_levels, lod->instances.data[0]->children.count); + } + + char prop_name[64]; + for (size_t i = 0; ; i++) { + int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i); + ufbx_prop *prop = ufbx_find_prop_len(&lod->props, prop_name, (size_t)len); + if (!prop) break; + num_levels = ufbxi_max_sz(num_levels, i + 1); + } + + ufbx_lod_level *levels = ufbxi_push_zero(&uc->result, ufbx_lod_level, num_levels); + ufbxi_check(levels); + + lod->relative_distances = ufbx_find_bool(&lod->props, "ThresholdsUsedAsPercentage", false); + lod->ignore_parent_transform = !ufbx_find_bool(&lod->props, "WorldSpace", true); + + lod->use_distance_limit = ufbx_find_bool(&lod->props, "MinMaxDistance", false); + lod->distance_limit_min = ufbx_find_real(&lod->props, "MinDistance", (ufbx_real)-100.0); + lod->distance_limit_max = ufbx_find_real(&lod->props, "MaxDistance", (ufbx_real)100.0); + + lod->lod_levels.data = levels; + lod->lod_levels.count = num_levels; + + for (size_t i = 0; i < num_levels; i++) { + ufbx_lod_level *level = &levels[i]; + + if (i > 0) { + int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i - 1); + level->distance = ufbx_find_real_len(&lod->props, prop_name, (size_t)len, 0.0f); + } else if (lod->relative_distances) { + level->distance = (ufbx_real)100.0; + } + + { + int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "DisplayLevels|Level%zu", i); + int64_t display = ufbx_find_int_len(&lod->props, prop_name, (size_t)len, 0); + if (display >= 0 && display <= 2) { + level->display = (ufbx_lod_display)display; + } + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_generate_normals(ufbxi_context *uc, ufbx_mesh *mesh) +{ + size_t num_indices = mesh->num_indices; + + mesh->generated_normals = true; + + ufbx_topo_edge *topo = ufbxi_push(&uc->tmp_stack, ufbx_topo_edge, num_indices); + ufbxi_check(topo); + + uint32_t *normal_indices = ufbxi_push(&uc->result, uint32_t, num_indices); + ufbxi_check(normal_indices); + + ufbx_compute_topology(mesh, topo, num_indices); + size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false); + + if (num_normals == mesh->num_vertices) { + mesh->vertex_normal.unique_per_vertex = true; + } + + ufbx_vec3 *normal_data = ufbxi_push(&uc->result, ufbx_vec3, num_normals + 1); + ufbxi_check(normal_data); + + normal_data[0] = ufbx_zero_vec3; + normal_data++; + + ufbx_compute_normals(mesh, &mesh->vertex_position, normal_indices, num_indices, normal_data, num_normals); + + mesh->vertex_normal.exists = true; + mesh->vertex_normal.values.data = normal_data; + mesh->vertex_normal.values.count = num_normals; + mesh->vertex_normal.indices.data = normal_indices; + mesh->vertex_normal.indices.count = num_indices; + mesh->vertex_normal.value_reals = 3; + + mesh->skinned_normal = mesh->vertex_normal; + + ufbxi_pop(&uc->tmp_stack, ufbx_topo_edge, num_indices, NULL); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_prop_prefix(ufbxi_context *uc, ufbx_string *dst, ufbx_string prefix) +{ + size_t stack_size = 0; + if (prefix.length > 0 && prefix.data[prefix.length - 1] != '|') { + stack_size = prefix.length + 1; + char *copy = ufbxi_push(&uc->tmp_stack, char, stack_size); + ufbxi_check(copy); + memcpy(copy, prefix.data, prefix.length); + copy[prefix.length] = '|'; + + prefix.data = copy; + prefix.length += 1; + } + + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prefix, false)); + *dst = prefix; + + if (stack_size > 0) { + ufbxi_pop(&uc->tmp_stack, char, stack_size, NULL); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_shader_texture_find_prefix(ufbxi_context *uc, ufbx_texture *texture, ufbx_shader_texture *shader) +{ + ufbx_string suffixes[3]; + size_t num_suffixes = 0; + + suffixes[num_suffixes++] = ufbxi_str_c(" Parameters/Connections"); + if (shader->shader_name.length > 0) { + suffixes[num_suffixes++] = shader->shader_name; + } + suffixes[num_suffixes++] = ufbxi_str_c("3dsMax|parameters"); + + ufbx_assert(num_suffixes <= ufbxi_arraycount(suffixes)); + + ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) { + ufbx_string suffix = *p_suffix; + + ufbxi_for_list(ufbx_prop, prop, texture->props.props) { + if (prop->type != UFBX_PROP_COMPOUND) continue; + if (ufbxi_ends_with(prop->name, suffix)) { + ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, prop->name)); + return 1; + } + } + } + + // Pre-7000 files don't have explicit Compound properties, so let's look for + // any property that has the suffix before the last `|` ... + ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) { + ufbx_string suffix = *p_suffix; + + ufbxi_for_list(ufbx_prop, prop, texture->props.props) { + ufbx_string name = prop->name; + while (name.length > 0) { + if (name.data[name.length - 1] == '|') { + break; + } + name.length--; + } + if (name.length <= 1) continue; + name.length--; + + if (ufbxi_ends_with(name, suffix)) { + ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, name)); + return 1; + } + } + } + + return 1; +} + +typedef struct { + uint64_t shader_id; + const char *shader_name; + const char *input_name; +} ufbxi_file_shader; + +// Known shaders that represent sampled images. +static const ufbxi_file_shader ufbxi_file_shaders[] = { + { UINT64_C(0x7e73161fad53b12a), "ai_image", "filename" }, + { 0, "OSLBitmap", ufbxi_Filename }, + { 0, "OSLBitmap2", ufbxi_Filename }, + { 0, "OSLBitmap3", ufbxi_Filename }, + { 0, "UberBitmap", ufbxi_Filename }, + { 0, "UberBitmap2", ufbxi_Filename }, +}; + +ufbxi_noinline static void ufbxi_update_shader_texture(ufbx_texture *texture, ufbx_shader_texture *shader) +{ + ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { + ufbx_prop *prop = input->prop; + if (prop) { + input->prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); + input->value_vec4 = prop->value_vec4; + input->value_int = prop->value_int; + input->value_str = prop->value_str; + input->value_blob = prop->value_blob; + input->texture = (ufbx_texture*)ufbx_get_prop_element(&texture->element, input->prop, UFBX_ELEMENT_TEXTURE); + } + + prop = input->texture_prop; + if (prop) { + input->texture_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); + ufbx_texture *tex = (ufbx_texture*)ufbx_get_prop_element(&texture->element, prop, UFBX_ELEMENT_TEXTURE); + if (tex) input->texture = tex; + } + + input->texture_enabled = input->texture != NULL; + prop = input->texture_enabled_prop; + if (prop) { + input->texture_enabled_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); + input->texture_enabled = prop->value_int != 0; + } + } + + if (shader->type == UFBX_SHADER_TEXTURE_SELECT_OUTPUT) { + ufbx_shader_texture_input *map = ufbx_find_shader_texture_input(shader, "sourceMap"); + ufbx_shader_texture_input *index = ufbx_find_shader_texture_input(shader, "outputChannelIndex"); + if (index) { + shader->main_texture_output_index = index->value_int; + } + if (map) { + shader->main_texture = map->texture; + map->texture_output_index = shader->main_texture_output_index; + } + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_shader_texture(ufbxi_context *uc, ufbx_texture *texture) +{ + uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDa", 0); + uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDb", 0); + uint64_t classid = (uint64_t)classid_a << 32u | classid_b; + + ufbx_string max_texture = ufbx_find_string(&texture->props, "3dsMax|MaxTexture", ufbx_empty_string); + + // Check first if the texture looks like it could be a shader. + ufbx_shader_texture_type type = (ufbx_shader_texture_type)UFBX_SHADER_TEXTURE_TYPE_COUNT; + + if (!strcmp(max_texture.data, "MULTIOUTPUT_TO_OSLMap") || classid == UINT64_C(0x896ef2fc44bd743f)) { + type = UFBX_SHADER_TEXTURE_SELECT_OUTPUT; + } else if (!strcmp(max_texture.data, "OSLMap") || classid == UINT64_C(0x7f9a7b9d6fcdf00d)) { + type = UFBX_SHADER_TEXTURE_OSL; + } else if (texture->type == UFBX_TEXTURE_FILE && texture->relative_filename.length == 0 && texture->absolute_filename.length == 0 && !texture->video) { + type = UFBX_SHADER_TEXTURE_UNKNOWN; + } + + if ((uint32_t)type == UFBX_SHADER_TEXTURE_TYPE_COUNT) return 1; + + ufbx_shader_texture *shader = ufbxi_push_zero(&uc->result, ufbx_shader_texture, 1); + ufbxi_check(shader); + + shader->type = type; + + static const char *const name_props[] = { + "3dsMax|params|OSLShaderName", + }; + + static const char *const source_props[] = { + "3dsMax|params|OSLCode", + }; + + shader->shader_source.data = ufbxi_empty_char; + shader->shader_name.data = ufbxi_empty_char; + + ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(name_props); i++) { + ufbx_prop *prop = ufbx_find_prop(&texture->props, name_props[i]); + if (prop) { + shader->shader_name = prop->value_str; + break; + } + } + + ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(source_props); i++) { + ufbx_prop *prop = ufbx_find_prop(&texture->props, source_props[i]); + if (prop) { + shader->shader_source = prop->value_str; + shader->raw_shader_source = prop->value_blob; + break; + } + } + + ufbxi_check(ufbxi_shader_texture_find_prefix(uc, texture, shader)); + + if (shader->shader_name.length == 0) { + ufbx_string name = shader->prop_prefix; + if (ufbxi_remove_suffix_c(&name, " Parameters/Connections|")) { + size_t begin = name.length; + while (begin > 0 && name.data[begin - 1] != '|') { + begin--; + } + + shader->shader_name.data = name.data + begin; + shader->shader_name.length = name.length - begin; + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &shader->shader_name, false)); + } + } + + if (shader->shader_name.length == 0) { + if (max_texture.length > 0) { + shader->shader_name = max_texture; + } + } + + if (classid != 0) { + shader->shader_type_id = classid; + } + + if (shader->prop_prefix.length == 0) { + // If we not find any shader properties so we might have guessed wrong. + // We "leak" (freed with scene) the shader in this case but it's negligible. + return 1; + } + + ufbxi_for_list(ufbx_prop, prop, texture->props.props) { + + ufbx_string name = prop->name; + if (!ufbxi_remove_prefix_str(&name, shader->prop_prefix)) continue; + + // Check if this property is a modifier to an existing input. + ufbx_string base_name = name; + if (ufbxi_remove_suffix_c(&base_name, "_map") || ufbxi_remove_suffix_c(&base_name, ".shader")) { + ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length); + if (base) { + base->texture_prop = prop; + continue; + } + } else if (ufbxi_remove_suffix_c(&base_name, ".connected") || ufbxi_remove_suffix_c(&base_name, "Enabled")) { + ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length); + if (base) { + base->texture_enabled_prop = prop; + continue; + } + } + + // Use `uc->tmp_arr` to store the texture inputs so we can search them while we insert new ones. + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, + (shader->inputs.count + 1) * sizeof(ufbx_shader_texture_input))); + shader->inputs.data = (ufbx_shader_texture_input*)uc->tmp_arr; + + // Add a new property + ufbx_shader_texture_input *input = &shader->inputs.data[shader->inputs.count++]; + memset(input, 0, sizeof(ufbx_shader_texture_input)); + + // NOTE: This is a bit hackish, we are using a suffix of an interned string. It won't compare + // pointer equal to the same string but that shouldn't matter.. + input->name = name; + + // Connect the property only, values and textures etc are fetched in `ufbxi_update_shader_texture()`. + input->prop = prop; + } + + // Retain the shader inputs + shader->inputs.data = ufbxi_push_copy(&uc->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data); + ufbxi_check(shader->inputs.data); + + texture->shader = shader; + texture->type = UFBX_TEXTURE_SHADER; + uc->scene.metadata.num_shader_textures++; + + if (!uc->opts.disable_quirks) { + ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(ufbxi_file_shaders); i++) { + const ufbxi_file_shader *fs = &ufbxi_file_shaders[i]; + + if ((fs->shader_id && shader->shader_type_id == fs->shader_id) || !strcmp(shader->shader_name.data, fs->shader_name)) { + ufbx_shader_texture_input *input = ufbx_find_shader_texture_input(shader, fs->input_name); + if (input) { + // TODO: Support for specifying relative filename here if ever needed + ufbx_prop *prop = input->prop; + texture->absolute_filename = prop->value_str; + texture->raw_absolute_filename = prop->value_blob; + texture->type = UFBX_TEXTURE_FILE; + break; + } + } + } + } + + ufbxi_update_shader_texture(texture, shader); + + return 1; +} + +ufbxi_noinline static void ufbxi_propagate_main_textures(ufbx_scene *scene) +{ + // We need to do at least 2^(N-1) passes for N shader textures + size_t mask = scene->metadata.num_shader_textures; + while (mask) { + mask >>= 1; + + ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { + ufbx_texture *texture = *p_texture; + ufbx_shader_texture *shader = texture->shader; + if (!shader) continue; + + ufbx_texture *main_tex = shader->main_texture; + if (!main_tex || shader->main_texture_output_index != 0) continue; + + ufbx_shader_texture *main_shader = main_tex->shader; + if (!main_shader || !main_shader->main_texture) continue; + + shader->main_texture = main_shader->main_texture; + shader->main_texture_output_index = main_shader->main_texture_output_index; + } + } + + // Remove cyclic main textures + ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { + ufbx_texture *texture = *p_texture; + ufbx_shader_texture *shader = texture->shader; + if (!shader || !shader->main_texture || shader->main_texture_output_index != 0) continue; + ufbx_texture *main_tex = shader->main_texture; + if (main_tex && main_tex->shader && main_tex->shader->main_texture) { + // Should have been propagated to `texture` + shader->main_texture = NULL; + } + } + + ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { + ufbx_texture *texture = *p_texture; + ufbx_shader_texture *shader = texture->shader; + if (!shader) continue; + + ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { + if (!input->texture || !input->texture->shader) continue; + ufbx_shader_texture *input_shader = input->texture->shader; + if (input_shader->main_texture) { + input->texture = input_shader->main_texture; + input->texture_output_index = input_shader->main_texture_output_index; + } + } + } + + ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) { + ufbx_material *material = *p_material; + + ufbxi_for_list(ufbx_material_texture, tex, material->textures) { + ufbx_shader_texture *shader = tex->texture->shader; + if (shader && shader->main_texture && shader->main_texture_output_index == 0) { + tex->texture = shader->main_texture; + } + } + } +} + +#define ufbxi_patch_empty(m_dst, m_len, m_src) \ + do { if (!(m_dst).m_len) m_dst = m_src; } while (0) + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_texture_file(ufbxi_context *uc, ufbx_texture *texture) +{ + texture->file_index = UFBX_NO_INDEX; + + const char *key = NULL; + + // HACK: Even the raw entries have a null terminator so we can offset the + // pointer by one for relative filenames. This guarantees that an overlapping + // absolute and relative filenames will get separate textures. + if (texture->raw_absolute_filename.size > 0) { + key = (const char*)texture->raw_absolute_filename.data; + } else if (texture->raw_relative_filename.size > 0) { + key = (const char*)texture->raw_relative_filename.data + 1; + } + + if (key == NULL) return 1; + uint32_t hash = ufbxi_hash_ptr(key); + ufbxi_texture_file_entry *entry = ufbxi_map_find(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key); + if (!entry) { + entry = ufbxi_map_insert(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key); + ufbxi_check(entry); + + ufbx_texture_file *file = ufbxi_push_zero(&uc->tmp, ufbx_texture_file, 1); + ufbxi_check(file); + + file->index = uc->texture_file_map.size - 1; + + entry->key = key; + entry->file = file; + } + + ufbx_texture_file *file = entry->file; + texture->file_index = file->index; + texture->has_file = true; + ufbxi_patch_empty(file->filename, length, texture->filename); + ufbxi_patch_empty(file->relative_filename, length, texture->relative_filename); + ufbxi_patch_empty(file->absolute_filename, length, texture->absolute_filename); + ufbxi_patch_empty(file->raw_filename, size, texture->raw_filename); + ufbxi_patch_empty(file->raw_relative_filename, size, texture->raw_relative_filename); + ufbxi_patch_empty(file->raw_absolute_filename, size, texture->raw_absolute_filename); + ufbxi_patch_empty(file->content, size, texture->content); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_pop_texture_files(ufbxi_context *uc) +{ + uint32_t num_files = uc->texture_file_map.size; + ufbx_texture_file *files = ufbxi_push(&uc->result, ufbx_texture_file, num_files); + ufbxi_check(files); + + uc->scene.texture_files.data = files; + uc->scene.texture_files.count = num_files; + + ufbxi_texture_file_entry *entries = (ufbxi_texture_file_entry*)uc->texture_file_map.items; + for (size_t i = 0; i < num_files; i++) { + memcpy(&files[i], entries[i].file, sizeof(ufbx_texture_file)); + } + + return 1; +} + +typedef struct { + ufbx_texture *texture; + size_t order; +} ufbxi_ordered_texture; + +ufbxi_noinline static bool ufbxi_ordered_texture_less_texture(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb; + return a->texture < b->texture; +} + +ufbxi_noinline static bool ufbxi_ordered_texture_less_order(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb; + return a->order < b->order; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_deduplicate_textures(ufbxi_context *uc, ufbxi_buf *dst_buf, ufbxi_ordered_texture **p_dst, size_t *p_dst_count, size_t count) +{ + ufbxi_ordered_texture *textures = ufbxi_push_pop(dst_buf, &uc->tmp_stack, ufbxi_ordered_texture, count); + ufbxi_check(textures); + + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_ordered_texture))); + + ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, count, &ufbxi_ordered_texture_less_texture, NULL); + + // Remove adjacent duplicates + size_t dst_ix = 0; + for (size_t src_ix = 0; src_ix < count; src_ix++) { + if (src_ix > 0 && textures[src_ix - 1].texture == textures[src_ix].texture) { + continue; + } else { + if (src_ix != dst_ix) { + textures[dst_ix] = textures[src_ix]; + } + dst_ix++; + } + } + + size_t new_count = dst_ix; + ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, new_count, &ufbxi_ordered_texture_less_order, NULL); + + *p_dst_count = new_count; + *p_dst = textures; + + return 1; +} + +typedef enum { + UFBXI_FILE_TEXTURE_FETCH_INITIAL, + UFBXI_FILE_TEXTURE_FETCH_STARTED, + UFBXI_FILE_TEXTURE_FETCH_FINISHED, +} ufbxi_file_texture_fetch_state; + +// Populate `ufbx_texture.file_textures[]` arrays. +ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_file_textures(ufbxi_context *uc) +{ + // We keep pointers to `ufbx_texture` in `tmp_stack` as a working set, since we don't know + // how deep the shader graphs might be. + + // Start by pushing all the textures into the stack + size_t num_stack_textures = uc->scene.textures.count; + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, num_stack_textures, uc->scene.textures.data)); + + // Compressed `ufbxi_file_texture_fetch_state` + uint8_t *states = ufbxi_push_zero(&uc->tmp, uint8_t, uc->scene.textures.count); + ufbxi_check(states); + + while (num_stack_textures-- > 0) { + ufbx_texture *texture = NULL; + ufbxi_pop(&uc->tmp_stack, ufbx_texture*, 1, &texture); + + ufbxi_file_texture_fetch_state state = (ufbxi_file_texture_fetch_state)states[texture->typed_id]; + if (state == UFBXI_FILE_TEXTURE_FETCH_FINISHED) continue; + ufbx_shader_texture *shader = texture->shader; + + if (state == UFBXI_FILE_TEXTURE_FETCH_STARTED) { + states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED; + + // HACK: Reuse `tmp_parse` for storing intermediate information as we can clear it. + ufbxi_buf_clear(&uc->tmp_parse); + + // Now all non-cyclical dependents should be processed. + size_t num_deps = 0; + + if (texture->type == UFBX_TEXTURE_FILE) { + ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); + ufbxi_check(dst); + dst->texture = texture; + dst->order = num_deps++; + } + + ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) { + ufbx_texture *dep_tex = layer->texture; + if (dep_tex->file_textures.count > 0) { + ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); + ufbxi_check(dst); + dst->texture = dep_tex; + dst->order = num_deps++; + } + } + + if (shader) { + ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { + ufbx_texture *dep_tex = input->texture; + if (dep_tex && dep_tex->file_textures.count > 0) { + ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); + ufbxi_check(dst); + dst->texture = dep_tex; + dst->order = num_deps++; + } + } + } + + // Deduplicate the direct dependencies first + ufbxi_ordered_texture *deps; + ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &deps, &num_deps, num_deps)); + + if (num_deps == 1) { + // If we have only a single dependency (that is not the same one) we can just copy the pointer + texture->file_textures = deps[0].texture->file_textures; + } else { + // Now collect all the file textures and deduplicate them + size_t num_files = 0; + ufbxi_for(ufbxi_ordered_texture, dep, deps, num_deps) { + ufbxi_for_ptr_list(ufbx_texture, p_tex, dep->texture->file_textures) { + ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); + ufbxi_check(dst); + dst->texture = *p_tex; + dst->order = num_files++; + } + } + + // Deduplicate the file textures + ufbxi_ordered_texture *files; + ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &files, &num_files, num_files)); + + texture->file_textures.count = num_files; + texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, num_files); + ufbxi_check(texture->file_textures.data); + + for (size_t i = 0; i < num_files; i++) { + texture->file_textures.data[i] = files[i].texture; + } + } + + } else { + if (texture->type == UFBX_TEXTURE_FILE) { + // Simple case: Just point to self + texture->file_textures.count = 1; + texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, 1); + ufbxi_check(texture->file_textures.data); + texture->file_textures.data[0] = texture; + + // In simple cases we can quit here, for more complex file textures queue + // the texture in case there are other file textures as inputs. + if (!texture->shader) { + states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED; + continue; + } + } + + // Complex: Process all dependencies first + states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_STARTED; + + // Push self first so we can return after processing dependencies + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &texture)); + num_stack_textures++; + + ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &layer->texture)); + num_stack_textures++; + } + + if (shader) { + ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { + if (input->texture) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &input->texture)); + num_stack_textures++; + } + } + } + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static ufbx_node *ufbxi_get_geometry_transform_node(ufbx_element *element) +{ + if (element->instances.count == 1) { + ufbx_node *node = element->instances.data[0]; + if (node->has_geometry_transform) return node; + } + return NULL; +} + +ufbxi_noinline static void ufbxi_mirror_vec3_list(const void *v_list, ufbx_mirror_axis axis, size_t stride) +{ + const ufbx_void_list *list = (const ufbx_void_list*)v_list; + if (axis == UFBX_MIRROR_AXIS_NONE || !list || list->count == 0) return; + if (!stride) stride = sizeof(ufbx_vec3); + + void *ptr = (char*)list->data + (size_t)((int)axis - 1) * sizeof(ufbx_real); + void *end = (char*)ptr + list->count * stride; + while (ptr != end) { + ufbx_real *v = (ufbx_real*)ptr; + *v = -*v; + ptr = (char*)ptr + stride; + } +} + +ufbxi_noinline static void ufbxi_scale_vec3_list(const void *v_list, ufbx_real scale, size_t stride) +{ + const ufbx_void_list *list = (const ufbx_void_list*)v_list; + if (!list || list->count == 0) return; + if (!stride) stride = sizeof(ufbx_vec3); + + void *ptr = list->data, *end = (char*)ptr + list->count * stride; + while (ptr != end) { + ufbx_vec3 *v = (ufbx_vec3*)ptr; + v->x *= scale; + v->y *= scale; + v->z *= scale; + ptr = (char*)ptr + stride; + } +} + +ufbxi_noinline static void ufbxi_transform_vec3_list(const void *v_list, const ufbx_matrix *matrix, size_t stride) +{ + const ufbx_void_list *list = (const ufbx_void_list*)v_list; + if (!list || list->count == 0) return; + if (!stride) stride = sizeof(ufbx_vec3); + + void *ptr = list->data, *end = (char*)ptr + list->count * stride; + while (ptr != end) { + ufbx_vec3 *v = (ufbx_vec3*)ptr; + *v = ufbx_transform_position(matrix, *v); + ptr = (char*)ptr + stride; + } +} + +ufbxi_noinline static void ufbxi_normalize_vec3_list(const ufbx_vec3_list *list) +{ + ufbxi_nounroll ufbxi_for_list(ufbx_vec3, normal, *list) { + *normal = ufbxi_normalize3(*normal); + } +} + +// Forward declare as we're kind of preprocessing ata here that would usually happen later. +ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node); + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_attrib_winding(ufbxi_context *uc, ufbx_mesh *mesh, ufbx_uint32_list *indices, bool is_position) +{ + // All zero, no flipping needed + if (indices->data == uc->zero_indices || indices->count == 0) return 1; + + if (indices->data == mesh->vertex_position.indices.data && !is_position) { + // Sharing indices with vertex position, already flipped. + return 1; + } else if (indices->data == uc->consecutive_indices) { + // Need to duplicate consecutive indices, but we can cache the per mesh. + if (uc->tmp_mesh_consecutive_indices) { + indices->data = uc->tmp_mesh_consecutive_indices; + return 1; + } + indices->data = ufbxi_push_copy(&uc->result, uint32_t, indices->count, indices->data); + ufbxi_check(indices->data); + uc->tmp_mesh_consecutive_indices = indices->data; + } + + uint32_t *data = indices->data; + ufbxi_for_list(ufbx_face, face, mesh->faces) { + if (face->num_indices == 0) continue; + size_t begin = face->index_begin + 1; + size_t end = face->index_begin + face->num_indices - 1; + while (begin < end) { + uint32_t tmp = data[begin]; + data[begin] = data[end]; + data[end] = tmp; + begin++; + end--; + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_winding(ufbxi_context *uc, ufbx_mesh *mesh) +{ + uc->tmp_mesh_consecutive_indices = NULL; + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_position.indices, true)); + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_normal.indices, false)); + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_crease.indices, false)); + if (mesh->uv_sets.count > 0) { + ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_uv.indices, false)); + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_tangent.indices, false)); + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_bitangent.indices, false)); + } + mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv; + mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent; + mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent; + } + if (mesh->color_sets.count > 0) { + ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_color.indices, false)); + } + mesh->vertex_color = mesh->color_sets.data[0].vertex_color; + } + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_position.indices, false)); + if (mesh->skinned_normal.indices.data != mesh->vertex_normal.indices.data) { + ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_normal.indices, false)); + } + + ufbxi_update_vertex_first_index(mesh); + + // Mapping from old index values to flipped ones, reserve index -1 + // (aka `UFBX_NO_INDEX`) for itself. + if (mesh->edges.count > 0) { + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, (mesh->num_indices + 1) * sizeof(uint32_t))); + uint32_t *index_mapping = (uint32_t*)uc->tmp_arr + 1; + index_mapping[-1] = UFBX_NO_INDEX; + ufbxi_for_list(ufbx_face, face, mesh->faces) { + if (face->num_indices == 0) continue; + uint32_t begin = face->index_begin; + uint32_t count = face->num_indices - 1; + index_mapping[begin] = begin; + for (uint32_t i = 0; i < count; i++) { + index_mapping[begin + 1 + i] = begin + count - i; + } + } + + ufbxi_for_list(ufbx_edge, p_edge, mesh->edges) { + uint32_t a = index_mapping[(int32_t)p_edge->a]; + uint32_t b = index_mapping[(int32_t)p_edge->b]; + p_edge->a = b; + p_edge->b = a; + } + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_modify_geometry(ufbxi_context *uc) +{ + bool do_mirror = false; + bool do_winding = uc->opts.reverse_winding; + bool do_scale = false; + bool do_geometry_transforms = false; + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY + || uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) { + // Prefetch geometry transforms for processing, they will later be overwritten in `ufbxi_update_node()`. + ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { + ufbx_node *node = *p_node; + if (node->is_root) continue; + + node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node); + if (!ufbxi_is_transform_identity(&node->geometry_transform)) { + node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform); + node->has_geometry_transform = true; + } else { + node->geometry_to_node = ufbx_identity_matrix; + node->has_geometry_transform = false; + } + } + do_geometry_transforms = true; + } + if (uc->mirror_axis != 0) { + do_mirror = true; + } + if (uc->scene.metadata.geometry_scale != 1.0f) { + do_scale = true; + } + + ufbx_real geometry_scale = uc->scene.metadata.geometry_scale; + ufbx_mirror_axis mirror_axis = uc->mirror_axis; + + ufbxi_for_ptr_list(ufbx_blend_shape, p_shape, uc->scene.blend_shapes) { + ufbx_blend_shape *shape = *p_shape; + + if (do_scale) { + ufbxi_scale_vec3_list(&shape->position_offsets, geometry_scale, 0); + } + + if (do_mirror) { + ufbxi_mirror_vec3_list(&shape->position_offsets, mirror_axis, 0); + ufbxi_mirror_vec3_list(&shape->normal_offsets, mirror_axis, 0); + } + } + + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { + ufbx_mesh *mesh = *p_mesh; + + if (do_scale) { + ufbxi_scale_vec3_list(&mesh->vertex_position.values, geometry_scale, 0); + } + + bool do_flip_winding = do_winding; + if (do_mirror) { + ufbxi_mirror_vec3_list(&mesh->vertex_position.values, mirror_axis, 0); + ufbxi_mirror_vec3_list(&mesh->vertex_normal.values, mirror_axis, 0); + ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + ufbxi_mirror_vec3_list(&set->vertex_tangent.values, mirror_axis, 0); + ufbxi_mirror_vec3_list(&set->vertex_bitangent.values, mirror_axis, 0); + } + if (!uc->opts.handedness_conversion_retain_winding) { + do_flip_winding = !do_flip_winding; + } + } + + // Flip face winding retaining the first vertex + if (do_flip_winding) { + mesh->reversed_winding = true; + ufbxi_check(ufbxi_flip_winding(uc, mesh)); + } + + ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&mesh->element); + if (do_geometry_transforms && geo_node) { + ufbx_matrix tangent_matrix = geo_node->geometry_to_node; + tangent_matrix.m03 = 0.0f; + tangent_matrix.m13 = 0.0f; + tangent_matrix.m23 = 0.0f; + ufbx_matrix normal_matrix = ufbx_matrix_for_normals(&geo_node->geometry_to_node); + + ufbxi_transform_vec3_list(&mesh->vertex_position.values, &geo_node->geometry_to_node, 0); + ufbxi_transform_vec3_list(&mesh->vertex_normal.values, &normal_matrix, 0); + ufbxi_normalize_vec3_list(&mesh->vertex_normal.values); + + ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + ufbxi_transform_vec3_list(&set->vertex_tangent.values, &tangent_matrix, 0); + ufbxi_transform_vec3_list(&set->vertex_bitangent.values, &tangent_matrix, 0); + ufbxi_normalize_vec3_list(&set->vertex_tangent.values); + ufbxi_normalize_vec3_list(&set->vertex_bitangent.values); + } + } + } + + ufbxi_for_ptr_list(ufbx_line_curve, p_curve, uc->scene.line_curves) { + ufbx_line_curve *curve = *p_curve; + + if (do_scale) { + ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, 0); + } + + if (do_mirror) { + ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, 0); + } + + ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element); + if (do_geometry_transforms && geo_node) { + ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, 0); + } + } + + ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) { + ufbx_nurbs_curve *curve = *p_curve; + + if (do_scale) { + ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, sizeof(ufbx_vec4)); + } + + if (do_mirror) { + ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, sizeof(ufbx_vec4)); + } + + ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element); + if (do_geometry_transforms && geo_node) { + ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4)); + } + } + + ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) { + ufbx_nurbs_surface *surface = *p_surface; + + if (do_scale) { + ufbxi_scale_vec3_list(&surface->control_points, geometry_scale, sizeof(ufbx_vec4)); + } + + if (do_mirror) { + ufbxi_mirror_vec3_list(&surface->control_points, mirror_axis, sizeof(ufbx_vec4)); + } + + ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&surface->element); + if (do_geometry_transforms && geo_node) { + ufbxi_transform_vec3_list(&surface->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4)); + } + } + + if (uc->opts.geometry_transform_handling != UFBX_GEOMETRY_TRANSFORM_HANDLING_PRESERVE) { + // Reset all geometry transforms if we're not preserving them + ufbx_props *defaults = NULL; + ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { + ufbx_node *node = *p_node; + if (!defaults) defaults = node->props.defaults; + + if (node->has_geometry_transform) { + ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricTranslation, 0.0f); + ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricRotation, 0.0f); + ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricScaling, 1.0f); + } + } + + if (defaults) { + ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricTranslation, 0.0f); + ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricRotation, 0.0f); + ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricScaling, 1.0f); + } + } + + return 1; +} + +ufbxi_noinline static void ufbxi_postprocess_scene(ufbxi_context *uc) +{ + if (uc->opts.normalize_normals || uc->opts.normalize_tangents) { + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { + ufbx_mesh *mesh = *p_mesh; + if (uc->opts.normalize_normals) { + ufbxi_normalize_vec3_list(&mesh->vertex_normal.values); + } + if (uc->opts.normalize_tangents) { + ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + ufbxi_normalize_vec3_list(&mesh->vertex_tangent.values); + ufbxi_normalize_vec3_list(&mesh->vertex_bitangent.values); + } + } + } + } + + if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { + uc->scene.metadata.ortho_size_unit = 1.0f / uc->scene.metadata.geometry_scale; + } else { + uc->scene.metadata.ortho_size_unit = 30.0f; + } +} + +ufbxi_noinline static size_t ufbxi_next_path_segment(const char *data, size_t begin, size_t length) +{ + for (size_t i = begin; i < length; i++) { + if (data[i] == '/' || data[i] == '\\') { + return i; + } + } + return length; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_absolute_to_relative_path(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_rel, const ufbxi_strblob *p_src, bool raw) +{ + const char *rel = ufbxi_strblob_data(p_rel, raw); + const char *src = ufbxi_strblob_data(p_src, raw); + size_t rel_length = ufbxi_strblob_length(p_rel, raw); + size_t src_length = ufbxi_strblob_length(p_src, raw); + + if (rel_length == 0 || src_length == 0) return 1; + + // Absolute paths must start with the same character (either drive or '/') + if (rel[0] != src[0]) return 1; + + // Find the last directory of the path we want to be relative to + while (rel_length > 0 && (rel[rel_length - 1] != '/' && rel[rel_length - 1] != '\\')) { + rel_length--; + } + + if (rel_length == 0) return 1; + char separator = rel[rel_length - 1]; + + size_t max_length = rel_length * 2 + src_length; + + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, max_length)); + char *tmp = uc->tmp_arr; + size_t tmp_length = 0; + + size_t rel_begin = 0; + size_t src_begin = 0; + while (rel_begin < rel_length && src_begin < src_length) { + size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length); + size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length); + if (rel_end != src_end || memcmp(rel + rel_begin, src + src_begin, src_end - src_begin) != 0) break; + + rel_begin = rel_end + 1; + src_begin = src_end + 1; + } + + while (rel_begin < rel_length) { + size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length); + tmp[tmp_length++] = '.'; + tmp[tmp_length++] = '.'; + tmp[tmp_length++] = separator; + rel_begin = rel_end + 1; + } + + while (src_begin < src_length) { + size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length); + size_t len = src_end - src_begin; + + memcpy(tmp + tmp_length, src + src_begin, len); + tmp_length += len; + + if (src_end < src_length) { + tmp[tmp_length++] = separator; + } + + src_begin = src_end + 1; + } + + ufbx_assert(tmp_length <= max_length); + + const char *dst = ufbxi_push_string(&uc->string_pool, tmp, tmp_length, NULL, true); + ufbxi_check(dst); + + ufbxi_strblob_set(p_dst, dst, tmp_length, raw); + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_filenames(ufbxi_context *uc, ufbxi_strblob *filename, ufbxi_strblob *absolute_filename, ufbxi_strblob *relative_filename, bool raw) +{ + if (ufbxi_strblob_length(relative_filename, raw) == 0) { + const ufbxi_strblob *original_file_path = raw + ? (const ufbxi_strblob*)&uc->scene.metadata.raw_original_file_path + : (const ufbxi_strblob*)&uc->scene.metadata.original_file_path; + + ufbxi_check(ufbxi_absolute_to_relative_path(uc, relative_filename, original_file_path, absolute_filename, raw)); + } + + ufbxi_check(ufbxi_resolve_relative_filename(uc, filename, relative_filename, raw)); + + return 1; +} + +ufbxi_noinline static bool ufbxi_file_content_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_file_content *a = (const ufbxi_file_content*)va, *b = (const ufbxi_file_content*)vb; + return ufbxi_str_less(a->absolute_filename, b->absolute_filename); +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_file_contents(ufbxi_context *uc, ufbxi_file_content *content, size_t count) +{ + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_file_content))); + ufbxi_stable_sort(sizeof(ufbxi_file_content), 32, content, uc->tmp_arr, count, &ufbxi_file_content_less, NULL); + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data) +{ + if (p_data->size == 0 || p_filename->length == 0) return 1; + ufbxi_file_content *content = ufbxi_push(&uc->tmp_stack, ufbxi_file_content, 1); + ufbxi_check(content); + + content->absolute_filename = *p_filename; + content->content = *p_data; + return 1; +} + +ufbxi_noinline static void ufbxi_fetch_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data) +{ + if (p_data->size > 0) return; + ufbx_string filename = *p_filename; + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbxi_file_content, 8, &index, uc->file_content, 0, uc->num_file_content, + ( ufbxi_str_less(a->absolute_filename, filename) ), + ( a->absolute_filename.data == filename.data )); + if (index != SIZE_MAX) { + *p_data = uc->file_content[index].content; + } +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_file_content(ufbxi_context *uc) +{ + size_t initial_stack = uc->tmp_stack.num_items; + + ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) { + ufbx_video *video = *p_video; + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->filename, (ufbxi_strblob*)&video->absolute_filename, (ufbxi_strblob*)&video->relative_filename, false)); + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->raw_filename, (ufbxi_strblob*)&video->raw_absolute_filename, (ufbxi_strblob*)&video->raw_relative_filename, true)); + ufbxi_check(ufbxi_push_file_content(uc, &video->absolute_filename, &video->content)); + } + + ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) { + ufbx_audio_clip *clip = *p_clip; + clip->absolute_filename = ufbx_find_string(&clip->props, "Path", ufbx_empty_string); + clip->relative_filename = ufbx_find_string(&clip->props, "RelPath", ufbx_empty_string); + clip->raw_absolute_filename = ufbx_find_blob(&clip->props, "Path", ufbx_empty_blob); + clip->raw_relative_filename = ufbx_find_blob(&clip->props, "RelPath", ufbx_empty_blob); + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->filename, (ufbxi_strblob*)&clip->absolute_filename, (ufbxi_strblob*)&clip->relative_filename, false)); + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->raw_filename, (ufbxi_strblob*)&clip->raw_absolute_filename, (ufbxi_strblob*)&clip->raw_relative_filename, true)); + ufbxi_check(ufbxi_push_file_content(uc, &clip->absolute_filename, &clip->content)); + } + + uc->num_file_content = uc->tmp_stack.num_items - initial_stack; + uc->file_content = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_file_content, uc->num_file_content); + ufbxi_check(uc->file_content); + ufbxi_check(ufbxi_sort_file_contents(uc, uc->file_content, uc->num_file_content)); + + ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) { + ufbx_video *video = *p_video; + ufbxi_fetch_file_content(uc, &video->absolute_filename, &video->content); + } + + ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) { + ufbx_audio_clip *clip = *p_clip; + ufbxi_fetch_file_content(uc, &clip->absolute_filename, &clip->content); + } + + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_validate_indices(ufbxi_context *uc, ufbx_uint32_list *indices, size_t max_index) +{ + if (max_index == 0 && uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_CLAMP) { + indices->data = NULL; + indices->count = 0; + return 1; + } + + ufbxi_nounroll ufbxi_for_list(uint32_t, p_ix, *indices) { + uint32_t ix = *p_ix; + if (ix >= max_index) { + ufbxi_check(ufbxi_fix_index(uc, p_ix, ix, max_index)); + } + } + + return 1; +} + +static bool ufbxi_material_part_usage_less(void *user, const void *va, const void *vb) +{ + ufbx_mesh_part *parts = (ufbx_mesh_part*)user; + uint32_t a = *(const uint32_t*)va, b = *(const uint32_t*)vb; + ufbx_mesh_part *pa = &parts[a]; + ufbx_mesh_part *pb = &parts[b]; + if (pa->face_indices.count == 0 || pb->face_indices.count == 0) { + if (pa->face_indices.count == pb->face_indices.count) return a < b; + return pa->face_indices.count > pb->face_indices.count; + } + return pa->face_indices.data[0] < pb->face_indices.data[0]; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh_material(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh) +{ + size_t num_materials = mesh->materials.count; + size_t num_parts = mesh->material_parts.count; + size_t num_faces = mesh->faces.count; + + ufbx_mesh_part *parts = mesh->material_parts.data; + ufbx_assert(!parts || (mesh->material_parts.count == num_materials) || (mesh->material_parts.count == 1 && num_materials == 0)); + + uint32_t *face_material = mesh->face_material.data; + + // Count the number of faces and triangles per material + ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { + ufbx_face face = mesh->faces.data[i]; + uint32_t mat_ix = 0; + + if (face_material) { + mat_ix = face_material[i]; + if (mat_ix >= num_materials) { + face_material[i] = 0; + mat_ix = 0; + } + } + + if (parts) { + ufbxi_mesh_part_add_face(&parts[mat_ix], face.num_indices); + } + } + + if (parts) { + // Allocate per-material buffers (clear `num_faces` to 0 to re-use it as + // an index when fetching the face indices). + uint32_t part_index = 0; + ufbxi_for(ufbx_mesh_part, part, parts, num_parts) { + part->index = part_index++; + part->face_indices.count = part->num_faces; + part->face_indices.data = ufbxi_push(buf, uint32_t, part->num_faces); + ufbxi_check_err(error, part->face_indices.data); + part->num_faces = 0; + } + + // Fetch the per-material face indices + ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { + uint32_t mat_ix = face_material ? face_material[i] : 0; + if (mat_ix < num_parts) { + ufbx_mesh_part *part = &parts[mat_ix]; + part->face_indices.data[part->num_faces++] = (uint32_t)i; + } + } + + mesh->material_part_usage_order.count = num_parts; + mesh->material_part_usage_order.data = ufbxi_push(buf, uint32_t, num_parts); + ufbxi_check_err(error, mesh->material_part_usage_order.data); + for (size_t i = 0; i < num_parts; i++) { + mesh->material_part_usage_order.data[i] = (uint32_t)i; + } + ufbxi_unstable_sort(mesh->material_part_usage_order.data, num_parts, sizeof(uint32_t), &ufbxi_material_part_usage_less, parts); + } + + return 1; +} + +typedef struct { + ufbxi_refcount refcount; + ufbx_anim anim; + uint32_t magic; +} ufbxi_anim_imp; + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_anim(ufbxi_context *uc, ufbx_anim **p_anim, ufbx_anim_layer **layers, size_t num_layers) +{ + ufbx_anim *anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1); + ufbxi_check(anim); + + anim->layers.data = layers; + anim->layers.count = num_layers; + + *p_anim = anim; + return 1; +} + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc) +{ + size_t num_elements = uc->num_elements; + + uc->scene.elements.count = num_elements; + uc->scene.elements.data = ufbxi_push(&uc->result, ufbx_element*, num_elements); + ufbxi_check(uc->scene.elements.data); + + uc->scene.metadata.element_buffer_size = uc->tmp_element_byte_offset; + char *element_data = (char*)ufbxi_push_pop(&uc->result, &uc->tmp_elements, uint64_t, uc->tmp_element_byte_offset/8); + ufbxi_check(element_data); + + size_t *element_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_offsets, size_t, uc->tmp_element_offsets.num_items); + ufbxi_buf_free(&uc->tmp_element_offsets); + ufbxi_check(element_offsets); + for (size_t i = 0; i < num_elements; i++) { + ufbx_element *element = (ufbx_element*)(element_data + element_offsets[i]); + + if (element->type == UFBX_ELEMENT_NODE) { + ufbx_node *node = (ufbx_node*)element; + if (node->scale_helper) { + ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id); + ufbx_assert(extra); + node->scale_helper = (ufbx_node*)(element_data + element_offsets[extra->scale_helper_id]); + } + } + + uc->scene.elements.data[i] = element; + } + + uc->scene.elements.count = num_elements; + ufbxi_buf_free(&uc->tmp_element_offsets); + ufbxi_buf_free(&uc->tmp_elements); + + uc->tmp_element_flag = ufbxi_push_zero(&uc->tmp, uint8_t, num_elements); + ufbxi_check(uc->tmp_element_flag); + + uc->scene.metadata.original_file_path = ufbx_find_string(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_string); + uc->scene.metadata.raw_original_file_path = ufbx_find_blob(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_blob); + + // Resolve and add the connections to elements + ufbxi_check(ufbxi_resolve_connections(uc)); + ufbxi_check(ufbxi_add_connections_to_elements(uc)); + ufbxi_check(ufbxi_linearize_nodes(uc)); + + for (size_t type = 0; type < UFBX_ELEMENT_TYPE_COUNT; type++) { + size_t num_typed = uc->tmp_typed_element_offsets[type].num_items; + size_t *typed_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_typed_element_offsets[type], size_t, num_typed); + ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]); + ufbxi_check(typed_offsets); + + ufbx_element_list *typed_elems = &uc->scene.elements_by_type[type]; + typed_elems->count = num_typed; + typed_elems->data = ufbxi_push(&uc->result, ufbx_element*, num_typed); + ufbxi_check(typed_elems->data); + + for (size_t i = 0; i < num_typed; i++) { + typed_elems->data[i] = (ufbx_element*)(element_data + typed_offsets[i]); + } + + ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]); + } + + // Create named elements + uc->scene.elements_by_name.count = num_elements; + uc->scene.elements_by_name.data = ufbxi_push(&uc->result, ufbx_name_element, num_elements); + ufbxi_check(uc->scene.elements_by_name.data); + + for (size_t i = 0; i < num_elements; i++) { + + ufbx_element *elem = uc->scene.elements.data[i]; + ufbx_name_element *name_elem = &uc->scene.elements_by_name.data[i]; + + name_elem->name = elem->name; + name_elem->type = elem->type; + name_elem->_internal_key = ufbxi_get_name_key(elem->name.data, elem->name.length); + name_elem->element = elem; + } + + ufbxi_check(ufbxi_sort_name_elements(uc, uc->scene.elements_by_name.data, num_elements)); + + // Setup node children arrays and attribute pointers/lists + ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { + ufbx_node *node = *p_node, *parent = node->parent; + if (parent) { + parent->children.count++; + if (parent->children.data == NULL) { + parent->children.data = p_node; + } + + if (node->is_geometry_transform_helper) { + parent->geometry_transform_helper = node; + } + + // Force top-level nodes to have `UFBX_INHERIT_MODE_NORMAL` to make unit scaling work. + if (parent->is_root && uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) { + node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL; + node->inherit_mode = UFBX_INHERIT_MODE_NORMAL; + } + + // RrSs nodes inherit scale from their parent, Rrs ignore the scale of + // their _immediate_ parent, potentially multiple if chained. + if (node->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) { + node->inherit_scale_node = parent; + } else if (node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { + node->inherit_scale_node = parent->inherit_scale_node; + } + } + + ufbx_connection_list conns = ufbxi_find_dst_connections(&node->element, NULL); + + ufbxi_for_list(ufbx_connection, conn, conns) { + ufbx_element *elem = conn->src; + ufbx_element_type type = elem->type; + if (!(type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB)) continue; + + size_t index = node->all_attribs.count++; + if (index == 0) { + node->attrib = elem; + node->attrib_type = type; + } else { + if (index == 1) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &node->attrib)); + } + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &elem)); + } + + switch (elem->type) { + case UFBX_ELEMENT_MESH: node->mesh = (ufbx_mesh*)elem; break; + case UFBX_ELEMENT_LIGHT: node->light = (ufbx_light*)elem; break; + case UFBX_ELEMENT_CAMERA: node->camera = (ufbx_camera*)elem; break; + case UFBX_ELEMENT_BONE: node->bone = (ufbx_bone*)elem; break; + default: /* No shorthand */ break; + } + } + + if (node->all_attribs.count > 1) { + node->all_attribs.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, node->all_attribs.count); + ufbxi_check(node->all_attribs.data); + } else if (node->all_attribs.count == 1) { + node->all_attribs.data = &node->attrib; + } + + ufbxi_check(ufbxi_fetch_dst_elements(uc, &node->materials, &node->element, false, false, NULL, UFBX_ELEMENT_MATERIAL)); + } + + // Resolve bind pose bones that don't use the normal connection system + ufbxi_for_ptr_list(ufbx_pose, p_pose, uc->scene.poses) { + ufbx_pose *pose = *p_pose; + + // HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer + size_t num_bones = pose->bone_poses.count; + ufbxi_tmp_bone_pose *tmp_poses = (ufbxi_tmp_bone_pose*)pose->bone_poses.data; + pose->bone_poses.data = ufbxi_push(&uc->result, ufbx_bone_pose, num_bones); + ufbxi_check(pose->bone_poses.data); + + // Filter only found bones + pose->bone_poses.count = 0; + for (size_t i = 0; i < num_bones; i++) { + ufbx_element *elem = ufbxi_find_element_by_fbx_id(uc, tmp_poses[i].bone_fbx_id); + if (!elem || elem->type != UFBX_ELEMENT_NODE) continue; + + ufbx_node *node = (ufbx_node*)elem; + ufbx_bone_pose *bone = &pose->bone_poses.data[pose->bone_poses.count++]; + bone->bone_node = node; + bone->bone_to_world = tmp_poses[i].bone_to_world; + + if (pose->is_bind_pose) { + if (node->bind_pose == NULL) { + node->bind_pose = pose; + } + + ufbx_connection_list node_conns = ufbxi_find_src_connections(elem, NULL); + ufbxi_for_list(ufbx_connection, conn, node_conns) { + if (conn->dst->type != UFBX_ELEMENT_SKIN_CLUSTER) continue; + ufbx_skin_cluster *cluster = (ufbx_skin_cluster*)conn->dst; + if (ufbxi_matrix_all_zero(&cluster->bind_to_world)) { + cluster->bind_to_world = bone->bone_to_world; + } + } + } + } + ufbxi_check(ufbxi_sort_bone_poses(uc, pose)); + } + + // Fetch pointers that may break elements + + // Setup node attribute instances + for (int type = UFBX_ELEMENT_TYPE_FIRST_ATTRIB; type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB; type++) { + ufbxi_for_ptr_list(ufbx_element, p_elem, uc->scene.elements_by_type[type]) { + ufbx_element *elem = *p_elem; + ufbxi_check(ufbxi_fetch_src_elements(uc, &elem->instances, elem, false, true, NULL, UFBX_ELEMENT_NODE)); + } + } + + bool search_node = uc->version < 7000; + + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, uc->scene.skin_clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + cluster->bone_node = (ufbx_node*)ufbxi_fetch_dst_element(&cluster->element, false, NULL, UFBX_ELEMENT_NODE); + } + + ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, uc->scene.skin_deformers) { + ufbx_skin_deformer *skin = *p_skin; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &skin->clusters, &skin->element, false, true, NULL, UFBX_ELEMENT_SKIN_CLUSTER)); + + // Remove clusters without a valid `bone` + if (!uc->opts.connect_broken_elements) { + size_t num_broken = 0; + for (size_t i = 0; i < skin->clusters.count; i++) { + if (!skin->clusters.data[i]->bone_node) { + num_broken++; + } else if (num_broken > 0) { + skin->clusters.data[i - num_broken] = skin->clusters.data[i]; + } + } + skin->clusters.count -= num_broken; + } + + size_t total_weights = 0; + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + ufbxi_check(SIZE_MAX - total_weights > cluster->num_weights); + total_weights += cluster->num_weights; + } + + size_t num_vertices = 0; + + // Iterate through meshes so we can pad the vertices to the largest one + { + ufbx_connection_list conns = ufbxi_find_src_connections(&skin->element, NULL); + ufbxi_for_list(ufbx_connection, conn, conns) { + ufbx_mesh *mesh = NULL; + if (conn->dst_prop.length > 0) continue; + if (conn->dst->type == UFBX_ELEMENT_MESH) { + mesh = (ufbx_mesh*)conn->dst; + } else if (conn->dst->type == UFBX_ELEMENT_NODE) { + ufbx_node *node = (ufbx_node*)conn->dst; + if (node->geometry_transform_helper) node = node->geometry_transform_helper; + mesh = node->mesh; + } + if (!mesh) continue; + num_vertices = ufbxi_max_sz(num_vertices, mesh->num_vertices); + } + } + + if (!uc->opts.skip_skin_vertices) { + skin->vertices.count = num_vertices; + skin->vertices.data = ufbxi_push_zero(&uc->result, ufbx_skin_vertex, num_vertices); + ufbxi_check(skin->vertices.data); + + skin->weights.count = total_weights; + skin->weights.data = ufbxi_push_zero(&uc->result, ufbx_skin_weight, total_weights); + ufbxi_check(skin->weights.data); + + bool retain_all = !uc->opts.clean_skin_weights; + + // Count the number of weights per vertex + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + for (size_t i = 0; i < cluster->num_weights; i++) { + uint32_t vertex = cluster->vertices.data[i]; + if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) { + skin->vertices.data[vertex].num_weights++; + } + } + } + + ufbx_real default_dq = skin->skinning_method == UFBX_SKINNING_METHOD_DUAL_QUATERNION ? 1.0f : 0.0f; + + // Prefix sum to assign the vertex weight offsets and set up default DQ values + uint32_t offset = 0; + uint32_t max_weights = 0; + for (size_t i = 0; i < num_vertices; i++) { + skin->vertices.data[i].weight_begin = offset; + skin->vertices.data[i].dq_weight = default_dq; + uint32_t num_weights = skin->vertices.data[i].num_weights; + offset += num_weights; + skin->vertices.data[i].num_weights = 0; + + if (num_weights > max_weights) max_weights = num_weights; + } + ufbx_assert(offset <= total_weights); + skin->max_weights_per_vertex = max_weights; + + // Copy the DQ weights to vertices + for (size_t i = 0; i < skin->num_dq_weights; i++) { + uint32_t vertex = skin->dq_vertices.data[i]; + if (vertex < num_vertices) { + skin->vertices.data[vertex].dq_weight = skin->dq_weights.data[i]; + } + } + + // Copy the weights to vertices + uint32_t cluster_index = 0; + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + for (size_t i = 0; i < cluster->num_weights; i++) { + uint32_t vertex = cluster->vertices.data[i]; + if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) { + uint32_t local_index = skin->vertices.data[vertex].num_weights++; + uint32_t index = skin->vertices.data[vertex].weight_begin + local_index; + skin->weights.data[index].cluster_index = cluster_index; + skin->weights.data[index].weight = cluster->weights.data[i]; + } + } + cluster_index++; + } + + // Sort the vertex weights by descending weight value + ufbxi_check(ufbxi_sort_skin_weights(uc, skin)); + } + } + + ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, uc->scene.blend_deformers) { + ufbx_blend_deformer *blend = *p_blend; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &blend->channels, &blend->element, false, true, NULL, UFBX_ELEMENT_BLEND_CHANNEL)); + } + + ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) { + ufbx_cache_deformer *deformer = *p_deformer; + deformer->channel = ufbx_find_string(&deformer->props, "ChannelName", ufbx_empty_string); + deformer->file = (ufbx_cache_file*)ufbxi_fetch_dst_element(&deformer->element, false, NULL, UFBX_ELEMENT_CACHE_FILE); + } + + ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { + ufbx_cache_file *cache = *p_cache; + + cache->absolute_filename = ufbx_find_string(&cache->props, "CacheAbsoluteFileName", ufbx_empty_string); + cache->relative_filename = ufbx_find_string(&cache->props, "CacheFileName", ufbx_empty_string); + + cache->raw_absolute_filename = ufbx_find_blob(&cache->props, "CacheAbsoluteFileName", ufbx_empty_blob); + cache->raw_relative_filename = ufbx_find_blob(&cache->props, "CacheFileName", ufbx_empty_blob); + + int64_t type = ufbx_find_int(&cache->props, "CacheFileType", 0); + if (type >= 0 && type <= UFBX_CACHE_FILE_FORMAT_MC) { + cache->format = (ufbx_cache_file_format)type; + } + + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->filename, (ufbxi_strblob*)&cache->absolute_filename, (ufbxi_strblob*)&cache->relative_filename, false)); + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->raw_filename, (ufbxi_strblob*)&cache->raw_absolute_filename, (ufbxi_strblob*)&cache->raw_relative_filename, true)); + } + + ufbx_assert(uc->tmp_full_weights.num_items == uc->scene.blend_channels.count); + ufbx_real_list *full_weights = ufbxi_push_pop(&uc->tmp, &uc->tmp_full_weights, ufbx_real_list, uc->tmp_full_weights.num_items); + ufbxi_buf_free(&uc->tmp_full_weights); + ufbxi_check(full_weights); + + ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, uc->scene.blend_channels) { + ufbx_blend_channel *channel = *p_channel; + + ufbxi_check(ufbxi_fetch_blend_keyframes(uc, &channel->keyframes, &channel->element)); + + for (size_t i = 0; i < channel->keyframes.count; i++) { + ufbx_blend_keyframe *key = &channel->keyframes.data[i]; + key->target_weight = 1.0f; + if (i < full_weights->count) { + if (!uc->blender_full_weights) { + key->target_weight = full_weights->data[i] / (ufbx_real)100.0; + } else if (full_weights->count == key->shape->num_offsets) { + if (i == 0) { + // Duplicate `index_data` for modification if we retain DOM + if (uc->opts.retain_dom) { + full_weights->data = ufbxi_push_copy(&uc->result, ufbx_real, full_weights->count, full_weights->data); + ufbxi_check(full_weights->data); + } + ufbxi_for_list(ufbx_real, p_weight, *full_weights) { + *p_weight /= (ufbx_real)100.0; + } + } + key->shape->offset_weights = *full_weights; + } + } + } + + ufbxi_check(ufbxi_sort_blend_keyframes(uc, channel->keyframes.data, channel->keyframes.count)); + full_weights++; + + if (channel->keyframes.count > 0) { + channel->target_shape = channel->keyframes.data[channel->keyframes.count - 1].shape; + } + } + + { + // Generate and patch procedural index buffers + uint32_t *zero_indices = ufbxi_push(&uc->result, uint32_t, uc->max_zero_indices); + uint32_t *consecutive_indices = ufbxi_push(&uc->result, uint32_t, uc->max_consecutive_indices); + ufbxi_check(zero_indices && consecutive_indices); + + memset(zero_indices, 0, sizeof(uint32_t) * uc->max_zero_indices); + for (size_t i = 0; i < uc->max_consecutive_indices; i++) { + consecutive_indices[i] = (uint32_t)i; + } + + uc->zero_indices = zero_indices; + uc->consecutive_indices = consecutive_indices; + + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { + ufbx_mesh *mesh = *p_mesh; + + ufbxi_patch_index_pointer(uc, &mesh->vertex_position.indices.data); + ufbxi_patch_index_pointer(uc, &mesh->vertex_normal.indices.data); + ufbxi_patch_index_pointer(uc, &mesh->vertex_color.indices.data); + ufbxi_patch_index_pointer(uc, &mesh->vertex_crease.indices.data); + ufbxi_patch_index_pointer(uc, &mesh->face_material.data); + ufbxi_patch_index_pointer(uc, &mesh->face_group.data); + + ufbxi_patch_index_pointer(uc, &mesh->skinned_position.indices.data); + ufbxi_patch_index_pointer(uc, &mesh->skinned_normal.indices.data); + + ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { + ufbxi_patch_index_pointer(uc, &set->vertex_uv.indices.data); + ufbxi_patch_index_pointer(uc, &set->vertex_bitangent.indices.data); + ufbxi_patch_index_pointer(uc, &set->vertex_tangent.indices.data); + } + + ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { + ufbxi_patch_index_pointer(uc, &set->vertex_color.indices.data); + } + + // Generate normals if necessary + if (!mesh->vertex_normal.exists && uc->opts.generate_missing_normals) { + ufbxi_check(ufbxi_generate_normals(uc, mesh)); + } + + // Assign first UV and color sets as the "canonical" ones + if (mesh->uv_sets.count > 0) { + mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv; + mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent; + mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent; + } + if (mesh->color_sets.count > 0) { + mesh->vertex_color = mesh->color_sets.data[0].vertex_color; + } + + if (mesh->face_group_parts.count == 1) { + ufbxi_patch_index_pointer(uc, &mesh->face_group_parts.data[0].face_indices.data); + } + + ufbxi_check(ufbxi_fetch_mesh_materials(uc, &mesh->materials, &mesh->element, true)); + + // Patch materials to instances if necessary + if (mesh->materials.count > 0) { + ufbxi_for_ptr_list(ufbx_node, p_node, mesh->instances) { + ufbx_node *node = *p_node; + if (node->materials.count < mesh->materials.count && mesh->materials.data[0] != NULL) { + ufbx_material **materials = ufbxi_push(&uc->result, ufbx_material*, mesh->materials.count); + ufbxi_check(materials); + ufbxi_nounroll for (size_t i = 0; i < node->materials.count; i++) { + materials[i] = node->materials.data[i]; + } + ufbxi_nounroll for (size_t i = node->materials.count; i < mesh->materials.count; i++) { + materials[i] = mesh->materials.data[i]; + } + node->materials.data = materials; + node->materials.count = mesh->materials.count; + } + } + } + + if (uc->retain_mesh_parts) { + size_t num_parts = ufbxi_max_sz(mesh->materials.count, 1); + mesh->material_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, num_parts); + ufbxi_check(mesh->material_parts.data); + mesh->material_parts.count = num_parts; + } + + if (mesh->materials.count <= 1) { + // Use the shared consecutive index buffer for mesh faces if there's only one material + // See HACK(consecutive-faces) in `ufbxi_read_mesh()`. + if (mesh->material_parts.count > 0) { + ufbx_mesh_part *part = &mesh->material_parts.data[0]; + part->num_faces = mesh->num_faces; + part->num_triangles = mesh->num_triangles; + part->num_empty_faces = mesh->num_empty_faces; + part->num_point_faces = mesh->num_point_faces; + part->num_line_faces = mesh->num_line_faces; + part->face_indices.data = uc->consecutive_indices; + part->face_indices.count = mesh->num_faces; + mesh->material_part_usage_order.data = uc->zero_indices; + mesh->material_part_usage_order.count = 1; + } + + if (mesh->materials.count == 1) { + mesh->face_material.data = uc->zero_indices; + mesh->face_material.count = mesh->num_faces; + } else { + mesh->face_material.data = NULL; + mesh->face_material.count = 0; + } + } else if (mesh->materials.count > 0) { + ufbxi_check(ufbxi_finalize_mesh_material(&uc->result, &uc->error, mesh)); + } + + // Fetch deformers + ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->skin_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_SKIN_DEFORMER)); + ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->blend_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_BLEND_DEFORMER)); + ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->cache_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_CACHE_DEFORMER)); + ufbxi_check(ufbxi_fetch_deformers(uc, &mesh->all_deformers, &mesh->element, search_node)); + + // Vertex position must always exist if not explicitly allowed to be missing + if (!mesh->vertex_position.exists && !uc->opts.allow_missing_vertex_position) { + ufbxi_check(mesh->num_indices == 0); + mesh->vertex_position.exists = true; + mesh->vertex_position.unique_per_vertex = true; + mesh->skinned_position.exists = true; + mesh->skinned_position.unique_per_vertex = true; + } + + // Update metadata + if (mesh->max_face_triangles > uc->scene.metadata.max_face_triangles) { + uc->scene.metadata.max_face_triangles = mesh->max_face_triangles; + } + } + } + + ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, uc->scene.stereo_cameras) { + ufbx_stereo_camera *stereo = *p_stereo; + stereo->left = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_LeftCamera, UFBX_ELEMENT_CAMERA); + stereo->right = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_RightCamera, UFBX_ELEMENT_CAMERA); + } + + ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) { + ufbx_nurbs_curve *curve = *p_curve; + ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &curve->basis)); + } + + ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) { + ufbx_nurbs_surface *surface = *p_surface; + ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_u)); + ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_v)); + + surface->material = (ufbx_material*)ufbxi_fetch_dst_element(&surface->element, true, NULL, UFBX_ELEMENT_MATERIAL); + } + + ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, uc->scene.anim_stacks) { + ufbx_anim_stack *stack = *p_stack; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &stack->layers, &stack->element, false, true, NULL, UFBX_ELEMENT_ANIM_LAYER)); + + ufbxi_check(ufbxi_push_anim(uc, &stack->anim, stack->layers.data, stack->layers.count)); + } + + ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, uc->scene.anim_layers) { + ufbx_anim_layer *layer = *p_layer; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->anim_values, &layer->element, false, true, NULL, UFBX_ELEMENT_ANIM_VALUE)); + + ufbxi_check(ufbxi_push_anim(uc, &layer->anim, p_layer, 1)); + + uint32_t min_id = UINT32_MAX, max_id = 0; + + // Combine the animated properties with elements (potentially duplicates!) + size_t num_anim_props = 0; + ufbxi_for_ptr_list(ufbx_anim_value, p_value, layer->anim_values) { + ufbx_anim_value *value = *p_value; + ufbxi_for_list(ufbx_connection, ac, value->element.connections_src) { + if (ac->src_prop.length == 0 && ac->dst_prop.length > 0) { + ufbx_anim_prop *aprop = ufbxi_push(&uc->tmp_stack, ufbx_anim_prop, 1); + uint32_t id = ac->dst->element_id; + min_id = ufbxi_min32(min_id, id); + max_id = ufbxi_max32(max_id, id); + uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1; + layer->_element_id_bitmask[(id >> 5) & id_mask] |= 1u << (id & 31); + ufbxi_check(aprop); + aprop->anim_value = value; + aprop->element = ac->dst; + aprop->_internal_key = ufbxi_get_name_key(ac->dst_prop.data, ac->dst_prop.length); + aprop->prop_name = ac->dst_prop; + num_anim_props++; + } + } + } + + if (min_id != UINT32_MAX) { + layer->_min_element_id = min_id; + layer->_max_element_id = max_id; + } + + switch (ufbxi_find_int(&layer->props, ufbxi_BlendMode, 0)) { + case 0: // Additive + layer->blended = true; + layer->additive = true; + break; + case 1: // Override + layer->blended = false; + layer->additive = false; + break; + case 2: // Override Passthrough + layer->blended = true; + layer->additive = false; + break; + default: // Unknown + layer->blended = false; + layer->additive = false; + break; + } + + ufbx_prop *weight_prop = ufbxi_find_prop(&layer->props, ufbxi_Weight); + if (weight_prop) { + layer->weight = weight_prop->value_real / (ufbx_real)100.0; + if (layer->weight < 0.0f) layer->weight = 0.0f; + if (layer->weight > 0.99999f) layer->weight = 1.0f; + layer->weight_is_animated = (weight_prop->flags & UFBX_PROP_FLAG_ANIMATED) != 0; + } else { + layer->weight = 1.0f; + layer->weight_is_animated = false; + } + layer->compose_rotation = ufbxi_find_int(&layer->props, ufbxi_RotationAccumulationMode, 0) == 0; + layer->compose_scale = ufbxi_find_int(&layer->props, ufbxi_ScaleAccumulationMode, 0) == 0; + + // Add a dummy NULL element animated prop at the end so we can iterate + // animated props without worrying about boundary conditions.. + { + ufbx_anim_prop *aprop = ufbxi_push_zero(&uc->tmp_stack, ufbx_anim_prop, 1); + ufbxi_check(aprop); + } + + layer->anim_props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_anim_prop, num_anim_props + 1); + ufbxi_check(layer->anim_props.data); + layer->anim_props.count = num_anim_props; + ufbxi_check(ufbxi_sort_anim_props(uc, layer->anim_props.data, layer->anim_props.count)); + } + + ufbxi_for_ptr_list(ufbx_anim_value, p_value, uc->scene.anim_values) { + ufbx_anim_value *value = *p_value; + + // TODO: Search for things like d|Visibility with a constructed name + value->default_value.x = ufbxi_find_real(&value->props, ufbxi_X, value->default_value.x); + value->default_value.x = ufbxi_find_real(&value->props, ufbxi_d_X, value->default_value.x); + value->default_value.y = ufbxi_find_real(&value->props, ufbxi_Y, value->default_value.y); + value->default_value.y = ufbxi_find_real(&value->props, ufbxi_d_Y, value->default_value.y); + value->default_value.z = ufbxi_find_real(&value->props, ufbxi_Z, value->default_value.z); + value->default_value.z = ufbxi_find_real(&value->props, ufbxi_d_Z, value->default_value.z); + + ufbxi_for_list(ufbx_connection, conn, value->element.connections_dst) { + if (conn->src->type == UFBX_ELEMENT_ANIM_CURVE && conn->src_prop.length == 0) { + ufbx_anim_curve *curve = (ufbx_anim_curve*)conn->src; + + uint32_t index = 0; + const char *name = conn->dst_prop.data; + if (name == ufbxi_Y || name == ufbxi_d_Y) index = 1; + if (name == ufbxi_Z || name == ufbxi_d_Z) index = 2; + + ufbx_prop *prop = ufbx_find_prop_len(&value->props, conn->dst_prop.data, conn->dst_prop.length); + if (prop) { + value->default_value.v[index] = prop->value_real; + } + value->curves[index] = curve; + } + } + } + + ufbxi_for_ptr_list(ufbx_anim_curve, p_curve, uc->scene.anim_curves) { + ufbx_anim_curve *curve = *p_curve; + if (curve->keyframes.count > 0) { + curve->min_time = curve->keyframes.data[0].time; + curve->max_time = curve->keyframes.data[curve->keyframes.count - 1].time; + } + } + + ufbxi_for_ptr_list(ufbx_shader, p_shader, uc->scene.shaders) { + ufbx_shader *shader = *p_shader; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &shader->bindings, &shader->element, false, false, NULL, UFBX_ELEMENT_SHADER_BINDING)); + + ufbx_prop *api = ufbx_find_prop(&shader->props, "RenderAPI"); + if (api) { + if (!strcmp(api->value_str.data, "ARNOLD_SHADER_ID")) { + shader->type = UFBX_SHADER_ARNOLD_STANDARD_SURFACE; + } else if (!strcmp(api->value_str.data, "OSL")) { + shader->type = UFBX_SHADER_OSL_STANDARD_SURFACE; + } else if (!strcmp(api->value_str.data, "SFX_PBS_SHADER")) { + shader->type = UFBX_SHADER_SHADERFX_GRAPH; + } + } + } + + ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) { + ufbx_material *material = *p_material; + material->shader = (ufbx_shader*)ufbxi_fetch_src_element(&material->element, false, NULL, UFBX_ELEMENT_SHADER); + + if (!strcmp(material->shading_model_name.data, "lambert") || !strcmp(material->shading_model_name.data, "Lambert")) { + material->shader_type = UFBX_SHADER_FBX_LAMBERT; + } else if (!strcmp(material->shading_model_name.data, "phong") || !strcmp(material->shading_model_name.data, "Phong")) { + material->shader_type = UFBX_SHADER_FBX_PHONG; + } + + if (material->shader) { + material->shader_type = material->shader->type; + } else { + if (uc->opts.use_blender_pbr_material && uc->exporter == UFBX_EXPORTER_BLENDER_BINARY && uc->exporter_version >= ufbx_pack_version(4,12,0)) { + material->shader_type = UFBX_SHADER_BLENDER_PHONG; + } + + // TODO: Is this too strict? + if (material->shader_type == UFBX_SHADER_UNKNOWN) { + uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDa", 0); + uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDb", 0); + if (classid_a == 0x3d6b1cecu && classid_b == 0xdeadc001u) { + material->shader_type = UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL; + material->shader_prop_prefix.data = "3dsMax|Parameters|"; + material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); + } else if (classid_a == 0xf1551e33u && classid_b == 0x37fb1337u) { + material->shader_type = UFBX_SHADER_OPENPBR_MATERIAL; + material->shader_prop_prefix.data = "3dsMax|Parameters|"; + material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); + } else if (classid_a == 0x38420192u && classid_b == 0x45fe4e1bu) { + material->shader_type = UFBX_SHADER_GLTF_MATERIAL; + material->shader_prop_prefix.data = "3dsMax|"; + material->shader_prop_prefix.length = strlen("3dsMax|"); + } else if (classid_a == 0xd00f1e00u && classid_b == 0xbe77e500u) { + material->shader_type = UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH; + material->shader_prop_prefix.data = "3dsMax|main|"; + material->shader_prop_prefix.length = strlen("3dsMax|main|"); + } else if (classid_a == 0xd00f1e00u && classid_b == 0x01dbad33u) { + material->shader_type = UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS; + material->shader_prop_prefix.data = "3dsMax|main|"; + material->shader_prop_prefix.length = strlen("3dsMax|main|"); + } + } + } + + ufbxi_check(ufbxi_fetch_textures(uc, &material->textures, &material->element, false)); + } + + // Ugh.. Patch the textures from meshes for legacy LayerElement-style textures + { + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { + ufbx_mesh *mesh = *p_mesh; + size_t num_materials = mesh->materials.count; + + ufbxi_mesh_extra *extra = (ufbxi_mesh_extra*)ufbxi_get_element_extra(uc, mesh->element.element_id); + if (!extra) continue; + if (num_materials == 0) continue; + + // TODO: This leaks currently to result, probably doesn't matter.. + ufbx_texture_list textures; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &textures, &mesh->element, true, false, NULL, UFBX_ELEMENT_TEXTURE)); + + size_t num_material_textures = 0; + ufbxi_for(ufbxi_tmp_mesh_texture, tex, extra->texture_arr, extra->texture_count) { + if (tex->all_same) { + int32_t texture_id = tex->num_faces > 0 ? (int32_t)tex->face_texture[0] : 0; + if (texture_id >= 0 && (size_t)texture_id < textures.count) { + ufbxi_tmp_material_texture *mat_texs = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, num_materials); + ufbxi_check(mat_texs); + num_material_textures += num_materials; + for (size_t i = 0; i < num_materials; i++) { + mat_texs[i].material_id = (int32_t)i; + mat_texs[i].texture_id = texture_id; + mat_texs[i].prop_name = tex->prop_name; + } + } + } else if (mesh->face_material.count) { + size_t num_faces = ufbxi_min_sz(tex->num_faces, mesh->num_faces); + int32_t prev_material = -1; + int32_t prev_texture = -1; + for (size_t i = 0; i < num_faces; i++) { + int32_t texture_id = (int32_t)tex->face_texture[i]; + int32_t material_id = (int32_t)mesh->face_material.data[i]; + if (texture_id < 0 || (size_t)texture_id >= textures.count) continue; + if (material_id < 0 || (size_t)material_id >= num_materials) continue; + if (material_id == prev_material && texture_id == prev_texture) continue; + prev_material = material_id; + prev_texture = texture_id; + + ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1); + ufbxi_check(mat_tex); + mat_tex->material_id = material_id; + mat_tex->texture_id = texture_id; + mat_tex->prop_name = tex->prop_name; + num_material_textures++; + } + } + } + + // Push a sentinel material texture to the end so we don't need to + // duplicate the material texture flushing code twice. + { + ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1); + ufbxi_check(mat_tex); + mat_tex->material_id = -1; + mat_tex->texture_id = -1; + mat_tex->prop_name = ufbx_empty_string; + } + + ufbxi_tmp_material_texture *mat_texs = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_material_texture, num_material_textures + 1); + ufbxi_check(mat_texs); + ufbxi_check(ufbxi_sort_tmp_material_textures(uc, mat_texs, num_material_textures)); + + int32_t prev_material = -2; + int32_t prev_texture = -2; + const char *prev_prop = NULL; + size_t num_textures_in_material = 0; + for (size_t i = 0; i < num_material_textures + 1; i++) { + ufbxi_tmp_material_texture mat_tex = mat_texs[i]; + if (mat_tex.material_id != prev_material) { + if (prev_material >= 0 && num_textures_in_material > 0) { + ufbx_material *mat = mesh->materials.data[prev_material]; + if (mat && mat->textures.count == 0) { + ufbx_material_texture *texs = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures_in_material); + ufbxi_check(texs); + mat->textures.data = texs; + mat->textures.count = num_textures_in_material; + } else { + ufbxi_pop(&uc->tmp_stack, ufbx_material_texture, num_textures_in_material, NULL); + } + } + + if (mat_tex.material_id < 0) break; + prev_material = mat_tex.material_id; + prev_texture = -1; + prev_prop = NULL; + num_textures_in_material = 0; + } + if (mat_tex.texture_id == prev_texture && mat_tex.prop_name.data == prev_prop) continue; + prev_texture = mat_tex.texture_id; + prev_prop = mat_tex.prop_name.data; + + ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1); + ufbxi_check(tex); + ufbx_assert(prev_texture >= 0 && (size_t)prev_texture < textures.count); + tex->texture = textures.data[prev_texture]; + tex->shader_prop = tex->material_prop = mat_tex.prop_name; + num_textures_in_material++; + } + } + } + + ufbxi_check(ufbxi_resolve_file_content(uc)); + + ufbxi_for_ptr_list(ufbx_texture, p_texture, uc->scene.textures) { + ufbx_texture *texture = *p_texture; + ufbxi_texture_extra *extra = (ufbxi_texture_extra*)ufbxi_get_element_extra(uc, texture->element.element_id); + + ufbx_prop *uv_set = ufbxi_find_prop(&texture->props, ufbxi_UVSet); + if (uv_set) { + texture->uv_set = uv_set->value_str; + } else { + texture->uv_set = ufbx_empty_string; + } + + texture->video = (ufbx_video*)ufbxi_fetch_dst_element(&texture->element, false, NULL, UFBX_ELEMENT_VIDEO); + if (texture->video) { + texture->content = texture->video->content; + } + + ufbxi_check(ufbxi_finalize_shader_texture(uc, texture)); + + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->filename, (ufbxi_strblob*)&texture->absolute_filename, (ufbxi_strblob*)&texture->relative_filename, false)); + ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->raw_filename, (ufbxi_strblob*)&texture->raw_absolute_filename, (ufbxi_strblob*)&texture->raw_relative_filename, true)); + + // Fetch layered texture layers and patch alphas/blend modes + if (texture->type == UFBX_TEXTURE_LAYERED) { + ufbxi_check(ufbxi_fetch_texture_layers(uc, &texture->layers, &texture->element)); + if (extra) { + for (size_t i = 0, num = ufbxi_min_sz(extra->num_alphas, texture->layers.count); i < num; i++) { + texture->layers.data[i].alpha = extra->alphas[i]; + } + for (size_t i = 0, num = ufbxi_min_sz(extra->num_blend_modes, texture->layers.count); i < num; i++) { + int32_t mode = extra->blend_modes[i]; + if (mode >= 0 && mode < UFBX_BLEND_OVERLAY) { + texture->layers.data[i].blend_mode = (ufbx_blend_mode)mode; + } + } + } + } + + ufbxi_check(ufbxi_insert_texture_file(uc, texture)); + } + + ufbxi_propagate_main_textures(&uc->scene); + ufbxi_check(ufbxi_pop_texture_files(uc)); + + // Second pass to fetch material maps + ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) { + ufbx_material *material = *p_material; + + ufbxi_check(ufbxi_sort_material_textures(uc, material->textures.data, material->textures.count)); + ufbxi_fetch_maps(&uc->scene, material); + + // Fetch `ufbx_material_texture.shader_prop` names + if (material->shader) { + ufbxi_for_ptr_list(ufbx_shader_binding, p_binding, material->shader->bindings) { + ufbx_shader_binding *binding = *p_binding; + + ufbxi_for_list(ufbx_shader_prop_binding, prop, binding->prop_bindings) { + ufbx_string name = prop->material_prop; + + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_material_texture, 4, &index, material->textures.data, 0, material->textures.count, + ( ufbxi_str_less(a->material_prop, name) ), ( a->material_prop.data == name.data )); + for (; index < material->textures.count && material->textures.data[index].shader_prop.data == name.data; index++) { + material->textures.data[index].shader_prop = prop->shader_prop; + } + } + } + } + } + + ufbxi_for_ptr_list(ufbx_display_layer, p_layer, uc->scene.display_layers) { + ufbx_display_layer *layer = *p_layer; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->nodes, &layer->element, false, true, NULL, UFBX_ELEMENT_NODE)); + } + + ufbxi_for_ptr_list(ufbx_selection_set, p_set, uc->scene.selection_sets) { + ufbx_selection_set *set = *p_set; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &set->nodes, &set->element, false, true, NULL, UFBX_ELEMENT_SELECTION_NODE)); + } + + ufbxi_for_ptr_list(ufbx_selection_node, p_node, uc->scene.selection_nodes) { + ufbx_selection_node *node = *p_node; + node->target_node = (ufbx_node*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_NODE); + node->target_mesh = (ufbx_mesh*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_MESH); + if (!node->target_mesh && node->target_node) { + node->target_mesh = node->target_node->mesh; + } else if (!node->target_node && node->target_mesh && node->target_mesh->instances.count > 0) { + node->target_node = node->target_mesh->instances.data[0]; + } + + ufbx_mesh *mesh = node->target_mesh; + if (mesh) { + ufbxi_check(ufbxi_validate_indices(uc, &node->vertices, mesh->num_vertices)); + ufbxi_check(ufbxi_validate_indices(uc, &node->edges, mesh->num_edges)); + ufbxi_check(ufbxi_validate_indices(uc, &node->faces, mesh->num_faces)); + } + } + + ufbxi_for_ptr_list(ufbx_constraint, p_constraint, uc->scene.constraints) { + ufbx_constraint *constraint = *p_constraint; + + size_t tmp_base = uc->tmp_stack.num_items; + + // Find property connections in _both_ src and dst connections as they are inconsistent + // in pre-7000 files. For example "Constrained Object" is a "PO" connection in 6100. + ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_src) { + if (conn->src_prop.length == 0 || conn->dst->type != UFBX_ELEMENT_NODE) continue; + ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->dst, conn->src_prop.data)); + } + ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_dst) { + if (conn->dst_prop.length == 0 || conn->src->type != UFBX_ELEMENT_NODE) continue; + ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->src, conn->dst_prop.data)); + } + + size_t num_targets = uc->tmp_stack.num_items - tmp_base; + constraint->targets.count = num_targets; + constraint->targets.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_constraint_target, num_targets); + ufbxi_check(constraint->targets.data); + } + + ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, uc->scene.audio_layers) { + ufbx_audio_layer *layer = *p_layer; + ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->clips, &layer->element, false, true, NULL, UFBX_ELEMENT_AUDIO_CLIP)); + } + + ufbxi_for_ptr_list(ufbx_lod_group, p_lod, uc->scene.lod_groups) { + ufbxi_check(ufbxi_finalize_lod_group(uc, *p_lod)); + } + + ufbxi_check(ufbxi_fetch_file_textures(uc)); + + // NOTE: This will be patched over in `ufbxi_update_scene()` if there are `anim_layers` + if (uc->scene.anim_layers.count == 0) { + ufbxi_check(ufbxi_push_anim(uc, &uc->scene.anim, NULL, 0)); + } + + uc->scene.metadata.ktime_second = uc->ktime_sec; + + // Maya seems to use scale of 100/3, Blender binary uses exactly 33, ASCII has always value of 1.0 + if (uc->version < 6000) { + uc->scene.metadata.bone_prop_size_unit = 1.0f; + } else if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { + uc->scene.metadata.bone_prop_size_unit = 33.0f; + } else if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { + uc->scene.metadata.bone_prop_size_unit = 1.0f; + } else { + uc->scene.metadata.bone_prop_size_unit = (ufbx_real)(100.0/3.0); + } + if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { + uc->scene.metadata.bone_prop_limb_length_relative = false; + } else { + uc->scene.metadata.bone_prop_limb_length_relative = true; + } + + return 1; +} + +// -- Interpret the read scene + +static ufbxi_forceinline void ufbxi_add_translate(ufbx_transform *t, ufbx_vec3 v) +{ + t->translation.x += v.x; + t->translation.y += v.y; + t->translation.z += v.z; +} + +static ufbxi_forceinline void ufbxi_sub_translate(ufbx_transform *t, ufbx_vec3 v) +{ + t->translation.x -= v.x; + t->translation.y -= v.y; + t->translation.z -= v.z; +} + +static ufbxi_forceinline void ufbxi_mul_scale(ufbx_transform *t, ufbx_vec3 v) +{ + t->translation.x *= v.x; + t->translation.y *= v.y; + t->translation.z *= v.z; + t->scale.x *= v.x; + t->scale.y *= v.y; + t->scale.z *= v.z; +} + +static ufbxi_forceinline void ufbxi_mul_scale_real(ufbx_transform *t, ufbx_real v) +{ + t->translation.x *= v; + t->translation.y *= v; + t->translation.z *= v; + t->scale.x *= v; + t->scale.y *= v; + t->scale.z *= v; +} + +static ufbxi_noinline ufbx_quat ufbxi_mul_quat(ufbx_quat a, ufbx_quat b) +{ + ufbx_quat r; + r.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; + r.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; + r.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; + r.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; + return r; +} + +static ufbxi_forceinline void ufbxi_add_weighted_vec3(ufbx_vec3 *r, ufbx_vec3 b, ufbx_real w) +{ + r->x += b.x * w; + r->y += b.y * w; + r->z += b.z * w; +} + +static ufbxi_forceinline void ufbxi_add_weighted_quat(ufbx_quat *r, ufbx_quat b, ufbx_real w) +{ + r->x += b.x * w; + r->y += b.y * w; + r->z += b.z * w; + r->w += b.w * w; +} + +static ufbxi_noinline void ufbxi_add_weighted_mat(ufbx_matrix *r, const ufbx_matrix *b, ufbx_real w) +{ + ufbxi_add_weighted_vec3(&r->cols[0], b->cols[0], w); + ufbxi_add_weighted_vec3(&r->cols[1], b->cols[1], w); + ufbxi_add_weighted_vec3(&r->cols[2], b->cols[2], w); + ufbxi_add_weighted_vec3(&r->cols[3], b->cols[3], w); +} + +static void ufbxi_mul_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order) +{ + if (ufbxi_is_vec3_zero(v)) return; + + ufbx_quat q = ufbx_euler_to_quat(v, order); + if (t->rotation.w != 1.0) { + t->rotation = ufbxi_mul_quat(q, t->rotation); + } else { + t->rotation = q; + } + + if (!ufbxi_is_vec3_zero(t->translation)) { + t->translation = ufbx_quat_rotate_vec3(q, t->translation); + } +} + +static void ufbxi_mul_rotate_quat(ufbx_transform *t, ufbx_quat q) +{ + if (ufbxi_is_quat_identity(q)) return; + + if (t->rotation.w != 1.0) { + t->rotation = ufbxi_mul_quat(q, t->rotation); + } else { + t->rotation = q; + } + + if (!ufbxi_is_vec3_zero(t->translation)) { + t->translation = ufbx_quat_rotate_vec3(q, t->translation); + } +} + +static void ufbxi_mul_inv_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order) +{ + if (ufbxi_is_vec3_zero(v)) return; + + ufbx_quat q = ufbx_euler_to_quat(v, order); + q.x = -q.x; q.y = -q.y; q.z = -q.z; + if (t->rotation.w != 1.0) { + t->rotation = ufbxi_mul_quat(q, t->rotation); + } else { + t->rotation = q; + } + + if (!ufbxi_is_vec3_zero(t->translation)) { + t->translation = ufbx_quat_rotate_vec3(q, t->translation); + } +} + +// -- Updating state from properties + +ufbxi_forceinline static void ufbxi_mirror_translation(ufbx_vec3 *p_vec, ufbx_mirror_axis axis) +{ + ufbxi_dev_assert(axis); + p_vec->v[axis - 1] = -p_vec->v[axis - 1]; +} + +ufbxi_forceinline static void ufbxi_mirror_rotation(ufbx_quat *p_quat, ufbx_mirror_axis axis) +{ + ufbxi_dev_assert(axis); + p_quat->v[axis % 3] = -p_quat->v[axis % 3]; + p_quat->v[(axis + 1) % 3] = -p_quat->v[(axis + 1) % 3]; +} + +ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node) +{ + ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + // WorldTransform = ParentWorldTransform * T * R * S * (OT * OR * OS) + + ufbxi_mul_scale(&t, scaling); + ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_add_translate(&t, translation); + + if (node->has_adjust_transform) { + t.translation.x *= node->adjust_translation_scale; + t.translation.y *= node->adjust_translation_scale; + t.translation.z *= node->adjust_translation_scale; + } + + if (node->adjust_mirror_axis) { + ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis); + ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); + } + + return t; +} + +ufbxi_noinline static ufbx_transform ufbxi_get_transform(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node, const ufbx_vec3 *translation_scale) +{ + ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scale_offset = ufbxi_find_vec3(props, ufbxi_ScalingOffset, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rot_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f); + + ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Lcl_Translation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); + + ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + // WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1 + // NOTE: Rpost is inverted (!) after converting from PostRotation Euler angles + + if (translation_scale) { + translation.x *= translation_scale->x; + translation.y *= translation_scale->y; + translation.z *= translation_scale->z; + } + + if (node->has_adjust_transform) { + ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation); + ufbxi_mul_scale_real(&t, node->adjust_post_scale); + } + + ufbxi_sub_translate(&t, scale_pivot); + ufbxi_mul_scale(&t, scaling); + ufbxi_add_translate(&t, scale_pivot); + + ufbxi_add_translate(&t, scale_offset); + + ufbxi_sub_translate(&t, rot_pivot); + ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_mul_rotate(&t, rotation, order); + ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_add_translate(&t, rot_pivot); + + ufbxi_add_translate(&t, rot_offset); + + ufbxi_add_translate(&t, translation); + + if (node->has_adjust_transform) { + ufbxi_add_translate(&t, node->adjust_pre_translation); + ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation); + ufbxi_mul_scale_real(&t, node->adjust_pre_scale); + t.translation.x *= node->adjust_translation_scale; + t.translation.y *= node->adjust_translation_scale; + t.translation.z *= node->adjust_translation_scale; + } + + if (node->adjust_mirror_axis) { + ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis); + ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); + } + + return t; +} + +ufbxi_noinline static ufbx_quat ufbxi_get_rotation(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node) +{ + ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + if (node->has_adjust_transform) { + ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation); + } + + ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_mul_rotate(&t, rotation, order); + ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ); + + if (node->has_adjust_transform) { + ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation); + } + + if (node->adjust_mirror_axis) { + ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); + } + + return t.rotation; +} + +ufbxi_noinline static ufbx_vec3 ufbxi_get_scale(const ufbx_props *props, const ufbx_node *node) +{ + ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + if (node->has_adjust_transform) { + ufbxi_mul_scale_real(&t, node->adjust_post_scale); + } + + ufbxi_mul_scale(&t, scaling); + + if (node->has_adjust_transform) { + ufbxi_mul_scale_real(&t, node->adjust_pre_scale); + } + + return t.scale; +} + +ufbxi_noinline static ufbx_transform ufbxi_get_texture_transform(const ufbx_props *props) +{ + ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_TextureScalingPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_TextureRotationPivot, 0.0f, 0.0f, 0.0f); + + ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + ufbxi_sub_translate(&t, scale_pivot); + ufbxi_mul_scale(&t, scaling); + ufbxi_add_translate(&t, scale_pivot); + + ufbxi_sub_translate(&t, rot_pivot); + ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_add_translate(&t, rot_pivot); + + ufbxi_add_translate(&t, translation); + + if (ufbxi_find_int(props, ufbxi_UVSwap, 0) != 0) { + const ufbx_vec3 swap_scale = { -1.0f, 0.0f, 0.0f }; + const ufbx_vec3 swap_rotate = { 0.0f, 0.0f, -90.0f }; + ufbxi_mul_scale(&t, swap_scale); + ufbxi_mul_rotate(&t, swap_rotate, UFBX_ROTATION_ORDER_XYZ); + } + + return t; +} + +ufbxi_noinline static ufbx_transform ufbxi_get_constraint_transform(const ufbx_props *props) +{ + ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f); + ufbx_vec3 rotation_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f); + + ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; + + ufbxi_mul_scale(&t, scaling); + ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); + ufbxi_mul_rotate(&t, rotation_offset, UFBX_ROTATION_ORDER_XYZ); + ufbxi_add_translate(&t, translation); + + return t; +} + +ufbxi_noinline static void ufbxi_update_node(ufbx_node *node, const ufbx_transform_override *overrides, size_t num_overrides) +{ + node->rotation_order = (ufbx_rotation_order)ufbxi_find_enum(&node->props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); + node->euler_rotation = ufbxi_find_vec3(&node->props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); + + if (!node->is_root) { + const ufbx_vec3 *transform_scale = NULL; + if (node->parent && node->parent->scale_helper) { + transform_scale = &node->parent->scale_helper->local_transform.scale; + } + node->local_transform = ufbxi_get_transform(&node->props, node->rotation_order, node, transform_scale); + if (node->is_scale_helper && node->parent && node->parent->inherit_scale_node) { + ufbx_node *scale_parent = node->parent->inherit_scale_node; + if (scale_parent->scale_helper) { + ufbx_vec3 inherit_scale = scale_parent->scale_helper->local_transform.scale; + node->local_transform.scale.x *= inherit_scale.x; + node->local_transform.scale.y *= inherit_scale.y; + node->local_transform.scale.z *= inherit_scale.z; + } + } + + if (num_overrides > 0) { + uint32_t typed_id = node->typed_id; + size_t override_ix = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_transform_override, 16, &override_ix, overrides, 0, num_overrides, + ( a->node_id < typed_id ), ( a->node_id == typed_id )); + if (override_ix != SIZE_MAX) { + node->local_transform = overrides[override_ix].transform; + } + } + node->node_to_parent = ufbx_transform_to_matrix(&node->local_transform); + node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node); + } else { + node->geometry_transform = ufbx_identity_transform; + } + + ufbx_matrix unscaled_node_to_parent = ufbxi_unscaled_transform_to_matrix(&node->local_transform); + + node->inherit_scale = node->local_transform.scale; + + ufbx_node *parent = node->parent; + if (parent) { + if (node->inherit_mode == UFBX_INHERIT_MODE_NORMAL) { + node->node_to_world = ufbx_matrix_mul(&parent->node_to_world, &node->node_to_parent); + node->unscaled_node_to_world = ufbx_matrix_mul(&parent->node_to_world, &unscaled_node_to_parent); + } else { + ufbx_transform transform = node->local_transform; + + ufbx_vec3 parent_scale = ufbxi_one_vec3; + if (node->inherit_scale_node) { + parent_scale = node->inherit_scale_node->inherit_scale; + } + + transform.scale.x *= parent_scale.x; + transform.scale.y *= parent_scale.y; + transform.scale.z *= parent_scale.z; + transform.translation.x *= parent->inherit_scale.x; + transform.translation.y *= parent->inherit_scale.y; + transform.translation.z *= parent->inherit_scale.z; + + ufbx_matrix node_to_unscaled_parent = ufbx_transform_to_matrix(&transform); + ufbx_matrix unscaled_node_to_unscaled_parent = ufbxi_unscaled_transform_to_matrix(&transform); + + node->inherit_scale = transform.scale; + node->node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &node_to_unscaled_parent); + node->unscaled_node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &unscaled_node_to_unscaled_parent); + } + } else { + node->node_to_world = node->node_to_parent; + node->unscaled_node_to_world = unscaled_node_to_parent; + } + + if (!ufbxi_is_transform_identity(&node->geometry_transform)) { + node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform); + node->geometry_to_world = ufbx_matrix_mul(&node->node_to_world, &node->geometry_to_node); + node->has_geometry_transform = true; + } else { + node->geometry_to_node = ufbx_identity_matrix; + node->geometry_to_world = node->node_to_world; + node->has_geometry_transform = false; + } + + node->visible = ufbxi_find_int(&node->props, ufbxi_Visibility, 1) != 0; +} + +ufbxi_noinline static void ufbxi_update_light(ufbx_light *light) +{ + // NOTE: FBX seems to store intensities 100x of what's specified in at least + // Maya and Blender, should there be a quirks mode to not do this for specific + // exporters. Does the FBX SDK do this transparently as well? + light->intensity = ufbxi_find_real(&light->props, ufbxi_Intensity, (ufbx_real)100.0) / (ufbx_real)100.0; + + light->color = ufbxi_find_vec3(&light->props, ufbxi_Color, 1.0f, 1.0f, 1.0f); + light->type = (ufbx_light_type)ufbxi_find_enum(&light->props, ufbxi_LightType, 0, UFBX_LIGHT_VOLUME); + light->decay = (ufbx_light_decay)ufbxi_find_enum(&light->props, ufbxi_DecayType, UFBX_LIGHT_DECAY_NONE, UFBX_LIGHT_DECAY_CUBIC); + light->area_shape = (ufbx_light_area_shape)ufbxi_find_enum(&light->props, ufbxi_AreaLightShape, 0, UFBX_LIGHT_AREA_SHAPE_SPHERE); + light->inner_angle = ufbxi_find_real(&light->props, ufbxi_HotSpot, 0.0f); + light->inner_angle = ufbxi_find_real(&light->props, ufbxi_InnerAngle, light->inner_angle); + light->outer_angle = ufbxi_find_real(&light->props, ufbxi_Cone_angle, 0.0f); + light->outer_angle = ufbxi_find_real(&light->props, ufbxi_ConeAngle, light->outer_angle); + light->outer_angle = ufbxi_find_real(&light->props, ufbxi_OuterAngle, light->outer_angle); + light->cast_light = ufbxi_find_int(&light->props, ufbxi_CastLight, 1) != 0; + light->cast_shadows = ufbxi_find_int(&light->props, ufbxi_CastShadows, 0) != 0; +} + +typedef struct { + // 1/1000 decimal fixed point for size + uint16_t film_size_x, film_size_y; +} ufbxi_aperture_format; + +static const ufbxi_aperture_format ufbxi_aperture_formats[] = { + { 1000, 1000, }, // UFBX_APERTURE_FORMAT_CUSTOM + { 404, 295, }, // UFBX_APERTURE_FORMAT_16MM_THEATRICAL + { 493, 292, }, // UFBX_APERTURE_FORMAT_SUPER_16MM + { 864, 630, }, // UFBX_APERTURE_FORMAT_35MM_ACADEMY + { 816, 612, }, // UFBX_APERTURE_FORMAT_35MM_TV_PROJECTION + { 980, 735, }, // UFBX_APERTURE_FORMAT_35MM_FULL_APERTURE + { 825, 446, }, // UFBX_APERTURE_FORMAT_35MM_185_PROJECTION + { 864, 732, }, // UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC + { 2066, 906, }, // UFBX_APERTURE_FORMAT_70MM_PROJECTION + { 1485, 991, }, // UFBX_APERTURE_FORMAT_VISTAVISION + { 2080, 1480, }, // UFBX_APERTURE_FORMAT_DYNAVISION + { 2772, 2072, }, // UFBX_APERTURE_FORMAT_IMAX +}; + +ufbxi_noinline static void ufbxi_update_camera(ufbx_scene *scene, ufbx_camera *camera) +{ + camera->projection_mode = (ufbx_projection_mode)ufbxi_find_enum(&camera->props, ufbxi_CameraProjectionType, 0, UFBX_PROJECTION_MODE_ORTHOGRAPHIC); + camera->aspect_mode = (ufbx_aspect_mode)ufbxi_find_enum(&camera->props, ufbxi_AspectRatioMode, 0, UFBX_ASPECT_MODE_FIXED_HEIGHT); + camera->aperture_mode = (ufbx_aperture_mode)ufbxi_find_enum(&camera->props, ufbxi_ApertureMode, UFBX_APERTURE_MODE_VERTICAL, UFBX_APERTURE_MODE_FOCAL_LENGTH); + camera->aperture_format = (ufbx_aperture_format)ufbxi_find_enum(&camera->props, ufbxi_ApertureFormat, UFBX_APERTURE_FORMAT_CUSTOM, UFBX_APERTURE_FORMAT_IMAX); + camera->gate_fit = (ufbx_gate_fit)ufbxi_find_enum(&camera->props, ufbxi_GateFit, 0, UFBX_GATE_FIT_STRETCH); + + camera->near_plane = ufbxi_find_real(&camera->props, ufbxi_NearPlane, 0.0f); + camera->far_plane = ufbxi_find_real(&camera->props, ufbxi_FarPlane, 0.0f); + + // Search both W/H and Width/Height but prefer the latter + ufbx_real aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectW, 0.0f); + ufbx_real aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectH, 0.0f); + aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectWidth, aspect_x); + aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectHeight, aspect_y); + + ufbx_real fov = ufbxi_find_real(&camera->props, ufbxi_FieldOfView, 0.0f); + ufbx_real fov_x = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewX, 0.0f); + ufbx_real fov_y = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewY, 0.0f); + + ufbx_real focal_length = ufbxi_find_real(&camera->props, ufbxi_FocalLength, 0.0f); + ufbx_real ortho_extent = scene->metadata.ortho_size_unit * ufbxi_find_real(&camera->props, ufbxi_OrthoZoom, 1.0f); + + ufbxi_aperture_format format = ufbxi_aperture_formats[camera->aperture_format]; + ufbx_vec2 film_size = { (ufbx_real)format.film_size_x * (ufbx_real)0.001, (ufbx_real)format.film_size_y * (ufbx_real)0.001 }; + ufbx_real squeeze_ratio = camera->aperture_format == UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC ? 2.0f : 1.0f; + + film_size.x = ufbxi_find_real(&camera->props, ufbxi_FilmWidth, film_size.x); + film_size.y = ufbxi_find_real(&camera->props, ufbxi_FilmHeight, film_size.y); + squeeze_ratio = ufbxi_find_real(&camera->props, ufbxi_FilmSqueezeRatio, squeeze_ratio); + + if (aspect_x <= 0.0f && aspect_y <= 0.0f) { + aspect_x = film_size.x > 0.0f ? film_size.x : 1.0f; + aspect_y = film_size.y > 0.0f ? film_size.y : 1.0f; + } else if (aspect_x <= 0.0f) { + if (film_size.x > 0.0f && film_size.y > 0.0f) { + aspect_x = aspect_y / film_size.y * film_size.x; + } else { + aspect_x = aspect_y; + } + } else if (aspect_y <= 0.0f) { + if (film_size.x > 0.0f && film_size.y > 0.0f) { + aspect_y = aspect_x / film_size.x * film_size.y; + } else { + aspect_y = aspect_x; + } + } + + film_size.y *= squeeze_ratio; + + // TODO: Should this be done always? + ortho_extent *= scene->metadata.geometry_scale; + camera->near_plane *= scene->metadata.geometry_scale; + camera->far_plane *= scene->metadata.geometry_scale; + + camera->focal_length_mm = focal_length; + camera->film_size_inch = film_size; + camera->squeeze_ratio = squeeze_ratio; + camera->orthographic_extent = ortho_extent; + + switch (camera->aspect_mode) { + case UFBX_ASPECT_MODE_WINDOW_SIZE: + case UFBX_ASPECT_MODE_FIXED_RATIO: + camera->resolution_is_pixels = false; + camera->resolution.x = aspect_x; + camera->resolution.y = aspect_y; + break; + case UFBX_ASPECT_MODE_FIXED_RESOLUTION: + camera->resolution_is_pixels = true; + camera->resolution.x = aspect_x; + camera->resolution.y = aspect_y; + break; + case UFBX_ASPECT_MODE_FIXED_WIDTH: + camera->resolution_is_pixels = true; + camera->resolution.x = aspect_x; + camera->resolution.y = aspect_x * aspect_y; + break; + case UFBX_ASPECT_MODE_FIXED_HEIGHT: + camera->resolution_is_pixels = true; + camera->resolution.x = aspect_y * aspect_x; + camera->resolution.y = aspect_y; + break; + default: + ufbxi_unreachable("Unexpected aspect mode"); + } + + ufbx_real aspect_ratio = camera->resolution.x / camera->resolution.y; + ufbx_real film_ratio = film_size.x / film_size.y; + + camera->aspect_ratio = aspect_ratio; + + ufbx_gate_fit effective_fit = camera->gate_fit; + if (effective_fit == UFBX_GATE_FIT_FILL) { + effective_fit = aspect_ratio > film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL; + } else if (effective_fit == UFBX_GATE_FIT_OVERSCAN) { + effective_fit = aspect_ratio < film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL; + } + + switch (effective_fit) { + case UFBX_GATE_FIT_NONE: + camera->aperture_size_inch = camera->film_size_inch; + camera->orthographic_size.x = ortho_extent; + camera->orthographic_size.y = ortho_extent; + break; + case UFBX_GATE_FIT_VERTICAL: + camera->aperture_size_inch.x = camera->film_size_inch.y * aspect_ratio; + camera->aperture_size_inch.y = camera->film_size_inch.y; + camera->orthographic_size.x = ortho_extent * aspect_ratio; + camera->orthographic_size.y = ortho_extent; + break; + case UFBX_GATE_FIT_HORIZONTAL: + camera->aperture_size_inch.x = camera->film_size_inch.x; + camera->aperture_size_inch.y = camera->film_size_inch.x / aspect_ratio; + camera->orthographic_size.x = ortho_extent; + camera->orthographic_size.y = ortho_extent / aspect_ratio; + break; + case UFBX_GATE_FIT_FILL: + case UFBX_GATE_FIT_OVERSCAN: + camera->aperture_size_inch = camera->film_size_inch; + camera->orthographic_size.x = ortho_extent; + camera->orthographic_size.y = ortho_extent; + ufbxi_unreachable("Unreachable, set to vertical/horizontal above"); + break; + case UFBX_GATE_FIT_STRETCH: + camera->aperture_size_inch = camera->film_size_inch; + camera->orthographic_size.x = ortho_extent; + camera->orthographic_size.y = ortho_extent; + // TODO: Not sure what to do here... + break; + default: + ufbxi_unreachable("Unexpected gate fit"); + } + + switch (camera->aperture_mode) { + case UFBX_APERTURE_MODE_HORIZONTAL_AND_VERTICAL: + camera->field_of_view_deg.x = fov_x; + camera->field_of_view_deg.y = fov_y; + camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov_x * (UFBXI_DEG_TO_RAD * 0.5f))); + camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov_y * (UFBXI_DEG_TO_RAD * 0.5f))); + break; + case UFBX_APERTURE_MODE_HORIZONTAL: + camera->field_of_view_deg.x = fov; + camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f))); + camera->field_of_view_tan.y = camera->field_of_view_tan.x / aspect_ratio; + camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f; + break; + case UFBX_APERTURE_MODE_VERTICAL: + camera->field_of_view_deg.y = fov; + camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f))); + camera->field_of_view_tan.x = camera->field_of_view_tan.y * aspect_ratio; + camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f; + break; + case UFBX_APERTURE_MODE_FOCAL_LENGTH: + camera->field_of_view_tan.x = camera->aperture_size_inch.x / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f; + camera->field_of_view_tan.y = camera->aperture_size_inch.y / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f; + camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f; + camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f; + break; + default: + ufbxi_unreachable("Unexpected aperture mode"); + } + + if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) { + camera->projection_plane = camera->field_of_view_tan; + } else { + camera->projection_plane = camera->orthographic_size; + } +} + +ufbxi_noinline static void ufbxi_update_bone(ufbx_scene *scene, ufbx_bone *bone) +{ + ufbx_real unit = scene->metadata.bone_prop_size_unit; + + bone->radius = ufbxi_find_real(&bone->props, ufbxi_Size, unit) / unit; + if (scene->metadata.bone_prop_limb_length_relative) { + bone->relative_length = ufbxi_find_real(&bone->props, ufbxi_LimbLength, 1.0f); + } else { + bone->relative_length = 1.0f; + } +} + +ufbxi_noinline static void ufbxi_update_line_curve(ufbx_line_curve *line) +{ + line->color = ufbxi_find_vec3(&line->props, ufbxi_Color, 1.0f, 1.0f, 1.0f); +} + +ufbxi_noinline static void ufbxi_update_pose(ufbx_pose *pose) +{ + ufbxi_for_list(ufbx_bone_pose, bone, pose->bone_poses) { + ufbx_node *node = bone->bone_node; + + const ufbx_matrix *parent_to_world = &ufbx_identity_matrix; + ufbx_bone_pose *bone_pose = ufbx_get_bone_pose(pose, node->parent); + if (bone_pose) { + parent_to_world = &bone_pose->bone_to_world; + } else if (node->parent) { + parent_to_world = &node->parent->node_to_world; + } + + ufbx_matrix world_to_parent = ufbx_matrix_invert(parent_to_world); + bone->bone_to_parent = ufbx_matrix_mul(&world_to_parent, &bone->bone_to_world); + } +} + +ufbxi_noinline static void ufbxi_update_skin_cluster(ufbx_skin_cluster *cluster) +{ + if (cluster->bone_node) { + cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bone_node->node_to_world, &cluster->geometry_to_bone); + } else { + cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bind_to_world, &cluster->geometry_to_bone); + } + cluster->geometry_to_world_transform = ufbx_matrix_to_transform(&cluster->geometry_to_world); +} + +ufbxi_noinline static void ufbxi_update_blend_channel(ufbx_blend_channel *channel) +{ + ufbx_real weight = ufbxi_find_real(&channel->props, ufbxi_DeformPercent, 0.0f) * (ufbx_real)0.01; + channel->weight = weight; + + ptrdiff_t num_keys = (ptrdiff_t)channel->keyframes.count; + if (num_keys > 0) { + ufbx_blend_keyframe *keys = channel->keyframes.data; + + // Reset the effective weights to zero and find the split around zero + ptrdiff_t last_negative = -1; + for (ptrdiff_t i = 0; i < num_keys; i++) { + keys[i].effective_weight = (ufbx_real)0.0; + if (keys[i].target_weight < 0.0) last_negative = i; + } + + // Find either the next or last keyframe away from zero + ufbx_blend_keyframe zero_key = { NULL }; + ufbx_blend_keyframe *prev = &zero_key, *next = &zero_key; + if (weight > 0.0) { + if (last_negative >= 0) prev = &keys[last_negative]; + for (ptrdiff_t i = last_negative + 1; i < num_keys; i++) { + prev = next; + next = &keys[i]; + if (next->target_weight > weight) break; + } + } else { + if (last_negative + 1 < num_keys) prev = &keys[last_negative + 1]; + for (ptrdiff_t i = last_negative; i >= 0; i--) { + prev = next; + next = &keys[i]; + if (next->target_weight < weight) break; + } + } + + // Linearly interpolate between the endpoints with the weight + ufbx_real delta = next->target_weight - prev->target_weight; + if (delta != 0.0) { + ufbx_real t = (weight - prev->target_weight) / delta; + prev->effective_weight = 1.0f - t; + next->effective_weight = t; + } + } +} + +ufbxi_noinline static void ufbxi_update_material(ufbx_scene *scene, ufbx_material *material) +{ + if (material->props.num_animated > 0) { + ufbxi_fetch_maps(scene, material); + } +} + +ufbxi_noinline static void ufbxi_update_texture(ufbx_texture *texture) +{ + texture->uv_transform = ufbxi_get_texture_transform(&texture->props); + if (!ufbxi_is_transform_identity(&texture->uv_transform)) { + texture->has_uv_transform = true; + texture->texture_to_uv = ufbx_transform_to_matrix(&texture->uv_transform); + texture->uv_to_texture = ufbx_matrix_invert(&texture->texture_to_uv); + } else { + texture->has_uv_transform = false; + texture->texture_to_uv = ufbx_identity_matrix; + texture->uv_to_texture = ufbx_identity_matrix; + } + texture->wrap_u = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeU, 0, UFBX_WRAP_CLAMP); + texture->wrap_v = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeV, 0, UFBX_WRAP_CLAMP); + + if (texture->shader) { + ufbxi_update_shader_texture(texture, texture->shader); + } +} + +ufbxi_noinline static void ufbxi_update_anim_stack(ufbx_scene *scene, ufbx_anim_stack *stack) +{ + ufbx_prop *begin, *end; + begin = ufbxi_find_prop(&stack->props, ufbxi_LocalStart); + end = ufbxi_find_prop(&stack->props, ufbxi_LocalStop); + if (!begin || !end) { + begin = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStart); + end = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStop); + } + + if (begin && end) { + stack->time_begin = (double)begin->value_int / (double)scene->metadata.ktime_second; + stack->time_end = (double)end->value_int / (double)scene->metadata.ktime_second; + } + + stack->anim->time_begin = stack->time_begin; + stack->anim->time_end = stack->time_end; +} + +ufbxi_noinline static void ufbxi_update_display_layer(ufbx_display_layer *layer) +{ + layer->visible = ufbxi_find_int(&layer->props, ufbxi_Show, 1) != 0; + layer->frozen = ufbxi_find_int(&layer->props, ufbxi_Freeze, 1) != 0; + layer->ui_color = ufbxi_find_vec3(&layer->props, ufbxi_Color, 0.8f, 0.8f, 0.8f); +} + +ufbxi_noinline static void ufbxi_find_bool3(bool *dst, ufbx_props *props, const char *name, bool default_value) +{ + size_t name_len = strlen(name); + char local[64]; + ufbx_assert(name_len < sizeof(local) - 2); + memcpy(local, name, name_len); + + size_t local_len = name_len + 1; + local[local_len] = '\0'; + + int64_t def = default_value ? 1 : 0; + local[name_len] = 'X'; + dst[0] = ufbx_find_int_len(props, local, local_len, def) != 0; + local[name_len] = 'Y'; + dst[1] = ufbx_find_int_len(props, local, local_len, def) != 0; + local[name_len] = 'Z'; + dst[2] = ufbx_find_int_len(props, local, local_len, def) != 0; +} + +ufbxi_noinline static void ufbxi_update_constraint(ufbx_constraint *constraint) +{ + ufbx_props *props = &constraint->props; + ufbx_constraint_type constraint_type = constraint->type; + + constraint->transform_offset = ufbxi_get_constraint_transform(props); + + constraint->weight = ufbxi_find_real(props, ufbxi_Weight, (ufbx_real)100.0) / (ufbx_real)100.0; + + ufbxi_for_list(ufbx_constraint_target, target, constraint->targets) { + ufbx_node *node = target->node; + + ufbx_real weight_scale = (ufbx_real)100.0; + if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) { + // IK weights seem to be not scaled 100x? + weight_scale = (ufbx_real)1.0; + } + + ufbx_prop *prop; // ufbxi_uninit + ufbx_string parts[2]; // ufbxi_uninit + parts[0] = node->name; + parts[1] = ufbxi_str_c(".Weight"); + prop = ufbx_find_prop_concat(props, parts, 2); + target->weight = (prop ? prop->value_real : weight_scale) / weight_scale; + + if (constraint_type == UFBX_CONSTRAINT_PARENT) { + parts[1] = ufbxi_str_c(".Offset T"); + prop = ufbx_find_prop_concat(props, parts, 2); + ufbx_vec3 t = prop ? prop->value_vec3 : ufbx_zero_vec3; + parts[1] = ufbxi_str_c(".Offset R"); + prop = ufbx_find_prop_concat(props, parts, 2); + ufbx_vec3 r = prop ? prop->value_vec3 : ufbx_zero_vec3; + parts[1] = ufbxi_str_c(".Offset S"); + prop = ufbx_find_prop_concat(props, parts, 2); + ufbx_vec3 s = prop ? prop->value_vec3 : ufbxi_one_vec3; + + target->transform.translation = t; + target->transform.rotation = ufbx_euler_to_quat(r, UFBX_ROTATION_ORDER_XYZ); + target->transform.scale = s; + } + } + + constraint->active = ufbx_find_int(props, "Active", 1) != 0; + if (constraint_type == UFBX_CONSTRAINT_AIM) { + ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1); + + const ufbx_vec3 default_aim = { 1.0f, 0.0f, 0.0f }; + const ufbx_vec3 default_up = { 0.0f, 1.0f, 0.0f }; + + int64_t up_type = ufbx_find_int(props, "WorldUpType", 0); + if (up_type >= 0 && up_type < UFBX_CONSTRAINT_AIM_UP_NONE) { + constraint->aim_up_type = (ufbx_constraint_aim_up_type)up_type; + } + constraint->aim_vector = ufbx_find_vec3(props, "AimVector", default_aim); + constraint->aim_up_vector = ufbx_find_vec3(props, "UpVector", default_up); + + } else if (constraint_type == UFBX_CONSTRAINT_PARENT) { + ufbxi_find_bool3(constraint->constrain_translation, props, "AffectTranslation", 1); + ufbxi_find_bool3(constraint->constrain_rotation, props, "AffectRotation", 1); + ufbxi_find_bool3(constraint->constrain_scale, props, "AffectScale", 0); + } else if (constraint_type == UFBX_CONSTRAINT_POSITION) { + ufbxi_find_bool3(constraint->constrain_translation, props, "Affect", 1); + } else if (constraint_type == UFBX_CONSTRAINT_ROTATION) { + ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1); + } else if (constraint_type == UFBX_CONSTRAINT_SCALE) { + ufbxi_find_bool3(constraint->constrain_scale, props, "Affect", 1); + } else if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) { + constraint->constrain_rotation[0] = true; + constraint->constrain_rotation[1] = true; + constraint->constrain_rotation[2] = true; + constraint->ik_pole_vector = ufbx_find_vec3(props, "PoleVectorType", ufbx_zero_vec3); + } +} + +ufbxi_noinline static void ufbxi_update_anim(ufbx_scene *scene) +{ + if (scene->anim_stacks.count > 0) { + scene->anim = scene->anim_stacks.data[0]->anim; + } +} + +static ufbxi_forceinline void ufbxi_mirror_matrix_dst(ufbx_matrix *m, ufbx_mirror_axis axis) +{ + if (axis == 0) return; + int32_t ax = (int32_t)axis - 1; + m->cols[0].v[ax] = -m->cols[0].v[ax]; + m->cols[1].v[ax] = -m->cols[1].v[ax]; + m->cols[2].v[ax] = -m->cols[2].v[ax]; + m->cols[3].v[ax] = -m->cols[3].v[ax]; +} + +static ufbxi_forceinline void ufbxi_mirror_matrix_src(ufbx_matrix *m, ufbx_mirror_axis axis) +{ + if (axis == 0) return; + int32_t ax = (int32_t)axis - 1; + m->cols[ax].x = -m->cols[ax].x; + m->cols[ax].y = -m->cols[ax].y; + m->cols[ax].z = -m->cols[ax].z; +} + +static ufbxi_noinline void ufbxi_mirror_matrix(ufbx_matrix *m, ufbx_mirror_axis axis) +{ + if (axis == 0) return; + ufbxi_mirror_matrix_src(m, axis); + ufbxi_mirror_matrix_dst(m, axis); +} + +ufbxi_noinline static void ufbxi_update_initial_clusters(ufbx_scene *scene) +{ + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + cluster->geometry_to_bone = cluster->mesh_node_to_bone; + } + + ufbx_mirror_axis mirror_axis = scene->metadata.mirror_axis; + ufbx_real geometry_scale = scene->metadata.geometry_scale; + + // Space conversion for bind matrices + { + ufbx_matrix world_to_units; + ufbx_real translation_scale = 1.0f; + + if (scene->metadata.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && scene->metadata.mirror_axis == UFBX_MIRROR_AXIS_NONE) { + world_to_units = scene->root_node->node_to_parent; + } else { + ufbx_transform root_transform; + root_transform.translation = ufbx_zero_vec3; + root_transform.rotation = scene->metadata.root_rotation; + root_transform.scale.x = scene->metadata.root_scale; + root_transform.scale.y = scene->metadata.root_scale; + root_transform.scale.z = scene->metadata.root_scale; + world_to_units = ufbx_transform_to_matrix(&root_transform); + translation_scale = scene->metadata.geometry_scale; + } + + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + cluster->bind_to_world = ufbx_matrix_mul(&world_to_units, &cluster->bind_to_world); + cluster->bind_to_world.cols[3].x *= translation_scale; + cluster->bind_to_world.cols[3].y *= translation_scale; + cluster->bind_to_world.cols[3].z *= translation_scale; + ufbxi_mirror_matrix(&cluster->bind_to_world, mirror_axis); + } + + ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) { + ufbxi_for_list(ufbx_bone_pose, pose, (*p_pose)->bone_poses) { + pose->bone_to_world = ufbx_matrix_mul(&world_to_units, &pose->bone_to_world); + pose->bone_to_world.cols[3].x *= translation_scale; + pose->bone_to_world.cols[3].y *= translation_scale; + pose->bone_to_world.cols[3].z *= translation_scale; + ufbxi_mirror_matrix(&pose->bone_to_world, mirror_axis); + } + } + } + + // Patch initial `mesh_node_to_bone` + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + + ufbx_skin_deformer *skin = (ufbx_skin_deformer*)ufbxi_fetch_src_element(&cluster->element, false, NULL, UFBX_ELEMENT_SKIN_DEFORMER); + if (!skin) continue; + + ufbx_node *node = (ufbx_node*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_NODE); + if (!node) { + ufbx_mesh *mesh = (ufbx_mesh*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_MESH); + if (mesh && mesh->instances.count > 0) { + node = mesh->instances.data[0]; + } + } + if (!node) continue; + + // Normalize to the non-helper node + if (node->is_geometry_transform_helper) { + node = node->parent; + } + + if (ufbxi_matrix_all_zero(&cluster->mesh_node_to_bone)) { + // If `mesh_node_to_bone` is not explicitly specified compute it from bind pose. + ufbx_matrix world_to_bind = ufbx_matrix_invert(&cluster->bind_to_world); + cluster->mesh_node_to_bone = ufbx_matrix_mul(&world_to_bind, &node->node_to_world); + } else { + // If `mesh_node_to_bone` is explicit, we may need to modify it for space conversion. + ufbxi_mirror_matrix(&cluster->mesh_node_to_bone, mirror_axis); + if (geometry_scale != 1.0f) { + cluster->mesh_node_to_bone.cols[3].x *= geometry_scale; + cluster->mesh_node_to_bone.cols[3].y *= geometry_scale; + cluster->mesh_node_to_bone.cols[3].z *= geometry_scale; + } + } + + // HACK: Account for geometry transforms by looking at the transform of the + // helper node if one is present. I don't think this is exactly how the skinning + // matrices are formed. + // TODO: Add a test with moving the skinned mesh root around. + if (node->geometry_transform_helper) { + ufbx_node *geo_node = node->geometry_transform_helper; + cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &geo_node->node_to_parent); + } else if (node->has_geometry_transform) { + cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &node->geometry_to_node); + } else { + cluster->geometry_to_bone = cluster->mesh_node_to_bone; + } + } +} + +ufbxi_noinline static ufbx_coordinate_axis ufbxi_find_axis(const ufbx_props *props, const char *axis_name, const char *sign_name) +{ + int64_t axis = ufbxi_find_int(props, axis_name, 3); + int64_t sign = ufbxi_find_int(props, sign_name, 2); + + switch (axis) { + case 0: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_X : UFBX_COORDINATE_AXIS_NEGATIVE_X; + case 1: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Y : UFBX_COORDINATE_AXIS_NEGATIVE_Y; + case 2: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Z : UFBX_COORDINATE_AXIS_NEGATIVE_Z; + default: return UFBX_COORDINATE_AXIS_UNKNOWN; + } +} + +static const ufbx_real ufbxi_time_mode_fps[] = { + 30.0f, // UFBX_TIME_MODE_DEFAULT + 120.0f, // UFBX_TIME_MODE_120_FPS + 100.0f, // UFBX_TIME_MODE_100_FPS + 60.0f, // UFBX_TIME_MODE_60_FPS + 50.0f, // UFBX_TIME_MODE_50_FPS + 48.0f, // UFBX_TIME_MODE_48_FPS + 30.0f, // UFBX_TIME_MODE_30_FPS + 30.0f, // UFBX_TIME_MODE_30_FPS_DROP + 29.97f, // UFBX_TIME_MODE_NTSC_DROP_FRAME + 29.97f, // UFBX_TIME_MODE_NTSC_FULL_FRAME + 25.0f, // UFBX_TIME_MODE_PAL + 24.0f, // UFBX_TIME_MODE_24_FPS + 1000.0f, // UFBX_TIME_MODE_1000_FPS + 23.976f, // UFBX_TIME_MODE_FILM_FULL_FRAME + 24.0f, // UFBX_TIME_MODE_CUSTOM + 96.0f, // UFBX_TIME_MODE_96_FPS + 72.0f, // UFBX_TIME_MODE_72_FPS + 59.94f, // UFBX_TIME_MODE_59_94_FPS +}; + +// Returns whether a non-identity matrix was needed +static ufbxi_noinline bool ufbxi_axis_matrix(ufbx_matrix *mat, ufbx_coordinate_axes src, ufbx_coordinate_axes dst) +{ + uint32_t src_x = (uint32_t)src.right; + uint32_t dst_x = (uint32_t)dst.right; + uint32_t src_y = (uint32_t)src.up; + uint32_t dst_y = (uint32_t)dst.up; + uint32_t src_z = (uint32_t)src.front; + uint32_t dst_z = (uint32_t)dst.front; + + if (src_x == dst_x && src_y == dst_y && src_z == dst_z) return false; + + // Remap axes (axis enum divided by 2) potentially flipping if the signs (enum parity) doesn't match + memset(mat, 0, sizeof(ufbx_matrix)); + mat->cols[src_x >> 1].v[dst_x >> 1] = ((src_x ^ dst_x) & 1) == 0 ? 1.0f : -1.0f; + mat->cols[src_y >> 1].v[dst_y >> 1] = ((src_y ^ dst_y) & 1) == 0 ? 1.0f : -1.0f; + mat->cols[src_z >> 1].v[dst_z >> 1] = ((src_z ^ dst_z) & 1) == 0 ? 1.0f : -1.0f; + + return true; +} + +ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufbx_scene *scene) +{ + ufbx_transform root_transform = ufbx_identity_transform; + if (!ufbxi_matrix_all_zero(&uc->axis_matrix)) { + root_transform = ufbx_matrix_to_transform(&uc->axis_matrix); + } + root_transform.scale.x *= uc->unit_scale; + root_transform.scale.y *= uc->unit_scale; + root_transform.scale.z *= uc->unit_scale; + + ufbx_space_conversion conversion = uc->opts.space_conversion; + + ufbx_quat light_post_rotation = ufbx_identity_quat; + ufbx_quat camera_post_rotation = ufbx_identity_quat; + ufbx_vec3 light_direction = { 0.0f, -1.0f, 0.0f }; + bool has_light_transform = false; + bool has_camera_transform = false; + + if (ufbx_coordinate_axes_valid(uc->opts.target_light_axes)) { + ufbx_matrix mat; // ufbxi_uninit + ufbx_coordinate_axes light_axes = { + UFBX_COORDINATE_AXIS_POSITIVE_X, + UFBX_COORDINATE_AXIS_NEGATIVE_Z, + UFBX_COORDINATE_AXIS_POSITIVE_Y, + }; + if (ufbxi_axis_matrix(&mat, uc->opts.target_light_axes, light_axes)) { + light_post_rotation = ufbx_matrix_to_transform(&mat).rotation; + + ufbx_matrix inv = ufbx_matrix_invert(&mat); + light_direction = ufbx_transform_direction(&inv, light_direction); + has_light_transform = true; + } + } + + if (ufbx_coordinate_axes_valid(uc->opts.target_camera_axes)) { + ufbx_matrix mat; // ufbxi_uninit + ufbx_coordinate_axes camera_axes = { + UFBX_COORDINATE_AXIS_POSITIVE_Z, + UFBX_COORDINATE_AXIS_POSITIVE_Y, + UFBX_COORDINATE_AXIS_NEGATIVE_X, + }; + if (ufbxi_axis_matrix(&mat, uc->opts.target_camera_axes, camera_axes)) { + camera_post_rotation = ufbx_matrix_to_transform(&mat).rotation; + has_camera_transform = true; + } + } + + ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) { + ufbx_light *light = *p_light; + light->local_direction.x = 0.0f; + light->local_direction.y = -1.0f; + light->local_direction.z = 0.0f; + } + + scene->metadata.space_conversion = conversion; + scene->metadata.geometry_transform_handling = uc->opts.geometry_transform_handling; + scene->metadata.inherit_mode_handling = uc->opts.inherit_mode_handling; + scene->metadata.pivot_handling = uc->opts.pivot_handling; + scene->metadata.handedness_conversion_axis = uc->opts.handedness_conversion_axis; + + ufbx_real root_scale = ufbxi_min3(root_transform.scale); + if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) { + scene->metadata.geometry_scale = root_scale; + scene->metadata.root_scale = 1.0f; + } else { + scene->metadata.geometry_scale = 1.0f; + scene->metadata.root_scale = root_scale; + } + scene->metadata.root_rotation = root_transform.rotation; + + ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) { + ufbx_node *node = *p_node; + + node->adjust_post_rotation = ufbx_identity_quat; + node->adjust_pre_rotation = ufbx_identity_quat; + node->adjust_pre_scale = 1.0f; + node->adjust_post_scale = 1.0f; + node->adjust_translation_scale = 1.0f; + + if (conversion == UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS) { + if (node->node_depth <= 1 && !node->is_root) { + node->adjust_pre_rotation = root_transform.rotation; + node->adjust_pre_scale = root_scale; + node->has_adjust_transform = true; + node->has_root_adjust_transform = true; + } + } else if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) { + if (!node->is_root) { + if (node->node_depth <= 1) { + node->adjust_pre_rotation = root_transform.rotation; + } + node->adjust_translation_scale = root_scale; + node->has_adjust_transform = true; + } + } + + if (node->parent) { + // We are not inheriting local scale, so propagate root scale manually and + // apply scale compensation if necessary. + ufbx_node *parent = node->parent; + if (parent->has_root_adjust_transform && node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { + node->adjust_post_scale *= root_scale; + node->has_adjust_transform = true; + node->has_root_adjust_transform = true; + } + if (parent->is_scale_compensate_parent && node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { + ufbx_vec3 scale = ufbxi_find_vec3(&parent->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); + ufbx_real size = scale.x; + if (ufbx_fabs(scale.y - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.y; + if (ufbx_fabs(scale.z - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.z; + node->adjust_post_scale *= 1.0f / size; + node->has_adjust_transform = true; + } + } + + if (node->all_attribs.count == 1) { + if (has_light_transform && node->light) { + node->adjust_post_rotation = light_post_rotation; + node->light->local_direction = light_direction; + node->has_adjust_transform = true; + } + if (has_camera_transform && node->camera) { + node->adjust_post_rotation = camera_post_rotation; + node->camera->projection_axes = uc->opts.target_camera_axes; + node->has_adjust_transform = true; + } + } + } +} + +ufbxi_noinline static void ufbxi_update_scene(ufbx_scene *scene, bool initial, const ufbx_transform_override *transform_overrides, size_t num_transform_overrides) +{ + ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) { + ufbxi_update_node(*p_node, transform_overrides, num_transform_overrides); + } + + ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) { + ufbxi_update_light(*p_light); + } + + ufbxi_for_ptr_list(ufbx_camera, p_camera, scene->cameras) { + ufbxi_update_camera(scene, *p_camera); + } + + ufbxi_for_ptr_list(ufbx_bone, p_bone, scene->bones) { + ufbxi_update_bone(scene, *p_bone); + } + + ufbxi_for_ptr_list(ufbx_line_curve, p_line, scene->line_curves) { + ufbxi_update_line_curve(*p_line); + } + + if (initial) { + ufbxi_update_initial_clusters(scene); + + ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) { + ufbxi_update_pose(*p_pose); + } + } + + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { + ufbxi_update_skin_cluster(*p_cluster); + } + + ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, scene->blend_channels) { + ufbxi_update_blend_channel(*p_channel); + } + + ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { + ufbxi_update_texture(*p_texture); + } + + ufbxi_propagate_main_textures(scene); + + ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) { + ufbxi_update_material(scene, *p_material); + } + + ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, scene->anim_stacks) { + ufbxi_update_anim_stack(scene, *p_stack); + } + + ufbxi_for_ptr_list(ufbx_display_layer, p_layer, scene->display_layers) { + ufbxi_update_display_layer(*p_layer); + } + + ufbxi_for_ptr_list(ufbx_constraint, p_constraint, scene->constraints) { + ufbxi_update_constraint(*p_constraint); + } + + ufbxi_update_anim(scene); +} + +static ufbxi_noinline void ufbxi_update_scene_metadata(ufbx_metadata *metadata) +{ + ufbx_props *props = &metadata->scene_props; + metadata->original_application.vendor = ufbx_find_string(props, "Original|ApplicationVendor", ufbx_empty_string); + metadata->original_application.name = ufbx_find_string(props, "Original|ApplicationName", ufbx_empty_string); + metadata->original_application.version = ufbx_find_string(props, "Original|ApplicationVersion", ufbx_empty_string); + metadata->latest_application.vendor = ufbx_find_string(props, "LastSaved|ApplicationVendor", ufbx_empty_string); + metadata->latest_application.name = ufbx_find_string(props, "LastSaved|ApplicationName", ufbx_empty_string); + metadata->latest_application.version = ufbx_find_string(props, "LastSaved|ApplicationVersion", ufbx_empty_string); +} + +static const ufbx_real ufbxi_pow10_targets[] = { + 0.0f, + (ufbx_real)1e-8, (ufbx_real)1e-7, (ufbx_real)1e-6, (ufbx_real)1e-5, + (ufbx_real)1e-4, (ufbx_real)1e-3, (ufbx_real)1e-2, (ufbx_real)1e-1, + (ufbx_real)1e+0, (ufbx_real)1e+1, (ufbx_real)1e+2, (ufbx_real)1e+3, + (ufbx_real)1e+4, (ufbx_real)1e+5, (ufbx_real)1e+6, (ufbx_real)1e+7, + (ufbx_real)1e+8, (ufbx_real)1e+9, +}; + +static ufbxi_noinline ufbx_real ufbxi_round_if_near(const ufbx_real *targets, size_t num_targets, ufbx_real value) +{ + for (size_t i = 0; i < num_targets; i++) { + double target = targets[i]; + double error = target * 9.5367431640625e-7; + if (error < 0.0) error = -error; + if (error < 7.52316384526264005e-37) error = 7.52316384526264005e-37; + if (value >= target - error && value <= target + error) { + return (ufbx_real)target; + } + } + return value; +} + +static ufbxi_noinline void ufbxi_update_scene_settings(ufbx_scene_settings *settings) +{ + ufbx_real unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_UnitScaleFactor, 1.0f); + ufbx_real original_unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_OriginalUnitScaleFactor, unit_scale_factor); + + settings->axes.up = ufbxi_find_axis(&settings->props, ufbxi_UpAxis, ufbxi_UpAxisSign); + settings->axes.front = ufbxi_find_axis(&settings->props, ufbxi_FrontAxis, ufbxi_FrontAxisSign); + settings->axes.right = ufbxi_find_axis(&settings->props, ufbxi_CoordAxis, ufbxi_CoordAxisSign); + settings->unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), unit_scale_factor * (ufbx_real)0.01); + settings->original_unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), original_unit_scale_factor * (ufbx_real)0.01); + settings->frames_per_second = ufbxi_find_real(&settings->props, ufbxi_CustomFrameRate, 24.0f); + settings->ambient_color = ufbxi_find_vec3(&settings->props, ufbxi_AmbientColor, 0.0f, 0.0f, 0.0f); + settings->original_axis_up = ufbxi_find_axis(&settings->props, ufbxi_OriginalUpAxis, ufbxi_OriginalUpAxisSign); + + ufbx_prop *default_camera = ufbxi_find_prop(&settings->props, ufbxi_DefaultCamera); + if (default_camera) { + settings->default_camera = default_camera->value_str; + } else { + settings->default_camera = ufbx_empty_string; + } + + settings->time_mode = (ufbx_time_mode)ufbxi_find_enum(&settings->props, ufbxi_TimeMode, UFBX_TIME_MODE_24_FPS, UFBX_TIME_MODE_59_94_FPS); + settings->time_protocol = (ufbx_time_protocol)ufbxi_find_enum(&settings->props, ufbxi_TimeProtocol, UFBX_TIME_PROTOCOL_DEFAULT, UFBX_TIME_PROTOCOL_DEFAULT); + settings->snap_mode = (ufbx_snap_mode)ufbxi_find_enum(&settings->props, ufbxi_SnapOnFrameMode, UFBX_SNAP_MODE_NONE, UFBX_SNAP_MODE_SNAP_AND_PLAY); + + if (settings->time_mode != UFBX_TIME_MODE_CUSTOM) { + settings->frames_per_second = ufbxi_time_mode_fps[settings->time_mode]; + } +} + +static ufbxi_noinline void ufbxi_update_scene_settings_obj(ufbxi_context *uc) +{ + ufbx_scene_settings *settings = &uc->scene.settings; + settings->original_unit_meters = settings->unit_meters = uc->opts.obj_unit_meters; + if (ufbx_coordinate_axes_valid(uc->opts.obj_axes)) { + settings->axes = uc->opts.obj_axes; + } else { + settings->axes.right = UFBX_COORDINATE_AXIS_UNKNOWN; + settings->axes.up = UFBX_COORDINATE_AXIS_UNKNOWN; + settings->axes.front = UFBX_COORDINATE_AXIS_UNKNOWN; + } +} + +// -- Geometry caches + +#if UFBXI_FEATURE_GEOMETRY_CACHE + +typedef struct { + ufbxi_refcount refcount; + ufbx_geometry_cache cache; + uint32_t magic; + bool owned_by_scene; + + ufbxi_buf string_buf; +} ufbxi_geometry_cache_imp; + +ufbx_static_assert(geometry_cache_imp_offset, offsetof(ufbxi_geometry_cache_imp, cache) == sizeof(ufbxi_refcount)); + +typedef struct { + ufbx_string name; + ufbx_string interpretation; + uint32_t sample_rate; + uint32_t start_time; + uint32_t end_time; + uint32_t current_time; + uint32_t consecutive_fails; + bool try_load; +} ufbxi_cache_tmp_channel; + +typedef enum { + UFBXI_CACHE_XML_TYPE_NONE, + UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME, + UFBXI_CACHE_XML_TYPE_SINGLE_FILE, +} ufbxi_cache_xml_type; + +typedef enum { + UFBXI_CACHE_XML_FORMAT_NONE, + UFBXI_CACHE_XML_FORMAT_MCC, + UFBXI_CACHE_XML_FORMAT_MCX, +} ufbxi_cache_xml_format; + +typedef struct { + ufbx_error error; + ufbx_string filename; + bool owned_by_scene; + bool ignore_if_not_found; + + ufbx_geometry_cache_opts opts; + + ufbxi_allocator *ator_tmp; + ufbxi_allocator ator_result; + + ufbxi_buf result; + ufbxi_buf tmp; + ufbxi_buf tmp_stack; + + ufbxi_cache_tmp_channel *channels; + size_t num_channels; + + // Temporary array + char *tmp_arr; + size_t tmp_arr_size; + + ufbxi_string_pool string_pool; + + ufbx_open_file_cb open_file_cb; + + double frames_per_second; + + ufbx_string stream_filename; + ufbx_stream stream; + + bool mc_for8; + + ufbx_string xml_filename; + uint32_t xml_ticks_per_frame; + ufbxi_cache_xml_type xml_type; + ufbxi_cache_xml_format xml_format; + + ufbx_string channel_name; + + char *name_buf; + size_t name_cap; + + uint64_t file_offset; + const char *pos, *pos_end; + + ufbx_geometry_cache cache; + ufbxi_geometry_cache_imp *imp; + + char buffer[128]; +} ufbxi_cache_context; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_read(ufbxi_cache_context *cc, void *dst, size_t size, bool allow_eof) +{ + size_t buffered = ufbxi_min_sz(ufbxi_to_size(cc->pos_end - cc->pos), size); + memcpy(dst, cc->pos, buffered); + cc->pos += buffered; + size -= buffered; + cc->file_offset += buffered; + if (size == 0) return 1; + dst = (char*)dst + buffered; + + if (size >= sizeof(cc->buffer)) { + size_t num_read = cc->stream.read_fn(cc->stream.user, dst, size); + ufbxi_check_err_msg(&cc->error, num_read <= size, "IO error"); + if (!allow_eof) { + ufbxi_check_err_msg(&cc->error, num_read == size, "Truncated file"); + } + cc->file_offset += num_read; + size -= num_read; + dst = (char*)dst + num_read; + } else { + size_t num_read = cc->stream.read_fn(cc->stream.user, cc->buffer, sizeof(cc->buffer)); + ufbxi_check_err_msg(&cc->error, num_read <= sizeof(cc->buffer), "IO error"); + if (!allow_eof) { + ufbxi_check_err_msg(&cc->error, num_read >= size, "Truncated file"); + } + cc->pos = cc->buffer; + cc->pos_end = cc->buffer + sizeof(cc->buffer); + + memcpy(dst, cc->pos, size); + cc->pos += size; + cc->file_offset += size; + + size_t num_written = ufbxi_min_sz(size, num_read); + size -= num_written; + dst = (char*)dst + num_written; + } + + if (size > 0) { + memset(dst, 0, size); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context *cc, uint64_t size) +{ + cc->file_offset += size; + + uint64_t buffered = ufbxi_min64((uint64_t)(cc->pos_end - cc->pos), size); + cc->pos += buffered; + size -= buffered; + + if (cc->stream.skip_fn) { + while (size >= UFBXI_MAX_SKIP_SIZE) { + size -= UFBXI_MAX_SKIP_SIZE; + ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file"); + + // Check that we can read at least one byte in case the file is broken + // and causes us to seek indefinitely forwards as `fseek()` does not + // report if we hit EOF... + char single_byte[1]; // ufbxi_uninit + size_t num_read = cc->stream.read_fn(cc->stream.user, single_byte, 1); + ufbxi_check_err_msg(&cc->error, num_read <= 1, "IO error"); + ufbxi_check_err_msg(&cc->error, num_read == 1, "Truncated file"); + } + + if (size > 0) { + ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, (size_t)size), "Truncated file"); + } + + } else { + char skip_buf[2048]; // ufbxi_uninit + while (size > 0) { + size_t to_skip = (size_t)ufbxi_min64(size, sizeof(skip_buf)); + size -= to_skip; + ufbxi_check_err_msg(&cc->error, cc->stream.read_fn(cc->stream.user, skip_buf, to_skip), "Truncated file"); + } + } + + return 1; +} + +#define ufbxi_cache_mc_tag(a,b,c,d) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u | (uint32_t)(d)) + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_tag(ufbxi_cache_context *cc, uint32_t *p_tag) +{ + char buf[4]; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, true)); + *p_tag = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; + if (*p_tag == ufbxi_cache_mc_tag('F','O','R','8')) { + cc->mc_for8 = true; + } + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u32(ufbxi_cache_context *cc, uint32_t *p_value) +{ + char buf[4]; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false)); + *p_value = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; + if (cc->mc_for8) { + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false)); + } + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u64(ufbxi_cache_context *cc, uint64_t *p_value) +{ + if (!cc->mc_for8) { + uint32_t v32; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &v32)); + *p_value = v32; + } else { + char buf[8]; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 8, false)); + uint32_t hi = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; + uint32_t lo = (uint32_t)(uint8_t)buf[4]<<24u | (uint32_t)(uint8_t)buf[5]<<16 | (uint32_t)(uint8_t)buf[6]<<8u | (uint32_t)(uint8_t)buf[7]; + *p_value = (uint64_t)hi << 32u | (uint64_t)lo; + } + return 1; +} + +static const uint8_t ufbxi_cache_data_format_size[] = { + 0, 4, 12, 8, 24, +}; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_mc(ufbxi_cache_context *cc) +{ + uint32_t version = 0, time_start = 0, time_end = 0; + uint32_t count = 0, time = 0; + char skip_buf[8]; // ufbxi_uninit + + for (;;) { + uint32_t tag; // ufbxi_uninit + uint64_t size; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_tag(cc, &tag)); + if (tag == 0) break; + + if (tag == ufbxi_cache_mc_tag('C','A','C','H') || tag == ufbxi_cache_mc_tag('M','Y','C','H')) { + continue; + } + if (cc->mc_for8) { + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, skip_buf, 4, false)); + } + + ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u64(cc, &size)); + uint64_t begin = cc->file_offset; + + size_t alignment = cc->mc_for8 ? 8 : 4; + + ufbx_cache_data_format format = UFBX_CACHE_DATA_FORMAT_UNKNOWN; + switch (tag) { + case ufbxi_cache_mc_tag('F','O','R','4'): cc->mc_for8 = false; break; + case ufbxi_cache_mc_tag('F','O','R','8'): cc->mc_for8 = true; break; + case ufbxi_cache_mc_tag('V','R','S','N'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &version)); break; + case ufbxi_cache_mc_tag('S','T','I','M'): + ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_start)); + time = time_start; + break; + case ufbxi_cache_mc_tag('E','T','I','M'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_end)); break; + case ufbxi_cache_mc_tag('T','I','M','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time)); break; + case ufbxi_cache_mc_tag('C','H','N','M'): { + ufbxi_check_err(&cc->error, size > 0 && size < SIZE_MAX); + size_t length = (size_t)size - 1; + size_t padded_length = ((size_t)size + alignment - 1) & ~(alignment - 1); + ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->name_buf, &cc->name_cap, padded_length)); + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, cc->name_buf, padded_length, false)); + cc->channel_name.data = cc->name_buf; + cc->channel_name.length = length; + ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->channel_name, false)); + } break; + case ufbxi_cache_mc_tag('S','I','Z','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &count)); break; + case ufbxi_cache_mc_tag('F','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT; break; + case ufbxi_cache_mc_tag('D','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE; break; + case ufbxi_cache_mc_tag('F','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_FLOAT; break; + case ufbxi_cache_mc_tag('D','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break; + case ufbxi_cache_mc_tag('D','B','L','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break; + default: ufbxi_fail_err(&cc->error, "Unknown tag"); + } + + if (format != UFBX_CACHE_DATA_FORMAT_UNKNOWN) { + ufbx_cache_frame *frame = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, 1); + ufbxi_check_err(&cc->error, frame); + + uint32_t elem_size = ufbxi_cache_data_format_size[format]; + uint64_t total_size = (uint64_t)elem_size * (uint64_t)count; + ufbxi_check_err(&cc->error, size >= elem_size * count); + + frame->channel = cc->channel_name; + frame->time = (double)time * (1.0/6000.0); + frame->filename = cc->stream_filename; + frame->data_format = format; + frame->data_encoding = UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN; + frame->data_offset = cc->file_offset; + frame->data_count = count; + frame->data_element_bytes = elem_size; + frame->data_total_bytes = total_size; + frame->file_format = UFBX_CACHE_FILE_FORMAT_MC; + + uint64_t end = begin + ((size + alignment - 1) & ~(uint64_t)(alignment - 1)); + ufbxi_check_err(&cc->error, end >= cc->file_offset); + uint64_t left = end - cc->file_offset; + ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, left)); + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_pc2(ufbxi_cache_context *cc) +{ + char header[32]; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, header, sizeof(header), false)); + + uint32_t version = ufbxi_read_u32(header + 12); + uint32_t num_points = ufbxi_read_u32(header + 16); + double start_frame = ufbxi_read_f32(header + 20); + double frames_per_sample = ufbxi_read_f32(header + 24); + uint32_t num_samples = ufbxi_read_u32(header + 28); + + (void)version; + + ufbx_cache_frame *frames = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, num_samples); + ufbxi_check_err(&cc->error, frames); + + uint64_t total_points = (uint64_t)num_points * (uint64_t)num_samples; + ufbxi_check_err(&cc->error, total_points < UINT64_MAX / 12); + + uint64_t offset = cc->file_offset; + + // Skip almost to the end of the data and try to read one byte as there's + // nothing after the data so we can't detect EOF.. + if (total_points > 0) { + char last_byte[1]; // ufbxi_uninit + ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, total_points * 12 - 1)); + ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, last_byte, 1, false)); + } + + for (uint32_t i = 0; i < num_samples; i++) { + ufbx_cache_frame *frame = &frames[i]; + + double sample_frame = start_frame + (double)i * frames_per_sample; + frame->channel = cc->channel_name; + frame->time = sample_frame / cc->frames_per_second; + frame->filename = cc->stream_filename; + frame->data_format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT; + frame->data_encoding = UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN; + frame->data_offset = offset; + frame->data_count = num_points; + frame->data_element_bytes = 12; + frame->data_total_bytes = num_points * 12; + frame->file_format = UFBX_CACHE_FILE_FORMAT_PC2; + offset += num_points * 12; + } + + return 1; +} + +static ufbxi_noinline bool ufbxi_tmp_channel_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_cache_tmp_channel *a = (const ufbxi_cache_tmp_channel *)va, *b = (const ufbxi_cache_tmp_channel *)vb; + return ufbxi_str_less(a->name, b->name); +} + +static ufbxi_noinline int ufbxi_cache_sort_tmp_channels(ufbxi_cache_context *cc, ufbxi_cache_tmp_channel *channels, size_t count) +{ + ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbxi_cache_tmp_channel))); + ufbxi_stable_sort(sizeof(ufbxi_cache_tmp_channel), 16, channels, cc->tmp_arr, count, &ufbxi_tmp_channel_less, NULL); + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml_imp(ufbxi_cache_context *cc, ufbxi_xml_document *doc) +{ + cc->xml_ticks_per_frame = 250; + cc->xml_filename = cc->stream_filename; + + ufbxi_xml_tag *tag_root = ufbxi_xml_find_child(doc->root, "Autodesk_Cache_File"); + if (tag_root) { + ufbxi_xml_tag *tag_type = ufbxi_xml_find_child(tag_root, "cacheType"); + ufbxi_xml_tag *tag_fps = ufbxi_xml_find_child(tag_root, "cacheTimePerFrame"); + ufbxi_xml_tag *tag_channels = ufbxi_xml_find_child(tag_root, "Channels"); + + size_t num_extra = 0; + ufbxi_for(ufbxi_xml_tag, tag, tag_root->children, tag_root->num_children) { + if (tag->num_children != 1) continue; + if (strcmp(tag->name.data, "extra") != 0) continue; + ufbx_string *extra = ufbxi_push(&cc->tmp_stack, ufbx_string, 1); + ufbxi_check_err(&cc->error, extra); + *extra = tag->children[0].text; + ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, extra, false)); + num_extra++; + } + cc->cache.extra_info.count = num_extra; + cc->cache.extra_info.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_string, num_extra); + ufbxi_check_err(&cc->error, cc->cache.extra_info.data); + + if (tag_type) { + ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag_type, "Type"); + ufbxi_xml_attrib *format = ufbxi_xml_find_attrib(tag_type, "Format"); + if (type) { + if (!strcmp(type->value.data, "OneFilePerFrame")) { + cc->xml_type = UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME; + } else if (!strcmp(type->value.data, "OneFile")) { + cc->xml_type = UFBXI_CACHE_XML_TYPE_SINGLE_FILE; + } + } + if (format) { + if (!strcmp(format->value.data, "mcc")) { + cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCC; + } else if (!strcmp(format->value.data, "mcx")) { + cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCX; + } + } + } + + if (tag_fps) { + ufbxi_xml_attrib *fps = ufbxi_xml_find_attrib(tag_fps, "TimePerFrame"); + if (fps) { + uint32_t value = ufbxi_parse_uint32_radix(fps->value.data, 10); + if (value > 0) { + cc->xml_ticks_per_frame = value; + } + } + } + + if (tag_channels) { + cc->channels = ufbxi_push_zero(&cc->tmp, ufbxi_cache_tmp_channel, tag_channels->num_children); + ufbxi_check_err(&cc->error, cc->channels); + + ufbxi_for(ufbxi_xml_tag, tag, tag_channels->children, tag_channels->num_children) { + ufbxi_xml_attrib *name = ufbxi_xml_find_attrib(tag, "ChannelName"); + ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag, "ChannelType"); + ufbxi_xml_attrib *interpretation = ufbxi_xml_find_attrib(tag, "ChannelInterpretation"); + if (!(name && type && interpretation)) continue; + + ufbxi_cache_tmp_channel *channel = &cc->channels[cc->num_channels++]; + channel->name = name->value; + channel->interpretation = interpretation->value; + ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->name, false)); + ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->interpretation, false)); + + ufbxi_xml_attrib *sampling_rate = ufbxi_xml_find_attrib(tag, "SamplingRate"); + ufbxi_xml_attrib *start_time = ufbxi_xml_find_attrib(tag, "StartTime"); + ufbxi_xml_attrib *end_time = ufbxi_xml_find_attrib(tag, "EndTime"); + if (sampling_rate && start_time && end_time) { + channel->sample_rate = ufbxi_parse_uint32_radix(sampling_rate->value.data, 10); + channel->start_time = ufbxi_parse_uint32_radix(start_time->value.data, 10); + channel->end_time = ufbxi_parse_uint32_radix(end_time->value.data, 10); + channel->current_time = channel->start_time; + channel->try_load = true; + } + } + } + } + + ufbxi_check_err(&cc->error, ufbxi_cache_sort_tmp_channels(cc, cc->channels, cc->num_channels)); + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml(ufbxi_cache_context *cc) +{ + ufbxi_xml_load_opts opts = { 0 }; + opts.ator = cc->ator_tmp; + opts.read_fn = cc->stream.read_fn; + opts.read_user = cc->stream.user; + opts.prefix = cc->pos; + opts.prefix_length = ufbxi_to_size(cc->pos_end - cc->pos); + ufbxi_xml_document *doc = ufbxi_load_xml(&opts, &cc->error); + ufbxi_check_err(&cc->error, doc); + + int xml_ok = ufbxi_cache_load_xml_imp(cc, doc); + ufbxi_free_xml(doc); + ufbxi_check_err(&cc->error, xml_ok); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_file(ufbxi_cache_context *cc, ufbx_string filename) +{ + cc->stream_filename = filename; + ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->stream_filename, false)); + + // Assume all files have at least 16 bytes of header + size_t magic_len = cc->stream.read_fn(cc->stream.user, cc->buffer, 16); + ufbxi_check_err_msg(&cc->error, magic_len <= 16, "IO error"); + ufbxi_check_err_msg(&cc->error, magic_len == 16, "Truncated file"); + cc->pos = cc->buffer; + cc->pos_end = cc->buffer + 16; + + cc->file_offset = 0; + + if (!memcmp(cc->buffer, "POINTCACHE2", 11)) { + ufbxi_check_err(&cc->error, ufbxi_cache_load_pc2(cc)); + } else if (!memcmp(cc->buffer, "FOR4", 4) || !memcmp(cc->buffer, "FOR8", 4)) { + ufbxi_check_err(&cc->error, ufbxi_cache_load_mc(cc)); + } else { + ufbxi_check_err(&cc->error, ufbxi_cache_load_xml(cc)); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_try_open_file(ufbxi_cache_context *cc, ufbx_string filename, const ufbx_blob *original_filename, bool *p_found) +{ + memset(&cc->stream, 0, sizeof(cc->stream)); + ufbxi_regression_assert(strlen(filename.data) == filename.length); + if (!ufbxi_open_file(&cc->open_file_cb, &cc->stream, filename.data, filename.length, original_filename, cc->ator_tmp, UFBX_OPEN_FILE_GEOMETRY_CACHE)) { + return 1; + } + + int ok = ufbxi_cache_load_file(cc, filename); + *p_found = true; + + if (cc->stream.close_fn) { + cc->stream.close_fn(cc->stream.user); + } + + return ok; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_frame_files(ufbxi_cache_context *cc) +{ + if (cc->xml_filename.length == 0) return 1; + + const char *extension = NULL; + switch (cc->xml_format) { + case UFBXI_CACHE_XML_FORMAT_MCC: extension = "mc"; break; + case UFBXI_CACHE_XML_FORMAT_MCX: extension = "mcx"; break; + default: return 1; + } + + // Ensure worst case space for `path/filenameFrame123Tick456.mcx` + size_t name_buf_len = cc->xml_filename.length + 64; + char *name_buf = ufbxi_push(&cc->tmp, char, name_buf_len); + ufbxi_check_err(&cc->error, name_buf); + + // Find the prefix before `.xml` + size_t prefix_len = cc->xml_filename.length; + for (size_t i = prefix_len; i > 0; --i) { + if (cc->xml_filename.data[i - 1] == '.') { + prefix_len = i - 1; + break; + } + } + memcpy(name_buf, cc->xml_filename.data, prefix_len); + + char *suffix_data = name_buf + prefix_len; + size_t suffix_len = name_buf_len - prefix_len; + + ufbx_string filename; + filename.data = name_buf; + + if (cc->xml_type == UFBXI_CACHE_XML_TYPE_SINGLE_FILE) { + filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, ".%s", extension); + bool found = false; + ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found)); + } else if (cc->xml_type == UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME) { + uint32_t lowest_time = 0; + for (;;) { + // Find the first `time >= lowest_time` value that has data in some channel + uint32_t time = UINT32_MAX; + ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) { + if (!chan->try_load || chan->consecutive_fails > 10) continue; + uint32_t sample_rate = chan->sample_rate ? chan->sample_rate : cc->xml_ticks_per_frame; + if (chan->current_time < lowest_time) { + uint32_t delta = (lowest_time - chan->current_time - 1) / sample_rate; + chan->current_time += delta * sample_rate; + if (UINT32_MAX - chan->current_time >= sample_rate) { + chan->current_time += sample_rate; + } else { + chan->try_load = false; + continue; + } + } + if (chan->current_time <= chan->end_time) { + time = ufbxi_min32(time, chan->current_time); + } + } + if (time == UINT32_MAX) break; + + // Try to load a file at the specified frame/tick + uint32_t frame = time / cc->xml_ticks_per_frame; + uint32_t tick = time % cc->xml_ticks_per_frame; + if (tick == 0) { + filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%u.%s", frame, extension); + } else { + filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%uTick%u.%s", frame, tick, extension); + } + bool found = false; + ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found)); + + // Update channel status + ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) { + if (chan->current_time == time) { + chan->consecutive_fails = found ? 0 : chan->consecutive_fails + 1; + } + } + + lowest_time = time + 1; + } + } + + return 1; +} + +static ufbxi_noinline bool ufbxi_cmp_cache_frame_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_cache_frame *a = (const ufbx_cache_frame *)va, *b = (const ufbx_cache_frame *)vb; + if (a->channel.data != b->channel.data) { + // Channel names should be interned + ufbxi_regression_assert(!ufbxi_str_equal(a->channel, b->channel)); + return ufbxi_str_less(a->channel, b->channel); + } + return a->time < b->time; +} + +static ufbxi_noinline int ufbxi_cache_sort_frames(ufbxi_cache_context *cc, ufbx_cache_frame *frames, size_t count) +{ + ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbx_cache_frame))); + ufbxi_stable_sort(sizeof(ufbx_cache_frame), 16, frames, cc->tmp_arr, count, &ufbxi_cmp_cache_frame_less, NULL); + return 1; +} + +typedef struct { + ufbx_cache_interpretation interpretation; + const char *pattern; +} ufbxi_cache_interpretation_name; + +static const ufbxi_cache_interpretation_name ufbxi_cache_interpretation_names[] = { + { UFBX_CACHE_INTERPRETATION_POINTS, "\\cpoints?" }, + { UFBX_CACHE_INTERPRETATION_VERTEX_POSITION, "\\cpositions?" }, + { UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL, "\\cnormals?" }, +}; + +static ufbxi_noinline int ufbxi_cache_setup_channels(ufbxi_cache_context *cc) +{ + ufbxi_cache_tmp_channel *tmp_chan = cc->channels, *tmp_end = ufbxi_add_ptr(tmp_chan, cc->num_channels); + + size_t begin = 0, num_channels = 0; + while (begin < cc->cache.frames.count) { + ufbx_cache_frame *frame = &cc->cache.frames.data[begin]; + size_t end = begin + 1; + while (end < cc->cache.frames.count && cc->cache.frames.data[end].channel.data == frame->channel.data) { + end++; + } + + ufbx_cache_channel *chan = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_channel, 1); + ufbxi_check_err(&cc->error, chan); + + chan->name = frame->channel; + chan->interpretation_name = ufbx_empty_string; + chan->frames.data = frame; + chan->frames.count = end - begin; + + while (tmp_chan < tmp_end && ufbxi_str_less(tmp_chan->name, chan->name)) { + tmp_chan++; + } + if (tmp_chan < tmp_end && ufbxi_str_equal(tmp_chan->name, chan->name)) { + chan->interpretation_name = tmp_chan->interpretation; + } + + if (frame->file_format == UFBX_CACHE_FILE_FORMAT_PC2) { + chan->interpretation = UFBX_CACHE_INTERPRETATION_VERTEX_POSITION; + } else { + ufbxi_for(const ufbxi_cache_interpretation_name, name, ufbxi_cache_interpretation_names, ufbxi_arraycount(ufbxi_cache_interpretation_names)) { + if (ufbxi_match(&chan->interpretation_name, name->pattern)) { + chan->interpretation = name->interpretation; + break; + } + } + } + + ufbx_mirror_axis mirror_axis = UFBX_MIRROR_AXIS_NONE; + ufbx_real scale_factor = 1.0f; + if (chan->interpretation != UFBX_CACHE_INTERPRETATION_UNKNOWN) { + mirror_axis = cc->opts.mirror_axis; + if (cc->opts.use_scale_factor) { + scale_factor = cc->opts.scale_factor; + } + } + chan->mirror_axis = mirror_axis; + chan->scale_factor = scale_factor; + ufbxi_for_list(ufbx_cache_frame, f, chan->frames) { + f->mirror_axis = mirror_axis; + f->scale_factor = scale_factor; + } + + num_channels++; + begin = end; + } + + cc->cache.channels.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_channel, num_channels); + ufbxi_check_err(&cc->error, cc->cache.channels.data); + cc->cache.channels.count = num_channels; + + return 1; +} + + +static ufbxi_noinline int ufbxi_cache_load_imp(ufbxi_cache_context *cc, ufbx_string filename) +{ + cc->tmp.ator = cc->ator_tmp; + cc->tmp_stack.ator = cc->ator_tmp; + + cc->channel_name.data = ufbxi_empty_char; + + if (!cc->open_file_cb.fn) { + cc->open_file_cb.fn = ufbx_default_open_file; + } + + // Make sure the filename we pass to `open_file_fn()` is NULL-terminated + char *filename_data = ufbxi_push(&cc->tmp, char, filename.length + 1); + ufbxi_check_err(&cc->error, filename_data); + memcpy(filename_data, filename.data, filename.length); + filename_data[filename.length] = '\0'; + ufbx_string filename_copy = { filename_data, filename.length }; + + // TODO: NULL termination! + bool found = false; + ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename_copy, NULL, &found)); + if (!found) { + ufbxi_set_err_info(&cc->error, filename.data, filename.length); + ufbxi_fail_err_msg(&cc->error, "open_file_fn()", "File not found"); + } + + cc->cache.root_filename = cc->stream_filename; + + ufbxi_check_err(&cc->error, ufbxi_cache_load_frame_files(cc)); + + size_t num_frames = cc->tmp_stack.num_items; + cc->cache.frames.count = num_frames; + cc->cache.frames.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_frame, num_frames); + ufbxi_check_err(&cc->error, cc->cache.frames.data); + + ufbxi_check_err(&cc->error, ufbxi_cache_sort_frames(cc, cc->cache.frames.data, cc->cache.frames.count)); + ufbxi_check_err(&cc->error, ufbxi_cache_setup_channels(cc)); + + // Must be last allocation! + cc->imp = ufbxi_push(&cc->result, ufbxi_geometry_cache_imp, 1); + ufbxi_check_err(&cc->error, cc->imp); + + ufbxi_init_ref(&cc->imp->refcount, UFBXI_CACHE_IMP_MAGIC, NULL); + + cc->imp->cache = cc->cache; + cc->imp->magic = UFBXI_CACHE_IMP_MAGIC; + cc->imp->owned_by_scene = cc->owned_by_scene; + cc->imp->refcount.ator = cc->ator_result; + cc->imp->refcount.buf = cc->result; + cc->imp->refcount.buf.ator = &cc->imp->refcount.ator; + cc->imp->string_buf = cc->string_pool.buf; + cc->imp->string_buf.ator = &cc->imp->refcount.ator; + + return 1; +} + +ufbxi_noinline static ufbx_geometry_cache *ufbxi_cache_load(ufbxi_cache_context *cc, ufbx_string filename) +{ + int ok = ufbxi_cache_load_imp(cc, filename); + + ufbxi_buf_free(&cc->tmp); + ufbxi_buf_free(&cc->tmp_stack); + ufbxi_free(cc->ator_tmp, char, cc->name_buf, cc->name_cap); + ufbxi_free(cc->ator_tmp, char, cc->tmp_arr, cc->tmp_arr_size); + if (!cc->owned_by_scene) { + ufbxi_string_pool_temp_free(&cc->string_pool); + ufbxi_free_ator(cc->ator_tmp); + } + + if (ok) { + return &cc->imp->cache; + } else { + ufbxi_fix_error_type(&cc->error, "Failed to load geometry cache", NULL); + if (!cc->owned_by_scene) { + ufbxi_buf_free(&cc->string_pool.buf); + ufbxi_free_ator(&cc->ator_result); + } + return NULL; + } +} + +ufbxi_noinline static ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error) +{ + ufbx_geometry_cache_opts opts; // ufbxi_uninit + if (user_opts) { + opts = *user_opts; + } else { + memset(&opts, 0, sizeof(opts)); + } + + ufbxi_cache_context cc = { UFBX_ERROR_NONE }; + ufbxi_allocator ator_tmp = { 0 }; + ufbxi_init_ator(&cc.error, &ator_tmp, &opts.temp_allocator, "temp"); + ufbxi_init_ator(&cc.error, &cc.ator_result, &opts.result_allocator, "result"); + cc.ator_tmp = &ator_tmp; + + cc.opts = opts; + + cc.open_file_cb = opts.open_file_cb; + + cc.string_pool.error = &cc.error; + ufbxi_map_init(&cc.string_pool.map, cc.ator_tmp, &ufbxi_map_cmp_string, NULL); + cc.string_pool.buf.ator = &cc.ator_result; + cc.string_pool.buf.unordered = true; + cc.string_pool.initial_size = 64; + cc.result.ator = &cc.ator_result; + + cc.frames_per_second = opts.frames_per_second > 0.0 ? opts.frames_per_second : 30.0; + + ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, filename); + if (p_error) { + if (cache) { + ufbxi_clear_error(p_error); + } else { + *p_error = cc.error; + } + } + return cache; +} + +static ufbxi_noinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp) +{ + ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); + ufbxi_buf_free(&imp->string_buf); +} + +#else + +typedef struct { + ufbxi_refcount refcount; + uint32_t magic; + bool owned_by_scene; +} ufbxi_geometry_cache_imp; + +static ufbxi_noinline ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error) +{ + if (p_error) { + memset(p_error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(p_error, "UFBX_ENABLE_GEOMETRY_CACHE"); + ufbxi_report_err_msg(p_error, "UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled"); + } + return NULL; +} + +static ufbxi_forceinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp) +{ +} + +#endif + +// -- External files + +typedef enum { + UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE, +} ufbxi_external_file_type; + +typedef struct { + ufbxi_external_file_type type; + ufbx_string filename; + ufbx_string absolute_filename; + size_t index; + void *data; + size_t data_size; +} ufbxi_external_file; + +static bool ufbxi_less_external_file(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_external_file *a = (const ufbxi_external_file*)va, *b = (const ufbxi_external_file*)vb; + if (a->type != b->type) return a->type < b->type; + int cmp = ufbxi_str_cmp(a->filename, b->filename); + if (cmp != 0) return cmp < 0; + if (a->index != b->index) return a->index < b->index; + return false; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_cache(ufbxi_context *uc, ufbxi_external_file *file) +{ +#if UFBXI_FEATURE_GEOMETRY_CACHE + ufbxi_cache_context cc = { UFBX_ERROR_NONE }; + cc.owned_by_scene = true; + + cc.open_file_cb = uc->opts.open_file_cb; + cc.frames_per_second = uc->scene.settings.frames_per_second; + + // Temporarily "borrow" allocators for the geometry cache + cc.ator_tmp = &uc->ator_tmp; + cc.string_pool = uc->string_pool; + cc.result = uc->result; + + cc.opts.mirror_axis = uc->mirror_axis; + cc.opts.use_scale_factor = true; + cc.opts.scale_factor = uc->scene.metadata.geometry_scale; + + ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, file->filename); + if (!cache) { + if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) { + memset(&cc.error, 0, sizeof(cc.error)); + cache = ufbxi_cache_load(&cc, file->absolute_filename); + } + } + + // Return the "borrowed" allocators + uc->string_pool = cc.string_pool; + uc->result = cc.result; + + if (!cache) { + if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) { + if (uc->opts.ignore_missing_external_files) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Failed to open geometry cache: %s", file->filename.data)); + return 1; + } else { + cc.error.type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND; + cc.error.description.data = "External file not found"; + cc.error.description.length = strlen("External file not found"); + } + } + + uc->error = cc.error; + return 0; + } + + file->data = cache; + return 1; +#else + if (uc->opts.ignore_missing_external_files) return 1; + + ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_GEOMETRY_CACHE"); + ufbxi_fail_msg("UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled"); +#endif +} + +static ufbxi_noinline ufbxi_external_file *ufbxi_find_external_file(ufbxi_external_file *files, size_t num_files, ufbxi_external_file_type type, const char *name) +{ + size_t ix = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbxi_external_file, 32, &ix, files, 0, num_files, + ( type != a->type ? type < a->type : strcmp(a->filename.data, name) < 0 ), + ( a->type == type && a->filename.data == name )); + return ix != SIZE_MAX ? &files[ix] : NULL; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_files(ufbxi_context *uc) +{ + size_t num_files = 0; + + // Gather external files to deduplicate them + ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { + ufbx_cache_file *cache = *p_cache; + if (cache->filename.length > 0) { + ufbxi_external_file *file = ufbxi_push_zero(&uc->tmp_stack, ufbxi_external_file, 1); + ufbxi_check(file); + file->index = num_files++; + file->type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE; + file->filename = cache->filename; + file->absolute_filename = cache->absolute_filename; + } + } + + // Sort and load the external files + ufbxi_external_file *files = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_external_file, num_files); + ufbxi_check(files); + ufbxi_unstable_sort(files, num_files, sizeof(ufbxi_external_file), &ufbxi_less_external_file, NULL); + + ufbxi_external_file_type prev_type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE; + const char *prev_name = NULL; + ufbxi_for(ufbxi_external_file, file, files, num_files) { + if (file->filename.data == prev_name && file->type == prev_type) continue; + if (file->type == UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE) { + ufbxi_check(ufbxi_load_external_cache(uc, file)); + } + prev_name = file->filename.data; + prev_type = file->type; + } + + // Patch the loaded files + ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { + ufbx_cache_file *cache = *p_cache; + ufbxi_external_file *file = ufbxi_find_external_file(files, num_files, + UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE, cache->filename.data); + if (file && file->data) { + cache->external_cache = (ufbx_geometry_cache*)file->data; + } + } + + // Patch the geometry deformers + ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) { + ufbx_cache_deformer *deformer = *p_deformer; + if (!deformer->file || !deformer->file->external_cache) continue; + ufbx_geometry_cache *cache = deformer->file->external_cache; + deformer->external_cache = cache; + + // HACK: It seems like channels may be connected even if the name is wrong + // and they work when exporting from Marvelous to Maya... + if (cache->channels.count == 1) { + deformer->external_channel = &cache->channels.data[0]; + } else { + ufbx_string channel = deformer->channel; + size_t ix = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_cache_channel, 16, &ix, cache->channels.data, 0, cache->channels.count, + ( ufbxi_str_less(a->name, channel) ), ( a->name.data == channel.data )); + if (ix != SIZE_MAX) { + deformer->external_channel = &cache->channels.data[ix]; + } + } + } + + return 1; +} + +static ufbxi_noinline void ufbxi_transform_to_axes(ufbxi_context *uc, ufbx_coordinate_axes dst_axes) +{ + if (!ufbx_coordinate_axes_valid(uc->scene.settings.axes)) return; + if (!ufbxi_axis_matrix(&uc->axis_matrix, uc->scene.settings.axes, dst_axes)) return; + + if (ufbx_matrix_determinant(&uc->axis_matrix) < 0.0f) { + if (uc->opts.handedness_conversion_axis != UFBX_MIRROR_AXIS_NONE) { + ufbx_mirror_axis mirror_axis = uc->opts.handedness_conversion_axis; + uc->mirror_axis = mirror_axis; + uc->scene.metadata.mirror_axis = uc->mirror_axis; + + ufbxi_mirror_matrix_dst(&uc->axis_matrix, uc->mirror_axis); + ufbxi_dev_assert(ufbx_matrix_determinant(&uc->axis_matrix) >= 0.0f); + + ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { + ufbx_node *node = *p_node; + if (!node->is_root) { + node->adjust_mirror_axis = mirror_axis; + } + } + } + } + + if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) { + ufbx_matrix axis_mat = uc->axis_matrix; + if (!ufbxi_is_transform_identity(&uc->scene.root_node->local_transform)) { + ufbx_matrix root_mat = ufbx_transform_to_matrix(&uc->scene.root_node->local_transform); + axis_mat = ufbx_matrix_mul(&root_mat, &axis_mat); + } + + ufbxi_mirror_matrix(&axis_mat, uc->mirror_axis); + + uc->scene.root_node->local_transform = ufbx_matrix_to_transform(&axis_mat); + uc->scene.root_node->node_to_parent = axis_mat; + } +} + +static ufbxi_noinline int ufbxi_scale_units(ufbxi_context *uc, ufbx_real target_meters) +{ + if (uc->scene.settings.unit_meters <= 0.0f) return 1; + target_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), target_meters); + + ufbx_real ratio = uc->scene.settings.unit_meters / target_meters; + ratio = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), ratio); + if (ratio == 1.0f) return 1; + + uc->unit_scale = ratio; + + if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) { + uc->scene.root_node->local_transform.scale.x *= ratio; + uc->scene.root_node->local_transform.scale.y *= ratio; + uc->scene.root_node->local_transform.scale.z *= ratio; + uc->scene.root_node->node_to_parent.m00 *= ratio; + uc->scene.root_node->node_to_parent.m01 *= ratio; + uc->scene.root_node->node_to_parent.m02 *= ratio; + uc->scene.root_node->node_to_parent.m10 *= ratio; + uc->scene.root_node->node_to_parent.m11 *= ratio; + uc->scene.root_node->node_to_parent.m12 *= ratio; + uc->scene.root_node->node_to_parent.m20 *= ratio; + uc->scene.root_node->node_to_parent.m21 *= ratio; + uc->scene.root_node->node_to_parent.m22 *= ratio; + } + + return 1; +} + +// -- Curve evaluation + +static ufbxi_forceinline double ufbxi_find_cubic_bezier_t(double p1, double p2, double x0) +{ + double p1_3 = p1 * 3.0, p2_3 = p2 * 3.0; + double a = p1_3 - p2_3 + 1.0; + double b = p2_3 - p1_3 - p1_3; + double c = p1_3; + + double a_3 = 3.0*a, b_2 = 2.0*b; + double t = x0; + double x1, t2, t3; + + // Manually unroll three iterations of Newton-Rhapson, this is enough + // for most tangents + t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; + t -= x1 / (a_3*t2 + b_2*t + c); + + t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; + t -= x1 / (a_3*t2 + b_2*t + c); + + t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; + t -= x1 / (a_3*t2 + b_2*t + c); + + // 4 ULP from 1.0 + const double eps = 8.881784197001252e-16; + if (ufbx_fabs(x1) <= eps) return t; + + // Perform more iterations until we reach desired accuracy + for (size_t i = 0; i < 4; i++) { + + t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; + t -= x1 / (a_3*t2 + b_2*t + c); + + t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; + t -= x1 / (a_3*t2 + b_2*t + c); + + if (ufbx_fabs(x1) <= eps) return t; + } + + return t; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_skinning(ufbx_scene *scene, ufbx_error *error, ufbxi_buf *buf_result, ufbxi_buf *buf_tmp, + double time, bool load_caches, ufbx_geometry_cache_data_opts *cache_opts) +{ +#if UFBXI_FEATURE_SKINNING_EVALUATION + size_t max_skinned_indices = 0; + + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) { + ufbx_mesh *mesh = *p_mesh; + if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue; + max_skinned_indices = ufbxi_max_sz(max_skinned_indices, mesh->num_indices); + } + + ufbx_topo_edge *topo = ufbxi_push(buf_tmp, ufbx_topo_edge, max_skinned_indices); + ufbxi_check_err(error, topo); + + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) { + ufbx_mesh *mesh = *p_mesh; + if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue; + if (mesh->num_vertices == 0) continue; + + size_t num_vertices = mesh->num_vertices; + ufbx_vec3 *result_pos = ufbxi_push(buf_result, ufbx_vec3, num_vertices + 1); + ufbxi_check_err(error, result_pos); + + result_pos[0] = ufbx_zero_vec3; + result_pos++; + + bool cached_position = false, cached_normals = false; + if (load_caches && mesh->cache_deformers.count > 0) { + ufbxi_for_ptr_list(ufbx_cache_deformer, p_cache, mesh->cache_deformers) { + ufbx_cache_channel *channel = (*p_cache)->external_channel; + if (!channel) continue; + + if ((channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_POSITION || channel->interpretation == UFBX_CACHE_INTERPRETATION_POINTS) && !cached_position) { + size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, result_pos, num_vertices, cache_opts); + if (num_read == num_vertices) { + mesh->skinned_is_local = true; + cached_position = true; + } + } else if (channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL && !cached_normals) { + // TODO: Is this right at all? + size_t num_normals = mesh->skinned_normal.values.count; + ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1); + ufbxi_check_err(error, normal_data); + normal_data[0] = ufbx_zero_vec3; + normal_data++; + + size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, normal_data, num_normals, cache_opts); + if (num_read == num_normals) { + cached_normals = true; + mesh->skinned_normal.values.data = normal_data; + } + } + } + } + + if (!cached_position) { + memcpy(result_pos, mesh->vertices.data, num_vertices * sizeof(ufbx_vec3)); + + ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, mesh->blend_deformers) { + ufbx_add_blend_vertex_offsets(*p_blend, result_pos, num_vertices, 1.0f); + } + + // TODO: What should we do about multiple skins?? + if (mesh->skin_deformers.count > 0) { + ufbx_matrix *fallback = mesh->instances.count > 0 ? &mesh->instances.data[0]->geometry_to_world : NULL; + ufbx_skin_deformer *skin = mesh->skin_deformers.data[0]; + for (size_t i = 0; i < num_vertices; i++) { + ufbx_matrix mat = ufbx_get_skin_vertex_matrix(skin, i, fallback); + result_pos[i] = ufbx_transform_position(&mat, result_pos[i]); + } + + mesh->skinned_is_local = false; + } + } + + mesh->skinned_position.values.data = result_pos; + + if (!cached_normals) { + size_t num_indices = mesh->num_indices; + uint32_t *normal_indices = ufbxi_push(buf_result, uint32_t, num_indices); + ufbxi_check_err(error, normal_indices); + + ufbx_compute_topology(mesh, topo, num_indices); + size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false); + + if (num_normals == mesh->num_vertices) { + mesh->skinned_normal.unique_per_vertex = true; + } + + ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1); + ufbxi_check_err(error, normal_data); + + normal_data[0] = ufbx_zero_vec3; + normal_data++; + + ufbx_compute_normals(mesh, &mesh->skinned_position, normal_indices, num_indices, normal_data, num_normals); + + mesh->generated_normals = true; + mesh->skinned_normal.exists = true; + mesh->skinned_normal.values.data = normal_data; + mesh->skinned_normal.values.count = num_normals; + mesh->skinned_normal.indices.data = normal_indices; + mesh->skinned_normal.indices.count = num_indices; + mesh->skinned_normal.value_reals = 3; + } + } + + return 1; +#else + ufbxi_fmt_err_info(error, "UFBX_ENABLE_SKINNING_EVALUATION"); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_SKINNING_EVALUATION", "Feature disabled"); + return 0; +#endif +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_fixup_opts_string(ufbxi_context *uc, ufbx_string *str, bool push) +{ + if (str->length > 0) { + if (str->length == SIZE_MAX) { + str->length = str->data ? strlen(str->data) : 0; + } + if (push) { + ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, str, false)); + } + } else { + str->data = ufbxi_empty_char; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_resolve_warning_elements(ufbxi_context *uc) +{ + size_t num_elements = uc->tmp_element_id.num_items; + uint32_t *element_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_id, uint32_t, num_elements); + ufbxi_check(element_ids); + + ufbxi_for_list(ufbx_warning, warning, uc->scene.metadata.warnings) { + uint32_t element_id = warning->element_id; + // Decode `element_id`, see HACK(warning-element) in `ufbxi_vwarnf_imp()` for the encoding. + if ((element_id & 0x80000000u) != 0 && element_id != ~0u) { + warning->element_id = element_ids[element_id & ~0x80000000u]; + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) +{ + // Check for deferred failure + if (uc->deferred_failure) return 0; + if (uc->deferred_load) { + ufbx_stream stream = { 0 }; + ufbx_open_file_opts opts = { 0 }; + const char *filename = uc->load_filename; + size_t filename_len = uc->load_filename_len; + bool ok = false; + if (filename_len == SIZE_MAX) { + opts.filename_null_terminated = true; + filename_len = strlen(filename); + } + if (uc->opts.filename.length == 0 || uc->opts.filename.data == NULL) { + uc->opts.filename.data = filename; + uc->opts.filename.length = filename_len; + } + ufbx_error error; + error.type = UFBX_ERROR_NONE; + if (uc->opts.open_main_file_with_default || uc->opts.open_file_cb.fn == &ufbx_default_open_file) { + ufbx_open_file_context ctx = (ufbx_open_file_context)&uc->ator_tmp; + ok = ufbx_open_file_ctx(&stream, ctx, filename, filename_len, &opts, &error); + } else { + ok = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->load_filename, filename_len, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_MAIN_MODEL); + } + if (!ok) { + if (error.type != UFBX_ERROR_NONE) { + // cppcheck-suppress uninitStructMember + uc->error = error; + } else { + ufbxi_set_err_info(&uc->error, filename, filename_len); + } + ufbxi_fail_msg("open_file_fn()", "File not found"); + } + uc->read_fn = stream.read_fn; + uc->skip_fn = stream.skip_fn; + uc->size_fn = stream.size_fn; + uc->close_fn = stream.close_fn; + uc->read_user = stream.user; + } + + if (uc->opts.progress_cb.fn && uc->progress_bytes_total == 0 && uc->size_fn) { + uint64_t total = uc->size_fn(uc->read_user); + ufbxi_check(total != UINT64_MAX); + uc->progress_bytes_total = total; + } + + ufbxi_check(uc->opts.path_separator >= 0x20 && uc->opts.path_separator <= 0x7e); + + ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.filename, false)); + ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.obj_mtl_path, true)); + ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.geometry_transform_helper_name, true)); + ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.scale_helper_name, true)); + + ufbxi_check(ufbxi_thread_pool_init(&uc->thread_pool, &uc->error, &uc->ator_tmp, &uc->opts.thread_opts)); + + if (!uc->opts.allow_unsafe) { + ufbxi_check_msg(uc->opts.index_error_handling != UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options"); + ufbxi_check_msg(uc->opts.unicode_error_handling != UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options"); + } else { + uc->scene.metadata.is_unsafe = true; + } + + if (uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_NO_INDEX) { + uc->scene.metadata.may_contain_no_index = true; + } + + uc->retain_mesh_parts = !uc->opts.ignore_geometry && !uc->opts.skip_mesh_parts; + uc->scene.metadata.may_contain_missing_vertex_position = uc->opts.allow_missing_vertex_position; + uc->scene.metadata.may_contain_broken_elements = uc->opts.connect_broken_elements; + + uc->scene.metadata.creator.data = ufbxi_empty_char; + + uc->unit_scale = 1.0f; + if (uc->data == NULL) { + ufbxi_dev_assert(uc->data_begin == NULL); + uc->data_begin = uc->data = ufbxi_zero_size_buffer; + } + + uc->retain_vertex_w = (uc->opts.retain_dom || uc->opts.retain_vertex_attrib_w) && !uc->opts.ignore_geometry; + + ufbxi_check(ufbxi_load_strings(uc)); + ufbxi_check(ufbxi_load_maps(uc)); + ufbxi_check(ufbxi_determine_format(uc)); + + ufbx_file_format format = uc->scene.metadata.file_format; + + if (format == UFBX_FILE_FORMAT_FBX) { + ufbxi_check(ufbxi_begin_parse(uc)); + if (uc->version < 6000) { + ufbxi_check(ufbxi_read_legacy_root(uc)); + } else { + ufbxi_check(ufbxi_read_root(uc)); + } + if (!ufbxi_supports_version(uc->version)) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNSUPPORTED_VERSION, "Unsupported FBX version (%u)", uc->version)); + } + ufbxi_update_scene_metadata(&uc->scene.metadata); + ufbxi_check(ufbxi_init_file_paths(uc)); + } else if (format == UFBX_FILE_FORMAT_OBJ) { + ufbxi_check(ufbxi_obj_load(uc)); + ufbxi_update_scene_metadata(&uc->scene.metadata); + } else if (format == UFBX_FILE_FORMAT_MTL) { + ufbxi_check(ufbxi_mtl_load(uc)); + ufbxi_update_scene_metadata(&uc->scene.metadata); + } + + // Fake DOM root if necessary + if (uc->opts.retain_dom && !uc->scene.dom_root) { + ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); + ufbxi_check(dom_root); + dom_root->name.data = ufbxi_empty_char; + uc->scene.dom_root = dom_root; + } + + ufbxi_check(ufbxi_pre_finalize_scene(uc)); + + // We can free `tmp_parse` already here as all parsing is done by now. + ufbxi_buf_free(&uc->tmp_parse); + + ufbxi_check(ufbxi_finalize_scene(uc)); + + ufbxi_update_scene_settings(&uc->scene.settings); + if (uc->scene.metadata.file_format == UFBX_FILE_FORMAT_OBJ) { + ufbxi_update_scene_settings_obj(uc); + } + + // Axis conversion + if (ufbx_coordinate_axes_valid(uc->opts.target_axes)) { + ufbxi_transform_to_axes(uc, uc->opts.target_axes); + } + + // Unit conversion + if (uc->opts.target_unit_meters > 0.0f) { + ufbxi_check(ufbxi_scale_units(uc, uc->opts.target_unit_meters)); + } + + // TODO: This could be done in evaluate as well with refactoring + ufbxi_update_adjust_transforms(uc, &uc->scene); + + ufbxi_check(ufbxi_modify_geometry(uc)); + ufbxi_postprocess_scene(uc); + + ufbxi_update_scene(&uc->scene, true, NULL, 0); + + // Force a non-NULL anim pointer + if (!uc->scene.anim) { + uc->scene.anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1); + } + + if (uc->opts.load_external_files) { + ufbxi_check(ufbxi_load_external_files(uc)); + } + + // Evaluate skinning if requested + if (uc->opts.evaluate_skinning) { + ufbx_geometry_cache_data_opts cache_opts = { 0 }; + cache_opts.open_file_cb = uc->opts.open_file_cb; + ufbxi_check(ufbxi_evaluate_skinning(&uc->scene, &uc->error, &uc->result, &uc->tmp, + 0.0, uc->opts.load_external_files && uc->opts.evaluate_caches, &cache_opts)); + } + + // Pop warnings to metadata + ufbxi_check(ufbxi_pop_warnings(&uc->warnings, &uc->scene.metadata.warnings, uc->scene.metadata.has_warning)); + ufbxi_check(ufbxi_resolve_warning_elements(uc)); + + // Copy local data to the scene + uc->scene.metadata.version = uc->version; + uc->scene.metadata.ascii = uc->from_ascii; + uc->scene.metadata.big_endian = uc->file_big_endian; + uc->scene.metadata.geometry_ignored = uc->opts.ignore_geometry; + uc->scene.metadata.animation_ignored = uc->opts.ignore_animation; + uc->scene.metadata.embedded_ignored = uc->opts.ignore_embedded; + + // Retain the scene, this must be the final allocation as we copy + // `ator_result` to `ufbx_scene_imp`. + ufbxi_scene_imp *imp = ufbxi_push(&uc->result, ufbxi_scene_imp, 1); + ufbxi_check(imp); + + ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, NULL); + + imp->magic = UFBXI_SCENE_IMP_MAGIC; + imp->scene = uc->scene; + imp->refcount.ator = uc->ator_result; + imp->refcount.ator.error = NULL; + + // Copy retained buffers and translate the allocator struct to the one + // contained within `ufbxi_scene_imp` + imp->refcount.buf = uc->result; + imp->refcount.buf.ator = &imp->refcount.ator; + imp->string_buf = uc->string_pool.buf; + imp->string_buf.ator = &imp->refcount.ator; + + imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size; + imp->scene.metadata.temp_memory_used = uc->ator_tmp.current_size; + imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs; + imp->scene.metadata.temp_allocs = uc->ator_tmp.num_allocs; + + ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) { + (*p_elem)->scene = &imp->scene; + } + + uc->scene_imp = imp; + + return 1; +} + +static ufbxi_noinline void ufbxi_free_temp(ufbxi_context *uc) +{ + ufbxi_thread_pool_free(&uc->thread_pool); + + ufbxi_string_pool_temp_free(&uc->string_pool); + ufbxi_buf_free(&uc->warnings.tmp_stack); + + ufbxi_map_free(&uc->prop_type_map); + ufbxi_map_free(&uc->fbx_id_map); + ufbxi_map_free(&uc->ptr_fbx_id_map); + ufbxi_map_free(&uc->texture_file_map); + ufbxi_map_free(&uc->anim_stack_map); + ufbxi_map_free(&uc->fbx_attr_map); + ufbxi_map_free(&uc->node_prop_set); + ufbxi_map_free(&uc->dom_node_map); + + ufbxi_buf_free(&uc->tmp); + ufbxi_buf_free(&uc->tmp_parse); + for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { + ufbxi_buf_free(&uc->tmp_thread_parse[i]); + } + ufbxi_buf_free(&uc->tmp_stack); + ufbxi_buf_free(&uc->tmp_connections); + ufbxi_buf_free(&uc->tmp_node_ids); + ufbxi_buf_free(&uc->tmp_elements); + ufbxi_buf_free(&uc->tmp_element_offsets); + ufbxi_buf_free(&uc->tmp_element_fbx_ids); + ufbxi_buf_free(&uc->tmp_element_ptrs); + for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { + ufbxi_buf_free(&uc->tmp_typed_element_offsets[i]); + } + ufbxi_buf_free(&uc->tmp_mesh_textures); + ufbxi_buf_free(&uc->tmp_full_weights); + ufbxi_buf_free(&uc->tmp_dom_nodes); + ufbxi_buf_free(&uc->tmp_element_id); + ufbxi_buf_free(&uc->tmp_ascii_spans); + + ufbxi_free(&uc->ator_tmp, ufbxi_node, uc->top_nodes, uc->top_nodes_cap); + ufbxi_free(&uc->ator_tmp, void*, uc->element_extra_arr, uc->element_extra_cap); + + ufbxi_free(&uc->ator_tmp, char, uc->ascii.token.str_data, uc->ascii.token.str_cap); + ufbxi_free(&uc->ator_tmp, char, uc->ascii.prev_token.str_data, uc->ascii.prev_token.str_cap); + + ufbxi_free(&uc->ator_tmp, char, uc->read_buffer, uc->read_buffer_size); + ufbxi_free(&uc->ator_tmp, char, uc->tmp_arr, uc->tmp_arr_size); + ufbxi_free(&uc->ator_tmp, char, uc->swap_arr, uc->swap_arr_size); + + ufbxi_obj_free(uc); + + ufbxi_free_ator(&uc->ator_tmp); +} + +static ufbxi_noinline void ufbxi_free_result(ufbxi_context *uc) +{ + ufbxi_buf_free(&uc->result); + ufbxi_buf_free(&uc->string_pool.buf); + + ufbxi_free_ator(&uc->ator_result); +} + +static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_opts *user_opts, ufbx_error *p_error) +{ + // Test endianness + { + uint8_t buf[2]; + uint16_t val = 0xbbaa; + memcpy(buf, &val, 2); + uc->local_big_endian = buf[0] == 0xbb; + } + + uc->double_parse_flags = ufbxi_parse_double_init_flags(); + + if (user_opts) { + uc->opts = *user_opts; + } else { + memset(&uc->opts, 0, sizeof(uc->opts)); + } + + if (uc->opts.file_size_estimate) { + uc->progress_bytes_total = uc->opts.file_size_estimate; + } + + if (uc->opts.ignore_all_content) { + uc->opts.ignore_geometry = true; + uc->opts.ignore_animation = true; + uc->opts.ignore_embedded = true; + } + + ufbx_inflate_retain inflate_retain; + inflate_retain.initialized = false; + + ufbxi_init_ator(&uc->error, &uc->ator_tmp, &uc->opts.temp_allocator, "temp"); + ufbxi_init_ator(&uc->error, &uc->ator_result, &uc->opts.result_allocator, "result"); + + if (uc->opts.read_buffer_size == 0) { + uc->opts.read_buffer_size = 0x4000; + } + if (uc->opts.read_buffer_size <= 32) { + uc->opts.read_buffer_size = 32; + } + + if (uc->opts.file_format_lookahead == 0) { + uc->opts.file_format_lookahead = 0x4000; + } else if (uc->opts.file_format_lookahead < UFBXI_MIN_FILE_FORMAT_LOOKAHEAD) { + uc->opts.file_format_lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD; + } + + if (!uc->opts.path_separator) { + uc->opts.path_separator = UFBX_PATH_SEPARATOR; + } + + if (!uc->opts.progress_cb.fn || uc->opts.progress_interval_hint >= SIZE_MAX) { + uc->progress_interval = SIZE_MAX; + } else if (uc->opts.progress_interval_hint > 0) { + uc->progress_interval = (size_t)uc->opts.progress_interval_hint; + } else { + uc->progress_interval = 0x4000; + } + + if (!uc->opts.open_file_cb.fn) { + uc->opts.open_file_cb.fn = &ufbx_default_open_file; + } + + if (!uc->opts.thread_opts.memory_limit) { + uc->opts.thread_opts.memory_limit = 32*1024*1024; + } + + uc->synthetic_id_counter = UFBXI_SYNTHETIC_ID_START; + + uc->string_pool.error = &uc->error; + ufbxi_map_init(&uc->string_pool.map, &uc->ator_tmp, &ufbxi_map_cmp_string, NULL); + uc->string_pool.buf.ator = &uc->ator_result; + uc->string_pool.buf.unordered = true; + uc->string_pool.initial_size = 1024; + uc->string_pool.error_handling = uc->opts.unicode_error_handling; + + ufbxi_map_init(&uc->prop_type_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); + ufbxi_map_init(&uc->fbx_id_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL); + ufbxi_map_init(&uc->ptr_fbx_id_map, &uc->ator_tmp, &ufbxi_map_cmp_ptr_id, NULL); + ufbxi_map_init(&uc->texture_file_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); + ufbxi_map_init(&uc->anim_stack_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); + ufbxi_map_init(&uc->fbx_attr_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL); + ufbxi_map_init(&uc->node_prop_set, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); + ufbxi_map_init(&uc->dom_node_map, &uc->ator_tmp, &ufbxi_map_cmp_uintptr, NULL); + + uc->tmp.ator = &uc->ator_tmp; + uc->tmp_parse.ator = &uc->ator_tmp; + uc->tmp_stack.ator = &uc->ator_tmp; + uc->tmp_connections.ator = &uc->ator_tmp; + uc->tmp_node_ids.ator = &uc->ator_tmp; + uc->tmp_elements.ator = &uc->ator_tmp; + uc->tmp_element_offsets.ator = &uc->ator_tmp; + uc->tmp_element_fbx_ids.ator = &uc->ator_tmp; + uc->tmp_element_ptrs.ator = &uc->ator_tmp; + for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { + uc->tmp_typed_element_offsets[i].ator = &uc->ator_tmp; + } + uc->tmp_mesh_textures.ator = &uc->ator_tmp; + uc->tmp_full_weights.ator = &uc->ator_tmp; + uc->tmp_dom_nodes.ator = &uc->ator_tmp; + uc->tmp_element_id.ator = &uc->ator_tmp; + uc->tmp_ascii_spans.ator = &uc->ator_tmp; + + for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { + uc->tmp_thread_parse[i].ator = &uc->ator_tmp; + uc->tmp_thread_parse[i].unordered = true; + uc->tmp_thread_parse[i].clearable = true; + } + + uc->result.ator = &uc->ator_result; + + uc->tmp.unordered = true; + uc->tmp_parse.unordered = true; + uc->tmp_parse.clearable = true; + uc->result.unordered = true; + + uc->warnings.error = &uc->error; + uc->warnings.result = &uc->result; + uc->warnings.tmp_stack.ator = &uc->ator_tmp; + uc->string_pool.warnings = &uc->warnings; + + // Set zero size `swap_arr` to a non-NULL buffer so we can tell the difference between empty + // array and an allocation failure. + uc->swap_arr = (char*)ufbxi_zero_size_buffer; + + // NOTE: Though `inflate_retain` leaks out of the scope we don't use it outside this function. + // cppcheck-suppress autoVariables + uc->inflate_retain = &inflate_retain; + + int ok = ufbxi_load_imp(uc); + + if (uc->close_fn) { + uc->close_fn(uc->read_user); + } + + ufbxi_free_temp(uc); + + if (ok) { + if (p_error) { + ufbxi_clear_error(p_error); + } + return &uc->scene_imp->scene; + } else { + ufbxi_fix_error_type(&uc->error, "Failed to load", p_error); + if (p_error && p_error->type == UFBX_ERROR_UNKNOWN && uc->scene.metadata.file_format == UFBX_FILE_FORMAT_FBX && !ufbxi_supports_version(uc->version)) { + p_error->description.data = "Unsupported version"; + p_error->description.length = strlen("Unsupported version"); + p_error->type = UFBX_ERROR_UNSUPPORTED_VERSION; + ufbxi_fmt_err_info(p_error, "%u", uc->version); + } + ufbxi_free_result(uc); + return NULL; + } +} + +// -- Animation evaluation + +static ufbxi_forceinline bool ufbxi_override_less_than_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) +{ + if (over->element_id != element_id) return over->element_id < element_id; + if (over->_internal_key != prop->_internal_key) return over->_internal_key < prop->_internal_key; + return strcmp(over->prop_name.data, prop->name.data); +} + +static ufbxi_forceinline bool ufbxi_override_equals_to_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) +{ + if (over->element_id != element_id) return false; + if (over->_internal_key != prop->_internal_key) return false; + return !strcmp(over->prop_name.data, prop->name.data); +} + +static ufbxi_noinline bool ufbxi_find_prop_override(const ufbx_prop_override_list *overrides, uint32_t element_id, ufbx_prop *prop) +{ + size_t ix = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_prop_override, 16, &ix, overrides->data, 0, overrides->count, + ( ufbxi_override_less_than_prop(a, element_id, prop) ), + ( ufbxi_override_equals_to_prop(a, element_id, prop) )); + + if (ix != SIZE_MAX) { + const ufbx_prop_override *over = &overrides->data[ix]; + const uint32_t clear_flags = UFBX_PROP_FLAG_NO_VALUE | UFBX_PROP_FLAG_NOT_FOUND; + prop->flags = (ufbx_prop_flags)(((uint32_t)prop->flags & ~clear_flags) | UFBX_PROP_FLAG_OVERRIDDEN); + prop->value_vec4 = over->value; + prop->value_real_arr[3] = 0.0f; + prop->value_int = over->value_int; + prop->value_str = over->value_str; + prop->value_blob.data = prop->value_str.data; + prop->value_blob.size = prop->value_str.length; + return true; + } else { + return false; + } +} + +static ufbxi_noinline ufbx_prop_override_list ufbxi_find_element_prop_overrides(const ufbx_prop_override_list *overrides, uint32_t element_id) +{ + size_t begin = overrides->count, end = begin; + + ufbxi_macro_lower_bound_eq(ufbx_prop_override, 32, &begin, overrides->data, 0, overrides->count, + (a->element_id < element_id), + (a->element_id == element_id)); + + ufbxi_macro_upper_bound_eq(ufbx_prop_override, 32, &end, overrides->data, begin, overrides->count, + (a->element_id == element_id)); + + ufbx_prop_override_list result = { overrides->data + begin, end - begin }; + return result; +} + +typedef struct ufbxi_anim_layer_combine_ctx { + const ufbx_anim *anim; + const ufbx_element *element; + double time; + ufbx_rotation_order rotation_order; + bool has_rotation_order; +} ufbxi_anim_layer_combine_ctx; + +static ufbxi_noinline double ufbxi_pow_abs(double v, double e) +{ + if (e <= 0.0) return 1.0; + if (e >= 1.0) return v; + double sign = v < 0.0 ? -1.0 : 1.0; + return sign * ufbx_pow(v * sign, e); +} + +// Recursion is limited by the fact that we recurse only when the property name is "Lcl Rotation" +// and when recursing we always evaluate the property "RotationOrder" +static ufbxi_noinline void ufbxi_combine_anim_layer(ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value) + ufbxi_recursive_function_void(ufbxi_combine_anim_layer, (ctx, layer, weight, prop_name, result, value), 2, + (ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value)) +{ + if (layer->compose_rotation && layer->blended && prop_name == ufbxi_Lcl_Rotation && !ctx->has_rotation_order) { + ufbx_prop rp = ufbx_evaluate_prop_len(ctx->anim, ctx->element, ufbxi_RotationOrder, sizeof(ufbxi_RotationOrder) - 1, ctx->time); + // NOTE: Defaults to 0 (UFBX_ROTATION_XYZ) gracefully if property is not found + if (rp.value_int >= 0 && rp.value_int <= UFBX_ROTATION_ORDER_SPHERIC) { + ctx->rotation_order = (ufbx_rotation_order)rp.value_int; + } else { + ctx->rotation_order = UFBX_ROTATION_ORDER_XYZ; + } + ctx->has_rotation_order = true; + } + + if (layer->additive) { + if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) { + result->x *= (ufbx_real)ufbxi_pow_abs(value->x, weight); + result->y *= (ufbx_real)ufbxi_pow_abs(value->y, weight); + result->z *= (ufbx_real)ufbxi_pow_abs(value->z, weight); + } else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) { + ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order); + ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order); + b = ufbx_quat_slerp(ufbx_identity_quat, b, weight); + ufbx_quat res = ufbxi_mul_quat(a, b); + *result = ufbx_quat_to_euler(res, ctx->rotation_order); + } else { + result->x += value->x * weight; + result->y += value->y * weight; + result->z += value->z * weight; + } + } else if (layer->blended) { + ufbx_real res_weight = 1.0f - weight; + if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) { + result->x = (ufbx_real)(ufbxi_pow_abs(result->x, res_weight) * ufbxi_pow_abs(value->x, weight)); + result->y = (ufbx_real)(ufbxi_pow_abs(result->y, res_weight) * ufbxi_pow_abs(value->y, weight)); + result->z = (ufbx_real)(ufbxi_pow_abs(result->z, res_weight) * ufbxi_pow_abs(value->z, weight)); + } else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) { + ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order); + ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order); + ufbx_quat res = ufbx_quat_slerp(a, b, weight); + *result = ufbx_quat_to_euler(res, ctx->rotation_order); + } else { + result->x = result->x * res_weight + value->x * weight; + result->y = result->y * res_weight + value->y * weight; + result->z = result->z * res_weight + value->z * weight; + } + } else { + *result = *value; + } +} + +static ufbxi_forceinline bool ufbxi_anim_layer_might_contain_id(const ufbx_anim_layer *layer, uint32_t id) +{ + uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1; + bool ok = id - layer->_min_element_id <= (layer->_max_element_id - layer->_min_element_id); + ok &= (layer->_element_id_bitmask[(id >> 5) & id_mask] & (1u << (id & 31))) != 0; + return ok; +} + +static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, size_t num_props, uint32_t flags) +{ + ufbxi_anim_layer_combine_ctx combine_ctx = { anim, element, time }; + + uint32_t element_id = element->element_id; + size_t num_layers = anim->layers.count; + for (size_t layer_ix = 0; layer_ix < num_layers; layer_ix++) { + ufbx_anim_layer *layer = anim->layers.data[layer_ix]; + if (!ufbxi_anim_layer_might_contain_id(layer, element_id)) continue; + + // Find the weight for the current layer + // TODO: Should this be searched from multiple layers? + ufbx_real weight = layer_ix < anim->override_layer_weights.count ? anim->override_layer_weights.data[layer_ix] : layer->weight; + if (layer->weight_is_animated && layer->blended) { + ufbx_anim_prop *weight_aprop = ufbxi_find_anim_prop_start(layer, &layer->element); + if (weight_aprop) { + weight = ufbx_evaluate_anim_value_real_flags(weight_aprop->anim_value, time, flags) / (ufbx_real)100.0; + if (weight < 0.0f) weight = 0.0f; + if (weight > 0.99999f) weight = 1.0f; + } + } + + ufbx_anim_prop *aprop = ufbxi_find_anim_prop_start(layer, element); + if (!aprop) continue; + + for (size_t i = 0; i < num_props; i++) { + ufbx_prop *prop = &props[i]; + + // Don't evaluate on top of overridden properties + if ((prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0) continue; + + // Connections override animation by default + if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) continue; + + // Skip until we reach `aprop >= prop` + // NOTE: No need to check for end as `anim_props` is terminated with a NULL sentinel. + while (aprop->element == element && aprop->_internal_key < prop->_internal_key) aprop++; + if (aprop->prop_name.data != prop->name.data) { + while (aprop->element == element && strcmp(aprop->prop_name.data, prop->name.data) < 0) aprop++; + } + + // TODO: Should we skip the blending for the first layer _per property_ + // This could be done by having `UFBX_PROP_FLAG_ANIMATION_EVALUATED` + // that gets set for the first layer of animation that is applied. + if (aprop->prop_name.data == prop->name.data) { + ufbx_vec3 v = ufbx_evaluate_anim_value_vec3_flags(aprop->anim_value, time, flags); + if (layer_ix == 0) { + prop->value_vec3 = v; + } else { + ufbxi_combine_anim_layer(&combine_ctx, layer, weight, prop->name.data, &prop->value_vec3, &v); + } + } + } + } + + ufbxi_for(ufbx_prop, prop, props, num_props) { + if (prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) continue; + prop->value_int = ufbxi_f64_to_i64(prop->value_real); + } +} + +// Recursion limited by not calling `ufbx_evaluate_prop_len()` with a connected property, +// meaning it will never call `ufbxi_evaluate_connected_prop()` again indirectly. +static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) + ufbxi_recursive_function_void(ufbxi_evaluate_connected_prop, (prop, anim, element, name, time, flags), 3, + (ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags)) +{ + ufbx_connection *conn = ufbxi_find_prop_connection(element, name); + + for (size_t i = 0; i < 1000 && conn; i++) { + ufbx_connection *next_conn = ufbxi_find_prop_connection(conn->src, conn->src_prop.data); + if (!next_conn) break; + conn = next_conn; + } + + // Found a non-cyclic connection + if (conn && !ufbxi_find_prop_connection(conn->src, conn->src_prop.data)) { + ufbx_prop ep = ufbx_evaluate_prop_len_flags(anim, conn->src, conn->src_prop.data, conn->src_prop.length, time, flags); + prop->value_vec4 = ep.value_vec4; + prop->value_int = ep.value_int; + prop->value_str = ep.value_str; + prop->value_blob = ep.value_blob; + } else { + // Connection not found, maybe it's animated? + prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags & ~(uint32_t)UFBX_PROP_FLAG_CONNECTED); + } +} + +typedef struct { + const ufbx_prop *prop, *prop_end; + const ufbx_prop_override *over, *over_end; + ufbx_prop tmp; +} ufbxi_prop_iter; + +static ufbxi_noinline void ufbxi_init_prop_iter_slow(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element) +{ + iter->prop = element->props.props.data; + iter->prop_end = element->props.props.data + element->props.props.count; + + ufbx_prop_override_list over = ufbxi_find_element_prop_overrides(&anim->prop_overrides, element->element_id); + iter->over = over.data; + iter->over_end = over.data + over.count; + if (over.count > 0) { + memset(&iter->tmp, 0, sizeof(ufbx_prop)); + } +} + +static ufbxi_forceinline void ufbxi_init_prop_iter(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element) +{ + iter->prop = element->props.props.data; + iter->prop_end = ufbxi_add_ptr(element->props.props.data, element->props.props.count); + iter->over = iter->over_end = NULL; + if (anim->prop_overrides.count > 0) { + ufbxi_init_prop_iter_slow(iter, anim, element); + } +} + +static ufbxi_noinline const ufbx_prop *ufbxi_next_prop_slow(ufbxi_prop_iter *iter) +{ + const ufbx_prop *prop = iter->prop; + const ufbx_prop_override *over = iter->over; + if (prop == iter->prop_end && over == iter->over_end) return NULL; + + // We can use `UINT32_MAX` as a terminating key (aka prefix) as prop names must + // be valid UTF-8 and the byte sequence "\xff\xff\xff\xff" is not valid. + uint32_t prop_key = prop != iter->prop_end ? prop->_internal_key : UINT32_MAX; + uint32_t over_key = over != iter->over_end ? over->_internal_key : UINT32_MAX; + + int cmp = 0; + if (prop_key != over_key) { + cmp = prop_key < over_key ? -1 : 1; + } else { + cmp = strcmp(prop->name.data, over->prop_name.data); + } + + if (cmp >= 0) { + ufbx_prop *dst = &iter->tmp; + dst->name = over->prop_name; + dst->_internal_key = over->_internal_key; + dst->type = UFBX_PROP_UNKNOWN; + dst->flags = UFBX_PROP_FLAG_OVERRIDDEN; + dst->value_str = over->value_str; + dst->value_blob.data = dst->value_str.data; + dst->value_blob.size = dst->value_str.length; + dst->value_int = over->value_int; + dst->value_vec4 = over->value; + iter->over = over + 1; + if (cmp == 0) { + iter->prop = prop + 1; + } + return dst; + } else { + iter->prop = prop + 1; + return prop; + } +} + +static ufbxi_forceinline const ufbx_prop *ufbxi_next_prop(ufbxi_prop_iter *iter) +{ + if (iter->over == iter->over_end) { + if (iter->prop == iter->prop_end) return NULL; + return iter->prop++; + } else { + return ufbxi_next_prop_slow(iter); + } +} + +static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, const char *const *prop_names, size_t max_props, uint32_t flags) +{ + const char *name = prop_names[0]; + uint32_t key = ufbxi_get_name_key_c(name); + size_t num_props = 0; + +#if defined(UFBX_REGRESSION) + for (size_t i = 1; i < max_props; i++) { + ufbx_assert(strcmp(prop_names[i - 1], prop_names[i]) < 0); + } +#endif + + size_t name_ix = 0; + + ufbxi_prop_iter iter; // ufbxi_uninit + ufbxi_init_prop_iter(&iter, anim, element); + const ufbx_prop *prop = NULL; + while ((prop = ufbxi_next_prop(&iter)) != NULL) { + while (name_ix < max_props) { + if (key > prop->_internal_key) break; + if (name == prop->name.data) { + if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { + ufbx_prop *dst = &props[num_props++]; + *dst = *prop; + ufbxi_evaluate_connected_prop(dst, anim, element, name, time, flags); + } else if ((prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN)) != 0) { + props[num_props++] = *prop; + } + break; + } else if (strcmp(name, prop->name.data) < 0) { + name_ix++; + if (name_ix < max_props) { + name = prop_names[name_ix]; + key = ufbxi_get_name_key_c(name); + } + } else { + break; + } + } + } + + ufbxi_evaluate_props(anim, element, time, props, num_props, flags); + + ufbx_props prop_list; + prop_list.props.data = props; + prop_list.props.count = prop_list.num_animated = num_props; + prop_list.defaults = (ufbx_props*)&element->props; + return prop_list; +} + +// Recursion limited by not calling `ufbx_evaluate_curve()` with `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. +static ufbxi_noinline ufbx_real ufbxi_extrapolate_curve(const ufbx_anim_curve *curve, double real_time, uint32_t flags) + ufbxi_recursive_function(ufbx_real, ufbxi_extrapolate_curve, (curve, real_time, flags), 3, + (const ufbx_anim_curve *curve, double real_time, uint32_t flags)) +{ + bool pre = real_time < curve->min_time; + const ufbx_keyframe *key; + ufbx_extrapolation ext; + if (pre) { + key = &curve->keyframes.data[0]; + ext = curve->pre_extrapolation; + } else { + key = &curve->keyframes.data[curve->keyframes.count - 1]; + ext = curve->post_extrapolation; + } + + if (ext.mode == UFBX_EXTRAPOLATION_CONSTANT) { + return key->value; + } else if (ext.mode == UFBX_EXTRAPOLATION_SLOPE) { + ufbx_tangent tangent = *(pre ? &key->right : &key->left); + return key->value + (ufbx_real)(tangent.dy * ((real_time - key->time) / tangent.dx)); + } else if (ext.repeat_count == 0) { + return key->value; + } + + // Perform all operations in KTime ticks to be frame perfect + double scale = (double)curve->element.scene->metadata.ktime_second; + double min_time = ufbx_rint(curve->min_time * scale); + double max_time = ufbx_rint(curve->max_time * scale); + double time = real_time * scale; + + double delta = pre ? min_time - time : time - max_time; + double duration = max_time - min_time; + + // Require at least one KTime unit + if (!(duration >= 1.0)) return key->value; + + double rep = delta / duration; + double rep_n = ufbx_floor(rep); + double rep_d = delta - rep_n * duration; + + if (ext.repeat_count > 0 && rep_n >= (double)ext.repeat_count) { + // Clamp to the repeat count to handle mirroring + rep_n = (double)(ext.repeat_count - 1); + rep_d = duration; + } + + if (ext.mode == UFBX_EXTRAPOLATION_MIRROR) { + double rep_parity = rep_n*0.5 - ufbx_floor(rep_n*0.5); + if (rep_parity <= 0.25) { + rep_d = duration - rep_d; + } + } + + if (pre) rep_d = duration - rep_d; + double new_time = (min_time + rep_d) / scale; + + ufbx_real value = ufbx_evaluate_curve_flags(curve, new_time, key->value, flags | UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION); + + if (ext.mode == UFBX_EXTRAPOLATION_REPEAT_RELATIVE) { + ufbx_real val_delta = curve->keyframes.data[curve->keyframes.count - 1].value - curve->keyframes.data[0].value; + if (pre) val_delta = -val_delta; + value += val_delta * (ufbx_real)(rep_n + 1.0); + } + + return value; +} + +#if UFBXI_FEATURE_SCENE_EVALUATION + +typedef struct { + char *src_element; + char *dst_element; + + ufbxi_scene_imp *src_imp; + ufbx_scene src_scene; + ufbx_evaluate_opts opts; + ufbx_anim *anim; + double time; + + ufbx_error error; + + // Allocators + ufbxi_allocator ator_result; + ufbxi_allocator ator_tmp; + + ufbxi_buf result; + ufbxi_buf tmp; + + ufbx_scene scene; + + ufbxi_scene_imp *scene_imp; +} ufbxi_eval_context; + +static ufbxi_forceinline ufbx_element *ufbxi_translate_element(ufbxi_eval_context *ec, void *elem) +{ + return elem ? (ufbx_element*)(ec->dst_element + ((char*)elem - ec->src_element)) : NULL; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_element_list(ufbxi_eval_context *ec, void *p_list) +{ + ufbx_element_list *list = (ufbx_element_list*)p_list; + size_t count = list->count; + ufbx_element **src = list->data; + ufbx_element **dst = ufbxi_push(&ec->result, ufbx_element*, count); + ufbxi_check_err(&ec->error, dst); + list->data = dst; + for (size_t i = 0; i < count; i++) { + dst[i] = ufbxi_translate_element(ec, src[i]); + } + return 1; +} + +static ufbxi_noinline void ufbxi_translate_maps(ufbxi_eval_context *ec, ufbx_material_map *maps, size_t count) +{ + ufbxi_nounroll ufbxi_for(ufbx_material_map, map, maps, count) { + map->texture = (ufbx_texture*)ufbxi_translate_element(ec, map->texture); + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_anim(ufbxi_eval_context *ec, ufbx_anim **p_anim) +{ + ufbx_anim *anim = ufbxi_push_copy(&ec->result, ufbx_anim, 1, *p_anim); + ufbxi_check_err(&ec->error, anim); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &anim->layers)); + *p_anim = anim; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context *ec) +{ + ec->scene = ec->src_scene; + size_t num_elements = ec->scene.elements.count; + + char *element_data = (char*)ufbxi_push(&ec->result, uint64_t, ec->scene.metadata.element_buffer_size/8); + ufbxi_check_err(&ec->error, element_data); + + ec->scene.elements.data = ufbxi_push(&ec->result, ufbx_element*, num_elements); + ufbxi_check_err(&ec->error, ec->scene.elements.data); + + ec->src_element = (char*)ec->src_scene.elements.data[0]; + ec->dst_element = element_data; + + for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { + ec->scene.elements_by_type[i].data = ufbxi_push(&ec->result, ufbx_element*, ec->scene.elements_by_type[i].count); + ufbxi_check_err(&ec->error, ec->scene.elements_by_type[i].data); + } + + size_t num_connections = ec->scene.connections_dst.count; + ec->scene.connections_src.data = ufbxi_push(&ec->result, ufbx_connection, num_connections); + ec->scene.connections_dst.data = ufbxi_push(&ec->result, ufbx_connection, num_connections); + ufbxi_check_err(&ec->error, ec->scene.connections_src.data); + ufbxi_check_err(&ec->error, ec->scene.connections_dst.data); + for (size_t i = 0; i < num_connections; i++) { + ufbx_connection *src = &ec->scene.connections_src.data[i]; + ufbx_connection *dst = &ec->scene.connections_dst.data[i]; + *src = ec->src_scene.connections_src.data[i]; + *dst = ec->src_scene.connections_dst.data[i]; + src->src = ufbxi_translate_element(ec, src->src); + src->dst = ufbxi_translate_element(ec, src->dst); + dst->src = ufbxi_translate_element(ec, dst->src); + dst->dst = ufbxi_translate_element(ec, dst->dst); + } + + ec->scene.elements_by_name.data = ufbxi_push(&ec->result, ufbx_name_element, num_elements); + ufbxi_check_err(&ec->error, ec->scene.elements_by_name.data); + + ec->scene.root_node = (ufbx_node*)ufbxi_translate_element(ec, ec->scene.root_node); + ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->scene.anim)); + + for (size_t i = 0; i < num_elements; i++) { + ufbx_element *src = ec->src_scene.elements.data[i]; + ufbx_element *dst = ufbxi_translate_element(ec, src); + size_t size = ufbx_element_type_size[src->type]; + ufbx_assert(size > 0); + memcpy(dst, src, size); + + ec->scene.elements.data[i] = dst; + ec->scene.elements_by_type[src->type].data[src->typed_id] = dst; + + dst->connections_src.data = ec->scene.connections_src.data + (dst->connections_src.data - ec->src_scene.connections_src.data); + dst->connections_dst.data = ec->scene.connections_dst.data + (dst->connections_dst.data - ec->src_scene.connections_dst.data); + if (dst->instances.count > 0) { + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &dst->instances)); + } + + ufbx_name_element named = ec->src_scene.elements_by_name.data[i]; + named.element = ufbxi_translate_element(ec, named.element); + ec->scene.elements_by_name.data[i] = named; + } + + ufbxi_for_ptr_list(ufbx_node, p_node, ec->scene.nodes) { + ufbx_node *node = *p_node; + node->parent = (ufbx_node*)ufbxi_translate_element(ec, node->parent); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->children)); + + node->attrib = ufbxi_translate_element(ec, node->attrib); + node->mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->mesh); + node->light = (ufbx_light*)ufbxi_translate_element(ec, node->light); + node->camera = (ufbx_camera*)ufbxi_translate_element(ec, node->camera); + node->bone = (ufbx_bone*)ufbxi_translate_element(ec, node->bone); + node->inherit_scale_node = (ufbx_node*)ufbxi_translate_element(ec, node->inherit_scale_node); + node->scale_helper = (ufbx_node*)ufbxi_translate_element(ec, node->scale_helper); + node->bind_pose = (ufbx_pose*)ufbxi_translate_element(ec, node->bind_pose); + + if (node->all_attribs.count > 1) { + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->all_attribs)); + } else if (node->all_attribs.count == 1) { + node->all_attribs.data = &node->attrib; + } + + node->geometry_transform_helper = (ufbx_node*)ufbxi_translate_element(ec, node->geometry_transform_helper); + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->materials)); + } + + ufbxi_for_ptr_list(ufbx_mesh, p_mesh, ec->scene.meshes) { + ufbx_mesh *mesh = *p_mesh; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->materials)); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->skin_deformers)); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->blend_deformers)); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->cache_deformers)); + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->all_deformers)); + } + + ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, ec->scene.stereo_cameras) { + ufbx_stereo_camera *stereo = *p_stereo; + stereo->left = (ufbx_camera*)ufbxi_translate_element(ec, stereo->left); + stereo->right = (ufbx_camera*)ufbxi_translate_element(ec, stereo->right); + } + + ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, ec->scene.skin_deformers) { + ufbx_skin_deformer *skin = *p_skin; + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &skin->clusters)); + } + + ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, ec->scene.skin_clusters) { + ufbx_skin_cluster *cluster = *p_cluster; + cluster->bone_node = (ufbx_node*)ufbxi_translate_element(ec, cluster->bone_node); + } + + ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, ec->scene.blend_deformers) { + ufbx_blend_deformer *blend = *p_blend; + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &blend->channels)); + } + + ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, ec->scene.blend_channels) { + ufbx_blend_channel *chan = *p_chan; + + ufbx_blend_keyframe *keys = ufbxi_push(&ec->result, ufbx_blend_keyframe, chan->keyframes.count); + ufbxi_check_err(&ec->error, keys); + for (size_t i = 0; i < chan->keyframes.count; i++) { + keys[i] = chan->keyframes.data[i]; + keys[i].shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, keys[i].shape); + } + chan->keyframes.data = keys; + chan->target_shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, chan->target_shape); + } + + ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, ec->scene.cache_deformers) { + ufbx_cache_deformer *deformer = *p_deformer; + deformer->file = (ufbx_cache_file*)ufbxi_translate_element(ec, deformer->file); + } + + ufbxi_for_ptr_list(ufbx_material, p_material, ec->scene.materials) { + ufbx_material *material = *p_material; + + material->shader = (ufbx_shader*)ufbxi_translate_element(ec, material->shader); + ufbxi_translate_maps(ec, material->fbx.maps, UFBX_MATERIAL_FBX_MAP_COUNT); + ufbxi_translate_maps(ec, material->pbr.maps, UFBX_MATERIAL_PBR_MAP_COUNT); + + ufbx_material_texture *textures = ufbxi_push(&ec->result, ufbx_material_texture, material->textures.count); + ufbxi_check_err(&ec->error, textures); + for (size_t i = 0; i < material->textures.count; i++) { + textures[i] = material->textures.data[i]; + textures[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, textures[i].texture); + } + material->textures.data = textures; + } + + ufbxi_for_ptr_list(ufbx_texture, p_texture, ec->scene.textures) { + ufbx_texture *texture = *p_texture; + texture->video = (ufbx_video*)ufbxi_translate_element(ec, texture->video); + + ufbx_texture_layer *layers = ufbxi_push(&ec->result, ufbx_texture_layer, texture->layers.count); + ufbxi_check_err(&ec->error, layers); + for (size_t i = 0; i < texture->layers.count; i++) { + layers[i] = texture->layers.data[i]; + layers[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, layers[i].texture); + } + texture->layers.data = layers; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &texture->file_textures)); + + if (texture->shader) { + ufbx_shader_texture *shader = texture->shader; + shader = ufbxi_push_copy(&ec->result, ufbx_shader_texture, 1, shader); + ufbxi_check_err(&ec->error, shader); + texture->shader = shader; + + ufbx_shader_texture_input *inputs = ufbxi_push_copy(&ec->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data); + ufbxi_check_err(&ec->error, inputs); + shader->inputs.data = inputs; + } + } + + ufbxi_for_ptr_list(ufbx_shader, p_shader, ec->scene.shaders) { + ufbx_shader *shader = *p_shader; + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &shader->bindings)); + } + + ufbxi_for_ptr_list(ufbx_display_layer, p_layer, ec->scene.display_layers) { + ufbx_display_layer *layer = *p_layer; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->nodes)); + } + + ufbxi_for_ptr_list(ufbx_selection_set, p_set, ec->scene.selection_sets) { + ufbx_selection_set *set = *p_set; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &set->nodes)); + } + + ufbxi_for_ptr_list(ufbx_selection_node, p_node, ec->scene.selection_nodes) { + ufbx_selection_node *node = *p_node; + + node->target_node = (ufbx_node*)ufbxi_translate_element(ec, node->target_node); + node->target_mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->target_mesh); + } + + ufbxi_for_ptr_list(ufbx_constraint, p_constraint, ec->scene.constraints) { + ufbx_constraint *constraint = *p_constraint; + + constraint->node = (ufbx_node*)ufbxi_translate_element(ec, constraint->node); + constraint->aim_up_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->aim_up_node); + constraint->ik_effector = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_effector); + constraint->ik_end_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_end_node); + + ufbx_constraint_target *targets = ufbxi_push(&ec->result, ufbx_constraint_target, constraint->targets.count); + ufbxi_check_err(&ec->error, targets); + for (size_t i = 0; i < constraint->targets.count; i++) { + targets[i] = constraint->targets.data[i]; + targets[i].node = (ufbx_node*)ufbxi_translate_element(ec, targets[i].node); + } + constraint->targets.data = targets; + } + + ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, ec->scene.audio_layers) { + ufbx_audio_layer *layer = *p_layer; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->clips)); + } + + ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, ec->scene.anim_stacks) { + ufbx_anim_stack *stack = *p_stack; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &stack->layers)); + ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &stack->anim)); + } + + ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, ec->scene.anim_layers) { + ufbx_anim_layer *layer = *p_layer; + + ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->anim_values)); + ufbx_anim_prop *props = ufbxi_push(&ec->result, ufbx_anim_prop, layer->anim_props.count + 1); + ufbxi_check_err(&ec->error, props); + for (size_t i = 0; i < layer->anim_props.count; i++) { + props[i] = layer->anim_props.data[i]; + props[i].element = ufbxi_translate_element(ec, props[i].element); + props[i].anim_value = (ufbx_anim_value*)ufbxi_translate_element(ec, props[i].anim_value); + } + // Maintain NULL sentinel + memset(props + layer->anim_props.count, 0, sizeof(ufbx_anim_prop)); + layer->anim_props.data = props; + } + + ufbxi_for_ptr_list(ufbx_pose, p_pose, ec->scene.poses) { + ufbx_pose *pose = *p_pose; + + ufbx_bone_pose *bones = ufbxi_push(&ec->result, ufbx_bone_pose, pose->bone_poses.count); + ufbxi_check_err(&ec->error, bones); + for (size_t i = 0; i < pose->bone_poses.count; i++) { + bones[i] = pose->bone_poses.data[i]; + bones[i].bone_node = (ufbx_node*)ufbxi_translate_element(ec, bones[i].bone_node); + } + pose->bone_poses.data = bones; + } + + ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->anim)); + + ufbxi_for_ptr_list(ufbx_anim_value, p_value, ec->scene.anim_values) { + ufbx_anim_value *value = *p_value; + value->curves[0] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[0]); + value->curves[1] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[1]); + value->curves[2] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[2]); + } + + ufbx_anim anim = *ec->anim; + ufbx_prop_override *over = anim.prop_overrides.data, *over_end = ufbxi_add_ptr(over, anim.prop_overrides.count); + + // Evaluate the properties + ufbxi_for_ptr_list(ufbx_element, p_elem, ec->scene.elements) { + ufbx_element *elem = *p_elem; + size_t num_animated = elem->props.num_animated; + size_t num_override = 0; + + // Setup the overrides for this element if found + while (over != over_end && over->element_id == elem->element_id) { + num_override++; + over++; + } + + num_animated += num_override; + if (num_animated == 0) continue; + + anim.prop_overrides.data = ufbxi_sub_ptr(over, num_override); + anim.prop_overrides.count = num_override; + + ufbx_prop *props = ufbxi_push(&ec->result, ufbx_prop, num_animated); + ufbxi_check_err(&ec->error, props); + + elem->props = ufbx_evaluate_props_flags(&anim, elem, ec->time, props, num_animated, ec->opts.evaluate_flags); + elem->props.defaults = &ec->src_scene.elements.data[elem->element_id]->props; + } + + // Update all derived values + ufbxi_update_scene(&ec->scene, false, anim.transform_overrides.data, anim.transform_overrides.count); + + // Evaluate skinning if requested + if (ec->opts.evaluate_skinning) { + ufbx_geometry_cache_data_opts cache_opts = { 0 }; + cache_opts.open_file_cb = ec->opts.open_file_cb; + ufbxi_check_err(&ec->error, ufbxi_evaluate_skinning(&ec->scene, &ec->error, &ec->result, &ec->tmp, + ec->time, ec->opts.load_external_files && ec->opts.evaluate_caches, &cache_opts)); + } + + // Retain the scene, this must be the final allocation as we copy + // `ator_result` to `ufbx_scene_imp`. + ufbxi_scene_imp *imp = ufbxi_push_zero(&ec->result, ufbxi_scene_imp, 1); + ufbxi_check_err(&ec->error, imp); + + ufbx_assert(ec->src_imp->magic == UFBXI_SCENE_IMP_MAGIC); + ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, &ec->src_imp->refcount); + + imp->magic = UFBXI_SCENE_IMP_MAGIC; + imp->scene = ec->scene; + imp->refcount.ator = ec->ator_result; + imp->refcount.ator.error = NULL; + + // Copy retained buffers and translate the allocator struct to the one + // contained within `ufbxi_scene_imp` + imp->refcount.buf = ec->result; + imp->refcount.buf.ator = &imp->refcount.ator; + + imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size; + imp->scene.metadata.temp_memory_used = ec->ator_tmp.current_size; + imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs; + imp->scene.metadata.temp_allocs = ec->ator_tmp.num_allocs; + + ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) { + (*p_elem)->scene = &imp->scene; + } + + ec->scene_imp = imp; + ec->result.ator = &ec->ator_result; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline ufbx_scene *ufbxi_evaluate_scene(ufbxi_eval_context *ec, ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *user_opts, ufbx_error *p_error) +{ + if (user_opts) { + ec->opts = *user_opts; + } else { + memset(&ec->opts, 0, sizeof(ec->opts)); + } + + ec->src_imp = ufbxi_get_imp(ufbxi_scene_imp, scene); + ec->src_scene = *scene; + ec->anim = anim ? (ufbx_anim*)anim : scene->anim; + ec->time = time; + + ufbxi_init_ator(&ec->error, &ec->ator_tmp, &ec->opts.temp_allocator, "temp"); + ufbxi_init_ator(&ec->error, &ec->ator_result, &ec->opts.result_allocator, "result"); + + ec->result.ator = &ec->ator_result; + ec->tmp.ator = &ec->ator_tmp; + + ec->result.unordered = true; + ec->tmp.unordered = true; + + if (ufbxi_evaluate_imp(ec)) { + ufbxi_buf_free(&ec->tmp); + ufbxi_free_ator(&ec->ator_tmp); + if (p_error) { + ufbxi_clear_error(p_error); + } + return &ec->scene_imp->scene; + } else { + ufbxi_fix_error_type(&ec->error, "Failed to evaluate", p_error); + ufbxi_buf_free(&ec->tmp); + ufbxi_buf_free(&ec->result); + ufbxi_free_ator(&ec->ator_tmp); + ufbxi_free_ator(&ec->ator_result); + return NULL; + } +} + +#endif + +typedef struct { + ufbx_error error; + ufbxi_allocator ator_result; + ufbxi_buf result; + const ufbx_scene *scene; + ufbx_anim_opts opts; + + ufbx_anim anim; + ufbxi_anim_imp *imp; +} ufbxi_create_anim_context; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_check_string(ufbx_error *error, ufbx_string *dst, const ufbx_string *src) +{ + size_t length = src->length != SIZE_MAX ? src->length : strlen(src->data); + const char *data = length != 0 ? src->data : ufbxi_empty_char; + if (length > 0) { + size_t valid_length = ufbxi_utf8_valid_length(data, length); + ufbxi_check_err_msg(error, valid_length == length, "Invalid UTF-8"); + } + + dst->data = data; + dst->length = length; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_anim_string(ufbxi_create_anim_context *ac, ufbx_string *str) +{ + size_t length = str->length; + if (length > 0) { + char *copy = ufbxi_push(&ac->result, char, length + 1); + ufbxi_check_err(&ac->error, copy); + memcpy(copy, str->data, length); + copy[str->length] = '\0'; + str->data = copy; + } else { + ufbx_assert(str->data == ufbxi_empty_char); + } + + return 1; +} + +static bool ufbxi_prop_override_prop_name_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb; + if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; + return ufbxi_str_less(a->prop_name, b->prop_name); +} + +static bool ufbxi_prop_override_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb; + if (a->element_id != b->element_id) return a->element_id < b->element_id; + if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; + return strcmp(a->prop_name.data, b->prop_name.data) < 0; +} + +static bool ufbxi_transform_override_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_transform_override *a = (const ufbx_transform_override*)va, *b = (const ufbx_transform_override*)vb; + return a->node_id < b->node_id; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_anim_context *ac) +{ + const ufbx_scene *scene = ac->scene; + ufbx_anim *anim = &ac->anim; + + ufbxi_init_ator(&ac->error, &ac->ator_result, &ac->opts.result_allocator, "result"); + ac->result.unordered = true; + ac->result.ator = &ac->ator_result; + + anim->ignore_connections = ac->opts.ignore_connections; + anim->custom = true; + + size_t num_layers = ac->opts.layer_ids.count; + anim->layers.count = num_layers; + anim->layers.data = ufbxi_push_zero(&ac->result, ufbx_anim_layer*, num_layers); + ufbxi_check_err(&ac->error, anim->layers.data); + + if (ac->opts.override_layer_weights.count > 0) { + ufbxi_check_err_msg(&ac->error, ac->opts.override_layer_weights.count == num_layers, "override_layer_weights[] count must match layer_ids[] count"); + anim->override_layer_weights.data = ufbxi_push_copy(&ac->result, ufbx_real, num_layers, ac->opts.override_layer_weights.data); + ufbxi_check_err(&ac->error, anim->override_layer_weights.data); + anim->override_layer_weights.count = num_layers; + } + + for (size_t i = 0; i < num_layers; i++) { + uint32_t index = ac->opts.layer_ids.data[i]; + ufbxi_check_err_msg(&ac->error, index < scene->anim_layers.count, "layer_ids out of bounds"); + anim->layers.data[i] = ac->scene->anim_layers.data[index]; + } + + ufbx_const_prop_override_desc_list prop_overrides = ac->opts.prop_overrides; + if (prop_overrides.count > 0) { + anim->prop_overrides.count = prop_overrides.count; + anim->prop_overrides.data = ufbxi_push_zero(&ac->result, ufbx_prop_override, prop_overrides.count); + ufbxi_check_err(&ac->error, anim->prop_overrides.data); + + for (size_t i = 0; i < prop_overrides.count; i++) { + const ufbx_prop_override_desc *src = &prop_overrides.data[i]; + ufbx_prop_override *dst = &anim->prop_overrides.data[i]; + + dst->element_id = src->element_id; + dst->value = src->value; + dst->value_int = src->value_int; + + if (dst->value.x != 0.0f && dst->value_int == 0) { + dst->value_int = (int64_t)dst->value.x; + } else if (dst->value_int != 0 && dst->value.x == 0.0f) { + dst->value.x = (ufbx_real)dst->value_int; + } + + ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->prop_name, &src->prop_name)); + ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->value_str, &src->value_str)); + + dst->_internal_key = ufbxi_get_name_key(dst->prop_name.data, dst->prop_name.length); + } + + // Sort `anim->prop_overrides` first by `prop_name` only so we can deduplicate and + // convert them to global strings in `ufbxi_strings[]` if possible. + ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_prop_name_less, NULL); + + const ufbx_string *global_str = ufbxi_strings, *global_end = global_str + ufbxi_arraycount(ufbxi_strings); + ufbx_string prev_name = { ufbxi_empty_char }; + ufbxi_for_list(ufbx_prop_override, over, anim->prop_overrides) { + if (over->value_str.length > 0) { + ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->value_str)); + } + + if (ufbxi_str_equal(over->prop_name, prev_name)) { + over->prop_name = prev_name; + continue; + } + + while (global_str != global_end && ufbxi_str_less(*global_str, over->prop_name)) { + ++global_str; + } + + if (global_str != global_end && ufbxi_str_equal(*global_str, over->prop_name)) { + over->prop_name = *global_str; + } else { + ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->prop_name)); + } + + prev_name = over->prop_name; + } + + // Sort `anim->prop_overrides` to the actual order expected by evaluation. + ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_less, NULL); + + for (size_t i = 1; i < prop_overrides.count; i++) { + const ufbx_prop_override *prev = &anim->prop_overrides.data[i - 1]; + const ufbx_prop_override *next = &anim->prop_overrides.data[i]; + if (prev->element_id == next->element_id && prev->prop_name.data == next->prop_name.data) { + ufbxi_fmt_err_info(&ac->error, "element %u prop \"%s\"", prev->element_id, prev->prop_name.data); + ufbxi_fail_err_msg(&ac->error, "Duplicate override", "Duplicate override"); + } + } + } + + if (ac->opts.transform_overrides.count > 0) { + anim->transform_overrides.count = ac->opts.transform_overrides.count; + anim->transform_overrides.data = ufbxi_push_copy(&ac->result, ufbx_transform_override, anim->transform_overrides.count, ac->opts.transform_overrides.data); + ufbxi_check_err(&ac->error, anim->transform_overrides.data); + ufbxi_unstable_sort(anim->transform_overrides.data, anim->transform_overrides.count, sizeof(ufbx_transform_override), &ufbxi_transform_override_less, NULL); + } + + ac->imp = ufbxi_push(&ac->result, ufbxi_anim_imp, 1); + ufbxi_check_err(&ac->error, ac->imp); + + ufbxi_init_ref(&ac->imp->refcount, UFBXI_ANIM_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, scene))->refcount); + + ac->imp->magic = UFBXI_ANIM_IMP_MAGIC; + ac->imp->anim = ac->anim; + ac->imp->refcount.ator = ac->ator_result; + ac->imp->refcount.buf = ac->result; + + return 1; +} + +// -- Animation baking + +typedef struct { + ufbxi_refcount refcount; + ufbx_baked_anim bake; + uint32_t magic; +} ufbxi_baked_anim_imp; + +#if UFBXI_FEATURE_ANIMATION_BAKING + +typedef struct { + double time; + uint32_t flags; +} ufbxi_bake_time; + +UFBX_LIST_TYPE(ufbxi_bake_time_list, ufbxi_bake_time); + +typedef struct { + ufbx_error error; + ufbxi_allocator ator_tmp; + ufbxi_allocator ator_result; + + ufbxi_buf result; + ufbxi_buf tmp; + ufbxi_buf tmp_prop; + ufbxi_buf tmp_times; + ufbxi_buf tmp_bake_props; + ufbxi_buf tmp_nodes; + ufbxi_buf tmp_elements; + ufbxi_buf tmp_props; + ufbxi_buf tmp_bake_stack; + + ufbxi_bake_time_list layer_weight_times; + + ufbx_baked_node **baked_nodes; + bool *nodes_to_bake; + + char *tmp_arr; + size_t tmp_arr_size; + + const ufbx_scene *scene; + const ufbx_anim *anim; + ufbx_bake_opts opts; + + double ktime_offset; + + double time_begin; + double time_end; + double time_min; + double time_max; + + ufbx_baked_anim bake; + ufbxi_baked_anim_imp *imp; +} ufbxi_bake_context; + +typedef struct { + uint32_t sort_id; + uint32_t element_id; + const char *prop_name; + ufbx_anim_value *anim_value; +} ufbxi_bake_prop; + +static bool ufbxi_bake_prop_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbxi_bake_prop *a = (const ufbxi_bake_prop*)va; + const ufbxi_bake_prop *b = (const ufbxi_bake_prop*)vb; + if (a->sort_id != b->sort_id) return a->sort_id < b->sort_id; + if (a->element_id != b->element_id) return a->element_id < b->element_id; + if (a->prop_name != b->prop_name) return strcmp(a->prop_name, b->prop_name) < 0; + return false; +} + +ufbx_static_assert(bake_step_left, UFBX_BAKED_KEY_STEP_LEFT == 0x1); +ufbx_static_assert(bake_step_right, UFBX_BAKED_KEY_STEP_RIGHT == 0x2); +ufbx_static_assert(bake_step_key, UFBX_BAKED_KEY_STEP_KEY == 0x4); +static ufbxi_forceinline int ufbxi_cmp_bake_time(ufbxi_bake_time a, ufbxi_bake_time b) +{ + if (a.time != b.time) return a.time < b.time ? -1 : 1; + // Bit twiddling for a fast sorting of `0x1 (LEFT) < 0x0 < 0x2 (RIGHT)` + // by `step ^ 1`: `0x0 (LEFT) < 0x1 < 0x3 (RIGHT)` + uint32_t a_step = a.flags & 0x3, b_step = b.flags & 0x3; + if (a_step != b_step) return (a_step ^ 0x1) < (b_step ^ 0x1) ? -1 : 1; + return 0; +} + +ufbxi_nodiscard static ufbxi_forceinline int ufbxi_bake_push_time(ufbxi_bake_context *bc, double time, uint32_t flags) +{ + ufbxi_bake_time *p_key = ufbxi_push_fast(&bc->tmp_times, ufbxi_bake_time, 1); + if (!p_key) return 0; + p_key->time = time; + p_key->flags = flags; + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_times(ufbxi_bake_context *bc, const ufbx_anim_value *anim_value, bool resample_linear, uint32_t key_flag) +{ + double sample_rate = bc->opts.resample_rate; + double min_duration = bc->opts.minimum_sample_rate > 0.0 ? 1.0 / bc->opts.minimum_sample_rate : 0.0; + + for (size_t curve_ix = 0; curve_ix < 3; curve_ix++) { + ufbx_anim_curve *curve = anim_value->curves[curve_ix]; + if (!curve) continue; + + const ufbx_keyframe *keys = curve->keyframes.data; + size_t num_keys = curve->keyframes.count; + for (size_t key_ix = 0; key_ix < num_keys; key_ix++) { + ufbx_keyframe a = keys[key_ix]; + double a_time = a.time; + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, key_flag)); + if (key_ix + 1 >= num_keys) break; + ufbx_keyframe b = keys[key_ix + 1]; + double b_time = b.time; + + // Skip fully flat sections + if (a.value == b.value && a.right.dy == 0.0f && b.left.dy == 0.0f) continue; + + if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_PREV) { + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, b_time, UFBX_BAKED_KEY_STEP_LEFT)); + } else if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT) { + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, UFBX_BAKED_KEY_STEP_RIGHT)); + } else if ((resample_linear || a.interpolation == UFBX_INTERPOLATION_CUBIC) && sample_rate > 0.0) { + double duration = b_time - a_time; + if (duration <= min_duration) continue; + + double factor = 1.0; + while (duration * sample_rate / factor >= (double)bc->opts.max_keyframe_segments) { + factor *= 2.0; + } + + double padding = 0.5 / sample_rate; + double start = ufbx_ceil((a_time + padding) * sample_rate / factor) * factor; + double stop = b_time - padding; + for (size_t i = 0; i < bc->opts.max_keyframe_segments; i++) { + double time = (start + (double)i * factor) / sample_rate; + if (time >= stop) break; + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time, 0)); + } + } + } + } + + return 1; +} + +static const char *const ufbxi_transform_props[] = { + ufbxi_Lcl_Translation, ufbxi_Lcl_Rotation, ufbxi_Lcl_Scaling, ufbxi_PreRotation, ufbxi_PostRotation, + ufbxi_RotationOffset, ufbxi_ScalingOffset, ufbxi_RotationPivot, ufbxi_ScalingPivot, ufbxi_RotationOrder, +}; + +static const char *const ufbxi_complex_translation_props[] = { + ufbxi_ScalingPivot, ufbxi_RotationPivot, ufbxi_RotationOffset, ufbxi_ScalingOffset, +}; + +static const char *const ufbxi_complex_rotation_props[] = { + ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder, +}; + +static const char *const ufbxi_complex_rotation_sources[] = { + ufbxi_Lcl_Rotation, ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder, +}; + +ufbxi_nodiscard static ufbxi_noinline bool ufbxi_in_list(const char *const *items, size_t count, const char *item) +{ + for (size_t i = 0; i < count; i++) { + if (items[i] == item) return true; + } + return false; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_sort_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time *times, size_t count) +{ + ufbxi_check_err(&bc->error, ufbxi_grow_array(&bc->ator_tmp, &bc->tmp_arr, &bc->tmp_arr_size, count * sizeof(ufbxi_bake_time))); + ufbxi_macro_stable_sort(ufbxi_bake_time, 32, times, bc->tmp_arr, count, ( ufbxi_cmp_bake_time(*a, *b) < 0 )); + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time_list *p_dst) +{ + if (bc->layer_weight_times.count > 0) { + ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_times, ufbxi_bake_time, bc->layer_weight_times.count, bc->layer_weight_times.data)); + } + + if (bc->tmp_times.num_items == 0) { + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_begin, 0)); + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_end, 0)); + } + + size_t num_times = bc->tmp_times.num_items; + ufbxi_bake_time *times = ufbxi_push_pop(&bc->tmp_prop, &bc->tmp_times, ufbxi_bake_time, num_times); + ufbxi_check_err(&bc->error, times); + + ufbxi_check_err(&bc->error, ufbxi_sort_bake_times(bc, times, num_times)); + + // Deduplicate times + if (num_times > 0) { + size_t dst = 0; + ufbxi_bake_time prev = times[0]; + for (size_t src = 1; src < num_times; src++) { + ufbxi_bake_time next = times[src]; + // Merge keys with the same time and step flags `(0x1, 0x2)` + if (next.time == prev.time) { + if (((next.flags ^ prev.flags) & 0x3) == 0) { + prev.flags |= next.flags; + continue; + } else if (prev.flags & UFBX_BAKED_KEY_STEP_LEFT) { + next.flags |= UFBX_BAKED_KEY_STEP_KEY; + } else if (next.flags & UFBX_BAKED_KEY_STEP_RIGHT) { + prev.flags |= UFBX_BAKED_KEY_STEP_KEY; + } + } + + times[dst++] = prev; + prev = next; + } + times[dst++] = prev; + num_times = dst; + } + + // Cull too close resampled keys, these may arise during merging multiple times + if (num_times > 0) { + double min_dist = 0.25 / bc->opts.resample_rate; + uint32_t keep_flags = UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT|UFBX_BAKED_KEY_STEP_KEY|UFBX_BAKED_KEY_KEYFRAME; + + size_t dst = 0; + for (size_t src = 0; src < num_times; src++) { + ufbxi_bake_time cur = times[src]; + double delta = UFBX_INFINITY; + + bool keep = true; + if ((cur.flags & keep_flags) == 0) { + if (dst > 0) delta = cur.time - times[dst - 1].time; + if (src + 1 < num_times) delta = ufbx_fmin(delta, times[src + 1].time - cur.time); + if (delta < min_dist) keep = false; + } + if (keep) { + times[dst++] = cur; + } + } + num_times = dst; + } + + // Enforce maximum sample rate + if (bc->opts.maximum_sample_rate > 0.0) { + const double epsilon = 0.0078125 / bc->opts.maximum_sample_rate; + double sample_rate = bc->opts.maximum_sample_rate; + double max_interval = 1.0 / bc->opts.maximum_sample_rate; + double min_interval = 1.0 / bc->opts.maximum_sample_rate - epsilon; + size_t dst = 0, src = 0; + + // Pre-expand constant keyframes + for (size_t i = 0; i < num_times; i++) { + if ((times[i].flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { + double sign = (times[i].flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -1.0 : 1.0; + double time = times[i].time + sign * max_interval; + if (i > 0) time = ufbx_fmax(time, times[i - 1].time); + if (i + 1 < num_times) time = ufbx_fmin(time, times[i + 1].time); + times[i].time = time; + times[i].flags = UFBX_BAKED_KEY_REDUCED; + } + } + + ufbxi_bake_time prev_time = { -UFBX_INFINITY }; + while (src < num_times) { + ufbxi_bake_time src_time = times[src]; + src++; + + size_t start_src = src; + ufbxi_bake_time next_time; + next_time.time = ufbx_ceil(src_time.time * sample_rate - epsilon) / sample_rate; + next_time.flags = UFBX_BAKED_KEY_REDUCED; + while (src < num_times && times[src].time <= next_time.time + epsilon) { + src++; + } + + if (src != start_src || src_time.time - prev_time.time <= min_interval) { + prev_time = next_time; + } else { + prev_time = src_time; + } + + if (dst == 0 || prev_time.time > times[dst - 1].time) { + times[dst++] = prev_time; + } + } + + num_times = dst; + } + + if (num_times > 0) { + if (times[0].time < bc->time_min) bc->time_min = times[0].time; + if (times[num_times - 1].time > bc->time_max) bc->time_max = times[num_times - 1].time; + } + + p_dst->data = times; + p_dst->count = num_times; + + return 1; +} + +#define ufbxi_add_epsilon(a, epsilon) ((a)>0 ? (a)*(epsilon) : (a)/(epsilon)) +#define ufbxi_sub_epsilon(a, epsilon) ((a)>0 ? (a)/(epsilon) : (a)*(epsilon)) + +static ufbxi_noinline bool ufbxi_postprocess_step(ufbxi_bake_context *bc, double prev_time, double next_time, double *p_time, ufbx_baked_key_flags flags) +{ + ufbxi_dev_assert((flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0); + bool left = (flags & UFBX_BAKED_KEY_STEP_LEFT) != 0; + + double step = 0.001; + double epsilon = 1.0 + UFBX_FLT_EPSILON * 4.0f; + + double time = *p_time; + switch (bc->opts.step_handling) { + case UFBX_BAKE_STEP_HANDLING_DEFAULT: + break; + case UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION: + step = bc->opts.step_custom_duration; + epsilon = 1.0 + bc->opts.step_custom_epsilon; + break; + case UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME: + return true; + case UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE: + if (left) { + *p_time = time = ufbx_nextafter(time, -UFBX_INFINITY); + return time > prev_time; + } else { + *p_time = time = ufbx_nextafter(time, UFBX_INFINITY); + return time < next_time; + } + case UFBX_BAKE_STEP_HANDLING_IGNORE: + return false; + default: + ufbxi_unreachable("Unhandled bake step handling"); + return false; + } + + if (left) { + double min_time = ufbx_fmax(prev_time + step, ufbxi_add_epsilon(prev_time, epsilon)); + *p_time = time = ufbx_fmin(time - step, ufbxi_sub_epsilon(time, epsilon)); + return time > min_time; + } else { + double max_time = ufbx_fmin(next_time - step, ufbxi_sub_epsilon(next_time, epsilon)); + *p_time = time = ufbx_fmax(time + step, ufbxi_add_epsilon(time, epsilon)); + return time < max_time; + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_vec3(ufbxi_bake_context *bc, ufbx_baked_vec3_list *p_dst, bool *p_constant, ufbx_baked_vec3_list src) +{ + if (src.count == 0) return 1; + + // Offset times + if (bc->ktime_offset != 0.0) { + double scale = (double)bc->scene->metadata.ktime_second; + double offset = bc->ktime_offset; + for (size_t i = 0; i < src.count; i++) { + src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale; + } + } + + // Postprocess stepped tangents + { + size_t dst = 0; + double prev_time = src.data[0].time; + for (size_t i = 0; i < src.count; i++) { + ufbx_baked_vec3 cur = src.data[i]; + double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY; + bool keep = true; + if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { + keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags); + } + if (keep) { + src.data[dst] = cur; + dst++; + prev_time = cur.time; + } + } + src.count = dst; + } + + if (bc->opts.key_reduction_enabled) { + double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold; + for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) { + size_t dst = 1; + for (size_t i = 1; i < src.count; i++) { + ufbx_baked_vec3 prev = src.data[i - 1]; + ufbx_baked_vec3 cur = src.data[i]; + if (i + 1 < src.count) { + ufbx_baked_vec3 next = src.data[i + 1]; + double delta = (cur.time - prev.time) / (next.time - prev.time); + ufbx_vec3 tmp = ufbxi_lerp3(prev.value, next.value, (ufbx_real)delta); + double error = 0.0; + error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x); + error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y); + error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z); + if (error <= threshold) { + src.data[dst] = src.data[i + 1]; + i += 1; + dst += 1; + continue; + } + } + + src.data[dst] = src.data[i]; + dst += 1; + } + if (dst == src.count) break; + src.count = dst; + } + } + + bool constant = true; + ufbx_vec3 ref = src.data[0].value; + for (size_t i = 1; i < src.count; i++) { + ufbx_vec3 v = src.data[i].value; + if (v.x != ref.x || v.y != ref.y || v.z != ref.z) { + constant = false; + break; + } + } + *p_constant = constant; + + p_dst->count = src.count; + p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_vec3, src.count, src.data); + ufbxi_check_err(&bc->error, p_dst->data); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_quat(ufbxi_bake_context *bc, ufbx_baked_quat_list *p_dst, bool *p_constant, ufbx_baked_quat_list src) +{ + if (src.count == 0) return 1; + + // Offset times + if (bc->ktime_offset != 0.0) { + double scale = (double)bc->scene->metadata.ktime_second; + double offset = bc->ktime_offset; + for (size_t i = 0; i < src.count; i++) { + src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale; + } + } + + // Postprocess stepped tangents + { + size_t dst = 0; + double prev_time = src.data[0].time; + for (size_t i = 0; i < src.count; i++) { + ufbx_baked_quat cur = src.data[i]; + double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY; + bool keep = true; + if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { + keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags); + } + if (keep) { + prev_time = cur.time; + src.data[dst] = cur; + dst++; + } + } + src.count = dst; + } + + // Fix quaternion antipodality + for (size_t i = 1; i < src.count; i++) { + src.data[i].value = ufbx_quat_fix_antipodal(src.data[i].value, src.data[i - 1].value); + } + + if (bc->opts.key_reduction_enabled) { + double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold; + for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) { + size_t dst = 1; + for (size_t i = 1; i < src.count; i++) { + ufbx_baked_quat prev = src.data[i - 1]; + ufbx_baked_quat cur = src.data[i]; + if (i + 1 < src.count) { + ufbx_baked_quat next = src.data[i + 1]; + double delta = (cur.time - prev.time) / (next.time - prev.time); + double error = 0.0; + + if (bc->opts.key_reduction_rotation) { + ufbx_quat tmp = ufbx_quat_slerp(prev.value, next.value, (ufbx_real)delta); + error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x); + error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y); + error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z); + error += ((double)tmp.w - (double)cur.value.w) * ((double)tmp.w - (double)cur.value.w); + } else { + error += ((double)prev.value.x - (double)cur.value.x) * ((double)prev.value.x - (double)cur.value.x); + error += ((double)prev.value.y - (double)cur.value.y) * ((double)prev.value.y - (double)cur.value.y); + error += ((double)prev.value.z - (double)cur.value.z) * ((double)prev.value.z - (double)cur.value.z); + error += ((double)prev.value.w - (double)cur.value.w) * ((double)prev.value.w - (double)cur.value.w); + error += ((double)next.value.x - (double)cur.value.x) * ((double)next.value.x - (double)cur.value.x); + error += ((double)next.value.y - (double)cur.value.y) * ((double)next.value.y - (double)cur.value.y); + error += ((double)next.value.z - (double)cur.value.z) * ((double)next.value.z - (double)cur.value.z); + error += ((double)next.value.w - (double)cur.value.w) * ((double)next.value.w - (double)cur.value.w); + error *= 0.5; + } + + if (error <= threshold) { + src.data[dst] = src.data[i + 1]; + i += 1; + dst += 1; + continue; + } + } + + src.data[dst] = src.data[i]; + dst += 1; + } + if (dst == src.count) break; + src.count = dst; + } + } + + bool constant = true; + ufbx_quat ref = src.data[0].value; + for (size_t i = 1; i < src.count; i++) { + ufbx_quat v = src.data[i].value; + if (v.x != ref.x || v.y != ref.y || v.z != ref.z || v.y != ref.w) { + constant = false; + break; + } + } + *p_constant = constant; + + p_dst->count = src.count; + p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_quat, src.count, src.data); + ufbxi_check_err(&bc->error, p_dst->data); + + return 1; +} + +static ufbxi_forceinline double ufbxi_bake_time_sample_time(ufbxi_bake_time time) +{ + // Move an infinitesimal step for stepped tangents + if ((time.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { + double dir = (time.flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -UFBX_INFINITY : UFBX_INFINITY; + return ufbx_nextafter(time.time, dir); + } else { + return time.time; + } +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_resampled_times(ufbxi_bake_context *bc, const ufbx_baked_vec3_list *p_keys) +{ + ufbx_baked_vec3_list keys = *p_keys; + + ufbxi_bake_time *times = ufbxi_push(&bc->tmp_times, ufbxi_bake_time, keys.count); + ufbxi_check_err(&bc->error, times); + for (size_t i = 0; i < keys.count; i++) { + ufbx_baked_key_flags flags = keys.data[i].flags; + double time = keys.data[i].time; + if ((flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 && i + 1 < keys.count && (keys.data[i + 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) { + time = keys.data[i + 1].time; + } else if ((flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && i > 0 && (keys.data[i - 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) { + time = keys.data[i - 1].time; + } + times[i].time = time; + times[i].flags = flags & 0x7; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) +{ + ufbx_assert(bc->baked_nodes && bc->nodes_to_bake); + + ufbx_node *node = (ufbx_node*)bc->scene->elements.data[element_id]; + ufbxi_dev_assert(node->element.type == UFBX_ELEMENT_NODE); + + bool complex_translation = false; + bool complex_rotation = false; + + for (size_t i = 0; i < ufbxi_arraycount(ufbxi_complex_translation_props); i++) { + const char *name = ufbxi_complex_translation_props[i]; + ufbx_prop *prop = ufbxi_find_prop(&node->props, name); + if (prop && !ufbxi_is_vec3_zero(prop->value_vec3)) { + complex_translation = true; + } + ufbxi_for(ufbxi_bake_prop, bprop, props, count) { + if (bprop->prop_name == name) { + complex_translation = true; + } + } + } + + for (size_t i = 0; i < ufbxi_arraycount(ufbxi_complex_rotation_props); i++) { + const char *name = ufbxi_complex_rotation_props[i]; + ufbxi_for(ufbxi_bake_prop, bprop, props, count) { + if (bprop->prop_name == name) { + complex_rotation = true; + } + } + } + + ufbxi_bake_time_list times_t, times_r, times_s; + + // Translation + bool resample_translation = false; + + // Account for the _resampled_ scale helper scale animation to keep the + // translation scale consistent with the parent scaling. + ufbx_baked_node *scale_helper_t = NULL; + ufbx_vec3 constant_scale_t = { 1.0f, 1.0f, 1.0f }; + if (!node->is_scale_helper && node->parent && node->parent->scale_helper) { + scale_helper_t = bc->baked_nodes[node->parent->scale_helper->typed_id]; + if (scale_helper_t) { + if (!scale_helper_t->constant_scale) { + resample_translation = true; + } + ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_t->scale_keys)); + } else { + constant_scale_t = node->parent->scale_helper->inherit_scale; + } + } + + if (complex_translation) { + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + // Literally any transform related property can affect complex translation + if (ufbxi_in_list(ufbxi_transform_props, ufbxi_arraycount(ufbxi_transform_props), prop->prop_name)) { + bool resample_linear = resample_translation || prop->prop_name != ufbxi_Lcl_Translation; + uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Translation ? UFBX_BAKED_KEY_KEYFRAME : 0; + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); + } + } + } else { + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + if (prop->prop_name == ufbxi_Lcl_Translation) { + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_translation, UFBX_BAKED_KEY_KEYFRAME)); + } + } + } + + ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_t)); + + // Rotation + if (complex_rotation) { + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + if (ufbxi_in_list(ufbxi_complex_rotation_sources, ufbxi_arraycount(ufbxi_complex_rotation_sources), prop->prop_name)) { + bool resample_linear = !bc->opts.no_resample_rotation || prop->prop_name != ufbxi_Lcl_Rotation; + uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Rotation ? UFBX_BAKED_KEY_KEYFRAME : 0; + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); + } + } + } else { + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + if (prop->prop_name == ufbxi_Lcl_Rotation) { + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, !bc->opts.no_resample_rotation, UFBX_BAKED_KEY_KEYFRAME)); + } + } + } + ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_r)); + + // Scaling + bool resample_scale = false; + + // Account for the resampled scale + ufbx_baked_node *scale_helper_s = NULL; + ufbx_vec3 constant_scale_s = { 1.0f, 1.0f, 1.0f }; + if (node->is_scale_helper && node->parent && node->parent->inherit_scale_node && node->parent->inherit_scale_node->scale_helper) { + ufbx_node *inherit_helper = node->parent->inherit_scale_node->scale_helper; + scale_helper_s = bc->baked_nodes[inherit_helper->typed_id]; + if (scale_helper_s) { + if (!scale_helper_s->constant_scale) { + resample_scale = true; + } + ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_s->scale_keys)); + } else { + constant_scale_s = inherit_helper->local_transform.scale; + } + } + + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + if (prop->prop_name == ufbxi_Lcl_Scaling) { + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_scale, UFBX_BAKED_KEY_KEYFRAME)); + } + } + ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_s)); + + ufbx_baked_vec3_list keys_t; + ufbx_baked_quat_list keys_r; + ufbx_baked_vec3_list keys_s; + + keys_t.count = times_t.count; + keys_t.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys_t.count); + ufbxi_check_err(&bc->error, keys_t.data); + + keys_r.count = times_r.count; + keys_r.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_quat, keys_r.count); + ufbxi_check_err(&bc->error, keys_r.data); + + keys_s.count = times_s.count; + keys_s.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys_s.count); + ufbxi_check_err(&bc->error, keys_s.data); + + size_t ix_t = 0, ix_r = 0, ix_s = 0; + while (ix_t < times_t.count || ix_r < times_r.count || ix_s < times_s.count) { + ufbxi_bake_time bake_time = { UFBX_INFINITY }; + uint32_t flags_r = 0, flags_t = 0, flags_s = 0; + + uint32_t flags = 0; + if (ix_r < times_r.count) { + bake_time = times_r.data[ix_r]; + flags_r = bake_time.flags; + bake_time.flags &= 0x7; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION; + } + if (ix_t < times_t.count) { + ufbxi_bake_time t = times_t.data[ix_t]; + int cmp = ufbxi_cmp_bake_time(t, bake_time); + if (cmp <= 0) { + if (cmp < 0) { + bake_time = t; + flags = 0; + } + bake_time.flags |= t.flags & 0x7; + flags_t = t.flags; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; + } + } + if (ix_s < times_s.count) { + ufbxi_bake_time t = times_s.data[ix_s]; + int cmp = ufbxi_cmp_bake_time(t, bake_time); + if (cmp <= 0) { + if (cmp < 0) { + bake_time = t; + flags = 0; + } + bake_time.flags |= t.flags & 0x7; + flags_s = t.flags; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_SCALE; + } + } + + flags |= UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER|UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE|UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES; + if (bc->opts.evaluate_flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) { + flags |= UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION; + } + + double eval_time = ufbxi_bake_time_sample_time(bake_time); + ufbx_transform transform = ufbx_evaluate_transform_flags(bc->anim, node, eval_time, flags); + + if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) { + if (scale_helper_t) { + ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_t->scale_keys, eval_time); + transform.translation.x *= scale.x; + transform.translation.y *= scale.y; + transform.translation.z *= scale.z; + } + + transform.translation.x *= constant_scale_t.x; + transform.translation.y *= constant_scale_t.y; + transform.translation.z *= constant_scale_t.z; + + keys_t.data[ix_t].time = bake_time.time; + keys_t.data[ix_t].value = transform.translation; + keys_t.data[ix_t].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_t); + ix_t++; + } + if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) { + keys_r.data[ix_r].time = bake_time.time; + keys_r.data[ix_r].value = transform.rotation; + keys_r.data[ix_r].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_r); + ix_r++; + } + if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) { + if (scale_helper_s) { + ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_s->scale_keys, eval_time); + transform.scale.x *= scale.x; + transform.scale.y *= scale.y; + transform.scale.z *= scale.z; + } + + transform.scale.x *= constant_scale_s.x; + transform.scale.y *= constant_scale_s.y; + transform.scale.z *= constant_scale_s.z; + + keys_s.data[ix_s].time = bake_time.time; + keys_s.data[ix_s].value = transform.scale; + keys_s.data[ix_s].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_s); + ix_s++; + } + } + + ufbx_baked_node *baked_node = ufbxi_push_zero(&bc->tmp_nodes, ufbx_baked_node, 1); + ufbxi_check_err(&bc->error, baked_node); + + baked_node->element_id = node->element_id; + baked_node->typed_id = node->typed_id; + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->translation_keys, &baked_node->constant_translation, keys_t)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_quat(bc, &baked_node->rotation_keys, &baked_node->constant_rotation, keys_r)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->scale_keys, &baked_node->constant_scale, keys_s)); + + bc->baked_nodes[node->typed_id] = baked_node; + + ufbxi_buf_clear(&bc->tmp_prop); + + // If this node is a scale helper, make sure to bake its siblings and + // potentially their scale helpers if they are not a part of the animation. + if (node->is_scale_helper) { + ufbx_assert(node->parent); + ufbxi_for_ptr_list(ufbx_node, p_child, node->parent->children) { + ufbx_node *child = *p_child; + if (child == node) continue; + if (!bc->nodes_to_bake[child->typed_id]) { + bc->nodes_to_bake[child->typed_id] = true; + ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_bake_stack, uint32_t, 1, &child->element_id)); + } + if (child->inherit_scale_node && child->inherit_scale_node->scale_helper && child->scale_helper + && bc->nodes_to_bake[child->inherit_scale_node->scale_helper->typed_id]) { + ufbx_assert(bc->baked_nodes[child->inherit_scale_node->scale_helper->typed_id]); + if (!bc->nodes_to_bake[child->scale_helper->typed_id]) { + bc->nodes_to_bake[child->scale_helper->typed_id] = true; + ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_bake_stack, uint32_t, 1, &child->scale_helper->element_id)); + } + } + } + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) +{ + ufbxi_check_err(&bc->error, ufbxi_bake_node_imp(bc, element_id, props, count)); + + // Baking a node may cause further nodes to be baked, so keep going + // until all dependencies are baked. + while (bc->tmp_bake_stack.num_items > 0) { + uint32_t child_id = 0; + ufbxi_pop(&bc->tmp_bake_stack, uint32_t, 1, &child_id); + ufbxi_check_err(&bc->error, ufbxi_bake_node_imp(bc, child_id, NULL, 0)); + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_context *bc, ufbx_element *element, const char *prop_name, ufbxi_bake_prop *props, size_t count) +{ + ufbxi_for(ufbxi_bake_prop, prop, props, count) { + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, false, UFBX_BAKED_KEY_KEYFRAME)); + } + + ufbxi_bake_time_list times; + ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×)); + + ufbx_baked_vec3_list keys; + keys.count = times.count; + keys.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys.count); + ufbxi_check_err(&bc->error, keys.data); + + ufbx_string name; + name.data = prop_name; + name.length = strlen(prop_name); + + for (size_t i = 0; i < times.count; i++) { + ufbxi_bake_time bake_time = times.data[i]; + double eval_time = ufbxi_bake_time_sample_time(bake_time); + ufbx_prop prop = ufbx_evaluate_prop_len_flags(bc->anim, element, name.data, name.length, eval_time, bc->opts.evaluate_flags); + keys.data[i].time = bake_time.time; + keys.data[i].value = prop.value_vec3; + keys.data[i].flags = (ufbx_baked_key_flags)bake_time.flags; + } + + ufbx_baked_prop *baked_prop = ufbxi_push_zero(&bc->tmp_props, ufbx_baked_prop, 1); + ufbxi_check_err(&bc->error, baked_prop); + + baked_prop->name.length = strlen(prop_name); + baked_prop->name.data = ufbxi_push_copy(&bc->result, char, baked_prop->name.length + 1, prop_name); + ufbxi_check_err(&bc->error, baked_prop->name.data); + + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_prop->keys, &baked_prop->constant_value, keys)); + + ufbxi_buf_clear(&bc->tmp_prop); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_element(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) +{ + ufbx_element *element = bc->scene->elements.data[element_id]; + if (element->type == UFBX_ELEMENT_NODE && !bc->opts.skip_node_transforms) { + ufbxi_check_err(&bc->error, ufbxi_bake_node(bc, element_id, props, count)); + } + + size_t begin = 0; + while (begin < count) { + const char *prop_name = props[begin].prop_name; + size_t end = begin + 1; + while (end < count && props[end].prop_name == prop_name) { + end++; + } + + // Don't bake transform related props for nodes unless specifically requested + if (element->type == UFBX_ELEMENT_NODE && !bc->opts.bake_transform_props && ufbxi_in_list(ufbxi_transform_props, ufbxi_arraycount(ufbxi_transform_props), prop_name)) { + begin = end; + continue; + } + + ufbxi_check_err(&bc->error, ufbxi_bake_anim_prop(bc, element, prop_name, props + begin, end - begin)); + begin = end; + } + + size_t num_props = bc->tmp_props.num_items; + if (num_props > 0) { + ufbx_baked_element *baked_elem = ufbxi_push_zero(&bc->tmp_elements, ufbx_baked_element, 1); + ufbxi_check_err(&bc->error, baked_elem); + + baked_elem->element_id = element->element_id; + baked_elem->props.count = num_props; + baked_elem->props.data = ufbxi_push_pop(&bc->result, &bc->tmp_props, ufbx_baked_prop, num_props); + ufbxi_check_err(&bc->error, baked_elem->props.data); + } + + return 1; +} + +static ufbxi_noinline bool ufbxi_baked_node_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_baked_node *a = (const ufbx_baked_node*)va, *b = (const ufbx_baked_node*)vb; + return a->typed_id < b->typed_id; +} + +static ufbxi_noinline bool ufbxi_baked_element_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_baked_element *a = (const ufbx_baked_element*)va, *b = (const ufbx_baked_element*)vb; + return a->element_id < b->element_id; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc) +{ + const ufbx_anim *anim = bc->anim; + const ufbx_scene *scene = bc->scene; + + if (!bc->opts.skip_node_transforms) { + bc->baked_nodes = ufbxi_push_zero(&bc->result, ufbx_baked_node*, scene->nodes.count); + ufbxi_check_err(&bc->error, bc->baked_nodes); + bc->nodes_to_bake = ufbxi_push_zero(&bc->result, bool, scene->nodes.count); + ufbxi_check_err(&bc->error, bc->nodes_to_bake); + } + + ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, anim->layers) { + ufbx_anim_layer *layer = *p_layer; + + ufbxi_for_list(ufbx_anim_prop, anim_prop, layer->anim_props) { + ufbxi_bake_prop *prop = ufbxi_push(&bc->tmp_bake_props, ufbxi_bake_prop, 1); + ufbxi_check_err(&bc->error, prop); + + ufbx_element *element = anim_prop->element; + + // Sort nodes by `typed_id` to make sure we process them in order. + if (element->type == UFBX_ELEMENT_NODE) { + if (bc->nodes_to_bake) { + bc->nodes_to_bake[element->typed_id] = true; + } + prop->sort_id = element->typed_id; + } else { + prop->sort_id = UINT32_MAX; + } + + prop->element_id = element->element_id; + prop->prop_name = anim_prop->prop_name.data; + prop->anim_value = anim_prop->anim_value; + } + } + + size_t num_props = bc->tmp_bake_props.num_items; + ufbxi_bake_prop *props = ufbxi_push_pop(&bc->tmp, &bc->tmp_bake_props, ufbxi_bake_prop, num_props); + ufbxi_check_err(&bc->error, props); + + ufbxi_unstable_sort(props, num_props, sizeof(ufbxi_bake_prop), &ufbxi_bake_prop_less, NULL); + + // Pre-bake layer weight times + if (!bc->opts.ignore_layer_weight_animation) { + bool has_weight_times = false; + ufbxi_for(ufbxi_bake_prop, prop, props, num_props) { + if (prop->prop_name != ufbxi_Weight) continue; + ufbx_element *element = scene->elements.data[prop->element_id]; + if (element->type == UFBX_ELEMENT_ANIM_LAYER) { + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, true, 0)); + has_weight_times = true; + } + } + + if (has_weight_times) { + ufbxi_bake_time_list weight_times = { 0 }; + ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, &weight_times)); + + bc->layer_weight_times.count = weight_times.count; + bc->layer_weight_times.data = ufbxi_push_copy(&bc->tmp, ufbxi_bake_time, weight_times.count, weight_times.data); + ufbxi_check_err(&bc->error, bc->layer_weight_times.data); + + ufbxi_buf_clear(&bc->tmp_prop); + } + } + + size_t begin = 0; + while (begin < num_props) { + uint32_t element_id = props[begin].element_id; + size_t end = begin + 1; + while (end < num_props && props[end].element_id == element_id) { + end++; + } + ufbxi_check_err(&bc->error, ufbxi_bake_element(bc, element_id, props + begin, end - begin)); + begin = end; + } + + size_t num_nodes = bc->tmp_nodes.num_items; + size_t num_elements = bc->tmp_elements.num_items; + + bc->bake.nodes.count = num_nodes; + bc->bake.nodes.data = ufbxi_push_pop(&bc->result, &bc->tmp_nodes, ufbx_baked_node, num_nodes); + ufbxi_check_err(&bc->error, bc->bake.nodes.data); + + bc->bake.elements.count = num_elements; + bc->bake.elements.data = ufbxi_push_pop(&bc->result, &bc->tmp_elements, ufbx_baked_element, num_elements); + ufbxi_check_err(&bc->error, bc->bake.elements.data); + + ufbxi_unstable_sort(bc->bake.nodes.data, bc->bake.nodes.count, sizeof(ufbx_baked_node), &ufbxi_baked_node_less, NULL); + ufbxi_unstable_sort(bc->bake.elements.data, bc->bake.elements.count, sizeof(ufbx_baked_element), &ufbxi_baked_element_less, NULL); + + if (bc->time_min < bc->time_max) { + bc->bake.key_time_min = bc->time_min; + bc->bake.key_time_max = bc->time_max; + } + + if (bc->time_begin < bc->time_end) { + bc->bake.playback_time_begin = bc->time_begin; + bc->bake.playback_time_end = bc->time_end; + bc->bake.playback_duration = bc->time_end - bc->time_begin; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_imp(ufbxi_bake_context *bc, const ufbx_anim *anim) +{ + if (bc->opts.resample_rate <= 0.0) bc->opts.resample_rate = 30.0; + if (bc->opts.minimum_sample_rate <= 0.0) bc->opts.minimum_sample_rate = 19.5; + if (bc->opts.max_keyframe_segments == 0) bc->opts.max_keyframe_segments = 32; + if (bc->opts.key_reduction_threshold == 0) bc->opts.key_reduction_threshold = 0.000001; + if (bc->opts.key_reduction_passes == 0) bc->opts.key_reduction_passes = 4; + + if (bc->opts.trim_start_time && anim->time_begin > 0.0) { + bc->ktime_offset = -anim->time_begin * (double)bc->scene->metadata.ktime_second; + } + + ufbxi_init_ator(&bc->error, &bc->ator_tmp, &bc->opts.temp_allocator, "temp"); + ufbxi_init_ator(&bc->error, &bc->ator_result, &bc->opts.result_allocator, "result"); + + bc->result.unordered = true; + bc->result.ator = &bc->ator_result; + + bc->tmp.unordered = true; + bc->tmp.ator = &bc->ator_tmp; + + bc->tmp_prop.ator = &bc->ator_tmp; + bc->tmp_prop.unordered = true; + bc->tmp_prop.clearable = true; + + bc->tmp_times.ator = &bc->ator_tmp; + bc->tmp_bake_props.ator = &bc->ator_tmp; + bc->tmp_nodes.ator = &bc->ator_tmp; + bc->tmp_elements.ator = &bc->ator_tmp; + bc->tmp_props.ator = &bc->ator_tmp; + bc->tmp_bake_stack.ator = &bc->ator_tmp; + + bc->anim = anim; + if (anim->time_begin < anim->time_end) { + bc->time_begin = anim->time_begin; + bc->time_end = anim->time_end; + } + bc->time_min = UFBX_INFINITY; + bc->time_max = -UFBX_INFINITY; + + bc->imp = ufbxi_push(&bc->result, ufbxi_baked_anim_imp, 1); + ufbxi_check_err(&bc->error, bc->imp); + + ufbxi_check_err(&bc->error, ufbxi_bake_anim(bc)); + + ufbxi_init_ref(&bc->imp->refcount, UFBXI_BAKED_ANIM_IMP_MAGIC, NULL); + + bc->bake.metadata.result_memory_used = bc->ator_result.current_size; + bc->bake.metadata.temp_memory_used = bc->ator_tmp.current_size; + bc->bake.metadata.result_allocs = bc->ator_result.num_allocs; + bc->bake.metadata.temp_allocs = bc->ator_tmp.num_allocs; + + bc->imp->magic = UFBXI_BAKED_ANIM_IMP_MAGIC; + bc->imp->bake = bc->bake; + bc->imp->refcount.ator = bc->ator_result; + bc->imp->refcount.buf = bc->result; + + return 1; +} + +#endif + +// -- NURBS + +static ufbxi_forceinline ufbx_real ufbxi_nurbs_weight(const ufbx_real_list *knots, size_t knot, size_t degree, ufbx_real u) +{ + if (knot >= knots->count) return 0.0f; + if (knots->count - knot < degree) return 0.0f; + ufbx_real prev_u = knots->data[knot], next_u = knots->data[knot + degree]; + if (prev_u >= next_u) return 0.0f; + if (u <= prev_u) return 0.0f; + if (u >= next_u) return 1.0f; + return (u - prev_u) / (next_u - prev_u); +} + +static ufbxi_forceinline ufbx_real ufbxi_nurbs_deriv(const ufbx_real_list *knots, size_t knot, size_t degree) +{ + if (knot >= knots->count) return 0.0f; + if (knots->count - knot < degree) return 0.0f; + ufbx_real prev_u = knots->data[knot], next_u = knots->data[knot + degree]; + if (prev_u >= next_u) return 0.0f; + return (ufbx_real)degree / (next_u - prev_u); +} + +typedef struct { + ufbxi_refcount refcount; + ufbx_line_curve curve; + uint32_t magic; +} ufbxi_line_curve_imp; + +ufbx_static_assert(line_curve_imp_offset, offsetof(ufbxi_line_curve_imp, curve) == sizeof(ufbxi_refcount)); + +#if UFBXI_FEATURE_TESSELLATION + +typedef struct { + ufbx_error error; + + ufbx_tessellate_curve_opts opts; + + const ufbx_nurbs_curve *curve; + + ufbxi_allocator ator_tmp; + ufbxi_allocator ator_result; + + ufbxi_buf result; + + ufbx_line_curve line; + + ufbxi_line_curve_imp *imp; + +} ufbxi_tessellate_curve_context; + +typedef struct { + ufbx_error error; + + ufbx_tessellate_surface_opts opts; + + const ufbx_nurbs_surface *surface; + + ufbxi_allocator ator_tmp; + ufbxi_allocator ator_result; + + ufbxi_buf tmp; + ufbxi_buf result; + + ufbxi_map position_map; + + ufbx_mesh mesh; + + ufbxi_mesh_imp *imp; + +} ufbxi_tessellate_surface_context; + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_curve_imp(ufbxi_tessellate_curve_context *tc) +{ + if (tc->opts.span_subdivision <= 0) { + tc->opts.span_subdivision = 4; + } + size_t num_sub = tc->opts.span_subdivision; + + const ufbx_nurbs_curve *curve = tc->curve; + ufbx_line_curve *line = &tc->line; + ufbxi_check_err_msg(&tc->error, curve->basis.valid && curve->control_points.count > 0, "Bad NURBS geometry"); + + ufbxi_init_ator(&tc->error, &tc->ator_tmp, &tc->opts.temp_allocator, "temp"); + ufbxi_init_ator(&tc->error, &tc->ator_result, &tc->opts.result_allocator, "result"); + + tc->result.unordered = true; + tc->result.ator = &tc->ator_result; + + size_t num_spans = curve->basis.spans.count; + + // Check conservatively that we don't overflow anything + { + size_t over_spans = num_spans * 2 * sizeof(ufbx_real); + size_t over = over_spans * num_sub; + ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over, over_spans, num_sub)); + } + + bool is_open = curve->basis.topology == UFBX_NURBS_TOPOLOGY_OPEN; + + size_t num_indices = num_spans + (num_spans - 1) * (num_sub - 1); + size_t num_vertices = num_indices - (is_open ? 0u : 1u); + ufbxi_check_err(&tc->error, num_indices <= INT32_MAX); + + uint32_t *indices = ufbxi_push(&tc->result, uint32_t, num_indices); + ufbx_vec3 *vertices = ufbxi_push(&tc->result, ufbx_vec3, num_vertices); + ufbx_line_segment *segments = ufbxi_push(&tc->result, ufbx_line_segment, 1); + ufbxi_check_err(&tc->error, indices && vertices && segments); + + for (size_t span_ix = 0; span_ix < num_spans; span_ix++) { + size_t num_splits = span_ix + 1 == num_spans ? 1 : num_sub; + + for (size_t sub_ix = 0; sub_ix < num_splits; sub_ix++) { + size_t ix = span_ix * num_sub + sub_ix; + + if (ix < num_vertices) { + ufbx_real u = curve->basis.spans.data[span_ix]; + if (sub_ix > 0) { + ufbx_real t = (ufbx_real)sub_ix / (ufbx_real)num_sub; + u = u * (1.0f - t) + t * curve->basis.spans.data[span_ix + 1]; + } + + ufbx_curve_point point = ufbx_evaluate_nurbs_curve(curve, u); + vertices[ix] = point.position; + indices[ix] = (uint32_t)ix; + } else { + indices[ix] = 0; + } + } + } + + segments[0].index_begin = 0; + segments[0].num_indices = (uint32_t)num_indices; + + line->element.name.data = ufbxi_empty_char; + line->element.type = UFBX_ELEMENT_LINE_CURVE; + line->element.typed_id = UINT32_MAX; + line->element.element_id = UINT32_MAX; + + line->color.x = 1.0f; + line->color.y = 1.0f; + line->color.z = 1.0f; + + line->control_points.data = vertices; + line->control_points.count = num_vertices; + line->point_indices.data = indices; + line->point_indices.count = num_indices; + line->segments.data = segments; + line->segments.count = 1; + + line->from_tessellated_nurbs = true; + + tc->imp = ufbxi_push(&tc->result, ufbxi_line_curve_imp, 1); + ufbxi_check_err(&tc->error, tc->imp); + + ufbxi_init_ref(&tc->imp->refcount, UFBXI_LINE_CURVE_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, curve->element.scene))->refcount); + + tc->imp->magic = UFBXI_LINE_CURVE_IMP_MAGIC; + tc->imp->curve = tc->line; + tc->imp->refcount.ator = tc->ator_result; + tc->imp->refcount.buf = tc->result; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_surface_imp(ufbxi_tessellate_surface_context *tc) +{ + if (tc->opts.span_subdivision_u <= 0) { + tc->opts.span_subdivision_u = 4; + } + if (tc->opts.span_subdivision_v <= 0) { + tc->opts.span_subdivision_v = 4; + } + + size_t sub_u = tc->opts.span_subdivision_u; + size_t sub_v = tc->opts.span_subdivision_v; + + const ufbx_nurbs_surface *surface = tc->surface; + ufbx_mesh *mesh = &tc->mesh; + ufbxi_check_err_msg(&tc->error, surface->basis_u.valid && surface->basis_v.valid + && surface->num_control_points_u > 0 && surface->num_control_points_v > 0, "Bad NURBS geometry"); + + ufbxi_init_ator(&tc->error, &tc->ator_tmp, &tc->opts.temp_allocator, "temp"); + ufbxi_init_ator(&tc->error, &tc->ator_result, &tc->opts.result_allocator, "result"); + + tc->result.unordered = true; + tc->tmp.unordered = true; + + tc->result.ator = &tc->ator_result; + tc->tmp.ator = &tc->ator_tmp; + + bool open_u = surface->basis_u.topology == UFBX_NURBS_TOPOLOGY_OPEN; + bool open_v = surface->basis_v.topology == UFBX_NURBS_TOPOLOGY_OPEN; + + size_t spans_u = surface->basis_u.spans.count; + size_t spans_v = surface->basis_v.spans.count; + + // Check conservatively that we don't overflow anything + { + size_t over_spans_u = spans_u * 2 * sizeof(ufbx_real); + size_t over_spans_v = spans_v * 2 * sizeof(ufbx_real); + size_t over_u = over_spans_u * sub_u; + size_t over_v = over_spans_v * sub_v; + size_t over_uv = over_u * over_v; + ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_u, over_spans_u, sub_u)); + ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_v, over_spans_v, sub_v)); + ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_uv, over_u, over_v)); + } + + size_t faces_u = (spans_u - 1) * sub_u; + size_t faces_v = (spans_v - 1) * sub_v; + + size_t indices_u = spans_u + (spans_u - 1) * (sub_u - 1); + size_t indices_v = spans_v + (spans_v - 1) * (sub_v - 1); + + size_t num_faces = faces_u * faces_v; + size_t num_indices = indices_u * indices_v; + ufbxi_check_err(&tc->error, num_indices <= INT32_MAX); + + uint32_t *position_ix = ufbxi_push(&tc->tmp, uint32_t, num_indices); + ufbx_vec3 *positions = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); + ufbx_vec3 *normals = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); + ufbx_vec2 *uvs = ufbxi_push(&tc->result, ufbx_vec2, num_indices + 1); + ufbx_vec3 *tangents = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); + ufbx_vec3 *bitangents = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); + ufbxi_check_err(&tc->error, position_ix && uvs && tangents && bitangents); + + *positions++ = ufbx_zero_vec3; + *normals++ = ufbx_zero_vec3; + *uvs++ = ufbx_zero_vec2; + *tangents++ = ufbx_zero_vec3; + *bitangents++ = ufbx_zero_vec3; + + uint32_t num_positions = 0; + + for (size_t span_v = 0; span_v < spans_v; span_v++) { + size_t splits_v = span_v + 1 == spans_v ? 1 : sub_v; + + for (size_t split_v = 0; split_v < splits_v; split_v++) { + size_t ix_v = span_v * sub_v + split_v; + ufbx_assert(ix_v < indices_v); + + ufbx_real v = surface->basis_v.spans.data[span_v]; + if (split_v > 0) { + ufbx_real t = (ufbx_real)split_v / (ufbx_real)splits_v; + v = v * (1.0f - t) + t * surface->basis_v.spans.data[span_v + 1]; + } + ufbx_real original_v = v; + if (span_v + 1 == spans_v && !open_v) { + v = surface->basis_v.spans.data[0]; + } + + for (size_t span_u = 0; span_u < spans_u; span_u++) { + size_t splits_u = span_u + 1 == spans_u ? 1 : sub_u; + for (size_t split_u = 0; split_u < splits_u; split_u++) { + size_t ix_u = span_u * sub_u + split_u; + ufbx_assert(ix_u < indices_u); + + ufbx_real u = surface->basis_u.spans.data[span_u]; + if (split_u > 0) { + ufbx_real t = (ufbx_real)split_u / (ufbx_real)splits_u; + u = u * (1.0f - t) + t * surface->basis_u.spans.data[span_u + 1]; + } + ufbx_real original_u = u; + if (span_u + 1 == spans_u && !open_u) { + u = surface->basis_u.spans.data[0]; + } + + ufbx_surface_point point = ufbx_evaluate_nurbs_surface(surface, u, v); + ufbx_vec3 pos = point.position; + + ufbx_vec3 tangent_u = ufbxi_slow_normalize3(&point.derivative_u); + ufbx_vec3 tangent_v = ufbxi_slow_normalize3(&point.derivative_v); + + // Check if there's any wrapped positions that we could match + size_t neighbors[5]; // ufbxi_uninit + size_t num_neighbors = 0; + + if ((span_v == 0 && (span_u > 0 || split_u > 0)) || (span_u == 0 && (span_v > 0 || split_v > 0))) { + // Top/left + neighbors[num_neighbors++] = 0; + } + if (span_v + 1 == spans_v) { + // Bottom + neighbors[num_neighbors++] = ix_u; + if (span_u > 0 || split_u > 0) { + neighbors[num_neighbors++] = ix_v * indices_u; + } + } + if (span_u + 1 == spans_u) { + // Right + neighbors[num_neighbors++] = ix_v * indices_u; + if (span_v > 0 || split_v > 0) { + neighbors[num_neighbors++] = indices_u - 1; + } + } + + size_t ix = ix_v * indices_u + ix_u; + + uint32_t pos_ix = num_positions; + for (size_t i = 0; i < num_neighbors; i++) { + size_t nb_ix = neighbors[i]; + ufbx_assert(nb_ix < ix); + uint32_t nb_pos_ix = position_ix[nb_ix]; + ufbx_vec3 nb_pos = positions[nb_pos_ix]; + ufbx_real dx = nb_pos.x - pos.x; + ufbx_real dy = nb_pos.y - pos.y; + ufbx_real dz = nb_pos.z - pos.z; + ufbx_real delta = dx*dx + dy*dy + dz*dz; + if (delta < 0.0000001f) { // TODO: Configurable / something more rigorous + pos_ix = nb_pos_ix; + break; + } + } + + position_ix[ix] = pos_ix; + if (pos_ix == num_positions) { + positions[pos_ix] = pos; + num_positions = pos_ix + 1; + } + uvs[ix].x = original_u; + uvs[ix].y = original_v; + tangents[ix] = tangent_u; + bitangents[ix] = tangent_v; + } + } + } + } + + ufbx_face *faces = ufbxi_push(&tc->result, ufbx_face, num_faces); + uint32_t *vertex_ix = ufbxi_push(&tc->result, uint32_t, num_faces * 4); + uint32_t *attrib_ix = ufbxi_push(&tc->result, uint32_t, num_faces * 4); + ufbxi_check_err(&tc->error, faces && vertex_ix && attrib_ix); + + size_t face_ix = 0; + size_t dst_index = 0; + + size_t num_triangles = 0; + + for (size_t face_v = 0; face_v < faces_v; face_v++) { + for (size_t face_u = 0; face_u < faces_u; face_u++) { + + attrib_ix[dst_index + 0] = (uint32_t)((face_v + 0) * indices_u + (face_u + 0)); + attrib_ix[dst_index + 1] = (uint32_t)((face_v + 0) * indices_u + (face_u + 1)); + attrib_ix[dst_index + 2] = (uint32_t)((face_v + 1) * indices_u + (face_u + 1)); + attrib_ix[dst_index + 3] = (uint32_t)((face_v + 1) * indices_u + (face_u + 0)); + + vertex_ix[dst_index + 0] = position_ix[attrib_ix[dst_index + 0]]; + vertex_ix[dst_index + 1] = position_ix[attrib_ix[dst_index + 1]]; + vertex_ix[dst_index + 2] = position_ix[attrib_ix[dst_index + 2]]; + vertex_ix[dst_index + 3] = position_ix[attrib_ix[dst_index + 3]]; + + bool is_triangle = false; + for (size_t prev_ix = 0; prev_ix < 4; prev_ix++) { + size_t next_ix = (prev_ix + 1) % 4; + if (vertex_ix[dst_index + prev_ix] == vertex_ix[dst_index + next_ix]) { + for (size_t i = next_ix; i < 3; i++) { + attrib_ix[dst_index + i] = attrib_ix[dst_index + i + 1]; + vertex_ix[dst_index + i] = vertex_ix[dst_index + i + 1]; + } + is_triangle = true; + break; + } + } + + faces[face_ix].index_begin = (uint32_t)dst_index; + faces[face_ix].num_indices = is_triangle ? 3 : 4; + dst_index += is_triangle ? 3 : 4; + num_triangles += is_triangle ? 1 : 2; + face_ix++; + } + } + + ufbxi_check_err(&tc->error, positions && normals); + + mesh->element.name.data = ufbxi_empty_char; + mesh->element.type = UFBX_ELEMENT_MESH; + mesh->element.typed_id = UINT32_MAX; + mesh->element.element_id = UINT32_MAX; + + mesh->vertices.data = positions; + mesh->vertices.count = num_positions; + mesh->num_vertices = num_positions; + mesh->vertex_indices.data = vertex_ix; + mesh->vertex_indices.count = dst_index; + + mesh->faces.data = faces; + mesh->faces.count = num_faces; + + mesh->vertex_position.exists = true; + mesh->vertex_position.values.data = positions; + mesh->vertex_position.values.count = num_positions; + mesh->vertex_position.indices.data = vertex_ix; + mesh->vertex_position.indices.count = dst_index; + mesh->vertex_position.unique_per_vertex = true; + + mesh->vertex_uv.exists = true; + mesh->vertex_uv.values.data = uvs; + mesh->vertex_uv.values.count = dst_index; + mesh->vertex_uv.indices.data = attrib_ix; + mesh->vertex_uv.indices.count = dst_index; + + mesh->vertex_normal.exists = true; + mesh->vertex_normal.values.data = normals; + mesh->vertex_normal.values.count = num_positions; + mesh->vertex_normal.indices.data = vertex_ix; + mesh->vertex_normal.indices.count = dst_index; + + mesh->vertex_tangent.exists = true; + mesh->vertex_tangent.values.data = tangents; + mesh->vertex_tangent.values.count = dst_index; + mesh->vertex_tangent.indices.data = attrib_ix; + mesh->vertex_tangent.indices.count = dst_index; + + mesh->vertex_bitangent.exists = true; + mesh->vertex_bitangent.values.data = bitangents; + mesh->vertex_bitangent.values.count = dst_index; + mesh->vertex_bitangent.indices.data = attrib_ix; + mesh->vertex_bitangent.indices.count = dst_index; + + mesh->num_faces = num_faces; + mesh->num_triangles = num_triangles; + mesh->num_indices = dst_index; + mesh->max_face_triangles = 2; + + if (surface->material) { + mesh->face_material.data = ufbxi_push_zero(&tc->result, uint32_t, num_faces); + ufbxi_check_err(&tc->error, mesh->face_material.data); + + ufbx_material **mat = ufbxi_push_zero(&tc->result, ufbx_material*, 1); + ufbxi_check_err(&tc->error, mat); + + *mat = surface->material; + mesh->materials.data = mat; + mesh->materials.count = 1; + } + + if (!tc->opts.skip_mesh_parts) { + mesh->material_parts.count = 1; + mesh->material_parts.data = ufbxi_push_zero(&tc->result, ufbx_mesh_part, 1); + ufbxi_check_err(&tc->error, mesh->material_parts.data); + } + + ufbxi_check_err(&tc->error, ufbxi_finalize_mesh_material(&tc->result, &tc->error, mesh)); + ufbxi_check_err(&tc->error, ufbxi_finalize_mesh(&tc->result, &tc->error, mesh)); + + mesh->generated_normals = true; + ufbx_compute_normals(mesh, &mesh->vertex_position, + mesh->vertex_normal.indices.data, mesh->vertex_normal.indices.count, + mesh->vertex_normal.values.data, mesh->vertex_normal.values.count); + + if (surface->flip_normals) { + ufbxi_nounroll ufbxi_for_list(ufbx_vec3, normal, mesh->vertex_normal.values) { + normal->x *= -1.0f; + normal->y *= -1.0f; + normal->z *= -1.0f; + } + } + + tc->imp = ufbxi_push(&tc->result, ufbxi_mesh_imp, 1); + ufbxi_check_err(&tc->error, tc->imp); + + ufbxi_init_ref(&tc->imp->refcount, UFBXI_MESH_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, surface->element.scene))->refcount); + + tc->imp->magic = UFBXI_MESH_IMP_MAGIC; + tc->imp->mesh = tc->mesh; + tc->imp->refcount.ator = tc->ator_result; + tc->imp->refcount.buf = tc->result; + tc->imp->mesh.subdivision_evaluated = true; + + return 1; +} + +#endif + +// -- Topology + +#if UFBXI_FEATURE_KD + +typedef struct { + ufbx_real split; + uint32_t index_plus_one; // 0 for empty + uint32_t slow_left; + uint32_t slow_right; + uint32_t slow_end; +} ufbxi_kd_node; + +typedef struct { + ufbx_face face; + ufbx_vertex_vec3 positions; + ufbx_vec3 axes[3]; + ufbxi_kd_node kd_nodes[1 << (UFBXI_KD_FAST_DEPTH + 1)]; + uint32_t *kd_indices; + + // Temporary + ufbx_vec3 cur_axis_dir; + ufbx_face cur_face; +} ufbxi_ngon_context; + +typedef struct { + ufbx_real min_t[2]; + ufbx_real max_t[2]; + ufbx_vec2 points[3]; + uint32_t indices[3]; +} ufbxi_kd_triangle; + +ufbxi_noinline static ufbx_vec2 ufbxi_ngon_project(ufbxi_ngon_context *nc, uint32_t index) +{ + ufbx_vec3 point = nc->positions.values.data[nc->positions.indices.data[nc->face.index_begin + index]]; + + ufbx_vec2 p; + p.x = ufbxi_dot3(nc->axes[0], point); + p.y = ufbxi_dot3(nc->axes[1], point); + return p; +} + +ufbxi_forceinline static ufbx_real ufbxi_orient2d(ufbx_vec2 a, ufbx_vec2 b, ufbx_vec2 c) +{ + return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); +} + +ufbxi_noinline static bool ufbxi_kd_check_point(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t index) +{ + if (index == tri->indices[0] || index == tri->indices[1] || index == tri->indices[2]) return false; + ufbx_vec2 p = ufbxi_ngon_project(nc, index); + + ufbx_real u = ufbxi_orient2d(p, tri->points[0], tri->points[1]); + ufbx_real v = ufbxi_orient2d(p, tri->points[1], tri->points[2]); + ufbx_real w = ufbxi_orient2d(p, tri->points[2], tri->points[0]); + + if (u <= 0.0f && v <= 0.0f && w <= 0.0f) return true; + if (u >= 0.0f && v >= 0.0f && w >= 0.0f) return true; + return false; +} + +// Recursion limited by 32-bit indices in input, minus halvings from `ufbxi_kd_check_fast()` +ufbxi_noinline static bool ufbxi_kd_check_slow(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t begin, uint32_t count, uint32_t axis) + ufbxi_recursive_function(bool, ufbxi_kd_check_slow, (nc, tri, begin, count, axis), 32 - UFBXI_KD_FAST_DEPTH, + (ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t begin, uint32_t count, uint32_t axis)) +{ + ufbx_vertex_vec3 pos = nc->positions; + uint32_t *kd_indices = nc->kd_indices; + + while (count > 0) { + uint32_t num_left = count / 2; + uint32_t begin_right = begin + num_left + 1; + uint32_t num_right = count - (num_left + 1); + + uint32_t index = kd_indices[begin + num_left]; + ufbx_vec3 point = pos.values.data[pos.indices.data[nc->face.index_begin + index]]; + ufbx_real split = ufbxi_dot3(point, nc->axes[axis]); + bool hit_left = tri->min_t[axis] <= split; + bool hit_right = tri->max_t[axis] >= split; + + if (hit_left && hit_right) { + if (ufbxi_kd_check_point(nc, tri, index)) { + return true; + } + + if (ufbxi_kd_check_slow(nc, tri, begin_right, num_right, axis ^ 1)) { + return true; + } + } + + axis ^= 1; + if (hit_left) { + count = num_left; + } else { + begin = begin_right; + count = num_right; + } + } + + return false; +} + +// Recursion limited by `UFBXI_KD_FAST_DEPTH` +ufbxi_noinline static bool ufbxi_kd_check_fast(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t kd_index, uint32_t axis, uint32_t depth) + ufbxi_recursive_function(bool, ufbxi_kd_check_fast, (nc, tri, kd_index, axis, depth), UFBXI_KD_FAST_DEPTH, + (ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t kd_index, uint32_t axis, uint32_t depth)) +{ + for (;;) { + ufbxi_kd_node node = nc->kd_nodes[kd_index]; + if (node.index_plus_one == 0) return false; + + bool hit_left = tri->min_t[axis] <= node.split; + bool hit_right = tri->max_t[axis] >= node.split; + + uint32_t side = hit_left ? 0 : 1; + uint32_t child_kd_index = kd_index * 2 + 1 + side; + if (hit_left && hit_right) { + + // Check for the point on the split plane + uint32_t index = node.index_plus_one - 1; + if (ufbxi_kd_check_point(nc, tri, index)) { + return true; + } + + // Recurse always to the right if we hit both sides + if (depth + 1 == UFBXI_KD_FAST_DEPTH) { + if (ufbxi_kd_check_slow(nc, tri, node.slow_right, node.slow_end - node.slow_right, axis ^ 1)) { + return true; + } + } else { + if (ufbxi_kd_check_fast(nc, tri, child_kd_index + 1, axis ^ 1, depth + 1)) { + return true; + } + } + } + + depth++; + axis ^= 1; + kd_index = child_kd_index; + + if (depth == UFBXI_KD_FAST_DEPTH) { + if (hit_left) { + return ufbxi_kd_check_slow(nc, tri, node.slow_left, node.slow_right - node.slow_left, axis); + } else { + return ufbxi_kd_check_slow(nc, tri, node.slow_right, node.slow_end - node.slow_right, axis); + } + } + } +} + +ufbxi_noinline static bool ufbxi_kd_check(ufbxi_ngon_context *nc, const ufbx_vec2 *points, const uint32_t *indices) +{ + ufbxi_kd_triangle tri; // ufbxi_uninit + tri.points[0] = points[0]; + tri.points[1] = points[1]; + tri.points[2] = points[2]; + tri.indices[0] = indices[0]; + tri.indices[1] = indices[1]; + tri.indices[2] = indices[2]; + tri.min_t[0] = ufbxi_min_real(ufbxi_min_real(points[0].x, points[1].x), points[2].x); + tri.min_t[1] = ufbxi_min_real(ufbxi_min_real(points[0].y, points[1].y), points[2].y); + tri.max_t[0] = ufbxi_max_real(ufbxi_max_real(points[0].x, points[1].x), points[2].x); + tri.max_t[1] = ufbxi_max_real(ufbxi_max_real(points[0].y, points[1].y), points[2].y); + return ufbxi_kd_check_fast(nc, &tri, 0, 0, 0); +} + +ufbxi_noinline static bool ufbxi_kd_index_less(void *user, const void *va, const void *vb) +{ + ufbxi_ngon_context *nc = (ufbxi_ngon_context*)user; + ufbx_vertex_vec3 *pos = &nc->positions; + const uint32_t a = *(const uint32_t*)va, b = *(const uint32_t*)vb; + ufbx_real da = ufbxi_dot3(nc->cur_axis_dir, pos->values.data[pos->indices.data[nc->cur_face.index_begin + a]]); + ufbx_real db = ufbxi_dot3(nc->cur_axis_dir, pos->values.data[pos->indices.data[nc->cur_face.index_begin + b]]); + return da < db; +} + +// Recursion limited by 32-bit indices in input +ufbxi_noinline static void ufbxi_kd_build(ufbxi_ngon_context *nc, uint32_t *indices, uint32_t *tmp, uint32_t num, uint32_t axis, uint32_t fast_index, uint32_t depth) + ufbxi_recursive_function_void(ufbxi_kd_build, (nc, indices, tmp, num, axis, fast_index, depth), 32, + (ufbxi_ngon_context *nc, uint32_t *indices, uint32_t *tmp, uint32_t num, uint32_t axis, uint32_t fast_index, uint32_t depth)) +{ + if (num == 0) return; + + ufbx_vertex_vec3 pos = nc->positions; + ufbx_vec3 axis_dir = nc->axes[axis]; + ufbx_face face = nc->face; + + nc->cur_axis_dir = axis_dir; + nc->cur_face = face; + + // Sort the remaining indices based on the axis + ufbxi_stable_sort(sizeof(uint32_t), 16, indices, tmp, num, &ufbxi_kd_index_less, nc); + + uint32_t num_left = num / 2; + uint32_t begin_right = num_left + 1; + uint32_t num_right = num - begin_right; + uint32_t dst_right = num_left + 1; + if (depth < UFBXI_KD_FAST_DEPTH) { + uint32_t skip_left = 1u << (UFBXI_KD_FAST_DEPTH - depth - 1); + dst_right = dst_right > skip_left ? dst_right - skip_left : 0; + + uint32_t index = indices[num_left]; + ufbxi_kd_node *kd = &nc->kd_nodes[fast_index]; + + kd->split = ufbxi_dot3(axis_dir, pos.values.data[pos.indices.data[face.index_begin + index]]); + kd->index_plus_one = index + 1; + + if (depth + 1 == UFBXI_KD_FAST_DEPTH) { + kd->slow_left = (uint32_t)(indices - nc->kd_indices); + kd->slow_right = kd->slow_left + num_left; + kd->slow_end = kd->slow_right + num_right; + } else { + kd->slow_left = UINT32_MAX; + kd->slow_right = UINT32_MAX; + kd->slow_end = UINT32_MAX; + } + } + + uint32_t child_fast = fast_index * 2 + 1; + ufbxi_kd_build(nc, indices, tmp, num_left, axis ^ 1, child_fast + 0, depth + 1); + + if (dst_right != begin_right) { + memmove(indices + dst_right, indices + begin_right, num_right * sizeof(uint32_t)); + } + + ufbxi_kd_build(nc, indices + dst_right, tmp, num_right, axis ^ 1, child_fast + 1, depth + 1); +} + +#endif + +#if UFBXI_FEATURE_TRIANGULATION + +ufbxi_noinline static ufbx_real ufbxi_ngon_tri_weight(const ufbx_vec2 *points) +{ + ufbx_vec2 p0 = points[0], p1 = points[1], p2 = points[2]; + ufbx_real orient = ufbxi_orient2d(p0, p1, p2); + if (orient <= 0.0f) return -1.0f; + + ufbx_real a = ufbxi_distsq2(p0, p1); + ufbx_real b = ufbxi_distsq2(p1, p2); + ufbx_real c = ufbxi_distsq2(p2, p0); + ufbx_real ab = (a + b - c) / (ufbx_real)ufbx_sqrt(4.0f * a * b); + ufbx_real bc = (b + c - a) / (ufbx_real)ufbx_sqrt(4.0f * b * c); + ufbx_real ca = (c + a - b) / (ufbx_real)ufbx_sqrt(4.0f * c * a); + return (ufbx_real)ufbx_fmax(UFBX_EPSILON, 2.0f - ufbx_fmax(ufbx_fmax(ab, bc), ca)); +} + +ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, uint32_t *indices, uint32_t num_indices) +{ + ufbx_face face = nc->face; + ufbx_assert(face.num_indices > 4); + + // Form an orthonormal basis to project the polygon into a 2D plane + ufbx_vec3 normal = ufbx_get_weighted_face_normal(&nc->positions, face); + ufbx_real len = ufbxi_length3(normal); + if (len > UFBX_EPSILON) { + normal = ufbxi_mul3(normal, 1.0f / len); + } else { + normal.x = 1.0f; + normal.y = 0.0f; + normal.z = 0.0f; + } + + ufbx_vec3 axis; // ufbxi_uninit + if (normal.x*normal.x < 0.5f) { + axis.x = 1.0f; + axis.y = 0.0f; + axis.z = 0.0f; + } else { + axis.x = 0.0f; + axis.y = 1.0f; + axis.z = 0.0f; + } + nc->axes[0] = ufbxi_slow_normalized_cross3(&axis, &normal); + nc->axes[1] = ufbxi_slow_normalized_cross3(&normal, &nc->axes[0]); + nc->axes[2] = normal; + + uint32_t *kd_indices = indices; + nc->kd_indices = kd_indices; + + uint32_t *kd_tmp = indices + face.num_indices; + + // Collect all the reflex corners for intersection testing. + uint32_t num_kd_indices = 0; + { + ufbx_vec2 a = ufbxi_ngon_project(nc, face.num_indices - 1); + ufbx_vec2 b = ufbxi_ngon_project(nc, 0); + for (uint32_t i = 0; i < face.num_indices; i++) { + uint32_t next = i + 1 < face.num_indices ? i + 1 : 0; + ufbx_vec2 c = ufbxi_ngon_project(nc, next); + + if (ufbxi_orient2d(a, b, c) <= 0.0f) { + kd_indices[num_kd_indices++] = i; + } + + a = b; + b = c; + } + } + + // Build a KD-tree of the vertices. + uint32_t num_skip_indices = (1u << (UFBXI_KD_FAST_DEPTH + 1)) - 1; + uint32_t kd_slow_indices = num_kd_indices > num_skip_indices ? num_kd_indices - num_skip_indices : 0; + ufbxi_ignore(kd_slow_indices); + ufbx_assert(kd_slow_indices + face.num_indices * 2 <= num_indices); + ufbxi_kd_build(nc, kd_indices, kd_tmp, num_kd_indices, 0, 0, 0); + + uint32_t *edges = indices + num_indices - face.num_indices * 2; + + // Initialize `edges` to be a connectivity structure where: + // `edges[2*i + 0]` is the previous vertex of `i` + // `edges[2*i + 1]` is the next vertex of `i` + // When clipped we mark indices with the high bit (0x80000000) + for (uint32_t i = 0; i < face.num_indices; i++) { + edges[i*2 + 0] = i > 0 ? i - 1 : face.num_indices - 1; + edges[i*2 + 1] = i + 1 < face.num_indices ? i + 1 : 0; + } + + // Core of the ear clipping algorithm. + // Iterate through the polygon corners looking for potential ears satisfying: + // - Angle must be less than 180deg + // - The triangle formed by the two edges must be contained within the polygon + // As these properties change only locally between modifications we only need + // to iterate the polygon once if we move backwards one step every time we clip an ear. + uint32_t indices_left = face.num_indices; + { + uint32_t point_indices[4] = { 0, 1, 2, 3 }; + ufbx_real weights[2]; // ufbxi_uninit + ufbx_vec2 points[4]; // ufbxi_uninit + + uint32_t num_steps = 0; + while (indices_left > 3) { + points[0] = ufbxi_ngon_project(nc, point_indices[0]); + points[1] = ufbxi_ngon_project(nc, point_indices[1]); + points[2] = ufbxi_ngon_project(nc, point_indices[2]); + points[3] = ufbxi_ngon_project(nc, point_indices[3]); + + weights[0] = ufbxi_ngon_tri_weight(points + 0); + weights[1] = ufbxi_ngon_tri_weight(points + 1); + + uint32_t first_side = weights[1] > weights[0] ? 1 : 0; + bool clipped = false; + ufbxi_nounroll for (uint32_t side_ix = 0; side_ix < 2; side_ix++) { + uint32_t side = side_ix ^ first_side; + if (!(weights[side] >= 0.0f)) break; + + // If there is no reflex angle contained within the triangle formed + // by `{ a, b, c }` connect the vertices `a - c` (prev, next) directly. + if (!ufbxi_kd_check(nc, points + side, point_indices + side)) { + uint32_t ia = point_indices[side + 0]; + uint32_t ib = point_indices[side + 1]; + uint32_t ic = point_indices[side + 2]; + + // Mark as clipped + edges[ib*2 + 0] |= 0x80000000; + edges[ib*2 + 1] |= 0x80000000; + + edges[ic*2 + 0] = ia; + edges[ia*2 + 1] = ic; + + indices_left -= 1; + + // TODO: This may cause O(n^2) behavior! + num_steps = 0; + + if (side == 1) { + point_indices[2] = point_indices[3]; + point_indices[3] = edges[point_indices[3]*2 + 1]; + } else { + point_indices[1] = point_indices[0]; + point_indices[0] = edges[point_indices[0]*2 + 0]; + } + + clipped = true; + break; + } + } + if (clipped) continue; + + // Continue forward + point_indices[0] = point_indices[1]; + point_indices[1] = point_indices[2]; + point_indices[2] = point_indices[3]; + point_indices[3] = edges[point_indices[3]*2 + 1]; + num_steps++; + + // If we have walked around the entire polygon it is irregular and + // ear cutting won't find any more triangles. + // TODO: This could be stricter? + if (num_steps >= face.num_indices*2) break; + } + + // Fallback: Cut non-ears until the polygon is completed. + // TODO: Could do something better here.. + uint32_t ix = point_indices[1]; + while (indices_left > 3) { + uint32_t prev = edges[ix*2 + 0]; + uint32_t next = edges[ix*2 + 1]; + + // Mark as clipped + edges[ix*2 + 0] |= 0x80000000; + edges[ix*2 + 1] |= 0x80000000; + + edges[prev*2 + 1] = next; + edges[next*2 + 0] = prev; + + indices_left -= 1; + ix = next; + } + + // Now we have a single triangle left at `ix`. + edges[ix*2 + 0] |= 0x80000000; + edges[ix*2 + 1] |= 0x80000000; + } + + // Expand the adjacency information `edges` into proper triangles. + // Care needs to be taken here as both refer to the same memory area: + // The last 4 triangles may overlap in source and destination so we write + // them to a stack buffer and copy them over in the end. + uint32_t max_triangles = face.num_indices - 2; + uint32_t num_triangles = 0, num_last_triangles = 0; + uint32_t last_triangles[4*3]; // ufbxi_uninit + + uint32_t index_begin = face.index_begin; + for (uint32_t ix = 0; ix < face.num_indices; ix++) { + uint32_t prev = edges[ix*2 + 0]; + uint32_t next = edges[ix*2 + 1]; + if (!(prev & 0x80000000)) continue; + + uint32_t *dst = indices + num_triangles * 3; + if (num_triangles + 4 >= max_triangles) { + dst = last_triangles + num_last_triangles * 3; + num_last_triangles++; + } + + dst[0] = index_begin + (prev & 0x7fffffff); + dst[1] = index_begin + ix; + dst[2] = index_begin + (next & 0x7fffffff); + num_triangles++; + } + + // Copy over the last triangles + ufbx_assert(num_triangles == max_triangles); + memcpy(indices + (max_triangles - num_last_triangles) * 3, last_triangles, num_last_triangles * 3 * sizeof(uint32_t)); + + return num_triangles; +} + +#endif + +static bool ufbxi_topo_less_index_prev_next(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; + if ((int32_t)a->prev != (int32_t)b->prev) return (int32_t)a->prev < (int32_t)b->prev; + return (int32_t)a->next < (int32_t)b->next; +} + +static bool ufbxi_topo_less_index_index(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; + return (int32_t)a->index < (int32_t)b->index; +} + +ufbxi_noinline static void ufbxi_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo) +{ + size_t num_indices = mesh->num_indices; + + // Temporarily use `prev` and `next` for vertices + for (uint32_t fi = 0; fi < mesh->num_faces; fi++) { + ufbx_face face = mesh->faces.data[fi]; + for (uint32_t pi = 0; pi < face.num_indices; pi++) { + ufbx_topo_edge *te = &topo[face.index_begin + pi]; + uint32_t ni = (pi + 1) % face.num_indices; + uint32_t va = mesh->vertex_indices.data[face.index_begin + pi]; + uint32_t vb = mesh->vertex_indices.data[face.index_begin + ni]; + + if (vb < va) { + uint32_t vt = va; va = vb; vb = vt; + } + te->index = face.index_begin + pi; + te->twin = UFBX_NO_INDEX; + te->edge = UFBX_NO_INDEX; + te->prev = va; + te->next = vb; + te->face = fi; + te->flags = (ufbx_topo_flags)0; + } + } + + ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_prev_next, NULL); + + if (mesh->edges.data) { + for (uint32_t ei = 0; ei < mesh->num_edges; ei++) { + ufbx_edge edge = mesh->edges.data[ei]; + uint32_t va = mesh->vertex_indices.data[edge.a]; + uint32_t vb = mesh->vertex_indices.data[edge.b]; + if (vb < va) { + uint32_t vt = va; va = vb; vb = vt; + } + + size_t ix = num_indices; + ufbxi_macro_lower_bound_eq(ufbx_topo_edge, 32, &ix, topo, 0, num_indices, + (a->prev == va ? a->next < vb : a->prev < va), (a->prev == va && a->next == vb)); + + for (; ix < num_indices && topo[ix].prev == va && topo[ix].next == vb; ix++) { + topo[ix].edge = ei; + } + } + } + + // Connect paired edges + for (size_t i0 = 0; i0 < num_indices; ) { + size_t i1 = i0; + + uint32_t a = topo[i0].prev, b = topo[i0].next; + while (i1 + 1 < num_indices && topo[i1 + 1].prev == a && topo[i1 + 1].next == b) i1++; + + if (i1 == i0 + 1) { + topo[i0].twin = topo[i1].index; + topo[i1].twin = topo[i0].index; + } else if (i1 > i0 + 1) { + for (size_t i = i0; i <= i1; i++) { + topo[i].flags = (ufbx_topo_flags)(topo[i].flags | UFBX_TOPO_NON_MANIFOLD); + } + } + + i0 = i1 + 1; + } + + ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_index, NULL); + + // Fix `prev` and `next` to the actual index values + for (uint32_t fi = 0; fi < mesh->num_faces; fi++) { + ufbx_face face = mesh->faces.data[fi]; + for (uint32_t i = 0; i < face.num_indices; i++) { + ufbx_topo_edge *to = &topo[face.index_begin + i]; + to->prev = (uint32_t)(face.index_begin + (i + face.num_indices - 1) % face.num_indices); + to->next = (uint32_t)(face.index_begin + (i + 1) % face.num_indices); + } + } +} + +static bool ufbxi_is_edge_smooth(const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index, bool assume_smooth) +{ + ufbxi_ignore(num_topo); + ufbx_assert((size_t)index < num_topo); + if (mesh->edge_smoothing.data) { + uint32_t edge = topo[index].edge; + if (edge != UFBX_NO_INDEX && mesh->edge_smoothing.data[edge]) return true; + } + + if (mesh->face_smoothing.data) { + if (mesh->face_smoothing.data[topo[index].face]) return true; + uint32_t twin = topo[index].twin; + if (twin != UFBX_NO_INDEX) { + if (mesh->face_smoothing.data[topo[twin].face]) return true; + } + } + + if (!mesh->edge_smoothing.data && !mesh->face_smoothing.data && mesh->vertex_normal.exists) { + uint32_t twin = topo[index].twin; + if (twin != UFBX_NO_INDEX && mesh->vertex_normal.exists) { + ufbx_assert((size_t)twin < num_topo); + ufbx_vec3 a0 = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); + ufbx_vec3 a1 = ufbx_get_vertex_vec3(&mesh->vertex_normal, topo[index].next); + ufbx_vec3 b0 = ufbx_get_vertex_vec3(&mesh->vertex_normal, topo[twin].next); + ufbx_vec3 b1 = ufbx_get_vertex_vec3(&mesh->vertex_normal, twin); + if (a0.x == b0.x && a0.y == b0.y && a0.z == b0.z) return true; + if (a1.x == b1.x && a1.y == b1.y && a1.z == b1.z) return true; + } + } else if (assume_smooth) { + return true; + } + + return false; +} + +// -- Subdivision + +#if UFBXI_FEATURE_SUBDIVISION + +typedef struct { + const void *data; + ufbx_real weight; +} ufbxi_subdivide_input; + +typedef int ufbxi_subdivide_sum_fn(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs); + +typedef struct { + ufbxi_subdivide_sum_fn *sum_fn; + void *sum_user; + + const void *values; + size_t stride; + + const uint32_t *indices; + + bool check_split_data; + bool ignore_indices; + + ufbx_subdivision_boundary boundary; + +} ufbxi_subdivide_layer_input; + +typedef struct { + void *values; + size_t num_values; + uint32_t *indices; + size_t num_indices; + bool unique_per_vertex; +} ufbxi_subdivide_layer_output; + +typedef struct { + ufbx_subdivision_weight *weights; + size_t num_weights; +} ufbxi_subdivision_vertex_weights; + +typedef struct { + ufbxi_mesh_imp *imp; + + ufbx_error error; + + ufbx_mesh *src_mesh_ptr; + ufbx_mesh src_mesh; + ufbx_mesh dst_mesh; + ufbx_topo_edge *topo; + size_t num_topo; + + ufbx_subdivide_opts opts; + + ufbxi_allocator ator_result; + ufbxi_allocator ator_tmp; + + ufbxi_buf result; + ufbxi_buf tmp; + ufbxi_buf source; + + ufbxi_subdivide_input *inputs; + size_t inputs_cap; + + ufbx_real *tmp_vertex_weights; + ufbx_subdivision_weight *tmp_weights; + size_t total_weights; + size_t max_vertex_weights; + +} ufbxi_subdivide_context; + +static int ufbxi_subdivide_sum_vec2(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) +{ + (void)user; + ufbx_vec2 dst = { 0 }; + ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { + const ufbx_vec2 *src = (const ufbx_vec2*)inputs[i].data; + ufbx_real weight = inputs[i].weight; + dst.x += src->x * weight; + dst.y += src->y * weight; + } + *(ufbx_vec2*)output = dst; + + return 1; +} + +static int ufbxi_subdivide_sum_vec3(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) +{ + (void)user; + ufbx_vec3 dst = { 0 }; + ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { + const ufbx_vec3 *src = (const ufbx_vec3*)inputs[i].data; + ufbx_real weight = inputs[i].weight; + dst.x += src->x * weight; + dst.y += src->y * weight; + dst.z += src->z * weight; + } + *(ufbx_vec3*)output = dst; + + return 1; +} + +static int ufbxi_subdivide_sum_vec4(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) +{ + (void)user; + ufbx_vec4 dst = { 0 }; + ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { + const ufbx_vec4 *src = (const ufbx_vec4*)inputs[i].data; + ufbx_real weight = inputs[i].weight; + dst.x += src->x * weight; + dst.y += src->y * weight; + dst.z += src->z * weight; + dst.w += src->w * weight; + } + *(ufbx_vec4*)output = dst; + + return 1; +} + +static ufbxi_noinline bool ufbxi_subdivision_weight_less(void *user, const void *va, const void *vb) +{ + (void)user; + ufbx_subdivision_weight a = *(const ufbx_subdivision_weight*)va, b = *(const ufbx_subdivision_weight*)vb; + ufbxi_dev_assert(a.index != b.index); + if (a.weight != b.weight) return a.weight > b.weight; + return a.index < b.index; +} + +static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) +{ + ufbxi_subdivide_context *sc = (ufbxi_subdivide_context*)user; + + ufbx_real *vertex_weights = sc->tmp_vertex_weights; + ufbx_subdivision_weight *tmp_weights = sc->tmp_weights; + size_t num_weights = 0; + + ufbxi_nounroll for (size_t input_ix = 0; input_ix != num_inputs; input_ix++) { + ufbxi_subdivision_vertex_weights src = *(const ufbxi_subdivision_vertex_weights*)inputs[input_ix].data; + ufbx_real input_weight = inputs[input_ix].weight; + + for (size_t weight_ix = 0; weight_ix < src.num_weights; weight_ix++) { + ufbx_real weight = input_weight * src.weights[weight_ix].weight; + if (weight < 1.175494351e-38f) continue; + + uint32_t vx = src.weights[weight_ix].index; + ufbxi_dev_assert(vx < sc->src_mesh.num_vertices); + + ufbx_real prev = vertex_weights[vx]; + vertex_weights[vx] = prev + weight; + if (prev == 0.0f) { + tmp_weights[num_weights++].index = vx; + } + } + } + + ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { + uint32_t vx = tmp_weights[i].index; + tmp_weights[i].weight = vertex_weights[vx]; + vertex_weights[vx] = 0.0f; + } + + ufbxi_unstable_sort(tmp_weights, num_weights, sizeof(ufbx_subdivision_weight), ufbxi_subdivision_weight_less, NULL); + + if (sc->max_vertex_weights != SIZE_MAX) { + num_weights = ufbxi_min_sz(sc->max_vertex_weights, num_weights); + + // Normalize weights + ufbx_real prefix_weight = 0.0f; + ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { + prefix_weight += tmp_weights[i].weight; + } + ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { + tmp_weights[i].weight /= prefix_weight; + } + } + + sc->total_weights += num_weights; + ufbx_subdivision_weight *weights = ufbxi_push_copy(&sc->tmp, ufbx_subdivision_weight, num_weights, tmp_weights); + ufbxi_check_err(&sc->error, weights); + + ufbxi_subdivision_vertex_weights *dst = (ufbxi_subdivision_vertex_weights*)output; + dst->weights = weights; + dst->num_weights = num_weights; + + return 1; +} + +static ufbxi_subdivide_sum_fn *const ufbxi_real_sum_fns[] = { + NULL, + &ufbxi_subdivide_sum_vec2, + &ufbxi_subdivide_sum_vec3, + &ufbxi_subdivide_sum_vec4, +}; + +ufbxi_noinline static bool ufbxi_is_edge_split(const ufbxi_subdivide_layer_input *input, const ufbx_topo_edge *topo, uint32_t index) +{ + uint32_t twin = topo[index].twin; + if (twin != UFBX_NO_INDEX) { + uint32_t a0 = input->indices[index]; + uint32_t a1 = input->indices[topo[index].next]; + uint32_t b0 = input->indices[topo[twin].next]; + uint32_t b1 = input->indices[twin]; + if (a0 == b0 && a1 == b1) return false; + if (!input->check_split_data) return true; + size_t stride = input->stride; + char *da0 = (char*)input->values + a0 * stride; + char *da1 = (char*)input->values + a1 * stride; + char *db0 = (char*)input->values + b0 * stride; + char *db1 = (char*)input->values + b1 * stride; + if (!memcmp(da0, db0, stride) && !memcmp(da1, db1, stride)) return false; + return true; + } + + return false; +} + +static ufbx_real ufbxi_edge_crease(const ufbx_mesh *mesh, bool split, const ufbx_topo_edge *topo, uint32_t index) +{ + if (topo[index].twin == UFBX_NO_INDEX) return 1.0f; + if (split) return 1.0f; + if (mesh->edge_crease.data && topo[index].edge != UFBX_NO_INDEX) return mesh->edge_crease.data[topo[index].edge] * (ufbx_real)10.0; + return 0.0f; +} + +static ufbxi_noinline int ufbxi_subdivide_layer(ufbxi_subdivide_context *sc, ufbxi_subdivide_layer_output *output, const ufbxi_subdivide_layer_input *input) +{ + ufbx_subdivision_boundary boundary = input->boundary; + + const ufbx_mesh *mesh = &sc->src_mesh; + const ufbx_topo_edge *topo = sc->topo; + size_t num_topo = sc->num_topo; + + uint32_t *edge_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); + ufbxi_check_err(&sc->error, edge_indices); + + size_t num_edge_values = 0; + for (uint32_t ix = 0; ix < (uint32_t)mesh->num_indices; ix++) { + uint32_t twin = topo[ix].twin; + if (twin < ix && !ufbxi_is_edge_split(input, topo, ix)) { + edge_indices[ix] = edge_indices[twin]; + } else { + edge_indices[ix] = (uint32_t)num_edge_values++; + } + } + + size_t stride = input->stride; + size_t num_initial_values = (num_edge_values + mesh->num_faces + mesh->num_indices); + char *values = (char*)ufbxi_push_size(&sc->tmp, stride, num_initial_values); + ufbxi_check_err(&sc->error, values); + + char *face_values = values; + char *edge_values = face_values + mesh->num_faces * stride; + char *vertex_values = edge_values + num_edge_values * stride; + + size_t num_vertex_values = 0; + + uint32_t *vertex_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); + ufbxi_check_err(&sc->error, vertex_indices); + + size_t min_inputs = ufbxi_max_sz(32, mesh->max_face_triangles + 2); + ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, min_inputs)); + ufbxi_subdivide_input *inputs = sc->inputs; + + // Assume initially unique per vertex, remove if not the case + output->unique_per_vertex = true; + + bool sharp_corners = false; + bool sharp_splits = false; + bool sharp_all = false; + + switch (boundary) { + case UFBX_SUBDIVISION_BOUNDARY_DEFAULT: + case UFBX_SUBDIVISION_BOUNDARY_SHARP_NONE: + case UFBX_SUBDIVISION_BOUNDARY_LEGACY: + // All smooth + break; + case UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS: + sharp_corners = true; + break; + case UFBX_SUBDIVISION_BOUNDARY_SHARP_BOUNDARY: + sharp_corners = true; + sharp_splits = true; + break; + case UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR: + sharp_all = true; + break; + default: + ufbxi_unreachable("Bad boundary mode"); + } + + ufbxi_subdivide_sum_fn *sum_fn = input->sum_fn; + void *sum_user = input->sum_user; + + // Mark unused indices as `UFBX_NO_INDEX` so we can patch non-manifold + ufbxi_nounroll for (size_t i = 0; i < mesh->num_indices; i++) { + vertex_indices[i] = UFBX_NO_INDEX; + } + + // Face points + for (size_t fi = 0; fi < mesh->num_faces; fi++) { + ufbx_face face = mesh->faces.data[fi]; + char *dst = face_values + fi * stride; + + ufbx_real weight = 1.0f / (ufbx_real)face.num_indices; + for (uint32_t ci = 0; ci < face.num_indices; ci++) { + uint32_t ix = face.index_begin + ci; + inputs[ci].data = (const char*)input->values + input->indices[ix] * stride; + inputs[ci].weight = weight; + } + + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, face.num_indices)); + } + + // Edge points + for (uint32_t ix = 0; ix < mesh->num_indices; ix++) { + char *dst = edge_values + edge_indices[ix] * stride; + + uint32_t twin = topo[ix].twin; + bool split = ufbxi_is_edge_split(input, topo, ix); + + if (split || (topo[ix].flags & UFBX_TOPO_NON_MANIFOLD) != 0) { + output->unique_per_vertex = false; + } + + ufbx_real crease = 0.0f; + if (split || twin == UFBX_NO_INDEX) { + crease = 1.0f; + } else if (topo[ix].edge != UFBX_NO_INDEX && mesh->edge_crease.data) { + crease = mesh->edge_crease.data[topo[ix].edge] * (ufbx_real)10.0; + } + if (sharp_all) crease = 1.0f; + + const char *v0 = (const char*)input->values + input->indices[ix] * stride; + const char *v1 = (const char*)input->values + input->indices[topo[ix].next] * stride; + + // TODO: Unify + if (twin < ix && !split) { + // Already calculated + } else if (crease <= 0.0f) { + const char *f0 = face_values + topo[ix].face * stride; + const char *f1 = face_values + topo[twin].face * stride; + inputs[0].data = v0; + inputs[0].weight = 0.25f; + inputs[1].data = v1; + inputs[1].weight = 0.25f; + inputs[2].data = f0; + inputs[2].weight = 0.25f; + inputs[3].data = f1; + inputs[3].weight = 0.25f; + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 4)); + } else if (crease >= 1.0f) { + inputs[0].data = v0; + inputs[0].weight = 0.5f; + inputs[1].data = v1; + inputs[1].weight = 0.5f; + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 2)); + } else if (crease < 1.0f) { + const char *f0 = face_values + topo[ix].face * stride; + const char *f1 = face_values + topo[twin].face * stride; + ufbx_real w0 = 0.25f + 0.25f * crease; + ufbx_real w1 = 0.25f - 0.25f * crease; + + inputs[0].data = v0; + inputs[0].weight = w0; + inputs[1].data = v1; + inputs[1].weight = w0; + inputs[2].data = f0; + inputs[2].weight = w1; + inputs[3].data = f1; + inputs[3].weight = w1; + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 4)); + } + } + + // Vertex points + for (size_t vi = 0; vi < mesh->num_vertices; vi++) { + uint32_t original_start = mesh->vertex_first_index.data[vi]; + if (original_start == UFBX_NO_INDEX) continue; + + // Find a topological boundary, or if not found a split edge + uint32_t start = original_start; + for (uint32_t cur = start;;) { + uint32_t prev = ufbx_topo_prev_vertex_edge(topo, num_topo, cur); + if (prev == UFBX_NO_INDEX) { start = cur; break; } // Topological boundary: Stop and use as start + if (ufbxi_is_edge_split(input, topo, prev)) start = cur; // Split edge: Consider as start + if (prev == original_start) break; // Loop: Stop, use original start or split if found + cur = prev; + } + + original_start = start; + while (start != UFBX_NO_INDEX) { + if (start != original_start) { + output->unique_per_vertex = false; + } + + uint32_t value_index = (uint32_t)num_vertex_values++; + char *dst = vertex_values + value_index * stride; + + // We need to compute the average crease value and keep track of + // two creased edges, if there's more we use the corner rule that + // does not need the information. + ufbx_real total_crease = 0.0f; + size_t num_crease = 0; + size_t num_split = 0; + bool on_boundary = false; + bool non_manifold = false; + size_t crease_input_indices[2]; // ufbxi_uninit + + // At start we always have two edges and a single face + uint32_t start_prev = topo[start].prev; + uint32_t end_edge = topo[start_prev].twin; + size_t valence = 2; + + non_manifold |= (topo[start].flags & UFBX_TOPO_NON_MANIFOLD) != 0; + non_manifold |= (topo[start_prev].flags & UFBX_TOPO_NON_MANIFOLD) != 0; + + const char *v0 = (const char*)input->values + input->indices[start] * stride; + + size_t num_inputs = 4; + + { + const char *e0 = (const char*)input->values + input->indices[topo[start].next] * stride; + const char *e1 = (const char*)input->values + input->indices[start_prev] * stride; + const char *f0 = face_values + topo[start].face * stride; + inputs[0].data = v0; + inputs[1].data = e0; + inputs[2].data = e1; + inputs[3].data = f0; + } + + bool start_split = ufbxi_is_edge_split(input, topo, start); + bool prev_split = end_edge != UFBX_NO_INDEX && ufbxi_is_edge_split(input, topo, end_edge); + + // Either of the first two edges may be creased + ufbx_real start_crease = ufbxi_edge_crease(mesh, start_split, topo, start); + if (start_crease > 0.0f) { + total_crease += start_crease; + crease_input_indices[num_crease++] = 1; + } + ufbx_real prev_crease = ufbxi_edge_crease(mesh, prev_split, topo, start_prev); + if (prev_crease > 0.0f) { + total_crease += prev_crease; + crease_input_indices[num_crease++] = 2; + } + + if (end_edge != UFBX_NO_INDEX) { + if (prev_split) { + num_split++; + } + } else { + on_boundary = true; + } + + ufbxi_check_err(&sc->error, vertex_indices[start] == UFBX_NO_INDEX); + vertex_indices[start] = value_index; + + if (start_split) { + // We need to special case if the first edge is split as we have + // handled it already in the code above.. + start = ufbx_topo_next_vertex_edge(topo, num_topo, start); + num_split++; + } else { + // Follow vertex edges until we either hit a topological/split boundary + // or loop back to the left edge we accounted for in `start_prev` + uint32_t cur = start; + for (;;) { + cur = ufbx_topo_next_vertex_edge(topo, num_topo, cur); + + // Topological boundary: Finished + if (cur == UFBX_NO_INDEX) { + on_boundary = true; + start = UFBX_NO_INDEX; + break; + } + + non_manifold |= (topo[cur].flags & UFBX_TOPO_NON_MANIFOLD) != 0; + ufbxi_check_err(&sc->error, vertex_indices[cur] == UFBX_NO_INDEX); + vertex_indices[cur] = value_index; + + bool split = ufbxi_is_edge_split(input, topo, cur); + + // Looped: Add the face from the other side still if not split + if (cur == end_edge && !split) { + ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, num_inputs + 1)); + const char *f0 = face_values + topo[cur].face * stride; + inputs[num_inputs].data = f0; + start = UFBX_NO_INDEX; + num_inputs += 1; + break; + } + + // Add the edge crease, this also handles boundaries as they + // have an implicit crease of 1.0 using `ufbxi_edge_crease()` + ufbx_real cur_crease = ufbxi_edge_crease(mesh, split, topo, cur); + if (cur_crease > 0.0f) { + total_crease += cur_crease; + if (num_crease < 2) crease_input_indices[num_crease] = num_inputs; + num_crease++; + } + + // Add the new edge and face to the sum + { + ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, num_inputs + 2)); + inputs = sc->inputs; + + const char *e0 = (char*)input->values + input->indices[topo[cur].next] * stride; + const char *f0 = face_values + topo[cur].face * stride; + inputs[num_inputs + 0].data = e0; + inputs[num_inputs + 1].data = f0; + num_inputs += 2; + } + valence++; + + // If we landed at a split edge advance to the next one + // and continue from there in the outer loop + if (split) { + start = ufbx_topo_next_vertex_edge(topo, num_topo, cur); + num_split++; + break; + } + } + } + + if (start == original_start) start = UFBX_NO_INDEX; + + // Weights for various subdivision masks + ufbx_real fe_weight = 1.0f / (ufbx_real)(valence*valence); + ufbx_real v_weight = (ufbx_real)(valence - 2) / (ufbx_real)valence; + + // Select the right subdivision mask depending on valence and crease + if (num_crease > 2 + || (sharp_corners && valence == 2 && (num_split > 0 || on_boundary)) + || (sharp_splits && (num_split > 0 || on_boundary)) + || sharp_all + || non_manifold) { + // Corner: Copy as-is + inputs[0].data = v0; + inputs[0].weight = 1.0f; + num_inputs = 1; + } else if (num_crease == 2) { + // Boundary: Interpolate edge + total_crease *= 0.5f; + if (total_crease < 0.0f) total_crease = 0.0f; + if (total_crease > 1.0f) total_crease = 1.0f; + + inputs[0].weight = v_weight * (1.0f - total_crease) + 0.75f * total_crease; + ufbx_real few = fe_weight * (1.0f - total_crease); + for (size_t i = 1; i < num_inputs; i++) { + inputs[i].weight = few; + } + + // Add weight to the creased edges + inputs[crease_input_indices[0]].weight += 0.125f * total_crease; + inputs[crease_input_indices[1]].weight += 0.125f * total_crease; + } else { + // Regular: Weighted sum with the accumulated edge/face points + inputs[0].weight = v_weight; + for (size_t i = 1; i < num_inputs; i++) { + inputs[i].weight = fe_weight; + } + + } + + if (mesh->vertex_crease.exists) { + ufbx_real v = ufbx_get_vertex_real(&mesh->vertex_crease, original_start); + v *= (ufbx_real)10.0; + if (v > 0.0f) { + if (v > 1.0) v = 1.0f; + + ufbx_real iv = 1.0f - v; + inputs[0].weight = 1.0f * v + (inputs[0].weight) * iv; + for (size_t i = 1; i < num_inputs; i++) { + inputs[i].weight *= iv; + } + } + } + +#if defined(UFBX_REGRESSION) + { + ufbx_real total_weight = 0.0f; + for (size_t i = 0; i < num_inputs; i++) { + total_weight += inputs[i].weight; + } + ufbx_assert(ufbx_fabs(total_weight - 1.0f) < 0.001f); + } +#endif + + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, num_inputs)); + } + } + + // Copy non-manifold vertex values as-is + for (size_t old_ix = 0; old_ix < mesh->num_indices; old_ix++) { + uint32_t ix = vertex_indices[old_ix]; + if (ix == UFBX_NO_INDEX) { + ix = (uint32_t)num_vertex_values++; + vertex_indices[old_ix] = ix; + const char *src = (const char*)input->values + input->indices[old_ix] * stride; + char *dst = vertex_values + ix * stride; + + inputs[0].data = src; + inputs[0].weight = 1.0f; + ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 1)); + } + } + + ufbx_assert(num_vertex_values <= mesh->num_indices); + size_t num_values = num_edge_values + mesh->num_faces + num_vertex_values; + char *new_values = (char*)ufbxi_push_size(&sc->result, stride, (num_values+1)); + ufbxi_check_err(&sc->error, new_values); + + memset(new_values, 0, stride); + new_values += stride; + + memcpy(new_values, values, num_values * stride); + + output->values = new_values; + output->num_values = num_values; + + if (!input->ignore_indices) { + uint32_t *new_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices * 4); + ufbxi_check_err(&sc->error, new_indices); + + uint32_t face_start = 0; + uint32_t edge_start = (uint32_t)(face_start + mesh->num_faces); + uint32_t vert_start = (uint32_t)(edge_start + num_edge_values); + uint32_t *p_ix = new_indices; + for (size_t ix = 0; ix < mesh->num_indices; ix++) { + p_ix[0] = vert_start + vertex_indices[ix]; + p_ix[1] = edge_start + edge_indices[ix]; + p_ix[2] = face_start + topo[ix].face; + p_ix[3] = edge_start + edge_indices[topo[ix].prev]; + p_ix += 4; + } + output->indices = new_indices; + output->num_indices = mesh->num_indices * 4; + } else { + output->indices = NULL; + output->num_indices = 0; + } + + return 1; +} + +static ufbxi_noinline int ufbxi_subdivide_attrib(ufbxi_subdivide_context *sc, ufbx_vertex_attrib *attrib, ufbx_subdivision_boundary boundary, bool check_split_data) +{ + if (!attrib->exists) return 1; + + ufbx_assert(attrib->value_reals >= 2 && attrib->value_reals <= 4); + + ufbxi_subdivide_layer_input input; // ufbxi_uninit + input.sum_fn = ufbxi_real_sum_fns[attrib->value_reals - 1]; + input.sum_user = NULL; + input.values = attrib->values.data; + input.indices = attrib->indices.data; + input.stride = attrib->value_reals * sizeof(ufbx_real); + input.boundary = boundary; + input.check_split_data = check_split_data; + input.ignore_indices = false; + + ufbxi_subdivide_layer_output output; // ufbxi_uninit + ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); + + attrib->values.data = output.values; + attrib->indices.data = output.indices; + attrib->values.count = output.num_values; + attrib->indices.count = output.num_indices; + + return 1; +} + +static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_subdivision_copy_weights(ufbxi_subdivide_context *sc, ufbx_subdivision_weight_range_list ranges, ufbx_subdivision_weight_list weights) +{ + ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, ranges.count); + ufbxi_check_return_err(&sc->error, dst, NULL); + + ufbxi_nounroll for (size_t i = 0; i != ranges.count; i++) { + ufbx_subdivision_weight_range range = ranges.data[i]; + dst[i].weights = weights.data + range.weight_begin; + dst[i].num_weights = range.num_weights; + } + + return dst; +} + +static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_init_source_vertex_weights(ufbxi_subdivide_context *sc, size_t num_vertices) +{ + ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, num_vertices); + ufbx_subdivision_weight *weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, num_vertices); + ufbxi_check_return_err(&sc->error, dst && weights, NULL); + + ufbxi_nounroll for (size_t i = 0; i != num_vertices; i++) { + dst[i].weights = weights + i; + dst[i].num_weights = 1; + weights[i].index = (uint32_t)i; + weights[i].weight = 1.0f; + } + + return dst; +} + +static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_init_skin_weights(ufbxi_subdivide_context *sc, size_t num_vertices, const ufbx_skin_deformer *skin) +{ + ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, num_vertices); + ufbxi_check_return_err(&sc->error, dst, NULL); + + for (size_t i = 0; i < num_vertices; i++) { + ufbxi_dev_assert(i < skin->vertices.count); + ufbx_skin_vertex vertex = skin->vertices.data[i]; + size_t num_weights = ufbxi_min_sz(sc->max_vertex_weights, vertex.num_weights); + + ufbx_subdivision_weight *weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, num_weights); + ufbxi_check_err(&sc->error, weights); + + const ufbx_skin_weight *skin_weights = skin->weights.data + vertex.weight_begin; + + dst[i].weights = weights; + dst[i].num_weights = num_weights; + ufbxi_nounroll for (size_t wi = 0; wi != num_weights; wi++) { + ufbxi_check_err(&sc->error, skin_weights[wi].cluster_index <= INT32_MAX); + weights[wi].index = skin_weights[wi].cluster_index; + weights[wi].weight = skin_weights[wi].weight; + } + } + + return dst; +} + +static ufbxi_noinline int ufbxi_subdivide_weights(ufbxi_subdivide_context *sc, ufbx_subdivision_weight_range_list *ranges, + ufbx_subdivision_weight_list *weights, const ufbxi_subdivision_vertex_weights *src) +{ + ufbxi_check_err(&sc->error, src); + + ufbxi_subdivide_layer_input input; // ufbxi_uninit + input.sum_fn = ufbxi_subdivide_sum_vertex_weights; + input.sum_user = sc; + input.values = src; + input.indices = sc->src_mesh.vertex_indices.data; + input.stride = sizeof(ufbxi_subdivision_vertex_weights); + input.boundary = sc->opts.boundary; + input.check_split_data = false; + input.ignore_indices = true; + + sc->total_weights = 0; + + ufbxi_subdivide_layer_output output; // ufbxi_uninit + ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); + + size_t num_vertices = output.num_values; + ufbx_assert(num_vertices == sc->dst_mesh.vertex_position.values.count); + + ufbx_subdivision_weight_range *dst_ranges = ufbxi_push(&sc->result, ufbx_subdivision_weight_range, num_vertices); + ufbx_subdivision_weight *dst_weights = ufbxi_push(&sc->result, ufbx_subdivision_weight, sc->total_weights); + ufbxi_check_err(&sc->error, ranges && weights); + + ufbxi_subdivision_vertex_weights *src_weights = (ufbxi_subdivision_vertex_weights*)output.values; + + size_t weight_offset = 0; + for (size_t vi = 0; vi < num_vertices; vi++) { + ufbxi_subdivision_vertex_weights ws = src_weights[vi]; + ufbxi_check_err(&sc->error, (size_t)UINT32_MAX - weight_offset >= ws.num_weights); + + dst_ranges[vi].weight_begin = (uint32_t)weight_offset; + dst_ranges[vi].num_weights = (uint32_t)ws.num_weights; + memcpy(dst_weights + weight_offset, ws.weights, ws.num_weights * sizeof(ufbx_subdivision_weight)); + weight_offset += ws.num_weights; + } + + ranges->data = dst_ranges; + ranges->count = num_vertices; + weights->data = dst_weights; + weights->count = sc->total_weights; + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_vertex_crease(ufbxi_subdivide_context *sc, ufbx_vertex_real *ufbxi_restrict dst, const ufbx_vertex_real *ufbxi_restrict src) +{ + size_t src_indices = src->indices.count; + size_t src_values = src->values.count; + + dst->values.count = src_values + 1; + dst->values.data = ufbxi_push(&sc->result, ufbx_real, dst->values.count); + ufbxi_check_err(&sc->error, dst->values.data); + dst->values.data[src_values] = 0.0f; + + dst->indices.count = src_indices * 4; + dst->indices.data = ufbxi_push(&sc->result, uint32_t, dst->indices.count); + ufbxi_check_err(&sc->error, dst->indices.data); + + // Reduce the amount of vertex crease on each iteration + ufbxi_nounroll for (size_t i = 0; i < src_values; i++) { + ufbx_real crease = src->values.data[i]; + if (crease < 0.999f) crease -= 0.1f; + if (crease < 0.0f) crease = 0.0f; + dst->values.data[i] = crease; + } + + // Write the crease at the vertex corner and zero (at `src_values`) on other ones + uint32_t zero_index = (uint32_t)src_values; + ufbxi_nounroll for (size_t i = 0; i < src_indices; i++) { + uint32_t *quad = dst->indices.data + i * 4; + quad[0] = src->indices.data[i]; + quad[1] = zero_index; + quad[2] = zero_index; + quad[3] = zero_index; + } + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_level(ufbxi_subdivide_context *sc) +{ + const ufbx_mesh *mesh = &sc->src_mesh; + ufbx_mesh *result = &sc->dst_mesh; + + *result = *mesh; + + ufbx_topo_edge *topo = ufbxi_push(&sc->tmp, ufbx_topo_edge, mesh->num_indices); + ufbxi_check_err(&sc->error, topo); + ufbx_compute_topology(mesh, topo, mesh->num_indices); + sc->topo = topo; + sc->num_topo = mesh->num_indices; + + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->vertex_position, sc->opts.boundary, false)); + + memset(&result->vertex_uv, 0, sizeof(result->vertex_uv)); + memset(&result->vertex_tangent, 0, sizeof(result->vertex_tangent)); + memset(&result->vertex_bitangent, 0, sizeof(result->vertex_bitangent)); + memset(&result->vertex_color, 0, sizeof(result->vertex_color)); + + result->uv_sets.data = ufbxi_push_copy(&sc->result, ufbx_uv_set, result->uv_sets.count, result->uv_sets.data); + ufbxi_check_err(&sc->error, result->uv_sets.data); + + result->color_sets.data = ufbxi_push_copy(&sc->result, ufbx_color_set, result->color_sets.count, result->color_sets.data); + ufbxi_check_err(&sc->error, result->color_sets.data); + + ufbxi_for_list(ufbx_uv_set, set, result->uv_sets) { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_uv, sc->opts.uv_boundary, true)); + if (sc->opts.interpolate_tangents) { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_tangent, sc->opts.uv_boundary, true)); + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_bitangent, sc->opts.uv_boundary, true)); + } else { + memset(&set->vertex_tangent, 0, sizeof(set->vertex_tangent)); + memset(&set->vertex_bitangent, 0, sizeof(set->vertex_bitangent)); + } + } + + ufbxi_for_list(ufbx_color_set, set, result->color_sets) { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_color, sc->opts.uv_boundary, true)); + } + + if (result->uv_sets.count > 0) { + result->vertex_uv = result->uv_sets.data[0].vertex_uv; + result->vertex_bitangent = result->uv_sets.data[0].vertex_bitangent; + result->vertex_tangent = result->uv_sets.data[0].vertex_tangent; + } + if (result->color_sets.count > 0) { + result->vertex_color = result->color_sets.data[0].vertex_color; + } + + if (sc->opts.interpolate_normals && !sc->opts.ignore_normals) { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->vertex_normal, sc->opts.boundary, true)); + ufbxi_for_list(ufbx_vec3, normal, result->vertex_normal.values) { + *normal = ufbxi_slow_normalize3(normal); + } + if (mesh->skinned_normal.values.data == mesh->vertex_normal.values.data) { + result->skinned_normal = result->vertex_normal; + } else { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->skinned_normal, sc->opts.boundary, true)); + ufbxi_for_list(ufbx_vec3, normal, result->skinned_normal.values) { + *normal = ufbxi_slow_normalize3(normal); + } + } + } + + if (result->vertex_crease.exists) { + ufbxi_check_err(&sc->error, ufbxi_subdivide_vertex_crease(sc, &result->vertex_crease, &mesh->vertex_crease)); + } + + if (mesh->skinned_position.values.data == mesh->vertex_position.values.data) { + result->skinned_position = result->vertex_position; + } else { + ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->skinned_position, sc->opts.boundary, false)); + } + + ufbx_subdivision_result *result_sub = ufbxi_push_zero(&sc->result, ufbx_subdivision_result, 1); + ufbxi_check_err(&sc->error, result_sub); + result->subdivision_result = result_sub; + + if (sc->opts.evaluate_source_vertices || sc->opts.evaluate_skin_weights) { + ufbx_subdivision_result *mesh_sub = mesh->subdivision_result; + + ufbx_skin_deformer *skin = NULL; + if (sc->opts.evaluate_skin_weights) { + if (mesh->skin_deformers.count > 0) { + ufbxi_check_err(&sc->error, sc->opts.skin_deformer_index < mesh->skin_deformers.count); + skin = mesh->skin_deformers.data[sc->opts.skin_deformer_index]; + } + } + + size_t max_weights = 0; + if (sc->opts.evaluate_source_vertices) { + max_weights = ufbxi_max_sz(max_weights, mesh->num_vertices); + } + if (skin) { + max_weights = ufbxi_max_sz(max_weights, skin->clusters.count); + } + + sc->tmp_vertex_weights = ufbxi_push_zero(&sc->tmp, ufbx_real, mesh->num_vertices); + sc->tmp_weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, max_weights); + ufbxi_check_err(&sc->error, sc->tmp_vertex_weights && sc->tmp_weights); + + if (sc->opts.evaluate_source_vertices) { + sc->max_vertex_weights = sc->opts.max_source_vertices ? sc->opts.max_source_vertices : SIZE_MAX; + + ufbxi_subdivision_vertex_weights *weights; + if (mesh_sub && mesh_sub->source_vertex_ranges.count > 0) { + weights = ufbxi_subdivision_copy_weights(sc, mesh_sub->source_vertex_ranges, mesh_sub->source_vertex_weights); + } else { + weights = ufbxi_init_source_vertex_weights(sc, mesh->num_vertices); + } + + ufbxi_check_err(&sc->error, ufbxi_subdivide_weights(sc, &result_sub->source_vertex_ranges, &result_sub->source_vertex_weights, weights)); + } + + if (skin) { + sc->max_vertex_weights = sc->opts.max_skin_weights ? sc->opts.max_skin_weights : SIZE_MAX; + + ufbxi_subdivision_vertex_weights *weights; + if (mesh_sub && mesh_sub->source_vertex_ranges.count > 0) { + weights = ufbxi_subdivision_copy_weights(sc, mesh_sub->skin_cluster_ranges, mesh_sub->skin_cluster_weights); + } else { + weights = ufbxi_init_skin_weights(sc, mesh->num_vertices, skin); + } + + ufbxi_check_err(&sc->error, ufbxi_subdivide_weights(sc, &result_sub->skin_cluster_ranges, &result_sub->skin_cluster_weights, weights)); + } + + } + + result->num_vertices = result->vertex_position.values.count; + result->num_indices = mesh->num_indices * 4; + result->num_faces = mesh->num_indices; + result->num_triangles = mesh->num_indices * 2; + + result->vertex_indices.data = result->vertex_position.indices.data; + result->vertex_indices.count = result->num_indices; + result->vertices.data = result->vertex_position.values.data; + result->vertices.count = result->num_vertices; + + result->faces.count = result->num_faces; + result->faces.data = ufbxi_push(&sc->result, ufbx_face, result->num_faces); + ufbxi_check_err(&sc->error, result->faces.data); + + for (size_t i = 0; i < result->num_faces; i++) { + result->faces.data[i].index_begin = (uint32_t)(i * 4); + result->faces.data[i].num_indices = 4; + } + + if (mesh->edges.data) { + result->num_edges = mesh->num_edges*2 + result->num_faces; + result->edges.count = result->num_edges; + result->edges.data = ufbxi_push(&sc->result, ufbx_edge, result->num_edges); + ufbxi_check_err(&sc->error, result->edges.data); + + if (mesh->edge_crease.data) { + result->edge_crease.count = result->num_edges; + result->edge_crease.data = ufbxi_push(&sc->result, ufbx_real, result->num_edges); + ufbxi_check_err(&sc->error, result->edge_crease.data); + } + if (mesh->edge_smoothing.data) { + result->edge_smoothing.count = result->num_edges; + result->edge_smoothing.data = ufbxi_push(&sc->result, bool, result->num_edges); + ufbxi_check_err(&sc->error, result->edge_smoothing.data); + } + if (mesh->edge_visibility.data) { + result->edge_visibility.count = result->num_edges; + result->edge_visibility.data = ufbxi_push(&sc->result, bool, result->num_edges); + ufbxi_check_err(&sc->error, result->edge_visibility.data); + } + + size_t di = 0; + for (size_t i = 0; i < mesh->num_edges; i++) { + ufbx_edge edge = mesh->edges.data[i]; + uint32_t face_ix = topo[edge.a].face; + ufbx_face face = mesh->faces.data[face_ix]; + uint32_t offset = edge.a - face.index_begin; + uint32_t next = (offset + 1) % (uint32_t)face.num_indices; + + uint32_t a = (face.index_begin + offset) * 4; + uint32_t b = (face.index_begin + next) * 4; + + result->edges.data[di + 0].a = a; + result->edges.data[di + 0].b = a + 1; + result->edges.data[di + 1].a = b + 3; + result->edges.data[di + 1].b = b; + + if (mesh->edge_crease.data) { + ufbx_real crease = mesh->edge_crease.data[i]; + if (crease < 0.999f) crease -= (ufbx_real)0.1; + if (crease < 0.0f) crease = 0.0f; + result->edge_crease.data[di + 0] = crease; + result->edge_crease.data[di + 1] = crease; + } + + if (mesh->edge_smoothing.data) { + result->edge_smoothing.data[di + 0] = mesh->edge_smoothing.data[i]; + result->edge_smoothing.data[di + 1] = mesh->edge_smoothing.data[i]; + } + + if (mesh->edge_visibility.data) { + result->edge_visibility.data[di + 0] = mesh->edge_visibility.data[i]; + result->edge_visibility.data[di + 1] = mesh->edge_visibility.data[i]; + } + + di += 2; + } + + for (size_t fi = 0; fi < result->num_faces; fi++) { + result->edges.data[di].a = (uint32_t)(fi * 4 + 1); + result->edges.data[di].b = (uint32_t)(fi * 4 + 2); + + if (result->edge_crease.data) { + result->edge_crease.data[di] = 0.0f; + } + + if (result->edge_smoothing.data) { + result->edge_smoothing.data[di + 0] = true; + } + + if (result->edge_visibility.data) { + result->edge_visibility.data[di + 0] = false; + } + + di++; + } + } + + if (mesh->face_material.data) { + result->face_material.count = result->num_faces; + result->face_material.data = ufbxi_push(&sc->result, uint32_t, result->num_faces); + ufbxi_check_err(&sc->error, result->face_material.data); + } + if (mesh->face_smoothing.data) { + result->face_smoothing.count = result->num_faces; + result->face_smoothing.data = ufbxi_push(&sc->result, bool, result->num_faces); + ufbxi_check_err(&sc->error, result->face_smoothing.data); + } + if (mesh->face_group.data) { + result->face_group.count = result->num_faces; + result->face_group.data = ufbxi_push(&sc->result, uint32_t, result->num_faces); + ufbxi_check_err(&sc->error, result->face_group.data); + } + if (mesh->face_hole.data) { + result->face_hole.count = result->num_faces; + result->face_hole.data = ufbxi_push(&sc->result, bool, result->num_faces); + ufbxi_check_err(&sc->error, result->face_hole.data); + } + + if (result->material_parts.count > 0) { + result->material_parts.data = ufbxi_push_zero(&sc->result, ufbx_mesh_part, result->material_parts.count); + ufbxi_check_err(&sc->error, result->materials.data); + } + + size_t index_offset = 0; + for (size_t i = 0; i < mesh->num_faces; i++) { + ufbx_face face = mesh->faces.data[i]; + + uint32_t mat = 0; + if (mesh->face_material.data) { + mat = mesh->face_material.data[i]; + for (size_t ci = 0; ci < face.num_indices; ci++) { + result->face_material.data[index_offset + ci] = mat; + } + } + if (mesh->face_smoothing.data) { + bool flag = mesh->face_smoothing.data[i]; + for (size_t ci = 0; ci < face.num_indices; ci++) { + result->face_smoothing.data[index_offset + ci] = flag; + } + } + if (mesh->face_group.data) { + uint32_t group = mesh->face_group.data[i]; + for (size_t ci = 0; ci < face.num_indices; ci++) { + result->face_group.data[index_offset + ci] = group; + } + } + if (mesh->face_hole.data) { + bool flag = mesh->face_hole.data[i]; + for (size_t ci = 0; ci < face.num_indices; ci++) { + result->face_hole.data[index_offset + ci] = flag; + } + } + index_offset += face.num_indices; + } + + // Will be filled in by `ufbxi_finalize_mesh()`. + result->vertex_first_index.count = 0; + + ufbxi_check_err(&sc->error, ufbxi_finalize_mesh_material(&sc->result, &sc->error, result)); + ufbxi_check_err(&sc->error, ufbxi_finalize_mesh(&sc->result, &sc->error, result)); + ufbxi_check_err(&sc->error, ufbxi_update_face_groups(&sc->result, &sc->error, result, true)); + + return 1; +} + +ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_imp(ufbxi_subdivide_context *sc, size_t level) +{ + if (sc->opts.boundary == UFBX_SUBDIVISION_BOUNDARY_DEFAULT) { + sc->opts.boundary = sc->src_mesh.subdivision_boundary; + } + + if (sc->opts.uv_boundary == UFBX_SUBDIVISION_BOUNDARY_DEFAULT) { + sc->opts.uv_boundary = sc->src_mesh.subdivision_uv_boundary; + } + + ufbxi_init_ator(&sc->error, &sc->ator_tmp, &sc->opts.temp_allocator, "temp"); + ufbxi_init_ator(&sc->error, &sc->ator_result, &sc->opts.result_allocator, "result"); + + sc->result.unordered = true; + sc->source.unordered = true; + sc->tmp.unordered = true; + + sc->source.ator = &sc->ator_tmp; + sc->tmp.ator = &sc->ator_tmp; + + for (size_t i = 1; i < level; i++) { + sc->result.ator = &sc->ator_tmp; + + ufbxi_check_err(&sc->error, ufbxi_subdivide_mesh_level(sc)); + + sc->src_mesh = sc->dst_mesh; + + ufbxi_buf_free(&sc->source); + ufbxi_buf_free(&sc->tmp); + sc->source = sc->result; + memset(&sc->result, 0, sizeof(sc->result)); + } + + sc->result.ator = &sc->ator_result; + ufbxi_check_err(&sc->error, ufbxi_subdivide_mesh_level(sc)); + ufbxi_buf_free(&sc->tmp); + + ufbx_mesh *mesh = &sc->dst_mesh; + + // Subdivision always results in a mesh that consists only of quads + mesh->max_face_triangles = 2; + mesh->num_empty_faces = 0; + mesh->num_point_faces = 0; + mesh->num_line_faces = 0; + + if (!sc->opts.interpolate_normals) { + memset(&mesh->vertex_normal, 0, sizeof(mesh->vertex_normal)); + memset(&mesh->skinned_normal, 0, sizeof(mesh->skinned_normal)); + } + + if (!sc->opts.interpolate_normals && !sc->opts.ignore_normals) { + + ufbx_topo_edge *topo = ufbxi_push(&sc->tmp, ufbx_topo_edge, mesh->num_indices); + ufbxi_check_err(&sc->error, topo); + ufbx_compute_topology(mesh, topo, mesh->num_indices); + + uint32_t *normal_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); + ufbxi_check_err(&sc->error, normal_indices); + + size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, mesh->num_indices, normal_indices, mesh->num_indices, true); + if (num_normals == mesh->num_vertices) { + mesh->skinned_normal.unique_per_vertex = true; + } + + ufbx_vec3 *normal_data = ufbxi_push(&sc->result, ufbx_vec3, num_normals + 1); + ufbxi_check_err(&sc->error, normal_data); + normal_data[0] = ufbx_zero_vec3; + normal_data++; + + ufbx_compute_normals(mesh, &mesh->skinned_position, normal_indices, mesh->num_indices, normal_data, num_normals); + + mesh->generated_normals = true; + mesh->vertex_normal.exists = true; + mesh->vertex_normal.values.data = normal_data; + mesh->vertex_normal.values.count = num_normals; + mesh->vertex_normal.indices.data = normal_indices; + mesh->vertex_normal.indices.count = mesh->num_indices; + + mesh->skinned_normal = mesh->vertex_normal; + } + + ufbxi_refcount *parent = NULL; + if (sc->src_mesh_ptr->subdivision_evaluated && sc->src_mesh_ptr->from_tessellated_nurbs) { + parent = &(ufbxi_get_imp(ufbxi_mesh_imp, sc->src_mesh_ptr))->refcount; + } else { + parent = &(ufbxi_get_imp(ufbxi_scene_imp, sc->src_mesh_ptr->element.scene))->refcount; + } + + ufbxi_patch_mesh_reals(mesh); + + sc->imp = ufbxi_push(&sc->result, ufbxi_mesh_imp, 1); + ufbxi_check_err(&sc->error, sc->imp); + + sc->dst_mesh.subdivision_result->result_memory_used = sc->ator_result.current_size; + sc->dst_mesh.subdivision_result->temp_memory_used = sc->ator_tmp.current_size; + sc->dst_mesh.subdivision_result->result_allocs = sc->ator_result.num_allocs; + sc->dst_mesh.subdivision_result->temp_allocs = sc->ator_tmp.num_allocs; + + ufbxi_init_ref(&sc->imp->refcount, UFBXI_MESH_IMP_MAGIC, parent); + + sc->imp->magic = UFBXI_MESH_IMP_MAGIC; + sc->imp->mesh = sc->dst_mesh; + sc->imp->refcount.ator = sc->ator_result; + sc->imp->refcount.buf = sc->result; + sc->imp->mesh.subdivision_evaluated = true; + + return 1; +} + +ufbxi_noinline static ufbx_mesh *ufbxi_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *user_opts, ufbx_error *p_error) +{ + ufbxi_subdivide_context sc = { 0 }; + if (user_opts) { + sc.opts = *user_opts; + } + + sc.src_mesh_ptr = (ufbx_mesh*)mesh; + sc.src_mesh = *mesh; + + int ok = ufbxi_subdivide_mesh_imp(&sc, level); + + ufbxi_free(&sc.ator_tmp, ufbxi_subdivide_input, sc.inputs, sc.inputs_cap); + ufbxi_buf_free(&sc.tmp); + ufbxi_buf_free(&sc.source); + + if (ok) { + ufbxi_free_ator(&sc.ator_tmp); + if (p_error) { + ufbxi_clear_error(p_error); + } + + ufbxi_mesh_imp *imp = sc.imp; + return &imp->mesh; + } else { + ufbxi_fix_error_type(&sc.error, "Failed to subdivide", p_error); + ufbxi_buf_free(&sc.result); + ufbxi_free_ator(&sc.ator_tmp); + ufbxi_free_ator(&sc.ator_result); + return NULL; + } +} + +#else + +ufbxi_noinline static ufbx_mesh *ufbxi_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *user_opts, ufbx_error *p_error) +{ + if (p_error) { + memset(p_error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(p_error, "UFBX_ENABLE_SUBDIVISION"); + ufbxi_report_err_msg(p_error, "UFBXI_FEATURE_SUBDIVISION", "Feature disabled"); + } + return NULL; +} + +#endif + +// -- Utility + +#if UFBXI_FEATURE_INDEX_GENERATION + +static int ufbxi_map_cmp_vertex(void *user, const void *va, const void *vb) +{ + size_t size = *(size_t*)user; +#if defined(UFBX_REGRESSION) + ufbx_assert(size % 8 == 0); +#endif + for (size_t i = 0; i < size; i += 8) { + uint64_t a = *(const uint64_t*)((const char*)va + i); + uint64_t b = *(const uint64_t*)((const char*)vb + i); + if (a != b) return a < b ? -1 : +1; + } + return 0; +} + +typedef struct { + char *begin, *ptr; + size_t vertex_size; + size_t packed_offset; +} ufbxi_vertex_stream; + +static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *user_streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) +{ + bool fail = false; + + ufbxi_allocator ator = { 0 }; + ufbxi_init_ator(error, &ator, allocator, "allocator"); + + ufbxi_vertex_stream local_streams[16]; // ufbxi_uninit + uint64_t local_packed_vertex[64]; // ufbxi_uninit + + ufbxi_vertex_stream *streams = NULL; + if (num_streams > ufbxi_arraycount(local_streams)) { + streams = ufbxi_alloc(&ator, ufbxi_vertex_stream, num_streams); + if (!streams) fail = true; + } else { + streams = local_streams; + } + + size_t packed_size = 0; + if (!fail) { + for (size_t i = 0; i < num_streams; i++) { + if (user_streams[i].vertex_count < num_indices) { + ufbxi_fmt_err_info(error, "%zu", i); + ufbxi_report_err_msg(error, "user_streams[i].vertex_count < num_indices", "Truncated vertex stream"); + fail = true; + break; + } + + size_t vertex_size = user_streams[i].vertex_size; + size_t align = ufbxi_size_align_mask(vertex_size); + packed_size = ufbxi_align_to_mask(packed_size, align); + streams[i].ptr = streams[i].begin = (char*)user_streams[i].data; + streams[i].vertex_size = vertex_size; + streams[i].packed_offset = packed_size; + packed_size += vertex_size; + } + packed_size = ufbxi_align_to_mask(packed_size, 7); + } + + if (!fail && packed_size == 0) { + ufbxi_report_err_msg(error, "packed_size != 0", "Zero vertex size"); + fail = true; + } + + char *packed_vertex = NULL; + if (!fail) { + if (packed_size > sizeof(local_packed_vertex)) { + ufbx_assert(packed_size % 8 == 0); + packed_vertex = (char*)ufbxi_alloc(&ator, uint64_t, packed_size / 8); + if (!packed_vertex) fail = true; + } else { + packed_vertex = (char*)local_packed_vertex; + } + } + + ufbxi_map map = { 0 }; + ufbxi_map_init(&map, &ator, &ufbxi_map_cmp_vertex, &packed_size); + + if (num_indices > 0 && !ufbxi_map_grow_size(&map, packed_size, num_indices)) { + fail = true; + } + + if (!fail) { + ufbx_assert(packed_vertex != NULL); + memset(packed_vertex, 0, packed_size); + + for (size_t i = 0; i < num_indices; i++) { + for (size_t si = 0; si < num_streams; si++) { + size_t size = streams[si].vertex_size, offset = streams[si].packed_offset; + char *ptr = streams[si].ptr; + memcpy(packed_vertex + offset, ptr, size); + streams[si].ptr = ptr + size; + } + + uint32_t hash = ufbxi_hash_string(packed_vertex, packed_size); + void *entry = ufbxi_map_find_size(&map, packed_size, hash, packed_vertex); + if (!entry) { + entry = ufbxi_map_insert_size(&map, packed_size, hash, packed_vertex); + if (!entry) { + fail = true; + break; + } + memcpy(entry, packed_vertex, packed_size); + } + uint32_t index = (uint32_t)(ufbxi_to_size((char*)entry - (char*)map.items) / packed_size); + indices[i] = index; + } + } + + size_t result_vertices = 0; + if (!fail) { + result_vertices = map.size; + + for (size_t si = 0; si < num_streams; si++) { + size_t vertex_size = streams[si].vertex_size; + char *dst = streams[si].begin; + char *src = ufbxi_add_ptr((char*)map.items, streams[si].packed_offset); + for (size_t i = 0; i < result_vertices; i++) { + memcpy(dst, src, vertex_size); + dst += vertex_size; + src += packed_size; + } + } + + ufbxi_clear_error(error); + } else { + ufbxi_fix_error_type(error, "Failed to generate indices", NULL); + } + + if (streams && streams != local_streams) { + ufbxi_free(&ator, ufbxi_vertex_stream, streams, num_streams); + } + if (packed_vertex && packed_vertex != (char*)local_packed_vertex) { + ufbxi_free(&ator, uint64_t, packed_vertex, packed_size / 8); + } + + ufbxi_map_free(&map); + ufbxi_free_ator(&ator); + + return result_vertices; +} + +#else + +static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *user_streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) +{ + if (error) { + memset(error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(error, "UFBX_ENABLE_INDEX_GENERATION"); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_INDEX_GENERATION", "Feature disabled"); + } + return 0; +} + +#endif + +static ufbxi_noinline void ufbxi_free_scene_imp(ufbxi_scene_imp *imp) +{ + ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); + ufbxi_buf_free(&imp->string_buf); +} + +static ufbxi_noinline void ufbxi_init_ref(ufbxi_refcount *refcount, uint32_t magic, ufbxi_refcount *parent) +{ + if (parent) { + ufbxi_retain_ref(parent); + } + + ufbxi_atomic_counter_init(&refcount->refcount); + refcount->self_magic = UFBXI_REFCOUNT_IMP_MAGIC; + refcount->type_magic = magic; + refcount->parent = parent; +} + +static ufbxi_noinline void ufbxi_retain_ref(ufbxi_refcount *refcount) +{ + ufbx_assert(refcount->self_magic == UFBXI_REFCOUNT_IMP_MAGIC); + size_t count = ufbxi_atomic_counter_inc(&refcount->refcount); + ufbxi_ignore(count); + ufbx_assert(count < SIZE_MAX / 2); +} + +static ufbxi_noinline void ufbxi_release_ref(ufbxi_refcount *refcount) +{ + while (refcount) { + ufbx_assert(refcount->self_magic == UFBXI_REFCOUNT_IMP_MAGIC); + if (ufbxi_atomic_counter_dec(&refcount->refcount) > 0) return; + ufbxi_atomic_counter_free(&refcount->refcount); + + ufbxi_refcount *parent = refcount->parent; + uint32_t type_magic = refcount->type_magic; + + refcount->self_magic = 0; + refcount->type_magic = 0; + + // Type-specific cleanup + switch (type_magic) { + case UFBXI_SCENE_IMP_MAGIC: ufbxi_free_scene_imp((ufbxi_scene_imp*)refcount); break; + case UFBXI_CACHE_IMP_MAGIC: ufbxi_free_geometry_cache_imp((ufbxi_geometry_cache_imp*)refcount); break; + default: break; + } + + // We need to free `data_buf` last and be careful to copy it to + // the stack since the `ufbxi_refcount` that contains it is allocated + // from the same result buffer! + ufbxi_allocator ator = refcount->ator; + ufbxi_buf buf = refcount->buf; + buf.ator = &ator; + ufbxi_buf_free(&buf); + ufbxi_free_ator(&ator); + + refcount = parent; + } +} + +static ufbxi_noinline void *ufbxi_uninitialized_options(ufbx_error *p_error) +{ + if (p_error) { + memset(p_error, 0, sizeof(ufbx_error)); + p_error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS; + p_error->description.data = "Uninitialized options"; + p_error->description.length = strlen("Uninitialized options"); + } + return NULL; +} + +#define ufbxi_check_opts_ptr(m_type, m_opts, m_error) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + ufbx_assert(opts_cleared_to_zero == 0); \ + if (opts_cleared_to_zero != 0) return (m_type*)ufbxi_uninitialized_options(m_error); \ + } } while (0) + +#define ufbxi_check_opts_return(m_value, m_opts, m_error) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + ufbx_assert(opts_cleared_to_zero == 0); \ + if (opts_cleared_to_zero != 0) { \ + ufbxi_uninitialized_options(m_error); \ + return m_value; \ + } \ + } } while (0) + +#define ufbxi_check_opts_return_no_error(m_value, m_opts) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + if (opts_cleared_to_zero != 0) return m_value; \ + } } while (0) + +// -- API + +#ifdef __cplusplus +extern "C" { +#endif + +ufbx_abi_data_def const ufbx_string ufbx_empty_string = { ufbxi_empty_char, 0 }; +ufbx_abi_data_def const ufbx_blob ufbx_empty_blob = { NULL, 0 }; +ufbx_abi_data_def const ufbx_matrix ufbx_identity_matrix = { 1,0,0, 0,1,0, 0,0,1, 0,0,0 }; +ufbx_abi_data_def const ufbx_transform ufbx_identity_transform = { {0,0,0}, {0,0,0,1}, {1,1,1} }; +ufbx_abi_data_def const ufbx_vec2 ufbx_zero_vec2 = { 0,0 }; +ufbx_abi_data_def const ufbx_vec3 ufbx_zero_vec3 = { 0,0,0 }; +ufbx_abi_data_def const ufbx_vec4 ufbx_zero_vec4 = { 0,0,0,0 }; +ufbx_abi_data_def const ufbx_quat ufbx_identity_quat = { 0,0,0,1 }; + +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_y_up = { + UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_POSITIVE_Z, +}; +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_z_up = { + UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_NEGATIVE_Y, +}; +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_y_up = { + UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_NEGATIVE_Z, +}; +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_z_up = { + UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_POSITIVE_Y, +}; + +ufbx_abi_data_def const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = { + sizeof(ufbx_unknown), + sizeof(ufbx_node), + sizeof(ufbx_mesh), + sizeof(ufbx_light), + sizeof(ufbx_camera), + sizeof(ufbx_bone), + sizeof(ufbx_empty), + sizeof(ufbx_line_curve), + sizeof(ufbx_nurbs_curve), + sizeof(ufbx_nurbs_surface), + sizeof(ufbx_nurbs_trim_surface), + sizeof(ufbx_nurbs_trim_boundary), + sizeof(ufbx_procedural_geometry), + sizeof(ufbx_stereo_camera), + sizeof(ufbx_camera_switcher), + sizeof(ufbx_marker), + sizeof(ufbx_lod_group), + sizeof(ufbx_skin_deformer), + sizeof(ufbx_skin_cluster), + sizeof(ufbx_blend_deformer), + sizeof(ufbx_blend_channel), + sizeof(ufbx_blend_shape), + sizeof(ufbx_cache_deformer), + sizeof(ufbx_cache_file), + sizeof(ufbx_material), + sizeof(ufbx_texture), + sizeof(ufbx_video), + sizeof(ufbx_shader), + sizeof(ufbx_shader_binding), + sizeof(ufbx_anim_stack), + sizeof(ufbx_anim_layer), + sizeof(ufbx_anim_value), + sizeof(ufbx_anim_curve), + sizeof(ufbx_display_layer), + sizeof(ufbx_selection_set), + sizeof(ufbx_selection_node), + sizeof(ufbx_character), + sizeof(ufbx_constraint), + sizeof(ufbx_audio_layer), + sizeof(ufbx_audio_clip), + sizeof(ufbx_pose), + sizeof(ufbx_metadata_object), +}; + +ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info) +{ + (void)user; + return ufbx_open_file_ctx(stream, info->context, path, path_len, NULL, NULL); +} + +ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) +{ + return ufbx_open_file_ctx(stream, (ufbx_open_file_context)NULL, path, path_len, opts, error); +} + +ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) +{ + bool ok = false; + ufbxi_file_context fc; // ufbxi_uninit + ufbxi_begin_file_context(&fc, ctx, NULL); + if (path_len == SIZE_MAX) path_len = strlen(path); +#if !defined(UFBX_NO_STDIO) + ok = ufbxi_stdio_open(&fc, stream, path, path_len, opts ? opts->filename_null_terminated : false); +#else + (void)stream; + (void)path; + (void)path_len; + (void)opts; + ufbxi_fmt_err_info(&fc.error, "UFBX_NO_STDIO"); + ufbxi_report_err_msg(&fc.error, "UFBX_NO_STDIO", "Feature disabled"); +#endif + ufbxi_end_file_context(&fc, error, ok); + return ok; +} + +ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) +{ + return ufbx_open_memory_ctx(stream, (ufbx_open_file_context)NULL, data, data_size, opts, error); +} + +ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) +{ + ufbx_open_memory_opts local_opts; // ufbxi_uninit + if (!opts) { + memset(&local_opts, 0, sizeof(local_opts)); + opts = &local_opts; + } + ufbx_assert(opts->_begin_zero == 0 && opts->_end_zero == 0); + + ufbxi_file_context fc; // ufbxi_uninit + ufbxi_begin_file_context(&fc, ctx, &opts->allocator); + + size_t copy_size = opts->no_copy ? 0 : data_size; + + // Align the allocation size to 8 bytes to make sure the header is aligned. + size_t self_size = ufbxi_align_to_mask(sizeof(ufbxi_memory_stream) + copy_size, 7); + + void *memory = ufbxi_alloc(&fc.ator, char, self_size); + if (!memory) { + ufbxi_end_file_context(&fc, error, false); + return false; + } + + ufbxi_memory_stream *mem = (ufbxi_memory_stream*)memory; + memset(mem, 0, sizeof(ufbxi_memory_stream)); + + mem->size = data_size; + mem->self_size = self_size; + mem->close_cb = opts->close_cb; + + if (opts->no_copy) { + mem->data = data; + } else { + memcpy(mem->data_copy, data, data_size); + mem->data = mem->data_copy; + } + + // Transplant the allocator in the result blob + if (fc.parent_ator) { + mem->parent_ator = fc.parent_ator; + } else { + fc.parent_ator = &mem->local_ator; + } + + stream->read_fn = ufbxi_memory_read; + stream->skip_fn = ufbxi_memory_skip; + stream->size_fn = ufbxi_memory_size; + stream->close_fn = ufbxi_memory_close; + stream->user = mem; + + ufbxi_end_file_context(&fc, error, true); + + return true; +} + +ufbx_abi bool ufbx_is_thread_safe(void) +{ + return UFBXI_THREAD_SAFE != 0; +} + +ufbx_abi ufbx_scene *ufbx_load_memory(const void *data, size_t size, const ufbx_load_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_scene, opts, error); + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + uc.data_begin = uc.data = (const char *)data; + uc.data_size = size; + uc.progress_bytes_total = size; + return ufbxi_load(&uc, opts, error); +} + +ufbx_abi ufbx_scene *ufbx_load_file(const char *filename, const ufbx_load_opts *opts, ufbx_error *error) +{ + return ufbx_load_file_len(filename, SIZE_MAX, opts, error); +} + +ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_len, const ufbx_load_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_scene, opts, error); + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + uc.deferred_load = true; + uc.load_filename = filename; + uc.load_filename_len = filename_len; + return ufbxi_load(&uc, opts, error); +} + +ufbx_abi ufbx_scene *ufbx_load_stdio(void *file_void, const ufbx_load_opts *opts, ufbx_error *error) +{ + return ufbx_load_stdio_prefix(file_void, NULL, 0, opts, error); +} + +ufbx_abi ufbx_scene *ufbx_load_stdio_prefix(void *file_void, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) +{ +#if !defined(UFBX_NO_STDIO) + if (!file_void) return NULL; + ufbx_stream stream = { 0 }; + ufbxi_stdio_init(&stream, file_void, false); + return ufbx_load_stream_prefix(&stream, prefix, prefix_size, opts, error); +#else + (void)file_void; + (void)prefix; + (void)prefix_size; + (void)opts; + + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + ufbxi_fmt_err_info(&uc.error, "UFBX_NO_STDIO"); + ufbxi_report_err_msg(&uc.error, "UFBX_NO_STDIO", "Feature disabled"); + uc.deferred_failure = true; + return ufbxi_load(&uc, NULL, error); +#endif +} + +ufbx_abi ufbx_scene *ufbx_load_stream(const ufbx_stream *stream, const ufbx_load_opts *opts, ufbx_error *error) +{ + return ufbx_load_stream_prefix(stream, NULL, 0, opts, error); +} + +ufbx_abi ufbx_scene *ufbx_load_stream_prefix(const ufbx_stream *stream, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_scene, opts, error); + ufbxi_context uc; // ufbxi_uninit + memset(&uc, 0, sizeof(ufbxi_context)); + uc.data_begin = uc.data = (const char *)prefix; + uc.data_size = prefix_size; + uc.read_fn = stream->read_fn; + uc.skip_fn = stream->skip_fn; + uc.size_fn = stream->size_fn; + uc.close_fn = stream->close_fn; + uc.read_user = stream->user; + + ufbx_scene *scene = ufbxi_load(&uc, opts, error); + return scene; +} + +ufbx_abi void ufbx_free_scene(ufbx_scene *scene) +{ + if (!scene) return; + + ufbxi_scene_imp *imp = ufbxi_get_imp(ufbxi_scene_imp, scene); + ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); + if (imp->magic != UFBXI_SCENE_IMP_MAGIC) return; + ufbxi_release_ref(&imp->refcount); +} + +ufbx_abi void ufbx_retain_scene(ufbx_scene *scene) +{ + if (!scene) return; + + ufbxi_scene_imp *imp = ufbxi_get_imp(ufbxi_scene_imp, scene); + ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); + if (imp->magic != UFBXI_SCENE_IMP_MAGIC) return; + ufbxi_retain_ref(&imp->refcount); +} + +ufbx_abi ufbxi_noinline size_t ufbx_format_error(char *dst, size_t dst_size, const ufbx_error *error) +{ + if (!dst || !dst_size) return 0; + if (!error) { + *dst = '\0'; + return 0; + } + + size_t offset = 0; + + { + int num; + if (error->info_length > 0 && error->info_length < UFBX_ERROR_INFO_LENGTH) { + num = ufbxi_snprintf(dst + offset, dst_size - offset, "ufbx v%u.%u.%u error: %s (%.*s)\n", + UFBX_SOURCE_VERSION/1000000, UFBX_SOURCE_VERSION/1000%1000, UFBX_SOURCE_VERSION%1000, + error->description.data ? error->description.data : "Unknown error", + (int)error->info_length, error->info); + } else { + num = ufbxi_snprintf(dst + offset, dst_size - offset, "ufbx v%u.%u.%u error: %s\n", + UFBX_SOURCE_VERSION/1000000, UFBX_SOURCE_VERSION/1000%1000, UFBX_SOURCE_VERSION%1000, + error->description.data ? error->description.data : "Unknown error"); + } + + if (num > 0) offset = ufbxi_min_sz(offset + (size_t)num, dst_size - 1); + } + + size_t stack_size = ufbxi_min_sz(error->stack_size, UFBX_ERROR_STACK_MAX_DEPTH); + int line_width = 6; + for (size_t i = 0; i < stack_size; i++) { + const ufbx_error_frame *frame = &error->stack[i]; + int num = ufbxi_snprintf(dst + offset, dst_size - offset, "%*u:%s: %s\n", line_width, frame->source_line, frame->function.data, frame->description.data); + if (num > 0) offset = ufbxi_min_sz(offset + (size_t)num, dst_size - 1); + } + + return offset; +} + +ufbx_abi ufbx_prop *ufbx_find_prop_len(const ufbx_props *props, const char *name, size_t name_len) +{ + uint32_t key = ufbxi_get_name_key(name, name_len); + ufbx_string name_str = ufbxi_safe_string(name, name_len); + + while (props) { + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_prop, 4, &index, props->props.data, 0, props->props.count, + ( ufbxi_cmp_prop_less_ref(a, name_str, key) ), ( a->_internal_key == key && ufbxi_str_equal(a->name, name_str) )); + if (index != SIZE_MAX) return &props->props.data[index]; + + props = props->defaults; + } + + return NULL; +} + +ufbx_abi ufbx_real ufbx_find_real_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_real def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_real; + } else { + return def; + } +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_find_vec3_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_vec3 def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_vec3; + } else { + return def; + } +} + +ufbx_abi ufbxi_noinline int64_t ufbx_find_int_len(const ufbx_props *props, const char *name, size_t name_len, int64_t def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_int; + } else { + return def; + } +} + +ufbx_abi bool ufbx_find_bool_len(const ufbx_props *props, const char *name, size_t name_len, bool def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_int != 0; + } else { + return def; + } +} + +ufbx_abi ufbxi_noinline ufbx_string ufbx_find_string_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_string def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_str; + } else { + return def; + } +} + +ufbx_abi ufbx_blob ufbx_find_blob_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_blob def) +{ + ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); + if (prop) { + return prop->value_blob; + } else { + return def; + } +} + +ufbx_abi ufbx_prop *ufbx_find_prop_concat(const ufbx_props *props, const ufbx_string *parts, size_t num_parts) +{ + uint32_t key = ufbxi_get_concat_key(parts, num_parts); + + while (props) { + size_t index = SIZE_MAX; + + ufbxi_macro_lower_bound_eq(ufbx_prop, 2, &index, props->props.data, 0, props->props.count, + ( ufbxi_cmp_prop_less_concat(a, parts, num_parts, key) ), + ( a->_internal_key == key && ufbxi_concat_str_cmp(&a->name, parts, num_parts) == 0 )); + if (index != SIZE_MAX) return &props->props.data[index]; + + props = props->defaults; + } + + return NULL; +} + +ufbx_abi ufbx_element *ufbx_find_element_len(const ufbx_scene *scene, ufbx_element_type type, const char *name, size_t name_len) +{ + if (!scene) return NULL; + ufbx_string name_str = ufbxi_safe_string(name, name_len); + uint32_t key = ufbxi_get_name_key(name, name_len); + + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_name_element, 16, &index, scene->elements_by_name.data, 0, scene->elements_by_name.count, + ( ufbxi_cmp_name_element_less_ref(a, name_str, type, key) ), ( ufbxi_str_equal(a->name, name_str) && a->type == type )); + + return index < SIZE_MAX ? scene->elements_by_name.data[index].element : NULL; +} + +ufbx_abi ufbx_element *ufbx_get_prop_element(const ufbx_element *element, const ufbx_prop *prop, ufbx_element_type type) +{ + ufbx_assert(element && prop); + if (!element || !prop) return NULL; + return ufbxi_fetch_dst_element((ufbx_element*)element, false, prop->name.data, type); +} + +ufbx_abi ufbx_element *ufbx_find_prop_element_len(const ufbx_element *element, const char *name, size_t name_len, ufbx_element_type type) +{ + const ufbx_prop *prop = ufbx_find_prop_len(&element->props, name, name_len); + if (prop) { + return ufbx_get_prop_element(element, prop, type); + } else { + return NULL; + } +} + +ufbx_abi ufbx_node *ufbx_find_node_len(const ufbx_scene *scene, const char *name, size_t name_len) +{ + return (ufbx_node*)ufbx_find_element_len(scene, UFBX_ELEMENT_NODE, name, name_len); +} + +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack_len(const ufbx_scene *scene, const char *name, size_t name_len) +{ + return (ufbx_anim_stack*)ufbx_find_element_len(scene, UFBX_ELEMENT_ANIM_STACK, name, name_len); +} + +ufbx_abi ufbx_material *ufbx_find_material_len(const ufbx_scene *scene, const char *name, size_t name_len) +{ + return (ufbx_material*)ufbx_find_element_len(scene, UFBX_ELEMENT_MATERIAL, name, name_len); +} + +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop_len(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop, size_t prop_len) +{ + ufbx_assert(layer); + ufbx_assert(element); + if (!layer || !element) return NULL; + + ufbx_string prop_str = ufbxi_safe_string(prop, prop_len); + + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &index, layer->anim_props.data, 0, layer->anim_props.count, + ( a->element != element ? a->element < element : ufbxi_str_less(a->prop_name, prop_str) ), + ( a->element == element && ufbxi_str_equal(a->prop_name, prop_str) )); + + if (index == SIZE_MAX) return NULL; + return &layer->anim_props.data[index]; +} + +ufbx_abi ufbxi_noinline ufbx_anim_prop_list ufbx_find_anim_props(const ufbx_anim_layer *layer, const ufbx_element *element) +{ + ufbx_anim_prop_list result = { 0 }; + ufbx_assert(layer); + ufbx_assert(element); + if (!layer || !element) return result; + + size_t begin = layer->anim_props.count, end = begin; + ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &begin, layer->anim_props.data, 0, layer->anim_props.count, + ( a->element < element ), ( a->element == element )); + + ufbxi_macro_upper_bound_eq(ufbx_anim_prop, 16, &end, layer->anim_props.data, begin, layer->anim_props.count, + ( a->element == element )); + + if (begin != end) { + result.data = layer->anim_props.data + begin; + result.count = end - begin; + } + + return result; +} + +ufbx_abi ufbxi_noinline ufbx_matrix ufbx_get_compatible_matrix_for_normals(const ufbx_node *node) +{ + if (!node) return ufbx_identity_matrix; + + ufbx_transform geom_rot = ufbx_identity_transform; + geom_rot.rotation = node->geometry_transform.rotation; + ufbx_matrix geom_rot_mat = ufbx_transform_to_matrix(&geom_rot); + + ufbx_matrix norm_mat = ufbx_matrix_mul(&node->node_to_world, &geom_rot_mat); + norm_mat = ufbx_matrix_for_normals(&norm_mat); + return norm_mat; +} + +ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value) +{ + return ufbx_evaluate_curve_flags(curve, time, default_value, 0); +} + +ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags) +{ + if (!curve) return default_value; + if (curve->keyframes.count <= 1) { + if (curve->keyframes.count == 1) { + return curve->keyframes.data[0].value; + } else { + return default_value; + } + } + + if ((flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) == 0) { + if (time < curve->min_time || time > curve->max_time) { + return ufbxi_extrapolate_curve(curve, time, flags); + } + } + + size_t begin = 0; + size_t end = curve->keyframes.count; + const ufbx_keyframe *keys = curve->keyframes.data; + while (end - begin >= 8) { + size_t mid = (begin + end) >> 1; + if (keys[mid].time <= time) { + begin = mid + 1; + } else { + end = mid; + } + } + + end = curve->keyframes.count; + for (; begin < end; begin++) { + const ufbx_keyframe *next = &keys[begin]; + if (next->time <= time) continue; + + // First keyframe + if (begin == 0) return next->value; + + const ufbx_keyframe *prev = next - 1; + + // Exact keyframe + if (prev->time == time) return prev->value; + + double rcp_delta = 1.0 / (next->time - prev->time); + double t = (time - prev->time) * rcp_delta; + + switch (prev->interpolation) { + + case UFBX_INTERPOLATION_CONSTANT_PREV: + return prev->value; + + case UFBX_INTERPOLATION_CONSTANT_NEXT: + return next->value; + + case UFBX_INTERPOLATION_LINEAR: + return (ufbx_real)(prev->value*(1.0 - t) + next->value*t); + + case UFBX_INTERPOLATION_CUBIC: + { + double x1 = prev->right.dx * rcp_delta; + double x2 = 1.0 - next->left.dx * rcp_delta; + t = ufbxi_find_cubic_bezier_t(x1, x2, t); + + double t2 = t*t, t3 = t2*t; + double u = 1.0 - t, u2 = u*u, u3 = u2*u; + + double y0 = prev->value; + double y3 = next->value; + double y1 = y0 + prev->right.dy; + double y2 = y3 - next->left.dy; + + return (ufbx_real)(u3*y0 + 3.0 * (u2*t*y1 + u*t2*y2) + t3*y3); + } + + default: + ufbxi_unreachable("Bad interpolation mode"); + return 0.0f; + + } + } + + // Last keyframe + return curve->keyframes.data[curve->keyframes.count - 1].value; +} + +ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time) +{ + return ufbx_evaluate_anim_value_real_flags(anim_value, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time) +{ + return ufbx_evaluate_anim_value_vec3_flags(anim_value, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) +{ + if (!anim_value) { + return 0.0f; + } + + ufbx_real res = anim_value->default_value.x; + if (anim_value->curves[0]) res = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res, flags); + return res; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) +{ + if (!anim_value) { + ufbx_vec3 zero = { 0.0f }; + return zero; + } + + ufbx_vec3 res = anim_value->default_value; + if (anim_value->curves[0]) res.x = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res.x, flags); + if (anim_value->curves[1]) res.y = ufbx_evaluate_curve_flags(anim_value->curves[1], time, res.y, flags); + if (anim_value->curves[2]) res.z = ufbx_evaluate_curve_flags(anim_value->curves[2], time, res.z, flags); + return res; +} + +ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time) +{ + return ufbx_evaluate_prop_len_flags(anim, element, name, name_len, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags) +{ + ufbx_prop result; + + ufbx_prop *prop = ufbx_find_prop_len(&element->props, name, name_len); + if (prop) { + result = *prop; + } else { + memset(&result, 0, sizeof(result)); + result.name.data = name; + result.name.length = name_len; + result._internal_key = ufbxi_get_name_key(name, name_len); + result.flags = UFBX_PROP_FLAG_NOT_FOUND; + result.value_str.data = ufbxi_empty_char; + result.value_str.length = 0; + result.value_blob.data = NULL; + result.value_blob.size = 0; + } + + if (anim->prop_overrides.count > 0) { + ufbxi_find_prop_override(&anim->prop_overrides, element->element_id, &result); + return result; + } + + if ((result.flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_CONNECTED)) == 0) return result; + + if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { + ufbxi_evaluate_connected_prop(&result, anim, element, prop->name.data, time, flags); + } + + ufbxi_evaluate_props(anim, element, time, &result, 1, flags); + + return result; +} + +ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size) +{ + return ufbx_evaluate_props_flags(anim, element, time, buffer, buffer_size, 0); +} + +ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags) +{ + ufbx_props ret = { NULL }; + if (!element) return ret; + + size_t num_anim = 0; + ufbxi_prop_iter iter; // ufbxi_uninit + ufbxi_init_prop_iter(&iter, anim, element); + const ufbx_prop *prop = NULL; + while ((prop = ufbxi_next_prop(&iter)) != NULL) { + if (!(prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN|UFBX_PROP_FLAG_CONNECTED))) continue; + if (num_anim >= buffer_size) break; + + ufbx_prop *dst = &buffer[num_anim++]; + *dst = *prop; + + if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { + ufbxi_evaluate_connected_prop(dst, anim, element, prop->name.data, time, flags); + } + } + + ufbxi_evaluate_props(anim, element, time, buffer, num_anim, flags); + + ret.props.data = buffer; + ret.props.count = ret.num_animated = num_anim; + ret.defaults = (ufbx_props*)&element->props; + return ret; +} + +ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time) +{ + return ufbx_evaluate_transform_flags(anim, node, time, 0); +} + +static const char *const ufbxi_transform_props_all[] = { + ufbxi_Lcl_Rotation, + ufbxi_Lcl_Scaling, + ufbxi_Lcl_Translation, + ufbxi_PostRotation, + ufbxi_PreRotation, + ufbxi_RotationOffset, + ufbxi_RotationOrder, + ufbxi_RotationPivot, + ufbxi_ScalingOffset, + ufbxi_ScalingPivot, +}; + +static const char *const ufbxi_transform_props_rotation[] = { + ufbxi_Lcl_Rotation, + ufbxi_PostRotation, + ufbxi_PreRotation, + ufbxi_RotationOrder, +}; + +static const char *const ufbxi_transform_props_scale[] = { + ufbxi_Lcl_Scaling, +}; + +static const char *const ufbxi_transform_props_rotation_scale[] = { + ufbxi_Lcl_Rotation, + ufbxi_Lcl_Scaling, + ufbxi_PostRotation, + ufbxi_PreRotation, + ufbxi_RotationOrder, +}; + +ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags) +{ + ufbx_assert(anim); + ufbx_assert(node); + if (!node) return ufbx_identity_transform; + if (!anim) return node->local_transform; + if (node->is_root) return node->local_transform; + + if ((flags & UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES) == 0) { + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; + } + + const char *const *prop_names = ufbxi_transform_props_all; + size_t num_prop_names = ufbxi_arraycount(ufbxi_transform_props_all); + uint32_t components = flags & (UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION); + if (components == (UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE)) { + prop_names = ufbxi_transform_props_rotation_scale; + num_prop_names = ufbxi_arraycount(ufbxi_transform_props_rotation_scale); + } else if (components == UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) { + prop_names = ufbxi_transform_props_rotation; + num_prop_names = ufbxi_arraycount(ufbxi_transform_props_rotation); + } else if (components == UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) { + prop_names = ufbxi_transform_props_scale; + num_prop_names = ufbxi_arraycount(ufbxi_transform_props_scale); + } else if (components == 0) { + return ufbx_identity_transform; + } + + const ufbx_vec3 *translation_scale = NULL; + ufbx_prop helper_scale; // ufbxi_uninit + ufbx_vec3 scale_factor = ufbxi_one_vec3; + bool use_scale_factor = false; + + if (node->parent && (flags & (UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION)) != 0) { + ufbx_node *parent = node->parent; + + if ((flags & UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE) == 0 && parent->inherit_scale_node) { + ufbx_node *p = parent->inherit_scale_node; + + if (node->is_scale_helper) { + use_scale_factor = true; + } + + while (p && p->scale_helper) { + ufbx_prop scale = ufbx_evaluate_prop(anim, &p->scale_helper->element, ufbxi_Lcl_Scaling, time); + scale_factor.x *= scale.value_vec3.x; + scale_factor.y *= scale.value_vec3.y; + scale_factor.z *= scale.value_vec3.z; + p = p->inherit_scale_node; + } + } + + if (parent->scale_helper && (flags & UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER) == 0) { + helper_scale = ufbx_evaluate_prop(anim, &parent->scale_helper->element, ufbxi_Lcl_Scaling, time); + if (helper_scale.flags & UFBX_PROP_FLAG_NOT_FOUND) { + helper_scale.value_vec3.x = 1.0f; + helper_scale.value_vec3.y = 1.0f; + helper_scale.value_vec3.z = 1.0f; + } + helper_scale.value_vec3.x *= scale_factor.x; + helper_scale.value_vec3.y *= scale_factor.y; + helper_scale.value_vec3.z *= scale_factor.z; + translation_scale = &helper_scale.value_vec3; + } + } + + uint32_t eval_flags = 0; + if (flags & UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION) { + eval_flags |= UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION; + } + + ufbx_prop buf[ufbxi_arraycount(ufbxi_transform_props_all)]; // ufbxi_uninit + ufbx_props props = ufbxi_evaluate_selected_props(anim, &node->element, time, buf, prop_names, num_prop_names, eval_flags); + ufbx_rotation_order order = (ufbx_rotation_order)ufbxi_find_enum(&props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); + + ufbx_transform transform; // ufbxi_uninit + if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) != 0) { + transform = ufbxi_get_transform(&props, order, node, translation_scale); + } else { + transform.translation = ufbx_zero_vec3; + if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) != 0) { + transform.rotation = ufbxi_get_rotation(&props, order, node); + } else { + transform.rotation = ufbx_identity_quat; + } + if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) != 0) { + transform.scale = ufbxi_get_scale(&props, node); + } else { + transform.scale = ufbxi_one_vec3; + } + } + + if (use_scale_factor) { + transform.scale.x *= scale_factor.x; + transform.scale.y *= scale_factor.y; + transform.scale.z *= scale_factor.z; + } + return transform; +} + +ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time) +{ + return ufbx_evaluate_blend_weight_flags(anim, channel, time, 0); +} + +ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags) +{ + const char *prop_names[] = { + ufbxi_DeformPercent, + }; + + ufbx_prop buf[ufbxi_arraycount(prop_names)]; // ufbxi_uninit + ufbx_props props = ufbxi_evaluate_selected_props(anim, &channel->element, time, buf, prop_names, ufbxi_arraycount(prop_names), flags); + return ufbxi_find_real(&props, ufbxi_DeformPercent, channel->weight * (ufbx_real)100.0) * (ufbx_real)0.01; +} + +ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_scene, opts, error); +#if UFBXI_FEATURE_SCENE_EVALUATION + ufbxi_eval_context ec = { 0 }; + return ufbxi_evaluate_scene(&ec, (ufbx_scene*)scene, anim, time, opts, error); +#else + if (error) { + memset(error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(error, "UFBX_ENABLE_SCENE_EVALUATION"); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_SCENE_EVALUATION", "Feature disabled"); + } + return NULL; +#endif +} + +ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_anim, opts, error); + ufbx_assert(scene); + + ufbxi_create_anim_context ac = { UFBX_ERROR_NONE }; + if (opts) { + ac.opts = *opts; + } + + ac.scene = scene; + + int ok = ufbxi_create_anim_imp(&ac); + + if (ok) { + ufbxi_clear_error(error); + ufbxi_anim_imp *imp = ac.imp; + return &imp->anim; + } else { + ufbxi_fix_error_type(&ac.error, "Failed to create anim", error); + ufbxi_buf_free(&ac.result); + ufbxi_free_ator(&ac.ator_result); + return NULL; + } +} + +ufbx_abi void ufbx_free_anim(ufbx_anim *anim) +{ + if (!anim) return; + if (!anim->custom) return; + + ufbxi_anim_imp *imp = ufbxi_get_imp(ufbxi_anim_imp, anim); + ufbx_assert(imp->magic == UFBXI_ANIM_IMP_MAGIC); + if (imp->magic != UFBXI_ANIM_IMP_MAGIC) return; + ufbxi_release_ref(&imp->refcount); +} + +ufbx_abi void ufbx_retain_anim(ufbx_anim *anim) +{ + if (!anim) return; + if (!anim->custom) return; + + ufbxi_anim_imp *imp = ufbxi_get_imp(ufbxi_anim_imp, anim); + ufbx_assert(imp->magic == UFBXI_ANIM_IMP_MAGIC); + if (imp->magic != UFBXI_ANIM_IMP_MAGIC) return; + ufbxi_retain_ref(&imp->refcount); +} + +ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_anim *anim, const ufbx_bake_opts *opts, ufbx_error *error) +{ + ufbx_assert(scene); +#if UFBXI_FEATURE_ANIMATION_BAKING + ufbxi_check_opts_ptr(ufbx_baked_anim, opts, error); + if (!anim) { + anim = scene->anim; + } + + ufbxi_bake_context bc = { UFBX_ERROR_NONE }; + if (opts) { + bc.opts = *opts; + } + + bc.scene = scene; + + int ok = ufbxi_bake_anim_imp(&bc, anim); + + ufbxi_buf_free(&bc.tmp); + ufbxi_buf_free(&bc.tmp_prop); + ufbxi_buf_free(&bc.tmp_times); + ufbxi_buf_free(&bc.tmp_bake_props); + ufbxi_buf_free(&bc.tmp_nodes); + ufbxi_buf_free(&bc.tmp_elements); + ufbxi_buf_free(&bc.tmp_props); + ufbxi_buf_free(&bc.tmp_bake_stack); + ufbxi_free(&bc.ator_tmp, char, bc.tmp_arr, bc.tmp_arr_size); + ufbxi_free_ator(&bc.ator_tmp); + + if (ok) { + ufbxi_clear_error(error); + ufbxi_baked_anim_imp *imp = bc.imp; + return &imp->bake; + } else { + ufbxi_fix_error_type(&bc.error, "Failed to bake anim", error); + ufbxi_buf_free(&bc.result); + ufbxi_free_ator(&bc.ator_result); + return NULL; + } +#else + if (error) { + memset(error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(error, "UFBX_ENABLE_ANIMATION_BAKING"); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_ANIMATION_BAKING", "Feature disabled"); + } + return NULL; +#endif +} + +ufbx_abi void ufbx_retain_baked_anim(ufbx_baked_anim *bake) +{ + if (!bake) return; + + ufbxi_baked_anim_imp *imp = ufbxi_get_imp(ufbxi_baked_anim_imp, bake); + ufbx_assert(imp->magic == UFBXI_BAKED_ANIM_IMP_MAGIC); + if (imp->magic != UFBXI_BAKED_ANIM_IMP_MAGIC) return; + ufbxi_retain_ref(&imp->refcount); +} + +ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake) +{ + if (!bake) return; + + ufbxi_baked_anim_imp *imp = ufbxi_get_imp(ufbxi_baked_anim_imp, bake); + ufbx_assert(imp->magic == UFBXI_BAKED_ANIM_IMP_MAGIC); + if (imp->magic != UFBXI_BAKED_ANIM_IMP_MAGIC) return; + ufbxi_release_ref(&imp->refcount); +} + + +ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id) +{ + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_baked_node, 8, &index, bake->nodes.data, 0, bake->nodes.count, + ( a->typed_id < typed_id ), ( a->typed_id == typed_id) ); + return index < SIZE_MAX ? &bake->nodes.data[index] : NULL; +} + +ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node) +{ + if (!bake || !node) return NULL; + return ufbx_find_baked_node_by_typed_id(bake, node->typed_id); +} + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id) +{ + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_baked_element, 8, &index, bake->elements.data, 0, bake->elements.count, + ( a->element_id < element_id ), ( a->element_id == element_id) ); + return index < SIZE_MAX ? &bake->elements.data[index] : NULL; +} + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element) +{ + if (!bake || !element) return NULL; + return ufbx_find_baked_element_by_element_id(bake, element->element_id); +} + +ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time) +{ + size_t begin = 0; + size_t end = keyframes.count; + const ufbx_baked_vec3 *keys = keyframes.data; + while (end - begin >= 8) { + size_t mid = (begin + end) >> 1; + if (keys[mid].time <= time) { + begin = mid + 1; + } else { + end = mid; + } + } + + end = keyframes.count; + for (; begin < end; begin++) { + const ufbx_baked_vec3 *next = &keys[begin]; + if (next->time <= time) continue; + if (begin == 0) return next->value; + + const ufbx_baked_vec3 *prev = next - 1; + if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; + if (time == prev->time) return prev->value; + double t = (time - prev->time) / (next->time - prev->time); + if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; + if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; + return ufbxi_lerp3(prev->value, next->value, (ufbx_real)t); + } + + return keyframes.data[keyframes.count - 1].value; +} + +ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time) +{ + size_t begin = 0; + size_t end = keyframes.count; + const ufbx_baked_quat *keys = keyframes.data; + while (end - begin >= 8) { + size_t mid = (begin + end) >> 1; + if (keys[mid].time <= time) { + begin = mid + 1; + } else { + end = mid; + } + } + + end = keyframes.count; + for (; begin < end; begin++) { + const ufbx_baked_quat *next = &keys[begin]; + if (next->time <= time) continue; + if (begin == 0) return next->value; + + const ufbx_baked_quat *prev = next - 1; + if (prev > keys && prev[-1].time == time) prev--; + if (time == prev->time) return prev->value; + double t = (time - prev->time) / (next->time - prev->time); + if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; + if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; + if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; + return ufbx_quat_slerp(prev->value, next->value, (ufbx_real)t); + } + + return keyframes.data[keyframes.count - 1].value; +} + +ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node) +{ + if (!pose || !node) return NULL; + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_bone_pose, 8, &index, pose->bone_poses.data, 0, pose->bone_poses.count, + ( a->bone_node->typed_id < node->typed_id ), ( a->bone_node == node )); + return index < SIZE_MAX ? &pose->bone_poses.data[index] : NULL; +} + +ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len) +{ + ufbx_string name_str = ufbxi_safe_string(name, name_len); + if (!material) return NULL; + + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_material_texture, 4, &index, material->textures.data, 0, material->textures.count, + ( ufbxi_str_less(a->material_prop, name_str) ), ( ufbxi_str_equal(a->material_prop, name_str) )); + return index < SIZE_MAX ? material->textures.data[index].texture : NULL; +} + +ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len) +{ + ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, name, name_len); + if (bindings.count > 0) { + return bindings.data[0].material_prop; + } + return ufbx_empty_string; +} + +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len) +{ + ufbx_shader_prop_binding_list bindings = { NULL, 0 }; + + ufbx_string name_str = ufbxi_safe_string(name, name_len); + if (!shader) return bindings; + + ufbxi_for_ptr_list(ufbx_shader_binding, p_bind, shader->bindings) { + ufbx_shader_binding *bind = *p_bind; + + size_t begin = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_shader_prop_binding, 4, &begin, bind->prop_bindings.data, 0, bind->prop_bindings.count, + ( ufbxi_str_less(a->shader_prop, name_str) ), ( ufbxi_str_equal(a->shader_prop, name_str) )); + + if (begin != SIZE_MAX) { + + size_t end = begin; + ufbxi_macro_upper_bound_eq(ufbx_shader_prop_binding, 4, &end, bind->prop_bindings.data, begin, bind->prop_bindings.count, + ( ufbxi_str_equal(a->shader_prop, name_str) )); + + bindings.data = bind->prop_bindings.data + begin; + bindings.count = end - begin; + break; + } + } + + return bindings; +} + +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len) +{ + ufbx_string name_str = ufbxi_safe_string(name, name_len); + + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_shader_texture_input, 4, &index, shader->inputs.data, 0, shader->inputs.count, + ( ufbxi_str_less(a->name, name_str) ), ( ufbxi_str_equal(a->name, name_str) )); + + if (index != SIZE_MAX) { + return &shader->inputs.data[index]; + } + + return NULL; +} + +ufbx_abi bool ufbx_coordinate_axes_valid(ufbx_coordinate_axes axes) +{ + if (axes.right < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.right > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; + if (axes.up < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.up > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; + if (axes.front < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.front > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; + + // Check that all the positive/negative axes are used + uint32_t mask = 0; + mask |= 1u << ((uint32_t)axes.right >> 1); + mask |= 1u << ((uint32_t)axes.up >> 1); + mask |= 1u << ((uint32_t)axes.front >> 1); + return (mask & 0x7u) == 0x7u; +} + +ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b) +{ + return ufbxi_mul_quat(a, b); +} + +ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v) +{ + return ufbxi_normalize3(v); +} + +ufbx_abi ufbxi_noinline ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; +} + +ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_normalize(ufbx_quat q) +{ + ufbx_real norm = ufbx_quat_dot(q, q); + if (norm == 0.0) return ufbx_identity_quat; + norm = (ufbx_real)ufbx_sqrt(norm); + q.x /= norm; + q.y /= norm; + q.z /= norm; + q.w /= norm; + return q; +} + +ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_fix_antipodal(ufbx_quat q, ufbx_quat reference) +{ + if (ufbx_quat_dot(q, reference) < 0.0f) { + q.x = -q.x; q.y = -q.y; q.z = -q.z; q.w = -q.w; + } + return q; +} + +ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_slerp(ufbx_quat a, ufbx_quat b, ufbx_real t) +{ + double dot = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; + if (dot < 0.0) { + dot = -dot; + b.x = -b.x; b.y = -b.y; b.z = -b.z; b.w = -b.w; + } + double omega = ufbx_acos(ufbx_fmin(ufbx_fmax(dot, 0.0), 1.0)); + if (omega <= 1.175494351e-38f) return a; + double rcp_so = 1.0 / ufbx_sin(omega); + double af = ufbx_sin((1.0 - t) * omega) * rcp_so; + double bf = ufbx_sin(t * omega) * rcp_so; + + double x = af*a.x + bf*b.x; + double y = af*a.y + bf*b.y; + double z = af*a.z + bf*b.z; + double w = af*a.w + bf*b.w; + double rcp_len = 1.0 / ufbx_sqrt(x*x + y*y + z*z + w*w); + + ufbx_quat ret; + ret.x = (ufbx_real)(x * rcp_len); + ret.y = (ufbx_real)(y * rcp_len); + ret.z = (ufbx_real)(z * rcp_len); + ret.w = (ufbx_real)(w * rcp_len); + return ret; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_quat_rotate_vec3(ufbx_quat q, ufbx_vec3 v) +{ + ufbx_real xy = q.x*v.y - q.y*v.x; + ufbx_real xz = q.x*v.z - q.z*v.x; + ufbx_real yz = q.y*v.z - q.z*v.y; + ufbx_vec3 r; + r.x = 2.0f * (+ q.w*yz + q.y*xy + q.z*xz) + v.x; + r.y = 2.0f * (- q.x*xy - q.w*xz + q.z*yz) + v.y; + r.z = 2.0f * (- q.x*xz - q.y*yz + q.w*xy) + v.z; + return r; +} + +ufbx_abi ufbxi_noinline ufbx_quat ufbx_euler_to_quat(ufbx_vec3 v, ufbx_rotation_order order) +{ + double vx = v.x * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); + double vy = v.y * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); + double vz = v.z * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); + double cx = ufbx_cos(vx), sx = ufbx_sin(vx); + double cy = ufbx_cos(vy), sy = ufbx_sin(vy); + double cz = ufbx_cos(vz), sz = ufbx_sin(vz); + ufbx_quat q; + + // Generated by `misc/gen_rotation_order.py` + switch (order) { + case UFBX_ROTATION_ORDER_XYZ: + q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); + break; + case UFBX_ROTATION_ORDER_XZY: + q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); + break; + case UFBX_ROTATION_ORDER_YZX: + q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); + break; + case UFBX_ROTATION_ORDER_YXZ: + q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); + break; + case UFBX_ROTATION_ORDER_ZXY: + q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); + break; + case UFBX_ROTATION_ORDER_ZYX: + q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); + q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); + q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); + q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); + break; + default: + q.x = q.y = q.z = 0.0f; q.w = 1.0f; + break; + } + + return q; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_quat_to_euler(ufbx_quat q, ufbx_rotation_order order) +{ + // TODO: Derive these rigorously + #if defined(UFBX_REAL_IS_FLOAT) + const double eps = 0.9999999; + #else + const double eps = 0.999999999; + #endif + + double vx, vy, vz; + double t; + + double qx = q.x, qy = q.y, qz = q.z, qw = q.w; + + // Generated by `misc/gen_quat_to_euler.py` + switch (order) { + case UFBX_ROTATION_ORDER_XYZ: + t = 2.0f*(qw*qy - qx*qz); + if (ufbx_fabs(t) < eps) { + vy = ufbx_asin(t); + vz = ufbx_atan2(2.0f*(qw*qz + qx*qy), 2.0f*(qw*qw + qx*qx) - 1.0f); + vx = -ufbx_atan2(-2.0f*(qw*qx + qy*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); + } else { + vy = ufbx_copysign(UFBXI_DPI*0.5, t); + vz = ufbx_atan2(-2.0f*t*(qw*qx - qy*qz), t*(2.0f*qw*qy + 2.0f*qx*qz)); + vx = 0.0f; + } + break; + case UFBX_ROTATION_ORDER_XZY: + t = 2.0f*(qw*qz + qx*qy); + if (ufbx_fabs(t) < eps) { + vz = ufbx_asin(t); + vy = ufbx_atan2(2.0f*(qw*qy - qx*qz), 2.0f*(qw*qw + qx*qx) - 1.0f); + vx = -ufbx_atan2(-2.0f*(qw*qx - qy*qz), 2.0f*(qw*qw + qy*qy) - 1.0f); + } else { + vz = ufbx_copysign(UFBXI_DPI*0.5, t); + vy = ufbx_atan2(2.0f*t*(qw*qx + qy*qz), -t*(2.0f*qx*qy - 2.0f*qw*qz)); + vx = 0.0f; + } + break; + case UFBX_ROTATION_ORDER_YZX: + t = 2.0f*(qw*qz - qx*qy); + if (ufbx_fabs(t) < eps) { + vz = ufbx_asin(t); + vx = ufbx_atan2(2.0f*(qw*qx + qy*qz), 2.0f*(qw*qw + qy*qy) - 1.0f); + vy = -ufbx_atan2(-2.0f*(qw*qy + qx*qz), 2.0f*(qw*qw + qx*qx) - 1.0f); + } else { + vz = ufbx_copysign(UFBXI_DPI*0.5, t); + vx = ufbx_atan2(-2.0f*t*(qw*qy - qx*qz), t*(2.0f*qw*qz + 2.0f*qx*qy)); + vy = 0.0f; + } + break; + case UFBX_ROTATION_ORDER_YXZ: + t = 2.0f*(qw*qx + qy*qz); + if (ufbx_fabs(t) < eps) { + vx = ufbx_asin(t); + vz = ufbx_atan2(2.0f*(qw*qz - qx*qy), 2.0f*(qw*qw + qy*qy) - 1.0f); + vy = -ufbx_atan2(-2.0f*(qw*qy - qx*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); + } else { + vx = ufbx_copysign(UFBXI_DPI*0.5, t); + vz = ufbx_atan2(2.0f*t*(qw*qy + qx*qz), -t*(2.0f*qy*qz - 2.0f*qw*qx)); + vy = 0.0f; + } + break; + case UFBX_ROTATION_ORDER_ZXY: + t = 2.0f*(qw*qx - qy*qz); + if (ufbx_fabs(t) < eps) { + vx = ufbx_asin(t); + vy = ufbx_atan2(2.0f*(qw*qy + qx*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); + vz = -ufbx_atan2(-2.0f*(qw*qz + qx*qy), 2.0f*(qw*qw + qy*qy) - 1.0f); + } else { + vx = ufbx_copysign(UFBXI_DPI*0.5, t); + vy = ufbx_atan2(-2.0f*t*(qw*qz - qx*qy), t*(2.0f*qw*qx + 2.0f*qy*qz)); + vz = 0.0f; + } + break; + case UFBX_ROTATION_ORDER_ZYX: + t = 2.0f*(qw*qy + qx*qz); + if (ufbx_fabs(t) < eps) { + vy = ufbx_asin(t); + vx = ufbx_atan2(2.0f*(qw*qx - qy*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); + vz = -ufbx_atan2(-2.0f*(qw*qz - qx*qy), 2.0f*(qw*qw + qx*qx) - 1.0f); + } else { + vy = ufbx_copysign(UFBXI_DPI*0.5, t); + vx = ufbx_atan2(2.0f*t*(qw*qz + qx*qy), -t*(2.0f*qx*qz - 2.0f*qw*qy)); + vz = 0.0f; + } + break; + default: + vx = vy = vz = 0.0; + break; + } + + vx *= UFBXI_RAD_TO_DEG_DOUBLE; + vy *= UFBXI_RAD_TO_DEG_DOUBLE; + vz *= UFBXI_RAD_TO_DEG_DOUBLE; + + ufbx_vec3 v = { (ufbx_real)vx, (ufbx_real)vy, (ufbx_real)vz }; + return v; +} + +ufbx_abi ufbxi_noinline ufbx_matrix ufbx_matrix_mul(const ufbx_matrix *a, const ufbx_matrix *b) +{ + ufbx_assert(a && b); + if (!a || !b) return ufbx_identity_matrix; + + ufbx_matrix dst; + + dst.m03 = a->m00*b->m03 + a->m01*b->m13 + a->m02*b->m23 + a->m03; + dst.m13 = a->m10*b->m03 + a->m11*b->m13 + a->m12*b->m23 + a->m13; + dst.m23 = a->m20*b->m03 + a->m21*b->m13 + a->m22*b->m23 + a->m23; + + dst.m00 = a->m00*b->m00 + a->m01*b->m10 + a->m02*b->m20; + dst.m10 = a->m10*b->m00 + a->m11*b->m10 + a->m12*b->m20; + dst.m20 = a->m20*b->m00 + a->m21*b->m10 + a->m22*b->m20; + + dst.m01 = a->m00*b->m01 + a->m01*b->m11 + a->m02*b->m21; + dst.m11 = a->m10*b->m01 + a->m11*b->m11 + a->m12*b->m21; + dst.m21 = a->m20*b->m01 + a->m21*b->m11 + a->m22*b->m21; + + dst.m02 = a->m00*b->m02 + a->m01*b->m12 + a->m02*b->m22; + dst.m12 = a->m10*b->m02 + a->m11*b->m12 + a->m12*b->m22; + dst.m22 = a->m20*b->m02 + a->m21*b->m12 + a->m22*b->m22; + + return dst; +} + +ufbx_abi ufbx_real ufbx_matrix_determinant(const ufbx_matrix *m) +{ + return + - m->m02*m->m11*m->m20 + m->m01*m->m12*m->m20 + m->m02*m->m10*m->m21 + - m->m00*m->m12*m->m21 - m->m01*m->m10*m->m22 + m->m00*m->m11*m->m22; +} + +ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m) +{ + ufbx_real det = ufbx_matrix_determinant(m); + + ufbx_matrix r; + if (ufbx_fabs(det) <= UFBX_EPSILON) { + memset(&r, 0, sizeof(r)); + return r; + } + + ufbx_real rcp_det = 1.0f / det; + + r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * rcp_det; + r.m10 = ( + m->m12*m->m20 - m->m10*m->m22) * rcp_det; + r.m20 = ( - m->m11*m->m20 + m->m10*m->m21) * rcp_det; + r.m01 = ( + m->m02*m->m21 - m->m01*m->m22) * rcp_det; + r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * rcp_det; + r.m21 = ( + m->m01*m->m20 - m->m00*m->m21) * rcp_det; + r.m02 = ( - m->m02*m->m11 + m->m01*m->m12) * rcp_det; + r.m12 = ( + m->m02*m->m10 - m->m00*m->m12) * rcp_det; + r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * rcp_det; + r.m03 = (m->m03*m->m12*m->m21 - m->m02*m->m13*m->m21 - m->m03*m->m11*m->m22 + m->m01*m->m13*m->m22 + m->m02*m->m11*m->m23 - m->m01*m->m12*m->m23) * rcp_det; + r.m13 = (m->m02*m->m13*m->m20 - m->m03*m->m12*m->m20 + m->m03*m->m10*m->m22 - m->m00*m->m13*m->m22 - m->m02*m->m10*m->m23 + m->m00*m->m12*m->m23) * rcp_det; + r.m23 = (m->m03*m->m11*m->m20 - m->m01*m->m13*m->m20 - m->m03*m->m10*m->m21 + m->m00*m->m13*m->m21 + m->m01*m->m10*m->m23 - m->m00*m->m11*m->m23) * rcp_det; + + return r; +} + +ufbx_abi ufbxi_noinline ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m) +{ + ufbx_real det = ufbx_matrix_determinant(m); + ufbx_real det_sign = det >= 0.0f ? 1.0f : -1.0f; + + ufbx_matrix r; + r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * det_sign; + r.m01 = ( + m->m12*m->m20 - m->m10*m->m22) * det_sign; + r.m02 = ( - m->m11*m->m20 + m->m10*m->m21) * det_sign; + r.m10 = ( + m->m02*m->m21 - m->m01*m->m22) * det_sign; + r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * det_sign; + r.m12 = ( + m->m01*m->m20 - m->m00*m->m21) * det_sign; + r.m20 = ( - m->m02*m->m11 + m->m01*m->m12) * det_sign; + r.m21 = ( + m->m02*m->m10 - m->m00*m->m12) * det_sign; + r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * det_sign; + r.m03 = r.m13 = r.m23 = 0.0f; + + return r; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_transform_position(const ufbx_matrix *m, ufbx_vec3 v) +{ + ufbx_assert(m); + if (!m) return ufbx_zero_vec3; + + ufbx_vec3 r; + r.x = m->m00*v.x + m->m01*v.y + m->m02*v.z + m->m03; + r.y = m->m10*v.x + m->m11*v.y + m->m12*v.z + m->m13; + r.z = m->m20*v.x + m->m21*v.y + m->m22*v.z + m->m23; + return r; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_transform_direction(const ufbx_matrix *m, ufbx_vec3 v) +{ + ufbx_assert(m); + if (!m) return ufbx_zero_vec3; + + ufbx_vec3 r; + r.x = m->m00*v.x + m->m01*v.y + m->m02*v.z; + r.y = m->m10*v.x + m->m11*v.y + m->m12*v.z; + r.z = m->m20*v.x + m->m21*v.y + m->m22*v.z; + return r; +} + +ufbx_abi ufbxi_noinline ufbx_matrix ufbx_transform_to_matrix(const ufbx_transform *t) +{ + ufbx_assert(t); + if (!t) return ufbx_identity_matrix; + + ufbx_quat q = t->rotation; + ufbx_real sx = 2.0f * t->scale.x, sy = 2.0f * t->scale.y, sz = 2.0f * t->scale.z; + ufbx_real xx = q.x*q.x, xy = q.x*q.y, xz = q.x*q.z, xw = q.x*q.w; + ufbx_real yy = q.y*q.y, yz = q.y*q.z, yw = q.y*q.w; + ufbx_real zz = q.z*q.z, zw = q.z*q.w; + ufbx_matrix m; + m.m00 = sx * (- yy - zz + 0.5f); + m.m10 = sx * (+ xy + zw); + m.m20 = sx * (- yw + xz); + m.m01 = sy * (- zw + xy); + m.m11 = sy * (- xx - zz + 0.5f); + m.m21 = sy * (+ xw + yz); + m.m02 = sz * (+ xz + yw); + m.m12 = sz * (- xw + yz); + m.m22 = sz * (- xx - yy + 0.5f); + m.m03 = t->translation.x; + m.m13 = t->translation.y; + m.m23 = t->translation.z; + return m; +} + +ufbx_abi ufbxi_noinline ufbx_transform ufbx_matrix_to_transform(const ufbx_matrix *m) +{ + ufbx_assert(m); + if (!m) return ufbx_identity_transform; + + ufbx_real det = ufbx_matrix_determinant(m); + + ufbx_transform t; + t.translation = m->cols[3]; + t.scale.x = ufbxi_length3(m->cols[0]); + t.scale.y = ufbxi_length3(m->cols[1]); + t.scale.z = ufbxi_length3(m->cols[2]); + + // Flip a single non-zero axis if negative determinant + ufbx_real sign_x = 1.0f; + ufbx_real sign_y = 1.0f; + ufbx_real sign_z = 1.0f; + if (det < 0.0f) { + if (t.scale.x > 0.0f) sign_x = -1.0f; + else if (t.scale.y > 0.0f) sign_y = -1.0f; + else if (t.scale.z > 0.0f) sign_z = -1.0f; + } + + ufbx_vec3 x = ufbxi_mul3(m->cols[0], t.scale.x > 0.0f ? sign_x / t.scale.x : 0.0f); + ufbx_vec3 y = ufbxi_mul3(m->cols[1], t.scale.y > 0.0f ? sign_y / t.scale.y : 0.0f); + ufbx_vec3 z = ufbxi_mul3(m->cols[2], t.scale.z > 0.0f ? sign_z / t.scale.z : 0.0f); + ufbx_real trace = x.x + y.y + z.z; + if (trace > 0.0f) { + ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, trace + 1.0)), b = (a != 0.0f) ? 0.5f / a : 0.0f; + t.rotation.x = (y.z - z.y) * b; + t.rotation.y = (z.x - x.z) * b; + t.rotation.z = (x.y - y.x) * b; + t.rotation.w = 0.5f * a; + } else if (x.x > y.y && x.x > z.z) { + ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 + x.x - y.y - z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; + t.rotation.x = 0.5f * a; + t.rotation.y = (y.x + x.y) * b; + t.rotation.z = (z.x + x.z) * b; + t.rotation.w = (y.z - z.y) * b; + } + else if (y.y > z.z) { + ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 - x.x + y.y - z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; + t.rotation.x = (y.x + x.y) * b; + t.rotation.y = 0.5f * a; + t.rotation.z = (z.y + y.z) * b; + t.rotation.w = (z.x - x.z) * b; + } + else { + ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 - x.x - y.y + z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; + t.rotation.x = (z.x + x.z) * b; + t.rotation.y = (z.y + y.z) * b; + t.rotation.z = 0.5f * a; + t.rotation.w = (x.y - y.x) * b; + } + + ufbx_real len = t.rotation.x*t.rotation.x + t.rotation.y*t.rotation.y + t.rotation.z*t.rotation.z + t.rotation.w*t.rotation.w; + if (ufbx_fabs(len - 1.0f) > UFBX_EPSILON) { + if (ufbx_fabs(len) <= UFBX_EPSILON) { + t.rotation = ufbx_identity_quat; + } else { + t.rotation.x /= len; + t.rotation.y /= len; + t.rotation.z /= len; + t.rotation.w /= len; + } + } + + t.scale.x *= sign_x; + t.scale.y *= sign_y; + t.scale.z *= sign_z; + + return t; +} + +ufbx_abi ufbxi_noinline ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic *panic, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) +{ + ufbx_assert(skin); + if (ufbxi_panicf(panic, vertex < skin->vertices.count, "vertex (%zu) out of bounds (%zu)", vertex, skin->vertices.count)) return ufbx_identity_matrix; + + if (!skin || vertex >= skin->vertices.count) return ufbx_identity_matrix; + ufbx_skin_vertex skin_vertex = skin->vertices.data[vertex]; + + ufbx_matrix mat = { 0.0f }; + ufbx_quat q0 = { 0.0f }, qe = { 0.0f }; + ufbx_quat first_q0 = { 0.0f }; + ufbx_vec3 qs = { 0.0f, 0.0f, 0.0f }; + ufbx_real total_weight = 0.0f; + + for (uint32_t i = 0; i < skin_vertex.num_weights; i++) { + ufbx_skin_weight weight = skin->weights.data[skin_vertex.weight_begin + i]; + ufbx_skin_cluster *cluster = skin->clusters.data[weight.cluster_index]; + const ufbx_node *node = cluster->bone_node; + if (!node) continue; + + total_weight += weight.weight; + if (skin_vertex.dq_weight > 0.0f) { + ufbx_transform t = cluster->geometry_to_world_transform; + ufbx_quat vq0 = t.rotation; + if (i == 0) first_q0 = vq0; + + if (ufbx_quat_dot(first_q0, vq0) < 0.0f) { + vq0.x = -vq0.x; + vq0.y = -vq0.y; + vq0.z = -vq0.z; + vq0.w = -vq0.w; + } + + ufbx_quat vqt = { 0.5f * t.translation.x, 0.5f * t.translation.y, 0.5f * t.translation.z }; + ufbx_quat vqe = ufbxi_mul_quat(vqt, vq0); + ufbxi_add_weighted_quat(&q0, vq0, weight.weight); + ufbxi_add_weighted_quat(&qe, vqe, weight.weight); + ufbxi_add_weighted_vec3(&qs, t.scale, weight.weight); + } + + if (skin_vertex.dq_weight < 1.0f) { + ufbxi_add_weighted_mat(&mat, &cluster->geometry_to_world, (1.0f-skin_vertex.dq_weight) * weight.weight); + } + } + + if (total_weight <= 0.0f) { + if (fallback) { + return *fallback; + } else { + return ufbx_identity_matrix; + } + } + + if (ufbx_fabs(total_weight - 1.0f) > UFBX_EPSILON) { + ufbx_real rcp_weight = ufbx_fabs(total_weight) > UFBX_EPSILON ? 1.0f / total_weight : 0.0f; + if (skin_vertex.dq_weight > 0.0f) { + q0.x *= rcp_weight; q0.y *= rcp_weight; q0.z *= rcp_weight; q0.w *= rcp_weight; + qe.x *= rcp_weight; qe.y *= rcp_weight; qe.z *= rcp_weight; qe.w *= rcp_weight; + qs.x *= rcp_weight; qs.y *= rcp_weight; qs.z *= rcp_weight; + } + if (skin_vertex.dq_weight < 1.0f) { + mat.m00 *= rcp_weight; mat.m01 *= rcp_weight; mat.m02 *= rcp_weight; mat.m03 *= rcp_weight; + mat.m10 *= rcp_weight; mat.m11 *= rcp_weight; mat.m12 *= rcp_weight; mat.m13 *= rcp_weight; + mat.m20 *= rcp_weight; mat.m21 *= rcp_weight; mat.m22 *= rcp_weight; mat.m23 *= rcp_weight; + } + } + + if (skin_vertex.dq_weight > 0.0f) { + ufbx_transform dqt; // ufbxi_uninit + ufbx_real rcp_len = (ufbx_real)(1.0 / ufbx_sqrt(q0.x*q0.x + q0.y*q0.y + q0.z*q0.z + q0.w*q0.w)); + ufbx_real rcp_len2x2 = 2.0f * rcp_len * rcp_len; + dqt.rotation.x = q0.x * rcp_len; + dqt.rotation.y = q0.y * rcp_len; + dqt.rotation.z = q0.z * rcp_len; + dqt.rotation.w = q0.w * rcp_len; + dqt.scale.x = qs.x; + dqt.scale.y = qs.y; + dqt.scale.z = qs.z; + dqt.translation.x = rcp_len2x2 * (- qe.w*q0.x + qe.x*q0.w - qe.y*q0.z + qe.z*q0.y); + dqt.translation.y = rcp_len2x2 * (- qe.w*q0.y + qe.x*q0.z + qe.y*q0.w - qe.z*q0.x); + dqt.translation.z = rcp_len2x2 * (- qe.w*q0.z - qe.x*q0.y + qe.y*q0.x + qe.z*q0.w); + ufbx_matrix dqm = ufbx_transform_to_matrix(&dqt); + if (skin_vertex.dq_weight < 1.0f) { + ufbxi_add_weighted_mat(&mat, &dqm, skin_vertex.dq_weight); + } else { + mat = dqm; + } + } + + return mat; +} + +ufbx_abi ufbxi_noinline uint32_t ufbx_get_blend_shape_offset_index(const ufbx_blend_shape *shape, size_t vertex) +{ + ufbx_assert(shape); + if (!shape) return UFBX_NO_INDEX; + + size_t index = SIZE_MAX; + uint32_t vertex_ix = (uint32_t)vertex; + + ufbxi_macro_lower_bound_eq(uint32_t, 16, &index, shape->offset_vertices.data, 0, shape->num_offsets, + ( *a < vertex_ix ), ( *a == vertex_ix )); + if (index >= UINT32_MAX) return UFBX_NO_INDEX; + + return (uint32_t)index; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_get_blend_shape_vertex_offset(const ufbx_blend_shape *shape, size_t vertex) +{ + uint32_t index = ufbx_get_blend_shape_offset_index(shape, vertex); + if (index == UFBX_NO_INDEX) return ufbx_zero_vec3; + return shape->position_offsets.data[index]; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_get_blend_vertex_offset(const ufbx_blend_deformer *blend, size_t vertex) +{ + ufbx_assert(blend); + if (!blend) return ufbx_zero_vec3; + + ufbx_vec3 offset = ufbx_zero_vec3; + + ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, blend->channels) { + ufbx_blend_channel *chan = *p_chan; + ufbxi_for_list(ufbx_blend_keyframe, key, chan->keyframes) { + if (key->effective_weight == 0.0f) continue; + + ufbx_vec3 key_offset = ufbx_get_blend_shape_vertex_offset(key->shape, vertex); + ufbxi_add_weighted_vec3(&offset, key_offset, key->effective_weight); + } + } + + return offset; +} + +ufbx_abi void ufbx_add_blend_shape_vertex_offsets(const ufbx_blend_shape *shape, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight) +{ + if (weight == 0.0f) return; + if (!vertices) return; + + size_t num_offsets = shape->num_offsets; + uint32_t *vertex_indices = shape->offset_vertices.data; + ufbx_vec3 *offsets = shape->position_offsets.data; + ufbx_real_list weights = shape->offset_weights; + for (size_t i = 0; i < num_offsets; i++) { + uint32_t index = vertex_indices[i]; + if (index < num_vertices) { + ufbx_real vertex_weight = weight; + if (i < weights.count) { + vertex_weight *= weights.data[i]; + } + ufbxi_add_weighted_vec3(&vertices[index], offsets[i], vertex_weight); + } + } +} + +ufbx_abi void ufbx_add_blend_vertex_offsets(const ufbx_blend_deformer *blend, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight) +{ + ufbx_assert(blend); + if (!blend) return; + + ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, blend->channels) { + ufbx_blend_channel *chan = *p_chan; + ufbxi_for_list(ufbx_blend_keyframe, key, chan->keyframes) { + if (key->effective_weight == 0.0f) continue; + ufbx_add_blend_shape_vertex_offsets(key->shape, vertices, num_vertices, weight * key->effective_weight); + } + } +} + +ufbx_abi size_t ufbx_evaluate_nurbs_basis(const ufbx_nurbs_basis *basis, ufbx_real u, ufbx_real *weights, size_t num_weights, ufbx_real *derivatives, size_t num_derivatives) +{ + ufbx_assert(basis); + if (!basis) return SIZE_MAX; + if (basis->order == 0) return SIZE_MAX; + if (!basis->valid) return SIZE_MAX; + + size_t degree = basis->order - 1; + ufbx_assert(degree >= 1); + + // Binary search for the knot span `[min_u, max_u]` where `min_u <= u < max_u` + ufbx_real_list knots = basis->knot_vector; + size_t knot = SIZE_MAX; + + if (u <= basis->t_min) { + knot = degree; + u = basis->t_min; + } else if (u >= basis->t_max) { + knot = basis->knot_vector.count - degree - 2; + u = basis->t_max; + } else { + ufbxi_macro_lower_bound_eq(ufbx_real, 8, &knot, knots.data, 0, knots.count - 1, + ( a[1] <= u ), ( a[0] <= u && u < a[1] )); + } + + // The found effective control points are found left from `knot`, locally + // we use `knot - ix` here as it's more convenient for the following algorithm + // but we return it as `knot - degree` so that users can find the control points + // at `points[knot], points[knot+1], ..., points[knot+degree]` + if (knot < degree) return SIZE_MAX; + + if (num_derivatives == 0) derivatives = NULL; + if (num_weights < basis->order) return knot - degree; + if (!weights) return knot - degree; + + weights[0] = 1.0f; + for (size_t p = 1; p <= degree; p++) { + + ufbx_real prev = 0.0f; + ufbx_real g = 1.0f - ufbxi_nurbs_weight(&knots, knot - p + 1, p, u); + ufbx_real dg = 0.0f; + if (derivatives && p == degree) { + dg = ufbxi_nurbs_deriv(&knots, knot - p + 1, p); + } + + for (size_t i = p; i > 0; i--) { + ufbx_real f = ufbxi_nurbs_weight(&knots, knot - p + i, p, u); + ufbx_real weight = weights[i - 1]; + weights[i] = f*weight + g*prev; + + if (derivatives && p == degree) { + ufbx_real df = ufbxi_nurbs_deriv(&knots, knot - p + i, p); + if (i < num_derivatives) { + derivatives[i] = df*weight - dg*prev; + } + dg = df; + } + + prev = weight; + g = 1.0f - f; + } + + weights[0] = g*prev; + if (derivatives && p == degree) { + derivatives[0] = -dg*prev; + } + } + + return knot - degree; +} + +ufbx_abi ufbxi_noinline ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nurbs_curve *curve, ufbx_real u) +{ + ufbx_curve_point result = { false }; + + ufbx_assert(curve); + if (!curve) return result; + + ufbx_real weights[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + ufbx_real derivs[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + size_t base = ufbx_evaluate_nurbs_basis(&curve->basis, u, weights, UFBXI_MAX_NURBS_ORDER, derivs, UFBXI_MAX_NURBS_ORDER); + if (base == SIZE_MAX) return result; + + ufbx_vec4 p = { 0 }; + ufbx_vec4 d = { 0 }; + + size_t order = curve->basis.order; + if (order > UFBXI_MAX_NURBS_ORDER) return result; + if (curve->control_points.count == 0) return result; + + for (size_t i = 0; i < order; i++) { + size_t ix = (base + i) % curve->control_points.count; + ufbx_vec4 cp = curve->control_points.data[ix]; + ufbx_real weight = weights[i] * cp.w, deriv = derivs[i] * cp.w; + + p.x += cp.x * weight; + p.y += cp.y * weight; + p.z += cp.z * weight; + p.w += weight; + + d.x += cp.x * deriv; + d.y += cp.y * deriv; + d.z += cp.z * deriv; + d.w += deriv; + } + + ufbx_real rcp_w = 1.0f / p.w; + result.valid = true; + result.position.x = p.x * rcp_w; + result.position.y = p.y * rcp_w; + result.position.z = p.z * rcp_w; + result.derivative.x = (d.x - d.w*result.position.x) * rcp_w; + result.derivative.y = (d.y - d.w*result.position.y) * rcp_w; + result.derivative.z = (d.z - d.w*result.position.z) * rcp_w; + return result; +} + +ufbx_abi ufbxi_noinline ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v) +{ + ufbx_surface_point result = { false }; + + ufbx_assert(surface); + if (!surface) return result; + + ufbx_real weights_u[UFBXI_MAX_NURBS_ORDER], weights_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + ufbx_real derivs_u[UFBXI_MAX_NURBS_ORDER], derivs_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + size_t base_u = ufbx_evaluate_nurbs_basis(&surface->basis_u, u, weights_u, UFBXI_MAX_NURBS_ORDER, derivs_u, UFBXI_MAX_NURBS_ORDER); + size_t base_v = ufbx_evaluate_nurbs_basis(&surface->basis_v, v, weights_v, UFBXI_MAX_NURBS_ORDER, derivs_v, UFBXI_MAX_NURBS_ORDER); + if (base_u == SIZE_MAX || base_v == SIZE_MAX) return result; + + ufbx_vec4 p = { 0 }; + ufbx_vec4 du = { 0 }; + ufbx_vec4 dv = { 0 }; + + size_t num_u = surface->num_control_points_u; + size_t num_v = surface->num_control_points_v; + size_t order_u = surface->basis_u.order; + size_t order_v = surface->basis_v.order; + if (order_u > UFBXI_MAX_NURBS_ORDER || order_v > UFBXI_MAX_NURBS_ORDER) return result; + if (num_u == 0 || num_v == 0) return result; + + for (size_t vi = 0; vi < order_v; vi++) { + size_t vix = (base_v + vi) % num_v; + ufbx_real weight_v = weights_v[vi], deriv_v = derivs_v[vi]; + + for (size_t ui = 0; ui < order_u; ui++) { + size_t uix = (base_u + ui) % num_u; + ufbx_real weight_u = weights_u[ui], deriv_u = derivs_u[ui]; + ufbx_vec4 cp = surface->control_points.data[vix * num_u + uix]; + + ufbx_real weight = weight_u * weight_v * cp.w; + ufbx_real wderiv_u = deriv_u * weight_v * cp.w; + ufbx_real wderiv_v = deriv_v * weight_u * cp.w; + + p.x += cp.x * weight; + p.y += cp.y * weight; + p.z += cp.z * weight; + p.w += weight; + + du.x += cp.x * wderiv_u; + du.y += cp.y * wderiv_u; + du.z += cp.z * wderiv_u; + du.w += wderiv_u; + + dv.x += cp.x * wderiv_v; + dv.y += cp.y * wderiv_v; + dv.z += cp.z * wderiv_v; + dv.w += wderiv_v; + } + } + + ufbx_real rcp_w = 1.0f / p.w; + result.valid = true; + result.position.x = p.x * rcp_w; + result.position.y = p.y * rcp_w; + result.position.z = p.z * rcp_w; + result.derivative_u.x = (du.x - du.w*result.position.x) * rcp_w; + result.derivative_u.y = (du.y - du.w*result.position.y) * rcp_w; + result.derivative_u.z = (du.z - du.w*result.position.z) * rcp_w; + result.derivative_v.x = (dv.x - dv.w*result.position.x) * rcp_w; + result.derivative_v.y = (dv.y - dv.w*result.position.y) * rcp_w; + result.derivative_v.z = (dv.z - dv.w*result.position.z) * rcp_w; + return result; +} + +ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error) +{ +#if UFBXI_FEATURE_TESSELLATION + ufbxi_check_opts_ptr(ufbx_line_curve, opts, error); + ufbx_assert(curve); + if (!curve) return NULL; + + ufbxi_tessellate_curve_context tc = { UFBX_ERROR_NONE }; + if (opts) { + tc.opts = *opts; + } + + tc.curve = curve; + + int ok = ufbxi_tessellate_nurbs_curve_imp(&tc); + + ufbxi_free_ator(&tc.ator_tmp); + + if (ok) { + ufbxi_clear_error(error); + ufbxi_line_curve_imp *imp = tc.imp; + return &imp->curve; + } else { + ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); + ufbxi_buf_free(&tc.result); + ufbxi_free_ator(&tc.ator_result); + return NULL; + } +#else + if (error) { + memset(error, 0, sizeof(ufbx_error)); + ufbxi_fmt_err_info(error, "UFBX_ENABLE_TESSELLATION"); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_TESSELLATION", "Feature disabled"); + } + return NULL; +#endif +} + +ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surface, const ufbx_tessellate_surface_opts *opts, ufbx_error *error) +{ +#if UFBXI_FEATURE_TESSELLATION + ufbx_assert(surface); + ufbxi_check_opts_ptr(ufbx_mesh, opts, error); + if (!surface) return NULL; + + ufbxi_tessellate_surface_context tc = { UFBX_ERROR_NONE }; + if (opts) { + tc.opts = *opts; + } + + tc.surface = surface; + + int ok = ufbxi_tessellate_nurbs_surface_imp(&tc); + + ufbxi_buf_free(&tc.tmp); + ufbxi_map_free(&tc.position_map); + ufbxi_free_ator(&tc.ator_tmp); + + if (ok) { + ufbxi_clear_error(error); + ufbxi_mesh_imp *imp = tc.imp; + return &imp->mesh; + } else { + ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); + ufbxi_buf_free(&tc.result); + ufbxi_free_ator(&tc.ator_result); + return NULL; + } +#else + if (error) { + memset(error, 0, sizeof(ufbx_error)); + ufbxi_report_err_msg(error, "UFBXI_FEATURE_TESSELLATION", "Feature disabled"); + } + return NULL; +#endif +} + +ufbx_abi void ufbx_free_line_curve(ufbx_line_curve *line_curve) +{ + if (!line_curve) return; + if (!line_curve->from_tessellated_nurbs) return; + + ufbxi_line_curve_imp *imp = ufbxi_get_imp(ufbxi_line_curve_imp, line_curve); + ufbx_assert(imp->magic == UFBXI_LINE_CURVE_IMP_MAGIC); + if (imp->magic != UFBXI_LINE_CURVE_IMP_MAGIC) return; + ufbxi_release_ref(&imp->refcount); +} + +ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *line_curve) +{ + if (!line_curve) return; + if (!line_curve->from_tessellated_nurbs) return; + + ufbxi_line_curve_imp *imp = ufbxi_get_imp(ufbxi_line_curve_imp, line_curve); + ufbx_assert(imp->magic == UFBXI_LINE_CURVE_IMP_MAGIC); + if (imp->magic != UFBXI_LINE_CURVE_IMP_MAGIC) return; + ufbxi_retain_ref(&imp->refcount); +} + +ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index) +{ + if (!mesh || index > UINT32_MAX) return UFBX_NO_INDEX; + uint32_t ix = (uint32_t)index; + + size_t face_ix = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_face, 4, &face_ix, mesh->faces.data, 0, mesh->faces.count, + ( a->index_begin + a->num_indices <= ix ), ( ix >= a->index_begin && ix < a->index_begin + a->num_indices )); + return (uint32_t)face_ix; +} + +ufbx_abi ufbxi_noinline uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) +{ +#if UFBXI_FEATURE_TRIANGULATION + if (face.num_indices < 3) return 0; + + size_t required_indices = ((size_t)face.num_indices - 2) * 3; + if (ufbxi_panicf(panic, num_indices >= required_indices, "Face needs at least %zu indices for triangles, got space for %zu", required_indices, num_indices)) return 0; + if (ufbxi_panicf(panic, face.index_begin < mesh->num_indices, "Face index begin (%u) out of bounds (%zu)", face.index_begin, mesh->num_indices)) return 0; + if (ufbxi_panicf(panic, mesh->num_indices - face.index_begin >= face.num_indices, "Face index end (%u + %u) out of bounds (%zu)", face.index_begin, face.num_indices, mesh->num_indices)) return 0; + + if (face.num_indices == 3) { + // Fast case: Already a triangle + indices[0] = face.index_begin + 0; + indices[1] = face.index_begin + 1; + indices[2] = face.index_begin + 2; + return 1; + } else if (face.num_indices == 4) { + // Quad: Split along the shortest axis unless a vertex crosses the axis + uint32_t i0 = face.index_begin + 0; + uint32_t i1 = face.index_begin + 1; + uint32_t i2 = face.index_begin + 2; + uint32_t i3 = face.index_begin + 3; + ufbx_vec3 v0 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i0]]; + ufbx_vec3 v1 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i1]]; + ufbx_vec3 v2 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i2]]; + ufbx_vec3 v3 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i3]]; + + ufbx_vec3 a = ufbxi_sub3(v2, v0); + ufbx_vec3 b = ufbxi_sub3(v3, v1); + + ufbx_vec3 na1 = ufbxi_normalize3(ufbxi_cross3(a, ufbxi_sub3(v1, v0))); + ufbx_vec3 na3 = ufbxi_normalize3(ufbxi_cross3(a, ufbxi_sub3(v0, v3))); + ufbx_vec3 nb0 = ufbxi_normalize3(ufbxi_cross3(b, ufbxi_sub3(v1, v0))); + ufbx_vec3 nb2 = ufbxi_normalize3(ufbxi_cross3(b, ufbxi_sub3(v2, v1))); + + ufbx_real dot_aa = ufbxi_dot3(a, a); + ufbx_real dot_bb = ufbxi_dot3(b, b); + ufbx_real dot_na = ufbxi_dot3(na1, na3); + ufbx_real dot_nb = ufbxi_dot3(nb0, nb2); + + bool split_a = dot_aa <= dot_bb; + + if (dot_na < 0.0f || dot_nb < 0.0f) { + split_a = dot_na >= dot_nb; + } + + if (split_a) { + indices[0] = i0; + indices[1] = i1; + indices[2] = i2; + indices[3] = i2; + indices[4] = i3; + indices[5] = i0; + } else { + indices[0] = i1; + indices[1] = i2; + indices[2] = i3; + indices[3] = i3; + indices[4] = i0; + indices[5] = i1; + } + + return 2; + } else { + ufbxi_ngon_context nc = { 0 }; + nc.positions = mesh->vertex_position; + nc.face = face; + + uint32_t num_indices_u32 = num_indices < UINT32_MAX ? (uint32_t)num_indices : UINT32_MAX; + + uint32_t local_indices[12]; // ufbxi_uninit + if (num_indices_u32 < 12) { + uint32_t num_tris = ufbxi_triangulate_ngon(&nc, local_indices, 12); + memcpy(indices, local_indices, num_tris * 3 * sizeof(uint32_t)); + return num_tris; + } else { + return ufbxi_triangulate_ngon(&nc, indices, num_indices_u32); + } + } +#else + ufbxi_panicf_imp(panic, "Triangulation disabled"); + return 0; +#endif +} + +ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *mesh, ufbx_topo_edge *indices, size_t num_indices) +{ + if (ufbxi_panicf(panic, num_indices >= mesh->num_indices, "Required mesh.num_indices (%zu) indices, got %zu", mesh->num_indices, num_indices)) return; + + ufbxi_compute_topology(mesh, indices); +} + +ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) +{ + if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; + if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; + uint32_t twin = topo[index].twin; + if (twin == UFBX_NO_INDEX) return UFBX_NO_INDEX; + if (ufbxi_panicf(panic, (size_t)twin < num_topo, "Corrupted topology structure")) return UFBX_NO_INDEX; + return topo[twin].next; +} + +ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) +{ + if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; + if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; + return topo[topo[index].prev].twin; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face) +{ + if (ufbxi_panicf(panic, face.index_begin <= positions->indices.count, "Face index begin (%u) out of bounds (%zu)", face.index_begin, positions->indices.count)) return ufbx_zero_vec3; + if (ufbxi_panicf(panic, positions->indices.count - face.index_begin >= face.num_indices, "Face index end (%u + %u) out of bounds (%zu)", face.index_begin, face.num_indices, positions->indices.count)) return ufbx_zero_vec3; + + if (face.num_indices < 3) { + return ufbx_zero_vec3; + } else if (face.num_indices == 3) { + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); + ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); + return ufbxi_cross3(ufbxi_sub3(b, a), ufbxi_sub3(c, a)); + } else if (face.num_indices == 4) { + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); + ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); + ufbx_vec3 d = ufbx_get_vertex_vec3(positions, face.index_begin + 3); + return ufbxi_cross3(ufbxi_sub3(c, a), ufbxi_sub3(d, b)); + } else { + // Newell's Method + ufbx_vec3 result = ufbx_zero_vec3; + for (size_t i = 0; i < face.num_indices; i++) { + size_t next = i + 1 < face.num_indices ? i + 1 : 0; + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + i); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + next); + result.x += (a.y - b.y) * (a.z + b.z); + result.y += (a.z - b.z) * (a.x + b.x); + result.z += (a.x - b.x) * (a.y + b.y); + } + return result; + } +} + +size_t ufbx_catch_generate_normal_mapping(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth) +{ + uint32_t next_index = 0; + if (ufbxi_panicf(panic, num_normal_indices >= mesh->num_indices, "Expected at least mesh.num_indices (%zu), got %zu", mesh->num_indices, num_normal_indices)) return 0; + + for (size_t i = 0; i < mesh->num_indices; i++) { + normal_indices[i] = UFBX_NO_INDEX; + } + + // Walk around vertices and merge around smooth edges + for (size_t vi = 0; vi < mesh->num_vertices; vi++) { + uint32_t original_start = mesh->vertex_first_index.data[vi]; + if (original_start == UFBX_NO_INDEX) continue; + uint32_t start = original_start, cur = start; + + for (;;) { + uint32_t prev = ufbx_topo_next_vertex_edge(topo, num_topo, cur); + if (!ufbxi_is_edge_smooth(mesh, topo, num_topo, cur, assume_smooth)) start = cur; + if (prev == UFBX_NO_INDEX) { start = cur; break; } + if (prev == original_start) break; + cur = prev; + } + + normal_indices[start] = next_index++; + uint32_t next = start; + for (;;) { + next = ufbx_topo_prev_vertex_edge(topo, num_topo, next); + if (next == UFBX_NO_INDEX || next == start) break; + + if (!ufbxi_is_edge_smooth(mesh, topo, num_topo, next, assume_smooth)) { + ++next_index; + } + normal_indices[next] = next_index - 1; + } + } + + // Assign non-manifold indices + for (size_t i = 0; i < mesh->num_indices; i++) { + if (normal_indices[i] == UFBX_NO_INDEX) { + normal_indices[i] = next_index++; + } + } + + return (size_t)next_index; +} + +ufbx_abi size_t ufbx_generate_normal_mapping(const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth) +{ + return ufbx_catch_generate_normal_mapping(NULL, mesh, topo, num_topo, normal_indices, num_normal_indices, assume_smooth); +} + +ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals) +{ + if (ufbxi_panicf(panic, num_normal_indices >= mesh->num_indices, "Expected at least mesh.num_indices (%zu), got %zu", mesh->num_indices, num_normal_indices)) return; + + memset(normals, 0, sizeof(ufbx_vec3)*num_normals); + + for (size_t fi = 0; fi < mesh->num_faces; fi++) { + ufbx_face face = mesh->faces.data[fi]; + ufbx_vec3 normal = ufbx_get_weighted_face_normal(positions, face); + for (size_t ix = 0; ix < face.num_indices; ix++) { + uint32_t index = normal_indices[face.index_begin + ix]; + + if (ufbxi_panicf(panic, index < num_normals, "Normal index (%u) out of bounds (%zu) at %zu", index, num_normals, ix)) return; + + ufbx_vec3 *n = &normals[index]; + *n = ufbxi_add3(*n, normal); + } + } + + for (size_t i = 0; i < num_normals; i++) { + ufbx_real len = ufbxi_length3(normals[i]); + if (len > 0.0f) { + normals[i].x /= len; + normals[i].y /= len; + normals[i].z /= len; + } + } +} + +ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals) +{ + ufbx_catch_compute_normals(NULL, mesh, positions, normal_indices, num_normal_indices, normals, num_normals); +} + +ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_mesh, opts, error); + if (!mesh) return NULL; + if (level == 0) return (ufbx_mesh*)mesh; + return ufbxi_subdivide_mesh(mesh, level, opts, error); +} + +ufbx_abi void ufbx_free_mesh(ufbx_mesh *mesh) +{ + if (!mesh) return; + if (!mesh->subdivision_evaluated && !mesh->from_tessellated_nurbs) return; + + ufbxi_mesh_imp *imp = ufbxi_get_imp(ufbxi_mesh_imp, mesh); + ufbx_assert(imp->magic == UFBXI_MESH_IMP_MAGIC); + if (imp->magic != UFBXI_MESH_IMP_MAGIC) return; + ufbxi_release_ref(&imp->refcount); +} + +ufbx_abi void ufbx_retain_mesh(ufbx_mesh *mesh) +{ + if (!mesh) return; + if (!mesh->subdivision_evaluated && !mesh->from_tessellated_nurbs) return; + + ufbxi_mesh_imp *imp = ufbxi_get_imp(ufbxi_mesh_imp, mesh); + ufbx_assert(imp->magic == UFBXI_MESH_IMP_MAGIC); + if (imp->magic != UFBXI_MESH_IMP_MAGIC) return; + ufbxi_retain_ref(&imp->refcount); +} + +ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache( + const char *filename, + const ufbx_geometry_cache_opts *opts, ufbx_error *error) +{ + return ufbx_load_geometry_cache_len(filename, strlen(filename), + opts, error); +} + +ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( + const char *filename, size_t filename_len, + const ufbx_geometry_cache_opts *opts, ufbx_error *error) +{ + ufbxi_check_opts_ptr(ufbx_geometry_cache, opts, error); + ufbx_string str = ufbxi_safe_string(filename, filename_len); + return ufbxi_load_geometry_cache(str, opts, error); +} + +ufbx_abi void ufbx_free_geometry_cache(ufbx_geometry_cache *cache) +{ + if (!cache) return; + + ufbxi_geometry_cache_imp *imp = ufbxi_get_imp(ufbxi_geometry_cache_imp, cache); + ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); + if (imp->magic != UFBXI_CACHE_IMP_MAGIC) return; + if (imp->owned_by_scene) return; + ufbxi_release_ref(&imp->refcount); +} + +ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache) +{ + if (!cache) return; + + ufbxi_geometry_cache_imp *imp = ufbxi_get_imp(ufbxi_geometry_cache_imp, cache); + ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); + if (imp->magic != UFBXI_CACHE_IMP_MAGIC) return; + if (imp->owned_by_scene) return; + ufbxi_retain_ref(&imp->refcount); +} + +typedef struct { + union { + double f64[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; + float f32[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; + } src; + ufbx_real dst[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; +} ufbxi_geometry_cache_buffer; + +ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) +{ +#if UFBXI_FEATURE_GEOMETRY_CACHE + ufbxi_check_opts_return_no_error(0, user_opts); + if (!frame || count == 0) return 0; + ufbx_assert(data); + if (!data) return 0; + + ufbx_geometry_cache_data_opts opts; // ufbxi_uninit + if (user_opts) { + opts = *user_opts; + } else { + memset(&opts, 0, sizeof(opts)); + } + + if (!opts.open_file_cb.fn) { + opts.open_file_cb.fn = ufbx_default_open_file; + } + + bool use_double = false; + + size_t src_count = 0; + + switch (frame->data_format) { + case UFBX_CACHE_DATA_FORMAT_UNKNOWN: src_count = 0; break; + case UFBX_CACHE_DATA_FORMAT_REAL_FLOAT: src_count = frame->data_count; break; + case UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT: src_count = frame->data_count * 3; break; + case UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE: src_count = frame->data_count; use_double = true; break; + case UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE: src_count = frame->data_count * 3; use_double = true; break; + default: ufbxi_unreachable("Bad data_format"); break; + } + + bool src_big_endian = false; + switch (frame->data_encoding) { + case UFBX_CACHE_DATA_ENCODING_UNKNOWN: return 0; + case UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN: src_big_endian = false; break; + case UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN: src_big_endian = true; break; + default: ufbxi_unreachable("Bad data_encoding"); break; + } + + // Test endianness + bool dst_big_endian; + { + uint8_t buf[2]; + uint16_t val = 0xbbaa; + memcpy(buf, &val, 2); + dst_big_endian = buf[0] == 0xbb; + } + + if (src_count == 0) return 0; + src_count = ufbxi_min_sz(src_count, count); + + ufbx_stream stream = { 0 }; + if (!ufbxi_open_file(&opts.open_file_cb, &stream, frame->filename.data, frame->filename.length, NULL, NULL, UFBX_OPEN_FILE_GEOMETRY_CACHE)) { + return 0; + } + + // Skip to the correct point in the file + uint64_t offset = frame->data_offset; + if (stream.skip_fn) { + while (offset > 0) { + size_t to_skip = (size_t)ufbxi_min64(offset, UFBXI_MAX_SKIP_SIZE); + if (!stream.skip_fn(stream.user, to_skip)) break; + offset -= to_skip; + } + } else { + char buffer[4096]; // ufbxi_uninit + while (offset > 0) { + size_t to_skip = (size_t)ufbxi_min64(offset, sizeof(buffer)); + size_t num_read = stream.read_fn(stream.user, buffer, to_skip); + if (num_read != to_skip) break; + offset -= to_skip; + } + } + + // Failed to skip all the way + if (offset > 0) { + if (stream.close_fn) { + stream.close_fn(stream.user); + } + return 0; + } + + ufbx_real *dst = data; + size_t mirror_ix = (size_t)frame->mirror_axis - 1; + ufbxi_geometry_cache_buffer buffer; // ufbxi_uninit + while (src_count > 0) { + size_t to_read = ufbxi_min_sz(src_count, UFBXI_GEOMETRY_CACHE_BUFFER_SIZE); + src_count -= to_read; + size_t num_read = 0; + if (use_double) { + size_t bytes_read = stream.read_fn(stream.user, buffer.src.f64, to_read * sizeof(double)); + if (bytes_read == SIZE_MAX) bytes_read = 0; + num_read = bytes_read / sizeof(double); + if (src_big_endian != dst_big_endian) { + for (size_t i = 0; i < num_read; i++) { + char t, *v = (char*)&buffer.src.f64[i]; + t = v[0]; v[0] = v[7]; v[7] = t; + t = v[1]; v[1] = v[6]; v[6] = t; + t = v[2]; v[2] = v[5]; v[5] = t; + t = v[3]; v[3] = v[4]; v[4] = t; + } + } + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] = (ufbx_real)buffer.src.f64[i]; + } + } else { + size_t bytes_read = stream.read_fn(stream.user, buffer.src.f32, to_read * sizeof(float)); + if (bytes_read == SIZE_MAX) bytes_read = 0; + num_read = bytes_read / sizeof(float); + if (src_big_endian != dst_big_endian) { + for (size_t i = 0; i < num_read; i++) { + char t, *v = (char*)&buffer.src.f32[i]; + t = v[0]; v[0] = v[3]; v[3] = t; + t = v[1]; v[1] = v[2]; v[2] = t; + } + } + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] = (ufbx_real)buffer.src.f32[i]; + } + } + + if (!opts.ignore_transform) { + ufbx_real scale = frame->scale_factor; + if (scale != 1.0f) { + for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] *= scale; + } + } + if (frame->mirror_axis) { + while (mirror_ix < num_read) { + buffer.dst[mirror_ix] = -buffer.dst[mirror_ix]; + mirror_ix += 3; + } + mirror_ix -= num_read; + } + } + + if (dst) { + ufbx_real weight = opts.use_weight ? opts.weight : 1.0f; + if (opts.additive) { + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + dst[i] += buffer.dst[i] * weight; + } + } else { + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + dst[i] = buffer.dst[i] * weight; + } + } + dst += num_read; + } + + if (num_read != to_read) break; + } + + if (stream.close_fn) { + stream.close_fn(stream.user); + } + + return ufbxi_to_size(dst - data); +#else + return 0; +#endif +} + +ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) +{ +#if UFBXI_FEATURE_GEOMETRY_CACHE + ufbxi_check_opts_return_no_error(0, user_opts); + if (!channel || count == 0) return 0; + ufbx_assert(data); + if (!data) return 0; + if (channel->frames.count == 0) return 0; + + ufbx_geometry_cache_data_opts opts; + if (user_opts) { + opts = *user_opts; + } else { + memset(&opts, 0, sizeof(opts)); + } + + size_t begin = 0; + size_t end = channel->frames.count; + const ufbx_cache_frame *frames = channel->frames.data; + while (end - begin >= 8) { + size_t mid = (begin + end) >> 1; + if (frames[mid].time < time) { + begin = mid + 1; + } else { + end = mid; + } + } + + const double eps = 0.00000001; + + end = channel->frames.count; + for (; begin < end; begin++) { + const ufbx_cache_frame *next = &frames[begin]; + if (next->time < time) continue; + + // First keyframe + if (begin == 0) { + return ufbx_read_geometry_cache_real(next, data, count, &opts); + } + + const ufbx_cache_frame *prev = next - 1; + + // Snap to exact frames if near + if (ufbx_fabs(next->time - time) < eps) { + return ufbx_read_geometry_cache_real(next, data, count, &opts); + } + if (ufbx_fabs(prev->time - time) < eps) { + return ufbx_read_geometry_cache_real(prev, data, count, &opts); + } + + double rcp_delta = 1.0 / (next->time - prev->time); + double t = (time - prev->time) * rcp_delta; + + ufbx_real original_weight = opts.use_weight ? opts.weight : 1.0f; + + opts.use_weight = true; + opts.weight = (ufbx_real)(original_weight * (1.0 - t)); + size_t num_prev = ufbx_read_geometry_cache_real(prev, data, count, &opts); + + opts.additive = true; + opts.weight = (ufbx_real)(original_weight * t); + return ufbx_read_geometry_cache_real(next, data, num_prev, &opts); + } + + // Last frame + const ufbx_cache_frame *last = &frames[end - 1]; + return ufbx_read_geometry_cache_real(last, data, count, &opts); +#else + return 0; +#endif +} + +ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_vec3(const ufbx_cache_frame *frame, ufbx_vec3 *data, size_t count, const ufbx_geometry_cache_data_opts *opts) +{ +#if UFBXI_FEATURE_GEOMETRY_CACHE + if (!frame || count == 0) return 0; + ufbx_assert(data); + if (!data) return 0; + return ufbx_read_geometry_cache_real(frame, (ufbx_real*)data, count * 3, opts) / 3; +#else + return 0; +#endif +} + +ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channel, double time, ufbx_vec3 *data, size_t count, const ufbx_geometry_cache_data_opts *opts) +{ +#if UFBXI_FEATURE_GEOMETRY_CACHE + if (!channel || count == 0) return 0; + ufbx_assert(data); + if (!data) return 0; + return ufbx_sample_geometry_cache_real(channel, time, (ufbx_real*)data, count * 3, opts) / 3; +#else + return 0; +#endif +} + +ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len) +{ + ufbx_string ref = ufbxi_safe_string(name, name_len); + ufbxi_for_ptr_list(ufbx_dom_node, p_child, parent->children) { + if (ufbxi_str_equal((*p_child)->name, ref)) return (ufbx_dom_node*)*p_child; + } + return NULL; +} + +ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) +{ + ufbx_error local_error; // ufbxi_uninit + if (!error) { + error = &local_error; + } + memset(error, 0, sizeof(ufbx_error)); + return ufbxi_generate_indices(streams, num_streams, indices, num_indices, allocator, error); +} + +ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx, uint32_t index) +{ + ufbxi_thread_pool_execute((ufbxi_thread_pool*)ctx, index); +} + +ufbx_abi void ufbx_thread_pool_set_user_ptr(ufbx_thread_pool_context ctx, void *user) +{ + ufbxi_thread_pool *pool = (ufbxi_thread_pool*)ctx; + pool->user_ptr = user; +} + +ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx) +{ + ufbxi_thread_pool *pool = (ufbxi_thread_pool*)ctx; + return pool->user_ptr; +} + +ufbx_abi ufbxi_noinline ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index) +{ + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return 0.0f; + return v->values.data[(int32_t)ix]; +} + +ufbx_abi ufbxi_noinline ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index) +{ + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec2; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec2; + return v->values.data[(int32_t)ix]; +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) +{ + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec3; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec3; + return v->values.data[(int32_t)ix]; +} + +ufbx_abi ufbxi_noinline ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index) +{ + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec4; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec4; + return v->values.data[(int32_t)ix]; +} + +ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) +{ + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; + if (v->values_w.count == 0) return 0.0f; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return 0.0f; + return v->values_w.data[(int32_t)ix]; +} + +ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_UNKNOWN ? (ufbx_unknown*)element : NULL; } +ufbx_abi ufbx_node *ufbx_as_node(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NODE ? (ufbx_node*)element : NULL; } +ufbx_abi ufbx_mesh *ufbx_as_mesh(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MESH ? (ufbx_mesh*)element : NULL; } +ufbx_abi ufbx_light *ufbx_as_light(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LIGHT ? (ufbx_light*)element : NULL; } +ufbx_abi ufbx_camera *ufbx_as_camera(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CAMERA ? (ufbx_camera*)element : NULL; } +ufbx_abi ufbx_bone *ufbx_as_bone(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BONE ? (ufbx_bone*)element : NULL; } +ufbx_abi ufbx_empty *ufbx_as_empty(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_EMPTY ? (ufbx_empty*)element : NULL; } +ufbx_abi ufbx_line_curve *ufbx_as_line_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LINE_CURVE ? (ufbx_line_curve*)element : NULL; } +ufbx_abi ufbx_nurbs_curve *ufbx_as_nurbs_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_CURVE ? (ufbx_nurbs_curve*)element : NULL; } +ufbx_abi ufbx_nurbs_surface *ufbx_as_nurbs_surface(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_SURFACE ? (ufbx_nurbs_surface*)element : NULL; } +ufbx_abi ufbx_nurbs_trim_surface *ufbx_as_nurbs_trim_surface(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_TRIM_SURFACE ? (ufbx_nurbs_trim_surface*)element : NULL; } +ufbx_abi ufbx_nurbs_trim_boundary *ufbx_as_nurbs_trim_boundary(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_TRIM_BOUNDARY ? (ufbx_nurbs_trim_boundary*)element : NULL; } +ufbx_abi ufbx_procedural_geometry *ufbx_as_procedural_geometry(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_PROCEDURAL_GEOMETRY ? (ufbx_procedural_geometry*)element : NULL; } +ufbx_abi ufbx_stereo_camera *ufbx_as_stereo_camera(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_STEREO_CAMERA ? (ufbx_stereo_camera*)element : NULL; } +ufbx_abi ufbx_camera_switcher *ufbx_as_camera_switcher(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CAMERA_SWITCHER ? (ufbx_camera_switcher*)element : NULL; } +ufbx_abi ufbx_marker *ufbx_as_marker(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MARKER ? (ufbx_marker*)element : NULL; } +ufbx_abi ufbx_lod_group *ufbx_as_lod_group(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LOD_GROUP ? (ufbx_lod_group*)element : NULL; } +ufbx_abi ufbx_skin_deformer *ufbx_as_skin_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SKIN_DEFORMER ? (ufbx_skin_deformer*)element : NULL; } +ufbx_abi ufbx_skin_cluster *ufbx_as_skin_cluster(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SKIN_CLUSTER ? (ufbx_skin_cluster*)element : NULL; } +ufbx_abi ufbx_blend_deformer *ufbx_as_blend_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_DEFORMER ? (ufbx_blend_deformer*)element : NULL; } +ufbx_abi ufbx_blend_channel *ufbx_as_blend_channel(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_CHANNEL ? (ufbx_blend_channel*)element : NULL; } +ufbx_abi ufbx_blend_shape *ufbx_as_blend_shape(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_SHAPE ? (ufbx_blend_shape*)element : NULL; } +ufbx_abi ufbx_cache_deformer *ufbx_as_cache_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CACHE_DEFORMER ? (ufbx_cache_deformer*)element : NULL; } +ufbx_abi ufbx_cache_file *ufbx_as_cache_file(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CACHE_FILE ? (ufbx_cache_file*)element : NULL; } +ufbx_abi ufbx_material *ufbx_as_material(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MATERIAL ? (ufbx_material*)element : NULL; } +ufbx_abi ufbx_texture *ufbx_as_texture(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_TEXTURE ? (ufbx_texture*)element : NULL; } +ufbx_abi ufbx_video *ufbx_as_video(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_VIDEO ? (ufbx_video*)element : NULL; } +ufbx_abi ufbx_shader *ufbx_as_shader(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SHADER ? (ufbx_shader*)element : NULL; } +ufbx_abi ufbx_shader_binding *ufbx_as_shader_binding(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SHADER_BINDING ? (ufbx_shader_binding*)element : NULL; } +ufbx_abi ufbx_anim_stack *ufbx_as_anim_stack(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_STACK ? (ufbx_anim_stack*)element : NULL; } +ufbx_abi ufbx_anim_layer *ufbx_as_anim_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_LAYER ? (ufbx_anim_layer*)element : NULL; } +ufbx_abi ufbx_anim_value *ufbx_as_anim_value(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_VALUE ? (ufbx_anim_value*)element : NULL; } +ufbx_abi ufbx_anim_curve *ufbx_as_anim_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_CURVE ? (ufbx_anim_curve*)element : NULL; } +ufbx_abi ufbx_display_layer *ufbx_as_display_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_DISPLAY_LAYER ? (ufbx_display_layer*)element : NULL; } +ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SELECTION_SET ? (ufbx_selection_set*)element : NULL; } +ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SELECTION_NODE ? (ufbx_selection_node*)element : NULL; } +ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CHARACTER ? (ufbx_character*)element : NULL; } +ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CONSTRAINT ? (ufbx_constraint*)element : NULL; } +ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_LAYER ? (ufbx_audio_layer*)element : NULL; } +ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_CLIP ? (ufbx_audio_clip*)element : NULL; } +ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_POSE ? (ufbx_pose*)element : NULL; } +ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_METADATA_OBJECT ? (ufbx_metadata_object*)element : NULL; } + +ufbx_abi bool ufbx_dom_is_array(const ufbx_dom_node *node) { + if (!node || node->values.count != 1) return false; + ufbx_dom_value v = node->values.data[0]; + return v.type >= UFBX_DOM_VALUE_ARRAY_I32 && v.type <= UFBX_DOM_VALUE_ARRAY_BLOB; +} +ufbx_abi size_t ufbx_dom_array_size(const ufbx_dom_node *node) { + return ufbx_dom_is_array(node) ? (size_t)node->values.data[0].value_int : (size_t)0; +} +ufbx_abi ufbx_int32_list ufbx_dom_as_int32_list(const ufbx_dom_node *node) { + ufbx_int32_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_I32) { + ufbx_dom_value value = node->values.data[0]; + list.data = (int32_t*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(int32_t); + } + return list; +} +ufbx_abi ufbx_int64_list ufbx_dom_as_int64_list(const ufbx_dom_node *node) { + ufbx_int64_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_I64) { + ufbx_dom_value value = node->values.data[0]; + list.data = (int64_t*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(int64_t); + } + return list; +} +ufbx_abi ufbx_float_list ufbx_dom_as_float_list(const ufbx_dom_node *node) { + ufbx_float_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_F32) { + ufbx_dom_value value = node->values.data[0]; + list.data = (float*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(float); + } + return list; +} +ufbx_abi ufbx_double_list ufbx_dom_as_double_list(const ufbx_dom_node *node) { + ufbx_double_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_F64) { + ufbx_dom_value value = node->values.data[0]; + list.data = (double*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(double); + } + return list; +} +ufbx_abi ufbx_real_list ufbx_dom_as_real_list(const ufbx_dom_node *node) { + ufbx_real_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == (sizeof(ufbx_real) == sizeof(double) ? UFBX_DOM_VALUE_ARRAY_F64 : UFBX_DOM_VALUE_ARRAY_F32)) { + ufbx_dom_value value = node->values.data[0]; + list.data = (ufbx_real*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(ufbx_real); + } + return list; +} +ufbx_abi ufbx_blob_list ufbx_dom_as_blob_list(const ufbx_dom_node *node) { + ufbx_blob_list list = { NULL, 0 }; + if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_BLOB) { + ufbx_dom_value value = node->values.data[0]; + list.data = (ufbx_blob*)value.value_blob.data; + list.count = value.value_blob.size / sizeof(ufbx_blob); + } + return list; +} + +// -- String API + +ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name) { return ufbx_find_prop_len(props, name, strlen(name)); } +ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def) { return ufbx_find_real_len(props, name, strlen(name), def); } +ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name, strlen(name), def); } +ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def) { return ufbx_find_int_len(props, name, strlen(name), def); } +ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def) { return ufbx_find_bool_len(props, name, strlen(name), def); } +ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def) { return ufbx_find_string_len(props, name, strlen(name), def); } +ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def) { return ufbx_find_blob_len(props, name, strlen(name), def); } +ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name, strlen(name), type); } +ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name) { return ufbx_find_element_len(scene, type, name, strlen(name)); } +ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name) { return ufbx_find_node_len(scene, name, strlen(name)); } +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name) { return ufbx_find_anim_stack_len(scene, name, strlen(name)); } +ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name) { return ufbx_find_material_len(scene, name, strlen(name)); } +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop) { return ufbx_find_anim_prop_len(layer, element, prop, strlen(prop)); } +ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) { return ufbx_evaluate_prop_len(anim, element, name, strlen(name), time); } +ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) { return ufbx_evaluate_prop_len_flags(anim, element, name, strlen(name), time, flags); } +ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { return ufbx_find_prop_texture_len(material, name, strlen(name)); } +ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_len(shader, name, strlen(name)); } +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_bindings_len(shader, name, strlen(name)); } +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name) { return ufbx_find_shader_texture_input_len(shader, name, strlen(name)); } +ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name) { return ufbx_dom_find_len(parent, name, strlen(name)); } + +// -- Catch API + +ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) { + return ufbx_catch_triangulate_face(NULL, indices, num_indices, mesh, face); +} +ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo) { + ufbx_catch_compute_topology(NULL, mesh, topo, num_topo); +} +ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { + return ufbx_catch_topo_next_vertex_edge(NULL, topo, num_topo, index); +} +ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { + return ufbx_catch_topo_prev_vertex_edge(NULL, topo, num_topo, index); +} +ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face) { + return ufbx_catch_get_weighted_face_normal(NULL, positions, face); +} + +#ifdef __cplusplus +} +#endif + +#endif + +#if defined(UFBX_STRING_PREFIX) + #undef strlen + #undef memcpy + #undef memmove + #undef memset + #undef memchr + #undef memcmp + #undef strcmp + #undef strncmp +#endif + +#if defined(_MSC_VER) + #pragma warning(pop) +#elif defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h b/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h new file mode 100644 index 000000000..9078a9608 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h @@ -0,0 +1,6062 @@ +#ifndef UFBX_UFBX_H_INCLUDED +#define UFBX_UFBX_H_INCLUDED + +// -- User configuration + +#if defined(UFBX_CONFIG_HEADER) + #include UFBX_CONFIG_HEADER +#endif + +// -- Headers + +#if !defined(UFBX_NO_LIBC_TYPES) + #include + #include + #include +#endif + +// -- Platform + +#ifndef UFBX_STDC + #if defined(__STDC_VERSION__) + #define UFBX_STDC __STDC_VERSION__ + #else + #define UFBX_STDC 0 + #endif +#endif + +#ifndef UFBX_CPP + #if defined(__cplusplus) + #define UFBX_CPP __cplusplus + #else + #define UFBX_CPP 0 + #endif +#endif + +#ifndef UFBX_PLATFORM_MSC + #if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) + #define UFBX_PLATFORM_MSC _MSC_VER + #else + #define UFBX_PLATFORM_MSC 0 + #endif +#endif + +#ifndef UFBX_PLATFORM_GNUC + #if !defined(UFBX_STANDARD_C) && defined(__GNUC__) + #define UFBX_PLATFORM_GNUC __GNUC__ + #else + #define UFBX_PLATFORM_GNUC 0 + #endif +#endif + +#ifndef UFBX_CPP11 + // MSVC does not advertise C++11 by default so we need special detection + #if UFBX_CPP >= 201103L || (UFBX_CPP > 0 && UFBX_PLATFORM_MSC >= 1900) + #define UFBX_CPP11 1 + #else + #define UFBX_CPP11 0 + #endif +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4061) // enumerator 'ENUM' in switch of enum 'enum' is not explicitly handled by a case label + #pragma warning(disable: 4201) // nonstandard extension used: nameless struct/union + #pragma warning(disable: 4505) // unreferenced local function has been removed + #pragma warning(disable: 4820) // type': 'N' bytes padding added after data member 'member' +#elif defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpedantic" + #pragma clang diagnostic ignored "-Wpadded" + #if defined(__cplusplus) + #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" + #pragma clang diagnostic ignored "-Wold-style-cast" + #endif +#elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #pragma GCC diagnostic ignored "-Wpadded" + #if defined(__cplusplus) + #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + #pragma GCC diagnostic ignored "-Wold-style-cast" + #else + #if __GNUC__ >= 5 + #pragma GCC diagnostic ignored "-Wc90-c99-compat" + #pragma GCC diagnostic ignored "-Wc99-c11-compat" + #endif + #endif +#endif + +#if UFBX_PLATFORM_MSC + #define ufbx_inline static __forceinline +#elif UFBX_PLATFORM_GNUC + #define ufbx_inline static inline __attribute__((always_inline, unused)) +#else + #define ufbx_inline static +#endif + +// Assertion function used in ufbx, defaults to C standard `assert()`. +// You can define this to your custom preferred assert macro, but in that case +// make sure that it is also used within `ufbx.c`. +// Defining `UFBX_NO_ASSERT` to any value disables assertions. +#ifndef ufbx_assert + #if defined(UFBX_NO_ASSERT) || defined(UFBX_NO_LIBC) + #define ufbx_assert(cond) (void)0 + #else + #include + #define ufbx_assert(cond) assert(cond) + #endif +#endif + +// Pointer may be `NULL`. +#define ufbx_nullable + +// Changing this value from default or calling this function can lead into +// breaking API guarantees. +#define ufbx_unsafe + +// Linkage of the main ufbx API functions. +// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. +// If you want to isolate ufbx to a single translation unit you can do the following: +// #define UFBX_STATIC +// #include "ufbx.h" +// #include "ufbx.c" +#ifndef ufbx_abi + #if defined(UFBX_STATIC) + #define ufbx_abi static + #else + #define ufbx_abi + #endif +#endif + +// Linkage of the main ufbx data fields in the header. +// Defaults to `extern`, or `static` if `UFBX_STATIC` is defined. +#ifndef ufbx_abi_data + #if defined(UFBX_STATIC) + #define ufbx_abi_data static + #else + #define ufbx_abi_data extern + #endif +#endif + +// Linkage of the main ufbx data fields in the source. +// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. +#ifndef ufbx_abi_data_definition + #if defined(UFBX_STATIC) + #define ufbx_abi_data_def static + #else + #define ufbx_abi_data_def + #endif +#endif + +// -- Configuration + +#ifndef UFBX_REAL_TYPE + #if defined(UFBX_REAL_IS_FLOAT) + #define UFBX_REAL_TYPE float + #else + #define UFBX_REAL_TYPE double + #endif +#endif + +// Limits for embedded arrays within structures. +#define UFBX_ERROR_STACK_MAX_DEPTH 8 +#define UFBX_PANIC_MESSAGE_LENGTH 128 +#define UFBX_ERROR_INFO_LENGTH 256 + +// Number of thread groups to use if threading is enabled. +// A thread group processes a number of tasks and is then waited and potentially +// re-used later. In essence, this controls the granularity of threading. +#define UFBX_THREAD_GROUP_COUNT 4 + +// -- Language + +// bindgen-disable + +#if UFBX_CPP11 + +template +struct ufbxi_type_is { }; + +template +struct ufbxi_type_is { using type = int; }; + +template +struct ufbx_converter { }; + +#define UFBX_CONVERSION_IMPL(p_name) \ + template ::from(*(const p_name*)nullptr))>::type> \ + operator T() const { return ufbx_converter::from(*this); } + +#define UFBX_CONVERSION_TO_IMPL(p_name) \ + template ::to(*(const T*)nullptr))>::type> \ + p_name(const T &t) { *this = ufbx_converter::to(t); } + +#define UFBX_CONVERSION_LIST_IMPL(p_name) \ + template ::from_list((p_name*)nullptr, (size_t)0))>::type> \ + operator T() const { return ufbx_converter::from_list(data, count); } + +#else + +#define UFBX_CONVERSION_IMPL(p_name) +#define UFBX_CONVERSION_TO_IMPL(p_name) +#define UFBX_CONVERSION_LIST_IMPL(p_name) + +#endif + +#if defined(__cplusplus) + #define UFBX_LIST_TYPE(p_name, p_type) struct p_name { p_type *data; size_t count; \ + p_type &operator[](size_t index) const { ufbx_assert(index < count); return data[index]; } \ + p_type *begin() const { return data; } \ + p_type *end() const { return data + count; } \ + UFBX_CONVERSION_LIST_IMPL(p_type) \ + } +#else + #define UFBX_LIST_TYPE(p_name, p_type) typedef struct p_name { p_type *data; size_t count; } p_name +#endif + +// This cannot be enabled automatically if supported as the source file may be +// compiled with a different compiler using different settings than the header +// consumers, in practice it should work but it causes issues such as #70. +#if (UFBX_STDC >= 202311L || UFBX_CPP11) && defined(UFBX_USE_EXPLICIT_ENUM) + #define UFBX_ENUM_REPR : int + #define UFBX_ENUM_FORCE_WIDTH(p_prefix) + #define UFBX_FLAG_REPR : int + #define UFBX_FLAG_FORCE_WIDTH(p_prefix) + #define UFBX_HAS_FORCE_32BIT 0 +#else + #define UFBX_ENUM_REPR + #define UFBX_ENUM_FORCE_WIDTH(p_prefix) p_prefix##_FORCE_32BIT = 0x7fffffff + #define UFBX_FLAG_REPR + #define UFBX_FLAG_FORCE_WIDTH(p_prefix) p_prefix##_FORCE_32BIT = 0x7fffffff + #define UFBX_HAS_FORCE_32BIT 1 +#endif + +#define UFBX_ENUM_TYPE(p_name, p_prefix, p_last) \ + enum { p_prefix##_COUNT = p_last + 1 } + +#if UFBX_CPP + #define UFBX_VERTEX_ATTRIB_IMPL(p_type) \ + p_type &operator[](size_t index) const { ufbx_assert(index < indices.count); return values.data[indices.data[index]]; } +#else + #define UFBX_VERTEX_ATTRIB_IMPL(p_type) +#endif + +#if UFBX_CPP11 + #define UFBX_CALLBACK_IMPL(p_name, p_fn, p_return, p_params, p_args) \ + template static p_return _cpp_adapter p_params { F &f = *static_cast(user); return f p_args; } \ + p_name() = default; \ + p_name(p_fn *f) : fn(f), user(nullptr) { } \ + template p_name(F *f) : fn(&_cpp_adapter), user(static_cast(f)) { } +#else + #define UFBX_CALLBACK_IMPL(p_name, p_fn, p_return, p_params, p_args) +#endif + +// bindgen-enable + +// -- Version + +// Packing/unpacking for `UFBX_HEADER_VERSION` and `ufbx_source_version`. +#define ufbx_pack_version(major, minor, patch) ((major)*1000000u + (minor)*1000u + (patch)) +#define ufbx_version_major(version) ((uint32_t)(version)/1000000u%1000u) +#define ufbx_version_minor(version) ((uint32_t)(version)/1000u%1000u) +#define ufbx_version_patch(version) ((uint32_t)(version)%1000u) + +// Version of the ufbx header. +// `UFBX_VERSION` is simply an alias of `UFBX_HEADER_VERSION`. +// `ufbx_source_version` contains the version of the corresponding source file. +// HINT: The version can be compared numerically to the result of `ufbx_pack_version()`, +// for example `#if UFBX_VERSION >= ufbx_pack_version(0, 12, 0)`. +#define UFBX_HEADER_VERSION ufbx_pack_version(0, 20, 0) +#define UFBX_VERSION UFBX_HEADER_VERSION + +// -- Basic types + +// Main floating point type used everywhere in ufbx, defaults to `double`. +// If you define `UFBX_REAL_IS_FLOAT` to any value, `ufbx_real` will be defined +// as `float` instead. +// You can also manually define `UFBX_REAL_TYPE` to any floating point type. +typedef UFBX_REAL_TYPE ufbx_real; + +// Null-terminated UTF-8 encoded string within an FBX file +typedef struct ufbx_string { + const char *data; + size_t length; + + UFBX_CONVERSION_IMPL(ufbx_string) +} ufbx_string; + +// Opaque byte buffer blob +typedef struct ufbx_blob { + const void *data; + size_t size; + + UFBX_CONVERSION_IMPL(ufbx_blob) +} ufbx_blob; + +// 2D vector +typedef struct ufbx_vec2 { + union { + struct { ufbx_real x, y; }; + ufbx_real v[2]; + }; + + UFBX_CONVERSION_IMPL(ufbx_vec2) +} ufbx_vec2; + +// 3D vector +typedef struct ufbx_vec3 { + union { + struct { ufbx_real x, y, z; }; + ufbx_real v[3]; + }; + + UFBX_CONVERSION_IMPL(ufbx_vec3) +} ufbx_vec3; + +// 4D vector +typedef struct ufbx_vec4 { + union { + struct { ufbx_real x, y, z, w; }; + ufbx_real v[4]; + }; + + UFBX_CONVERSION_IMPL(ufbx_vec4) +} ufbx_vec4; + +// Quaternion +typedef struct ufbx_quat { + union { + struct { ufbx_real x, y, z, w; }; + ufbx_real v[4]; + }; + + UFBX_CONVERSION_IMPL(ufbx_quat) +} ufbx_quat; + +// Order in which Euler-angle rotation axes are applied for a transform +// NOTE: The order in the name refers to the order of axes *applied*, +// not the multiplication order: eg. `UFBX_ROTATION_ORDER_XYZ` is `Z*Y*X` +// [TODO: Figure out what the spheric rotation order is...] +typedef enum ufbx_rotation_order UFBX_ENUM_REPR { + UFBX_ROTATION_ORDER_XYZ, + UFBX_ROTATION_ORDER_XZY, + UFBX_ROTATION_ORDER_YZX, + UFBX_ROTATION_ORDER_YXZ, + UFBX_ROTATION_ORDER_ZXY, + UFBX_ROTATION_ORDER_ZYX, + UFBX_ROTATION_ORDER_SPHERIC, + + UFBX_ENUM_FORCE_WIDTH(UFBX_ROTATION_ORDER) +} ufbx_rotation_order; + +UFBX_ENUM_TYPE(ufbx_rotation_order, UFBX_ROTATION_ORDER, UFBX_ROTATION_ORDER_SPHERIC); + +// Explicit translation+rotation+scale transformation. +// NOTE: Rotation is a quaternion, not Euler angles! +typedef struct ufbx_transform { + ufbx_vec3 translation; + ufbx_quat rotation; + ufbx_vec3 scale; + + UFBX_CONVERSION_IMPL(ufbx_transform) +} ufbx_transform; + +// 4x3 matrix encoding an affine transformation. +// `cols[0..2]` are the X/Y/Z basis vectors, `cols[3]` is the translation +typedef struct ufbx_matrix { + union { + struct { + ufbx_real m00, m10, m20; + ufbx_real m01, m11, m21; + ufbx_real m02, m12, m22; + ufbx_real m03, m13, m23; + }; + ufbx_vec3 cols[4]; + ufbx_real v[12]; + }; + + UFBX_CONVERSION_IMPL(ufbx_matrix) +} ufbx_matrix; + +typedef struct ufbx_void_list { + void *data; + size_t count; +} ufbx_void_list; + +UFBX_LIST_TYPE(ufbx_bool_list, bool); +UFBX_LIST_TYPE(ufbx_uint32_list, uint32_t); +UFBX_LIST_TYPE(ufbx_real_list, ufbx_real); +UFBX_LIST_TYPE(ufbx_vec2_list, ufbx_vec2); +UFBX_LIST_TYPE(ufbx_vec3_list, ufbx_vec3); +UFBX_LIST_TYPE(ufbx_vec4_list, ufbx_vec4); +UFBX_LIST_TYPE(ufbx_string_list, ufbx_string); + +// Sentinel value used to represent a missing index. +#define UFBX_NO_INDEX ((uint32_t)~0u) + +// -- Document object model + +typedef enum ufbx_dom_value_type UFBX_ENUM_REPR { + UFBX_DOM_VALUE_NUMBER, + UFBX_DOM_VALUE_STRING, + UFBX_DOM_VALUE_BLOB, + UFBX_DOM_VALUE_ARRAY_I32, + UFBX_DOM_VALUE_ARRAY_I64, + UFBX_DOM_VALUE_ARRAY_F32, + UFBX_DOM_VALUE_ARRAY_F64, + UFBX_DOM_VALUE_ARRAY_BLOB, + UFBX_DOM_VALUE_ARRAY_IGNORED, + + UFBX_ENUM_FORCE_WIDTH(UFBX_DOM_VALUE_TYPE) +} ufbx_dom_value_type; + +UFBX_ENUM_TYPE(ufbx_dom_value_type, UFBX_DOM_VALUE_TYPE, UFBX_DOM_VALUE_ARRAY_IGNORED); + +typedef struct ufbx_dom_node ufbx_dom_node; + +UFBX_LIST_TYPE(ufbx_int32_list, int32_t); +UFBX_LIST_TYPE(ufbx_int64_list, int64_t); +UFBX_LIST_TYPE(ufbx_float_list, float); +UFBX_LIST_TYPE(ufbx_double_list, double); +UFBX_LIST_TYPE(ufbx_blob_list, ufbx_blob); + +typedef struct ufbx_dom_value { + ufbx_dom_value_type type; + ufbx_string value_str; + ufbx_blob value_blob; + int64_t value_int; + double value_float; +} ufbx_dom_value; + +UFBX_LIST_TYPE(ufbx_dom_node_list, ufbx_dom_node*); +UFBX_LIST_TYPE(ufbx_dom_value_list, ufbx_dom_value); + +struct ufbx_dom_node { + ufbx_string name; + ufbx_dom_node_list children; + ufbx_dom_value_list values; +}; + +// -- Properties + +// FBX elements have properties which are arbitrary key/value pairs that can +// have inherited default values or be animated. In most cases you don't need +// to access these unless you need a feature not implemented directly in ufbx. +// NOTE: Prefer using `ufbx_find_prop[_len](...)` to search for a property by +// name as it can find it from the defaults if necessary. + +typedef struct ufbx_prop ufbx_prop; +typedef struct ufbx_props ufbx_props; + +// Data type contained within the property. All the data fields are always +// populated regardless of type, so there's no need to switch by type usually +// eg. `prop->value_real` and `prop->value_int` have the same value (well, close) +// if `prop->type == UFBX_PROP_INTEGER`. String values are not converted from/to. +typedef enum ufbx_prop_type UFBX_ENUM_REPR { + UFBX_PROP_UNKNOWN, + UFBX_PROP_BOOLEAN, + UFBX_PROP_INTEGER, + UFBX_PROP_NUMBER, + UFBX_PROP_VECTOR, + UFBX_PROP_COLOR, + UFBX_PROP_COLOR_WITH_ALPHA, + UFBX_PROP_STRING, + UFBX_PROP_DATE_TIME, + UFBX_PROP_TRANSLATION, + UFBX_PROP_ROTATION, + UFBX_PROP_SCALING, + UFBX_PROP_DISTANCE, + UFBX_PROP_COMPOUND, + UFBX_PROP_BLOB, + UFBX_PROP_REFERENCE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_PROP_TYPE) +} ufbx_prop_type; + +UFBX_ENUM_TYPE(ufbx_prop_type, UFBX_PROP_TYPE, UFBX_PROP_REFERENCE); + +// Property flags: Advanced information about properties, not usually needed. +typedef enum ufbx_prop_flags UFBX_FLAG_REPR { + // Supports animation. + // NOTE: ufbx ignores this and allows animations on non-animatable properties. + UFBX_PROP_FLAG_ANIMATABLE = 0x1, + + // User defined (custom) property. + UFBX_PROP_FLAG_USER_DEFINED = 0x2, + + // Hidden in UI. + UFBX_PROP_FLAG_HIDDEN = 0x4, + + // Disallow modification from UI for components. + UFBX_PROP_FLAG_LOCK_X = 0x10, + UFBX_PROP_FLAG_LOCK_Y = 0x20, + UFBX_PROP_FLAG_LOCK_Z = 0x40, + UFBX_PROP_FLAG_LOCK_W = 0x80, + + // Disable animation from components. + UFBX_PROP_FLAG_MUTE_X = 0x100, + UFBX_PROP_FLAG_MUTE_Y = 0x200, + UFBX_PROP_FLAG_MUTE_Z = 0x400, + UFBX_PROP_FLAG_MUTE_W = 0x800, + + // Property created by ufbx when an element has a connected `ufbx_anim_prop` + // but doesn't contain the `ufbx_prop` it's referring to. + // NOTE: The property may have been found in the templated defaults. + UFBX_PROP_FLAG_SYNTHETIC = 0x1000, + + // The property has at least one `ufbx_anim_prop` in some layer. + UFBX_PROP_FLAG_ANIMATED = 0x2000, + + // Used by `ufbx_evaluate_prop()` to indicate the the property was not found. + UFBX_PROP_FLAG_NOT_FOUND = 0x4000, + + // The property is connected to another one. + // This use case is relatively rare so `ufbx_prop` does not track connections + // directly. You can find connections from `ufbx_element.connections_dst` where + // `ufbx_connection.dst_prop` is this property and `ufbx_connection.src_prop` is defined. + UFBX_PROP_FLAG_CONNECTED = 0x8000, + + // The value of this property is undefined (represented as zero). + UFBX_PROP_FLAG_NO_VALUE = 0x10000, + + // This property has been overridden by the user. + // See `ufbx_anim.prop_overrides` for more information. + UFBX_PROP_FLAG_OVERRIDDEN = 0x20000, + + // Value type. + // `REAL/VEC2/VEC3/VEC4` are mutually exclusive but may coexist with eg. `STRING` + // in some rare cases where the string defines the unit for the vector. + UFBX_PROP_FLAG_VALUE_REAL = 0x100000, + UFBX_PROP_FLAG_VALUE_VEC2 = 0x200000, + UFBX_PROP_FLAG_VALUE_VEC3 = 0x400000, + UFBX_PROP_FLAG_VALUE_VEC4 = 0x800000, + UFBX_PROP_FLAG_VALUE_INT = 0x1000000, + UFBX_PROP_FLAG_VALUE_STR = 0x2000000, + UFBX_PROP_FLAG_VALUE_BLOB = 0x4000000, + + UFBX_FLAG_FORCE_WIDTH(UFBX_PROP_FLAGS) +} ufbx_prop_flags; + +// Single property with name/type/value. +struct ufbx_prop { + ufbx_string name; + + uint32_t _internal_key; + + ufbx_prop_type type; + ufbx_prop_flags flags; + + ufbx_string value_str; + ufbx_blob value_blob; + int64_t value_int; + union { + ufbx_real value_real_arr[4]; + ufbx_real value_real; + ufbx_vec2 value_vec2; + ufbx_vec3 value_vec3; + ufbx_vec4 value_vec4; + }; +}; + +UFBX_LIST_TYPE(ufbx_prop_list, ufbx_prop); + +// List of alphabetically sorted properties with potential defaults. +// For animated objects in as scene from `ufbx_evaluate_scene()` this list +// only has the animated properties, the originals are stored under `defaults`. +struct ufbx_props { + ufbx_prop_list props; + size_t num_animated; + + ufbx_nullable ufbx_props *defaults; +}; + +typedef struct ufbx_scene ufbx_scene; + +// -- Elements + +// Element is the lowest level representation of the FBX file in ufbx. +// An element contains type, id, name, and properties (see `ufbx_props` above) +// Elements may be connected to each other arbitrarily via `ufbx_connection` + +typedef struct ufbx_element ufbx_element; + +// Unknown +typedef struct ufbx_unknown ufbx_unknown; + +// Nodes +typedef struct ufbx_node ufbx_node; + +// Node attributes (common) +typedef struct ufbx_mesh ufbx_mesh; +typedef struct ufbx_light ufbx_light; +typedef struct ufbx_camera ufbx_camera; +typedef struct ufbx_bone ufbx_bone; +typedef struct ufbx_empty ufbx_empty; + +// Node attributes (curves/surfaces) +typedef struct ufbx_line_curve ufbx_line_curve; +typedef struct ufbx_nurbs_curve ufbx_nurbs_curve; +typedef struct ufbx_nurbs_surface ufbx_nurbs_surface; +typedef struct ufbx_nurbs_trim_surface ufbx_nurbs_trim_surface; +typedef struct ufbx_nurbs_trim_boundary ufbx_nurbs_trim_boundary; + +// Node attributes (advanced) +typedef struct ufbx_procedural_geometry ufbx_procedural_geometry; +typedef struct ufbx_stereo_camera ufbx_stereo_camera; +typedef struct ufbx_camera_switcher ufbx_camera_switcher; +typedef struct ufbx_marker ufbx_marker; +typedef struct ufbx_lod_group ufbx_lod_group; + +// Deformers +typedef struct ufbx_skin_deformer ufbx_skin_deformer; +typedef struct ufbx_skin_cluster ufbx_skin_cluster; +typedef struct ufbx_blend_deformer ufbx_blend_deformer; +typedef struct ufbx_blend_channel ufbx_blend_channel; +typedef struct ufbx_blend_shape ufbx_blend_shape; +typedef struct ufbx_cache_deformer ufbx_cache_deformer; +typedef struct ufbx_cache_file ufbx_cache_file; + +// Materials +typedef struct ufbx_material ufbx_material; +typedef struct ufbx_texture ufbx_texture; +typedef struct ufbx_video ufbx_video; +typedef struct ufbx_shader ufbx_shader; +typedef struct ufbx_shader_binding ufbx_shader_binding; + +// Animation +typedef struct ufbx_anim_stack ufbx_anim_stack; +typedef struct ufbx_anim_layer ufbx_anim_layer; +typedef struct ufbx_anim_value ufbx_anim_value; +typedef struct ufbx_anim_curve ufbx_anim_curve; + +// Collections +typedef struct ufbx_display_layer ufbx_display_layer; +typedef struct ufbx_selection_set ufbx_selection_set; +typedef struct ufbx_selection_node ufbx_selection_node; + +// Constraints +typedef struct ufbx_character ufbx_character; +typedef struct ufbx_constraint ufbx_constraint; + +// Audio +typedef struct ufbx_audio_layer ufbx_audio_layer; +typedef struct ufbx_audio_clip ufbx_audio_clip; + +// Miscellaneous +typedef struct ufbx_pose ufbx_pose; +typedef struct ufbx_metadata_object ufbx_metadata_object; + +UFBX_LIST_TYPE(ufbx_element_list, ufbx_element*); +UFBX_LIST_TYPE(ufbx_unknown_list, ufbx_unknown*); +UFBX_LIST_TYPE(ufbx_node_list, ufbx_node*); +UFBX_LIST_TYPE(ufbx_mesh_list, ufbx_mesh*); +UFBX_LIST_TYPE(ufbx_light_list, ufbx_light*); +UFBX_LIST_TYPE(ufbx_camera_list, ufbx_camera*); +UFBX_LIST_TYPE(ufbx_bone_list, ufbx_bone*); +UFBX_LIST_TYPE(ufbx_empty_list, ufbx_empty*); +UFBX_LIST_TYPE(ufbx_line_curve_list, ufbx_line_curve*); +UFBX_LIST_TYPE(ufbx_nurbs_curve_list, ufbx_nurbs_curve*); +UFBX_LIST_TYPE(ufbx_nurbs_surface_list, ufbx_nurbs_surface*); +UFBX_LIST_TYPE(ufbx_nurbs_trim_surface_list, ufbx_nurbs_trim_surface*); +UFBX_LIST_TYPE(ufbx_nurbs_trim_boundary_list, ufbx_nurbs_trim_boundary*); +UFBX_LIST_TYPE(ufbx_procedural_geometry_list, ufbx_procedural_geometry*); +UFBX_LIST_TYPE(ufbx_stereo_camera_list, ufbx_stereo_camera*); +UFBX_LIST_TYPE(ufbx_camera_switcher_list, ufbx_camera_switcher*); +UFBX_LIST_TYPE(ufbx_marker_list, ufbx_marker*); +UFBX_LIST_TYPE(ufbx_lod_group_list, ufbx_lod_group*); +UFBX_LIST_TYPE(ufbx_skin_deformer_list, ufbx_skin_deformer*); +UFBX_LIST_TYPE(ufbx_skin_cluster_list, ufbx_skin_cluster*); +UFBX_LIST_TYPE(ufbx_blend_deformer_list, ufbx_blend_deformer*); +UFBX_LIST_TYPE(ufbx_blend_channel_list, ufbx_blend_channel*); +UFBX_LIST_TYPE(ufbx_blend_shape_list, ufbx_blend_shape*); +UFBX_LIST_TYPE(ufbx_cache_deformer_list, ufbx_cache_deformer*); +UFBX_LIST_TYPE(ufbx_cache_file_list, ufbx_cache_file*); +UFBX_LIST_TYPE(ufbx_material_list, ufbx_material*); +UFBX_LIST_TYPE(ufbx_texture_list, ufbx_texture*); +UFBX_LIST_TYPE(ufbx_video_list, ufbx_video*); +UFBX_LIST_TYPE(ufbx_shader_list, ufbx_shader*); +UFBX_LIST_TYPE(ufbx_shader_binding_list, ufbx_shader_binding*); +UFBX_LIST_TYPE(ufbx_anim_stack_list, ufbx_anim_stack*); +UFBX_LIST_TYPE(ufbx_anim_layer_list, ufbx_anim_layer*); +UFBX_LIST_TYPE(ufbx_anim_value_list, ufbx_anim_value*); +UFBX_LIST_TYPE(ufbx_anim_curve_list, ufbx_anim_curve*); +UFBX_LIST_TYPE(ufbx_display_layer_list, ufbx_display_layer*); +UFBX_LIST_TYPE(ufbx_selection_set_list, ufbx_selection_set*); +UFBX_LIST_TYPE(ufbx_selection_node_list, ufbx_selection_node*); +UFBX_LIST_TYPE(ufbx_character_list, ufbx_character*); +UFBX_LIST_TYPE(ufbx_constraint_list, ufbx_constraint*); +UFBX_LIST_TYPE(ufbx_audio_layer_list, ufbx_audio_layer*); +UFBX_LIST_TYPE(ufbx_audio_clip_list, ufbx_audio_clip*); +UFBX_LIST_TYPE(ufbx_pose_list, ufbx_pose*); +UFBX_LIST_TYPE(ufbx_metadata_object_list, ufbx_metadata_object*); + +typedef enum ufbx_element_type UFBX_ENUM_REPR { + UFBX_ELEMENT_UNKNOWN, // < `ufbx_unknown` + UFBX_ELEMENT_NODE, // < `ufbx_node` + UFBX_ELEMENT_MESH, // < `ufbx_mesh` + UFBX_ELEMENT_LIGHT, // < `ufbx_light` + UFBX_ELEMENT_CAMERA, // < `ufbx_camera` + UFBX_ELEMENT_BONE, // < `ufbx_bone` + UFBX_ELEMENT_EMPTY, // < `ufbx_empty` + UFBX_ELEMENT_LINE_CURVE, // < `ufbx_line_curve` + UFBX_ELEMENT_NURBS_CURVE, // < `ufbx_nurbs_curve` + UFBX_ELEMENT_NURBS_SURFACE, // < `ufbx_nurbs_surface` + UFBX_ELEMENT_NURBS_TRIM_SURFACE, // < `ufbx_nurbs_trim_surface` + UFBX_ELEMENT_NURBS_TRIM_BOUNDARY, // < `ufbx_nurbs_trim_boundary` + UFBX_ELEMENT_PROCEDURAL_GEOMETRY, // < `ufbx_procedural_geometry` + UFBX_ELEMENT_STEREO_CAMERA, // < `ufbx_stereo_camera` + UFBX_ELEMENT_CAMERA_SWITCHER, // < `ufbx_camera_switcher` + UFBX_ELEMENT_MARKER, // < `ufbx_marker` + UFBX_ELEMENT_LOD_GROUP, // < `ufbx_lod_group` + UFBX_ELEMENT_SKIN_DEFORMER, // < `ufbx_skin_deformer` + UFBX_ELEMENT_SKIN_CLUSTER, // < `ufbx_skin_cluster` + UFBX_ELEMENT_BLEND_DEFORMER, // < `ufbx_blend_deformer` + UFBX_ELEMENT_BLEND_CHANNEL, // < `ufbx_blend_channel` + UFBX_ELEMENT_BLEND_SHAPE, // < `ufbx_blend_shape` + UFBX_ELEMENT_CACHE_DEFORMER, // < `ufbx_cache_deformer` + UFBX_ELEMENT_CACHE_FILE, // < `ufbx_cache_file` + UFBX_ELEMENT_MATERIAL, // < `ufbx_material` + UFBX_ELEMENT_TEXTURE, // < `ufbx_texture` + UFBX_ELEMENT_VIDEO, // < `ufbx_video` + UFBX_ELEMENT_SHADER, // < `ufbx_shader` + UFBX_ELEMENT_SHADER_BINDING, // < `ufbx_shader_binding` + UFBX_ELEMENT_ANIM_STACK, // < `ufbx_anim_stack` + UFBX_ELEMENT_ANIM_LAYER, // < `ufbx_anim_layer` + UFBX_ELEMENT_ANIM_VALUE, // < `ufbx_anim_value` + UFBX_ELEMENT_ANIM_CURVE, // < `ufbx_anim_curve` + UFBX_ELEMENT_DISPLAY_LAYER, // < `ufbx_display_layer` + UFBX_ELEMENT_SELECTION_SET, // < `ufbx_selection_set` + UFBX_ELEMENT_SELECTION_NODE, // < `ufbx_selection_node` + UFBX_ELEMENT_CHARACTER, // < `ufbx_character` + UFBX_ELEMENT_CONSTRAINT, // < `ufbx_constraint` + UFBX_ELEMENT_AUDIO_LAYER, // < `ufbx_audio_layer` + UFBX_ELEMENT_AUDIO_CLIP, // < `ufbx_audio_clip` + UFBX_ELEMENT_POSE, // < `ufbx_pose` + UFBX_ELEMENT_METADATA_OBJECT, // < `ufbx_metadata_object` + + UFBX_ELEMENT_TYPE_FIRST_ATTRIB = UFBX_ELEMENT_MESH, + UFBX_ELEMENT_TYPE_LAST_ATTRIB = UFBX_ELEMENT_LOD_GROUP, + + UFBX_ENUM_FORCE_WIDTH(UFBX_ELEMENT_TYPE) +} ufbx_element_type; + +UFBX_ENUM_TYPE(ufbx_element_type, UFBX_ELEMENT_TYPE, UFBX_ELEMENT_METADATA_OBJECT); + +// Connection between two elements. +// Source and destination are somewhat arbitrary but the destination is +// often the "container" like a parent node or mesh containing a deformer. +typedef struct ufbx_connection { + ufbx_element *src; + ufbx_element *dst; + ufbx_string src_prop; + ufbx_string dst_prop; +} ufbx_connection; + +UFBX_LIST_TYPE(ufbx_connection_list, ufbx_connection); + +// Element "base-class" common to each element. +// Some fields (like `connections_src`) are advanced and not visible +// in the specialized element structs. +// NOTE: The `element_id` value is consistent when loading the +// _same_ file, but re-exporting the file will invalidate them. +struct ufbx_element { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + ufbx_element_type type; + ufbx_connection_list connections_src; + ufbx_connection_list connections_dst; + ufbx_nullable ufbx_dom_node *dom_node; + ufbx_scene *scene; +}; + +// -- Unknown + +struct ufbx_unknown { + // Shared "base-class" header, see `ufbx_element`. + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // FBX format specific type information. + // In ASCII FBX format: + // super_type: ID, "type::name", "sub_type" { ... } + ufbx_string type; + ufbx_string super_type; + ufbx_string sub_type; +}; + +// -- Nodes + +// Inherit type specifies how hierarchial node transforms are combined. +// This only affects the final scaling, as rotation and translation are always +// inherited correctly. +// NOTE: These don't map to `"InheritType"` property as there may be new ones for +// compatibility with various exporters. +typedef enum ufbx_inherit_mode UFBX_ENUM_REPR { + + // Normal matrix composition of hierarchy: `R*S*r*s`. + // child.node_to_world = parent.node_to_world * child.node_to_parent; + UFBX_INHERIT_MODE_NORMAL, + + // Ignore parent scale when computing the transform: `R*r*s`. + // ufbx_transform t = node.local_transform; + // t.translation *= parent.inherit_scale; + // t.scale *= node.inherit_scale_node.inherit_scale; + // child.node_to_world = parent.unscaled_node_to_world * t; + // Also known as "Segment scale compensate" in some software. + UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE, + + // Apply parent scale component-wise: `R*r*S*s`. + // ufbx_transform t = node.local_transform; + // t.translation *= parent.inherit_scale; + // t.scale *= node.inherit_scale_node.inherit_scale; + // child.node_to_world = parent.unscaled_node_to_world * t; + UFBX_INHERIT_MODE_COMPONENTWISE_SCALE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_INHERIT_MODE) +} ufbx_inherit_mode; + +UFBX_ENUM_TYPE(ufbx_inherit_mode, UFBX_INHERIT_MODE, UFBX_INHERIT_MODE_COMPONENTWISE_SCALE); + +// Axis used to mirror transformations for handedness conversion. +typedef enum ufbx_mirror_axis UFBX_ENUM_REPR { + + UFBX_MIRROR_AXIS_NONE, + UFBX_MIRROR_AXIS_X, + UFBX_MIRROR_AXIS_Y, + UFBX_MIRROR_AXIS_Z, + + UFBX_ENUM_FORCE_WIDTH(UFBX_MIRROR_AXIS) +} ufbx_mirror_axis; + +UFBX_ENUM_TYPE(ufbx_mirror_axis, UFBX_MIRROR_AXIS, UFBX_MIRROR_AXIS_Z); + +// Nodes form the scene transformation hierarchy and can contain attached +// elements such as meshes or lights. In normal cases a single `ufbx_node` +// contains only a single attached element, so using `type/mesh/...` is safe. +struct ufbx_node { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Node hierarchy + + // Parent node containing this one if not root. + // + // Always non-`NULL` for non-root nodes unless + // `ufbx_load_opts.allow_nodes_out_of_root` is enabled. + ufbx_nullable ufbx_node *parent; + + // List of child nodes parented to this node. + ufbx_node_list children; + + // Common attached element type and typed pointers. Set to `NULL` if not in + // use, so checking `attrib_type` is not required. + // + // HINT: If you need less common attributes access `ufbx_node.attrib`, you + // can use utility functions like `ufbx_as_nurbs_curve(attrib)` to convert + // and check the attribute in one step. + ufbx_nullable ufbx_mesh *mesh; + ufbx_nullable ufbx_light *light; + ufbx_nullable ufbx_camera *camera; + ufbx_nullable ufbx_bone *bone; + + // Less common attributes use these fields. + // + // Defined even if it is one of the above, eg. `ufbx_mesh`. In case there + // is multiple attributes this will be the first one. + ufbx_nullable ufbx_element *attrib; + + // Geometry transform helper if one exists. + // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. + ufbx_nullable ufbx_node *geometry_transform_helper; + + // Scale helper if one exists. + // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. + ufbx_nullable ufbx_node *scale_helper; + + // `attrib->type` if `attrib` is defined, otherwise `UFBX_ELEMENT_UNKNOWN`. + ufbx_element_type attrib_type; + + // List of _all_ attached attribute elements. + // + // In most cases there is only zero or one attributes per node, but if you + // have a very exotic FBX file nodes may have multiple attributes. + ufbx_element_list all_attribs; + + // Local transform in parent, geometry transform is a non-inherited + // transform applied only to attachments like meshes + ufbx_inherit_mode inherit_mode; + ufbx_inherit_mode original_inherit_mode; + ufbx_transform local_transform; + ufbx_transform geometry_transform; + + // Combined scale when using `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE`. + // Contains `local_transform.scale` otherwise. + ufbx_vec3 inherit_scale; + + // Node where scale is inherited from for `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE` + // and even for `UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE`. + // For componentwise-scale nodes, this will point to `parent`, for scale ignoring + // nodes this will point to the parent of the nearest componentwise-scaled node + // in the parent chain. + ufbx_nullable ufbx_node *inherit_scale_node; + + // Raw Euler angles in degrees for those who want them + + // Specifies the axis order `euler_rotation` is applied in. + ufbx_rotation_order rotation_order; + // Rotation around the local X/Y/Z axes in `rotation_order`. + // The angles are specified in degrees. + ufbx_vec3 euler_rotation; + + // Matrices derived from the transformations, for transforming geometry + // prefer using `geometry_to_world` as that supports geometric transforms. + + // Transform from this node to `parent` space. + // Equivalent to `ufbx_transform_to_matrix(&local_transform)`. + ufbx_matrix node_to_parent; + // Transform from this node to the world space, ie. multiplying all the + // `node_to_parent` matrices of the parent chain together. + ufbx_matrix node_to_world; + // Transform from the attribute to this node. Does not affect the transforms + // of `children`! + // Equivalent to `ufbx_transform_to_matrix(&geometry_transform)`. + ufbx_matrix geometry_to_node; + // Transform from attribute space to world space. + // Equivalent to `ufbx_matrix_mul(&node_to_world, &geometry_to_node)`. + ufbx_matrix geometry_to_world; + // Transform from this node to world space, ignoring self scaling. + ufbx_matrix unscaled_node_to_world; + + // ufbx-specific adjustment for switching between coodrinate/unit systems. + // HINT: In most cases you don't need to deal with these as these are baked + // into all the transforms above and into `ufbx_evaluate_transform()`. + ufbx_vec3 adjust_pre_translation; // < Translation applied between parent and self + ufbx_quat adjust_pre_rotation; // < Rotation applied between parent and self + ufbx_real adjust_pre_scale; // < Scaling applied between parent and self + ufbx_quat adjust_post_rotation; // < Rotation applied in local space at the end + ufbx_real adjust_post_scale; // < Scaling applied in local space at the end + ufbx_real adjust_translation_scale; // < Scaling applied to translation only + ufbx_mirror_axis adjust_mirror_axis; // < Mirror translation and rotation on this axis + + // Materials used by `mesh` or other `attrib`. + // There may be multiple copies of a single `ufbx_mesh` with different materials + // in the `ufbx_node` instances. + ufbx_material_list materials; + + // Bind pose + ufbx_nullable ufbx_pose *bind_pose; + + // Visibility state. + bool visible; + + // True if this node is the implicit root node of the scene. + bool is_root; + + // True if the node has a non-identity `geometry_transform`. + bool has_geometry_transform; + + // If `true` the transform is adjusted by ufbx, not enabled by default. + // See `adjust_pre_rotation`, `adjust_pre_scale`, `adjust_post_rotation`, + // and `adjust_post_scale`. + bool has_adjust_transform; + + // Scale is adjusted by root scale. + bool has_root_adjust_transform; + + // True if this node is a synthetic geometry transform helper. + // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. + bool is_geometry_transform_helper; + + // True if the node is a synthetic scale compensation helper. + // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. + bool is_scale_helper; + + // Parent node to children that can compensate for parent scale. + bool is_scale_compensate_parent; + + // How deep is this node in the parent hierarchy. Root node is at depth `0` + // and the immediate children of root at `1`. + uint32_t node_depth; +}; + +// Vertex attribute: All attributes are stored in a consistent indexed format +// regardless of how it's actually stored in the file. +// +// `values` is a contiguous array of attribute values. +// `indices` maps each mesh index into a value in the `values` array. +// +// If `unique_per_vertex` is set then the attribute is guaranteed to have a +// single defined value per vertex accessible via: +// attrib.values.data[attrib.indices.data[mesh->vertex_first_index[vertex_ix]] +typedef struct ufbx_vertex_attrib { + // Is this attribute defined by the mesh. + bool exists; + // List of values the attribute uses. + ufbx_void_list values; + // Indices into `values[]`, indexed up to `ufbx_mesh.num_indices`. + ufbx_uint32_list indices; + // Number of `ufbx_real` entries per value. + size_t value_reals; + // `true` if this attribute is defined per vertex, instead of per index. + bool unique_per_vertex; + // Optional 4th 'W' component for the attribute. + // May be defined for the following: + // ufbx_mesh.vertex_normal + // ufbx_mesh.vertex_tangent / ufbx_uv_set.vertex_tangent + // ufbx_mesh.vertex_bitangent / ufbx_uv_set.vertex_bitangent + // NOTE: This is not loaded by default, set `ufbx_load_opts.retain_vertex_attrib_w`. + ufbx_real_list values_w; +} ufbx_vertex_attrib; + +// 1D vertex attribute, see `ufbx_vertex_attrib` for information +typedef struct ufbx_vertex_real { + bool exists; + ufbx_real_list values; + ufbx_uint32_list indices; + size_t value_reals; + bool unique_per_vertex; + ufbx_real_list values_w; + + UFBX_VERTEX_ATTRIB_IMPL(ufbx_real) +} ufbx_vertex_real; + +// 2D vertex attribute, see `ufbx_vertex_attrib` for information +typedef struct ufbx_vertex_vec2 { + bool exists; + ufbx_vec2_list values; + ufbx_uint32_list indices; + size_t value_reals; + bool unique_per_vertex; + ufbx_real_list values_w; + + UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec2) +} ufbx_vertex_vec2; + +// 3D vertex attribute, see `ufbx_vertex_attrib` for information +typedef struct ufbx_vertex_vec3 { + bool exists; + ufbx_vec3_list values; + ufbx_uint32_list indices; + size_t value_reals; + bool unique_per_vertex; + ufbx_real_list values_w; + + UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec3) +} ufbx_vertex_vec3; + +// 4D vertex attribute, see `ufbx_vertex_attrib` for information +typedef struct ufbx_vertex_vec4 { + bool exists; + ufbx_vec4_list values; + ufbx_uint32_list indices; + size_t value_reals; + bool unique_per_vertex; + ufbx_real_list values_w; + + UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec4) +} ufbx_vertex_vec4; + +// Vertex UV set/layer +typedef struct ufbx_uv_set { + ufbx_string name; + uint32_t index; + + // Vertex attributes, see `ufbx_mesh` attributes for more information + ufbx_vertex_vec2 vertex_uv; // < UV / texture coordinates + ufbx_vertex_vec3 vertex_tangent; // < (optional) Tangent vector in UV.x direction + ufbx_vertex_vec3 vertex_bitangent; // < (optional) Tangent vector in UV.y direction +} ufbx_uv_set; + +// Vertex color set/layer +typedef struct ufbx_color_set { + ufbx_string name; + uint32_t index; + + // Vertex attributes, see `ufbx_mesh` attributes for more information + ufbx_vertex_vec4 vertex_color; // < Per-vertex RGBA color +} ufbx_color_set; + +UFBX_LIST_TYPE(ufbx_uv_set_list, ufbx_uv_set); +UFBX_LIST_TYPE(ufbx_color_set_list, ufbx_color_set); + +// Edge between two _indices_ in a mesh +typedef struct ufbx_edge { + union { + struct { uint32_t a, b; }; + uint32_t indices[2]; + }; +} ufbx_edge; + +UFBX_LIST_TYPE(ufbx_edge_list, ufbx_edge); + +// Polygonal face with arbitrary number vertices, a single face contains a +// contiguous range of mesh indices, eg. `{5,3}` would have indices 5, 6, 7 +// +// NOTE: `num_indices` maybe less than 3 in which case the face is invalid! +// [TODO #23: should probably remove the bad faces at load time] +typedef struct ufbx_face { + uint32_t index_begin; + uint32_t num_indices; +} ufbx_face; + +UFBX_LIST_TYPE(ufbx_face_list, ufbx_face); + +// Subset of mesh faces used by a single material or group. +typedef struct ufbx_mesh_part { + + // Index of the mesh part. + uint32_t index; + + // Sub-set of the geometry + size_t num_faces; // < Number of faces (polygons) + size_t num_triangles; // < Number of triangles if triangulated + + size_t num_empty_faces; // < Number of faces with zero vertices + size_t num_point_faces; // < Number of faces with a single vertex + size_t num_line_faces; // < Number of faces with two vertices + + // Indices to `ufbx_mesh.faces[]`. + // Always contains `num_faces` elements. + ufbx_uint32_list face_indices; + +} ufbx_mesh_part; + +UFBX_LIST_TYPE(ufbx_mesh_part_list, ufbx_mesh_part); + +typedef struct ufbx_face_group { + int32_t id; // < Numerical ID for this group. + ufbx_string name; // < Name for the face group. +} ufbx_face_group; + +UFBX_LIST_TYPE(ufbx_face_group_list, ufbx_face_group); + +typedef struct ufbx_subdivision_weight_range { + uint32_t weight_begin; + uint32_t num_weights; +} ufbx_subdivision_weight_range; + +UFBX_LIST_TYPE(ufbx_subdivision_weight_range_list, ufbx_subdivision_weight_range); + +typedef struct ufbx_subdivision_weight { + ufbx_real weight; + uint32_t index; +} ufbx_subdivision_weight; + +UFBX_LIST_TYPE(ufbx_subdivision_weight_list, ufbx_subdivision_weight); + +typedef struct ufbx_subdivision_result { + size_t result_memory_used; + size_t temp_memory_used; + size_t result_allocs; + size_t temp_allocs; + + // Weights of vertices in the source model. + // Defined if `ufbx_subdivide_opts.evaluate_source_vertices` is set. + ufbx_subdivision_weight_range_list source_vertex_ranges; + ufbx_subdivision_weight_list source_vertex_weights; + + // Weights of skin clusters in the source model. + // Defined if `ufbx_subdivide_opts.evaluate_skin_weights` is set. + ufbx_subdivision_weight_range_list skin_cluster_ranges; + ufbx_subdivision_weight_list skin_cluster_weights; + +} ufbx_subdivision_result; + +typedef enum ufbx_subdivision_display_mode UFBX_ENUM_REPR { + UFBX_SUBDIVISION_DISPLAY_DISABLED, + UFBX_SUBDIVISION_DISPLAY_HULL, + UFBX_SUBDIVISION_DISPLAY_HULL_AND_SMOOTH, + UFBX_SUBDIVISION_DISPLAY_SMOOTH, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SUBDIVISION_DISPLAY_MODE) +} ufbx_subdivision_display_mode; + +UFBX_ENUM_TYPE(ufbx_subdivision_display_mode, UFBX_SUBDIVISION_DISPLAY_MODE, UFBX_SUBDIVISION_DISPLAY_SMOOTH); + +typedef enum ufbx_subdivision_boundary UFBX_ENUM_REPR { + UFBX_SUBDIVISION_BOUNDARY_DEFAULT, + UFBX_SUBDIVISION_BOUNDARY_LEGACY, + // OpenSubdiv: `VTX_BOUNDARY_EDGE_AND_CORNER` / `FVAR_LINEAR_CORNERS_ONLY` + UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS, + // OpenSubdiv: `VTX_BOUNDARY_EDGE_ONLY` / `FVAR_LINEAR_NONE` + UFBX_SUBDIVISION_BOUNDARY_SHARP_NONE, + // OpenSubdiv: `FVAR_LINEAR_BOUNDARIES` + UFBX_SUBDIVISION_BOUNDARY_SHARP_BOUNDARY, + // OpenSubdiv: `FVAR_LINEAR_ALL` + UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SUBDIVISION_BOUNDARY) +} ufbx_subdivision_boundary; + +UFBX_ENUM_TYPE(ufbx_subdivision_boundary, UFBX_SUBDIVISION_BOUNDARY, UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR); + +// Polygonal mesh geometry. +// +// Example mesh with two triangles (x, z) and a quad (y). +// The faces have a constant UV coordinate x/y/z. +// The vertices have _per vertex_ normals that point up/down. +// +// ^ ^ ^ +// A---B-----C +// |x / /| +// | / y / | +// |/ / z| +// D-----E---F +// v v v +// +// Attributes may have multiple values within a single vertex, for example a +// UV seam vertex has two UV coordinates. Thus polygons are defined using +// an index that counts each corner of each face polygon. If an attribute is +// defined (even per-vertex) it will always have a valid `indices` array. +// +// {0,3} {3,4} {7,3} faces ({ index_begin, num_indices }) +// 0 1 2 3 4 5 6 7 8 9 index +// +// 0 1 3 1 2 4 3 2 4 5 vertex_indices[index] +// A B D B C E D C E F vertices[vertex_indices[index]] +// +// 0 0 1 0 0 1 1 0 1 1 vertex_normal.indices[index] +// ^ ^ v ^ ^ v v ^ v v vertex_normal.data[vertex_normal.indices[index]] +// +// 0 0 0 1 1 1 1 2 2 2 vertex_uv.indices[index] +// x x x y y y y z z z vertex_uv.data[vertex_uv.indices[index]] +// +// Vertex position can also be accessed uniformly through an accessor: +// 0 1 3 1 2 4 3 2 4 5 vertex_position.indices[index] +// A B D B C E D C E F vertex_position.data[vertex_position.indices[index]] +// +// Some geometry data is specified per logical vertex. Vertex positions are +// the only attribute that is guaranteed to be defined _uniquely_ per vertex. +// Vertex attributes _may_ be defined per vertex if `unique_per_vertex == true`. +// You can access the per-vertex values by first finding the first index that +// refers to the given vertex. +// +// 0 1 2 3 4 5 vertex +// A B C D E F vertices[vertex] +// +// 0 1 4 2 5 9 vertex_first_index[vertex] +// 0 0 0 1 1 1 vertex_normal.indices[vertex_first_index[vertex]] +// ^ ^ ^ v v v vertex_normal.data[vertex_normal.indices[vertex_first_index[vertex]]] +// +struct ufbx_mesh { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Number of "logical" vertices that would be treated as a single point, + // one vertex may be split to multiple indices for split attributes, eg. UVs + size_t num_vertices; // < Number of logical "vertex" points + size_t num_indices; // < Number of combiend vertex/attribute tuples + size_t num_faces; // < Number of faces (polygons) in the mesh + size_t num_triangles; // < Number of triangles if triangulated + + // Number of edges in the mesh. + // NOTE: May be zero in valid meshes if the file doesn't contain edge adjacency data! + size_t num_edges; + + size_t max_face_triangles; // < Maximum number of triangles in a face in this mesh + + size_t num_empty_faces; // < Number of faces with zero vertices + size_t num_point_faces; // < Number of faces with a single vertex + size_t num_line_faces; // < Number of faces with two vertices + + // Faces and optional per-face extra data + ufbx_face_list faces; // < Face index range + ufbx_bool_list face_smoothing; // < Should the face have soft normals + ufbx_uint32_list face_material; // < Indices to `ufbx_mesh.materials[]` and `ufbx_node.materials[]` + ufbx_uint32_list face_group; // < Face polygon group index, indices to `ufbx_mesh.face_groups[]` + ufbx_bool_list face_hole; // < Should the face be hidden as a "hole" + + // Edges and optional per-edge extra data + ufbx_edge_list edges; // < Edge index range + ufbx_bool_list edge_smoothing; // < Should the edge have soft normals + ufbx_real_list edge_crease; // < Crease value for subdivision surfaces + ufbx_bool_list edge_visibility; // < Should the edge be visible + + // Logical vertices and positions, alternatively you can use + // `vertex_position` for consistent interface with other attributes. + ufbx_uint32_list vertex_indices; + ufbx_vec3_list vertices; + + // First index referring to a given vertex, `UFBX_NO_INDEX` if the vertex is unused. + ufbx_uint32_list vertex_first_index; + + // Vertex attributes, see the comment over the struct. + // + // NOTE: Not all meshes have all attributes, in that case `indices/data == NULL`! + // + // NOTE: UV/tangent/bitangent and color are the from first sets, + // use `uv_sets/color_sets` to access the other layers. + ufbx_vertex_vec3 vertex_position; // < Vertex positions + ufbx_vertex_vec3 vertex_normal; // < (optional) Normal vectors, always defined if `ufbx_load_opts.generate_missing_normals` + ufbx_vertex_vec2 vertex_uv; // < (optional) UV / texture coordinates + ufbx_vertex_vec3 vertex_tangent; // < (optional) Tangent vector in UV.x direction + ufbx_vertex_vec3 vertex_bitangent; // < (optional) Tangent vector in UV.y direction + ufbx_vertex_vec4 vertex_color; // < (optional) Per-vertex RGBA color + ufbx_vertex_real vertex_crease; // < (optional) Crease value for subdivision surfaces + + // Multiple named UV/color sets + // NOTE: The first set contains the same data as `vertex_uv/color`! + ufbx_uv_set_list uv_sets; + ufbx_color_set_list color_sets; + + // Materials used by the mesh. + // NOTE: These can be wrong if you want to support per-instance materials! + // Use `ufbx_node.materials[]` to get the per-instance materials at the same indices. + ufbx_material_list materials; + + // Face groups for this mesh. + ufbx_face_group_list face_groups; + + // Segments that use a given material. + // Defined even if the mesh doesn't have any materials. + ufbx_mesh_part_list material_parts; + + // Segments for each face group. + ufbx_mesh_part_list face_group_parts; + + // Order of `material_parts` by first face that refers to it. + // Useful for compatibility with FBX SDK and various importers using it, + // as they use this material order by default. + ufbx_uint32_list material_part_usage_order; + + // Skinned vertex positions, for efficiency the skinned positions are the + // same as the static ones for non-skinned meshes and `skinned_is_local` + // is set to true meaning you need to transform them manually using + // `ufbx_transform_position(&node->geometry_to_world, skinned_pos)`! + bool skinned_is_local; + ufbx_vertex_vec3 skinned_position; + ufbx_vertex_vec3 skinned_normal; + + // Deformers + ufbx_skin_deformer_list skin_deformers; + ufbx_blend_deformer_list blend_deformers; + ufbx_cache_deformer_list cache_deformers; + ufbx_element_list all_deformers; + + // Subdivision + uint32_t subdivision_preview_levels; + uint32_t subdivision_render_levels; + ufbx_subdivision_display_mode subdivision_display_mode; + ufbx_subdivision_boundary subdivision_boundary; + ufbx_subdivision_boundary subdivision_uv_boundary; + + // The winding of the faces has been reversed. + bool reversed_winding; + + // Normals have been generated instead of evaluated. + // Either from missing normals (via `ufbx_load_opts.generate_missing_normals`), skinning, + // tessellation, or subdivision. + bool generated_normals; + + // Subdivision (result) + bool subdivision_evaluated; + ufbx_nullable ufbx_subdivision_result *subdivision_result; + + // Tessellation (result) + bool from_tessellated_nurbs; +}; + +// The kind of light source +typedef enum ufbx_light_type UFBX_ENUM_REPR { + // Single point at local origin, at `node->world_transform.position` + UFBX_LIGHT_POINT, + // Infinite directional light pointing locally towards `light->local_direction` + // For global: `ufbx_transform_direction(&node->node_to_world, light->local_direction)` + UFBX_LIGHT_DIRECTIONAL, + // Cone shaped light towards `light->local_direction`, between `light->inner/outer_angle`. + // For global: `ufbx_transform_direction(&node->node_to_world, light->local_direction)` + UFBX_LIGHT_SPOT, + // Area light, shape specified by `light->area_shape` + // TODO: Units? + UFBX_LIGHT_AREA, + // Volumetric light source + // TODO: How does this work + UFBX_LIGHT_VOLUME, + + UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_TYPE) +} ufbx_light_type; + +UFBX_ENUM_TYPE(ufbx_light_type, UFBX_LIGHT_TYPE, UFBX_LIGHT_VOLUME); + +// How fast does the light intensity decay at a distance +typedef enum ufbx_light_decay UFBX_ENUM_REPR { + UFBX_LIGHT_DECAY_NONE, // < 1 (no decay) + UFBX_LIGHT_DECAY_LINEAR, // < 1 / d + UFBX_LIGHT_DECAY_QUADRATIC, // < 1 / d^2 (physically accurate) + UFBX_LIGHT_DECAY_CUBIC, // < 1 / d^3 + + UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_DECAY) +} ufbx_light_decay; + +UFBX_ENUM_TYPE(ufbx_light_decay, UFBX_LIGHT_DECAY, UFBX_LIGHT_DECAY_CUBIC); + +typedef enum ufbx_light_area_shape UFBX_ENUM_REPR { + UFBX_LIGHT_AREA_SHAPE_RECTANGLE, + UFBX_LIGHT_AREA_SHAPE_SPHERE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_AREA_SHAPE) +} ufbx_light_area_shape; + +UFBX_ENUM_TYPE(ufbx_light_area_shape, UFBX_LIGHT_AREA_SHAPE, UFBX_LIGHT_AREA_SHAPE_SPHERE); + +// Light source attached to a `ufbx_node` +struct ufbx_light { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Color and intensity of the light, usually you want to use `color * intensity` + // NOTE: `intensity` is 0.01x of the property `"Intensity"` as that matches + // matches values in DCC programs before exporting. + ufbx_vec3 color; + ufbx_real intensity; + + // Direction the light is aimed at in node's local space, usually -Y + ufbx_vec3 local_direction; + + // Type of the light and shape parameters + ufbx_light_type type; + ufbx_light_decay decay; + ufbx_light_area_shape area_shape; + ufbx_real inner_angle; + ufbx_real outer_angle; + + bool cast_light; + bool cast_shadows; +}; + +typedef enum ufbx_projection_mode UFBX_ENUM_REPR { + // Perspective projection. + UFBX_PROJECTION_MODE_PERSPECTIVE, + + // Orthographic projection. + UFBX_PROJECTION_MODE_ORTHOGRAPHIC, + + UFBX_ENUM_FORCE_WIDTH(UFBX_PROJECTION_MODE) +} ufbx_projection_mode; + +UFBX_ENUM_TYPE(ufbx_projection_mode, UFBX_PROJECTION_MODE, UFBX_PROJECTION_MODE_ORTHOGRAPHIC); + +// Method of specifying the rendering resolution from properties +// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! +typedef enum ufbx_aspect_mode UFBX_ENUM_REPR { + // No defined resolution + UFBX_ASPECT_MODE_WINDOW_SIZE, + // `"AspectWidth"` and `"AspectHeight"` are relative to each other + UFBX_ASPECT_MODE_FIXED_RATIO, + // `"AspectWidth"` and `"AspectHeight"` are both pixels + UFBX_ASPECT_MODE_FIXED_RESOLUTION, + // `"AspectWidth"` is pixels, `"AspectHeight"` is relative to width + UFBX_ASPECT_MODE_FIXED_WIDTH, + // < `"AspectHeight"` is pixels, `"AspectWidth"` is relative to height + UFBX_ASPECT_MODE_FIXED_HEIGHT, + + UFBX_ENUM_FORCE_WIDTH(UFBX_ASPECT_MODE) +} ufbx_aspect_mode; + +UFBX_ENUM_TYPE(ufbx_aspect_mode, UFBX_ASPECT_MODE, UFBX_ASPECT_MODE_FIXED_HEIGHT); + +// Method of specifying the field of view from properties +// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! +typedef enum ufbx_aperture_mode UFBX_ENUM_REPR { + // Use separate `"FieldOfViewX"` and `"FieldOfViewY"` as horizontal/vertical FOV angles + UFBX_APERTURE_MODE_HORIZONTAL_AND_VERTICAL, + // Use `"FieldOfView"` as horizontal FOV angle, derive vertical angle via aspect ratio + UFBX_APERTURE_MODE_HORIZONTAL, + // Use `"FieldOfView"` as vertical FOV angle, derive horizontal angle via aspect ratio + UFBX_APERTURE_MODE_VERTICAL, + // Compute the field of view from the render gate size and focal length + UFBX_APERTURE_MODE_FOCAL_LENGTH, + + UFBX_ENUM_FORCE_WIDTH(UFBX_APERTURE_MODE) +} ufbx_aperture_mode; + +UFBX_ENUM_TYPE(ufbx_aperture_mode, UFBX_APERTURE_MODE, UFBX_APERTURE_MODE_FOCAL_LENGTH); + +// Method of specifying the render gate size from properties +// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! +typedef enum ufbx_gate_fit UFBX_ENUM_REPR { + // Use the film/aperture size directly as the render gate + UFBX_GATE_FIT_NONE, + // Fit the render gate to the height of the film, derive width from aspect ratio + UFBX_GATE_FIT_VERTICAL, + // Fit the render gate to the width of the film, derive height from aspect ratio + UFBX_GATE_FIT_HORIZONTAL, + // Fit the render gate so that it is fully contained within the film gate + UFBX_GATE_FIT_FILL, + // Fit the render gate so that it fully contains the film gate + UFBX_GATE_FIT_OVERSCAN, + // Stretch the render gate to match the film gate + // TODO: Does this differ from `UFBX_GATE_FIT_NONE`? + UFBX_GATE_FIT_STRETCH, + + UFBX_ENUM_FORCE_WIDTH(UFBX_GATE_FIT) +} ufbx_gate_fit; + +UFBX_ENUM_TYPE(ufbx_gate_fit, UFBX_GATE_FIT, UFBX_GATE_FIT_STRETCH); + +// Camera film/aperture size defaults +// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! +typedef enum ufbx_aperture_format UFBX_ENUM_REPR { + UFBX_APERTURE_FORMAT_CUSTOM, // < Use `"FilmWidth"` and `"FilmHeight"` + UFBX_APERTURE_FORMAT_16MM_THEATRICAL, // < 0.404 x 0.295 inches + UFBX_APERTURE_FORMAT_SUPER_16MM, // < 0.493 x 0.292 inches + UFBX_APERTURE_FORMAT_35MM_ACADEMY, // < 0.864 x 0.630 inches + UFBX_APERTURE_FORMAT_35MM_TV_PROJECTION, // < 0.816 x 0.612 inches + UFBX_APERTURE_FORMAT_35MM_FULL_APERTURE, // < 0.980 x 0.735 inches + UFBX_APERTURE_FORMAT_35MM_185_PROJECTION, // < 0.825 x 0.446 inches + UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC, // < 0.864 x 0.732 inches (squeeze ratio: 2) + UFBX_APERTURE_FORMAT_70MM_PROJECTION, // < 2.066 x 0.906 inches + UFBX_APERTURE_FORMAT_VISTAVISION, // < 1.485 x 0.991 inches + UFBX_APERTURE_FORMAT_DYNAVISION, // < 2.080 x 1.480 inches + UFBX_APERTURE_FORMAT_IMAX, // < 2.772 x 2.072 inches + + UFBX_ENUM_FORCE_WIDTH(UFBX_APERTURE_FORMAT) +} ufbx_aperture_format; + +UFBX_ENUM_TYPE(ufbx_aperture_format, UFBX_APERTURE_FORMAT, UFBX_APERTURE_FORMAT_IMAX); + +typedef enum ufbx_coordinate_axis UFBX_ENUM_REPR { + UFBX_COORDINATE_AXIS_POSITIVE_X, + UFBX_COORDINATE_AXIS_NEGATIVE_X, + UFBX_COORDINATE_AXIS_POSITIVE_Y, + UFBX_COORDINATE_AXIS_NEGATIVE_Y, + UFBX_COORDINATE_AXIS_POSITIVE_Z, + UFBX_COORDINATE_AXIS_NEGATIVE_Z, + UFBX_COORDINATE_AXIS_UNKNOWN, + + UFBX_ENUM_FORCE_WIDTH(UFBX_COORDINATE_AXIS) +} ufbx_coordinate_axis; + +UFBX_ENUM_TYPE(ufbx_coordinate_axis, UFBX_COORDINATE_AXIS, UFBX_COORDINATE_AXIS_UNKNOWN); + +// Coordinate axes the scene is represented in. +// NOTE: `front` is the _opposite_ from forward! +typedef struct ufbx_coordinate_axes { + ufbx_coordinate_axis right; + ufbx_coordinate_axis up; + ufbx_coordinate_axis front; +} ufbx_coordinate_axes; + +// Camera attached to a `ufbx_node` +struct ufbx_camera { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Projection mode (perspective/orthographic). + ufbx_projection_mode projection_mode; + + // If set to `true`, `resolution` represents actual pixel values, otherwise + // it's only useful for its aspect ratio. + bool resolution_is_pixels; + + // Render resolution, either in pixels or arbitrary units, depending on above + ufbx_vec2 resolution; + + // Horizontal/vertical field of view in degrees + // Valid if `projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE`. + ufbx_vec2 field_of_view_deg; + + // Component-wise `tan(field_of_view_deg)`, also represents the size of the + // proection frustum slice at distance of 1. + // Valid if `projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE`. + ufbx_vec2 field_of_view_tan; + + // Orthographic camera extents. + // Valid if `projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC`. + ufbx_real orthographic_extent; + + // Orthographic camera size. + // Valid if `projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC`. + ufbx_vec2 orthographic_size; + + // Size of the projection plane at distance 1. + // Equal to `field_of_view_tan` if perspective, `orthographic_size` if orthographic. + ufbx_vec2 projection_plane; + + // Aspect ratio of the camera. + ufbx_real aspect_ratio; + + // Near plane of the frustum in units from the camera. + ufbx_real near_plane; + + // Far plane of the frustum in units from the camera. + ufbx_real far_plane; + + // Coordinate system that the projection uses. + // FBX saves cameras with +X forward and +Y up, but you can override this using + // `ufbx_load_opts.target_camera_axes` and it will be reflected here. + ufbx_coordinate_axes projection_axes; + + // Advanced properties used to compute the above + ufbx_aspect_mode aspect_mode; + ufbx_aperture_mode aperture_mode; + ufbx_gate_fit gate_fit; + ufbx_aperture_format aperture_format; + ufbx_real focal_length_mm; // < Focal length in millimeters + ufbx_vec2 film_size_inch; // < Film size in inches + ufbx_vec2 aperture_size_inch; // < Aperture/film gate size in inches + ufbx_real squeeze_ratio; // < Anamoprhic stretch ratio +}; + +// Bone attached to a `ufbx_node`, provides the logical length of the bone +// but most interesting information is directly in `ufbx_node`. +struct ufbx_bone { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Visual radius of the bone + ufbx_real radius; + + // Length of the bone relative to the distance between two nodes + ufbx_real relative_length; + + // Is the bone a root bone + bool is_root; +}; + +// Empty/NULL/locator connected to a node, actual details in `ufbx_node` +struct ufbx_empty { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; +}; + +// -- Node attributes (curves/surfaces) + +// Segment of a `ufbx_line_curve`, indices refer to `ufbx_line_curve.point_indices[]` +typedef struct ufbx_line_segment { + uint32_t index_begin; + uint32_t num_indices; +} ufbx_line_segment; + +UFBX_LIST_TYPE(ufbx_line_segment_list, ufbx_line_segment); + +struct ufbx_line_curve { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + ufbx_vec3 color; + + ufbx_vec3_list control_points; // < List of possible values the line passes through + ufbx_uint32_list point_indices; // < Indices to `control_points[]` the line goes through + + ufbx_line_segment_list segments; + + // Tessellation (result) + bool from_tessellated_nurbs; +}; + +typedef enum ufbx_nurbs_topology UFBX_ENUM_REPR { + // The endpoints are not connected. + UFBX_NURBS_TOPOLOGY_OPEN, + // Repeats first `ufbx_nurbs_basis.order - 1` control points after the end. + UFBX_NURBS_TOPOLOGY_PERIODIC, + // Repeats the first control point after the end. + UFBX_NURBS_TOPOLOGY_CLOSED, + + UFBX_ENUM_FORCE_WIDTH(UFBX_NURBS_TOPOLOGY) +} ufbx_nurbs_topology; + +UFBX_ENUM_TYPE(ufbx_nurbs_topology, UFBX_NURBS_TOPOLOGY, UFBX_NURBS_TOPOLOGY_CLOSED); + +// NURBS basis functions for an axis +typedef struct ufbx_nurbs_basis { + + // Number of control points influencing a point on the curve/surface. + // Equal to the degree plus one. + uint32_t order; + + // Topology (periodicity) of the dimension. + ufbx_nurbs_topology topology; + + // Subdivision of the parameter range to control points. + ufbx_real_list knot_vector; + + // Range for the parameter value. + ufbx_real t_min; + ufbx_real t_max; + + // Parameter values of control points. + ufbx_real_list spans; + + // `true` if this axis is two-dimensional. + bool is_2d; + + // Number of control points that need to be copied to the end. + // This is just for convenience as it could be derived from `topology` and + // `order`. If for example `num_wrap_control_points == 3` you should repeat + // the first 3 control points after the end. + // HINT: You don't need to worry about this if you use ufbx functions + // like `ufbx_evaluate_nurbs_curve()` as they handle this internally. + size_t num_wrap_control_points; + + // `true` if the parametrization is well defined. + bool valid; + +} ufbx_nurbs_basis; + +struct ufbx_nurbs_curve { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Basis in the U axis + ufbx_nurbs_basis basis; + + // Linear array of control points + // NOTE: The control points are _not_ homogeneous, meaning you have to multiply + // them by `w` before evaluating the surface. + ufbx_vec4_list control_points; +}; + +struct ufbx_nurbs_surface { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Basis in the U/V axes + ufbx_nurbs_basis basis_u; + ufbx_nurbs_basis basis_v; + + // Number of control points for the U/V axes + size_t num_control_points_u; + size_t num_control_points_v; + + // 2D array of control points. + // Memory layout: `V * num_control_points_u + U` + // NOTE: The control points are _not_ homogeneous, meaning you have to multiply + // them by `w` before evaluating the surface. + ufbx_vec4_list control_points; + + // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. + uint32_t span_subdivision_u; + uint32_t span_subdivision_v; + + // If `true` the resulting normals should be flipped when evaluated. + bool flip_normals; + + // Material for the whole surface. + // NOTE: May be `NULL`! + ufbx_nullable ufbx_material *material; +}; + +struct ufbx_nurbs_trim_surface { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; +}; + +struct ufbx_nurbs_trim_boundary { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; +}; + +// -- Node attributes (advanced) + +struct ufbx_procedural_geometry { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; +}; + +struct ufbx_stereo_camera { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + ufbx_nullable ufbx_camera *left; + ufbx_nullable ufbx_camera *right; +}; + +struct ufbx_camera_switcher { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; +}; + +typedef enum ufbx_marker_type UFBX_ENUM_REPR { + UFBX_MARKER_UNKNOWN, // < Unknown marker type + UFBX_MARKER_FK_EFFECTOR, // < FK (Forward Kinematics) effector + UFBX_MARKER_IK_EFFECTOR, // < IK (Inverse Kinematics) effector + + UFBX_ENUM_FORCE_WIDTH(UFBX_MARKER_TYPE) +} ufbx_marker_type; + +UFBX_ENUM_TYPE(ufbx_marker_type, UFBX_MARKER_TYPE, UFBX_MARKER_IK_EFFECTOR); + +// Tracking marker for effectors +struct ufbx_marker { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // Type of the marker + ufbx_marker_type type; +}; + +// LOD level display mode. +typedef enum ufbx_lod_display UFBX_ENUM_REPR { + UFBX_LOD_DISPLAY_USE_LOD, // < Display the LOD level if the distance is appropriate. + UFBX_LOD_DISPLAY_SHOW, // < Always display the LOD level. + UFBX_LOD_DISPLAY_HIDE, // < Never display the LOD level. + + UFBX_ENUM_FORCE_WIDTH(UFBX_LOD_DISPLAY) +} ufbx_lod_display; + +UFBX_ENUM_TYPE(ufbx_lod_display, UFBX_LOD_DISPLAY, UFBX_LOD_DISPLAY_HIDE); + +// Single LOD level within an LOD group. +// Specifies properties of the Nth child of the _node_ containing the LOD group. +typedef struct ufbx_lod_level { + + // Minimum distance to show this LOD level. + // NOTE: In world units by default, or in screen percentage if + // `ufbx_lod_group.relative_distances` is set. + ufbx_real distance; + + // LOD display mode. + // NOTE: Mostly for editing, you should probably ignore this + // unless making a modeling program. + ufbx_lod_display display; + +} ufbx_lod_level; + +UFBX_LIST_TYPE(ufbx_lod_level_list, ufbx_lod_level); + +// Group of LOD (Level of Detail) levels for an object. +// The actual LOD models are defined in the parent `ufbx_node.children`. +struct ufbx_lod_group { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + ufbx_node_list instances; + }; }; + + // If set to `true`, `ufbx_lod_level.distance` represents a screen size percentage. + bool relative_distances; + + // LOD levels matching in order to `ufbx_node.children`. + ufbx_lod_level_list lod_levels; + + // If set to `true` don't account for parent transform when computing the distance. + bool ignore_parent_transform; + + // If `use_distance_limit` is enabled hide the group if the distance is not between + // `distance_limit_min` and `distance_limit_max`. + bool use_distance_limit; + ufbx_real distance_limit_min; + ufbx_real distance_limit_max; +}; + +// -- Deformers + +// Method to evaluate the skinning on a per-vertex level +typedef enum ufbx_skinning_method UFBX_ENUM_REPR { + // Linear blend skinning: Blend transformation matrices by vertex weights + UFBX_SKINNING_METHOD_LINEAR, + // One vertex should have only one bone attached + UFBX_SKINNING_METHOD_RIGID, + // Convert the transformations to dual quaternions and blend in that space + UFBX_SKINNING_METHOD_DUAL_QUATERNION, + // Blend between `UFBX_SKINNING_METHOD_LINEAR` and `UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR` + // The blend weight can be found either per-vertex in `ufbx_skin_vertex.dq_weight` + // or in `ufbx_skin_deformer.dq_vertices/dq_weights` (indexed by vertex). + UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SKINNING_METHOD) +} ufbx_skinning_method; + +UFBX_ENUM_TYPE(ufbx_skinning_method, UFBX_SKINNING_METHOD, UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR); + +// Skin weight information for a single mesh vertex +typedef struct ufbx_skin_vertex { + + // Each vertex is influenced by weights from `ufbx_skin_deformer.weights[]` + // The weights are sorted by decreasing weight so you can take the first N + // weights to get a cheaper approximation of the vertex. + // NOTE: The weights are not guaranteed to be normalized! + uint32_t weight_begin; // < Index to start from in the `weights[]` array + uint32_t num_weights; // < Number of weights influencing the vertex + + // Blend weight between Linear Blend Skinning (0.0) and Dual Quaternion (1.0). + // Should be used if `skinning_method == UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR` + ufbx_real dq_weight; + +} ufbx_skin_vertex; + +UFBX_LIST_TYPE(ufbx_skin_vertex_list, ufbx_skin_vertex); + +// Single per-vertex per-cluster weight, see `ufbx_skin_vertex` +typedef struct ufbx_skin_weight { + uint32_t cluster_index; // < Index into `ufbx_skin_deformer.clusters[]` + ufbx_real weight; // < Amount this bone influence the vertex +} ufbx_skin_weight; + +UFBX_LIST_TYPE(ufbx_skin_weight_list, ufbx_skin_weight); + +// Skin deformer specifies a binding between a logical set of bones (a skeleton) +// and a mesh. Each bone is represented by a `ufbx_skin_cluster` that contains +// the binding matrix and a `ufbx_node *bone` that has the current transformation. +struct ufbx_skin_deformer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + ufbx_skinning_method skinning_method; + + // Clusters (bones) in the skin + ufbx_skin_cluster_list clusters; + + // Per-vertex weight information + ufbx_skin_vertex_list vertices; + ufbx_skin_weight_list weights; + + // Largest amount of weights a single vertex can have + size_t max_weights_per_vertex; + + // Blend weights between Linear Blend Skinning (0.0) and Dual Quaternion (1.0). + // HINT: You probably want to use `vertices` and `ufbx_skin_vertex.dq_weight` instead! + // NOTE: These may be out-of-bounds for a given mesh, `vertices` is always safe. + size_t num_dq_weights; + ufbx_uint32_list dq_vertices; + ufbx_real_list dq_weights; +}; + +// Cluster of vertices bound to a single bone. +struct ufbx_skin_cluster { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // The bone node the cluster is attached to + // NOTE: Always valid if found from `ufbx_skin_deformer.clusters[]` unless + // `ufbx_load_opts.connect_broken_elements` is `true`. + ufbx_nullable ufbx_node *bone_node; + + // Binding matrix from local mesh vertices to the bone + ufbx_matrix geometry_to_bone; + + // Binding matrix from local mesh _node_ to the bone. + // NOTE: Prefer `geometry_to_bone` in most use cases! + ufbx_matrix mesh_node_to_bone; + + // Matrix that specifies the rest/bind pose transform of the node, + // not generally needed for skinning, use `geometry_to_bone` instead. + ufbx_matrix bind_to_world; + + // Precomputed matrix/transform that accounts for the current bone transform + // ie. `ufbx_matrix_mul(&cluster->bone->node_to_world, &cluster->geometry_to_bone)` + ufbx_matrix geometry_to_world; + ufbx_transform geometry_to_world_transform; + + // Raw weights indexed by each _vertex_ of a mesh (not index!) + // HINT: It may be simpler to use `ufbx_skin_deformer.vertices[]/weights[]` instead! + // NOTE: These may be out-of-bounds for a given mesh, `ufbx_skin_deformer.vertices` is always safe. + size_t num_weights; // < Number of vertices in the cluster + ufbx_uint32_list vertices; // < Vertex indices in `ufbx_mesh.vertices[]` + ufbx_real_list weights; // < Per-vertex weight values +}; + +// Blend shape deformer can contain multiple channels (think of sliders between morphs) +// that may optionally have in-between keyframes. +struct ufbx_blend_deformer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Independent morph targets of the deformer. + ufbx_blend_channel_list channels; +}; + +// Blend shape associated with a target weight in a series of morphs +typedef struct ufbx_blend_keyframe { + // The target blend shape offsets. + ufbx_blend_shape *shape; + + // Weight value at which to apply the keyframe at full strength + ufbx_real target_weight; + + // The weight the shape should be currently applied with + ufbx_real effective_weight; +} ufbx_blend_keyframe; + +UFBX_LIST_TYPE(ufbx_blend_keyframe_list, ufbx_blend_keyframe); + +// Blend channel consists of multiple morph-key targets that are interpolated. +// In simple cases there will be only one keyframe that is the target shape. +struct ufbx_blend_channel { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Current weight of the channel + ufbx_real weight; + + // Key morph targets to blend between depending on `weight` + // In usual cases there's only one target per channel + ufbx_blend_keyframe_list keyframes; + + // Final blend shape ignoring any intermediate blend shapes. + ufbx_nullable ufbx_blend_shape *target_shape; +}; + +// Blend shape target containing the actual vertex offsets +struct ufbx_blend_shape { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Vertex offsets to apply over the base mesh + // NOTE: The `offset_vertices` may be out-of-bounds for a given mesh! + size_t num_offsets; // < Number of vertex offsets in the following arrays + ufbx_uint32_list offset_vertices; // < Indices to `ufbx_mesh.vertices[]` + ufbx_vec3_list position_offsets; // < Always specified per-vertex offsets + ufbx_vec3_list normal_offsets; // < Empty if not specified + + // Optional weights for the offsets. + // NOTE: These are technically not supported in FBX and are only written by Blender. + ufbx_real_list offset_weights; +}; + +typedef enum ufbx_cache_file_format UFBX_ENUM_REPR { + UFBX_CACHE_FILE_FORMAT_UNKNOWN, // < Unknown cache file format + UFBX_CACHE_FILE_FORMAT_PC2, // < .pc2 Point cache file + UFBX_CACHE_FILE_FORMAT_MC, // < .mc/.mcx Maya cache file + + UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_FILE_FORMAT) +} ufbx_cache_file_format; + +UFBX_ENUM_TYPE(ufbx_cache_file_format, UFBX_CACHE_FILE_FORMAT, UFBX_CACHE_FILE_FORMAT_MC); + +typedef enum ufbx_cache_data_format UFBX_ENUM_REPR { + UFBX_CACHE_DATA_FORMAT_UNKNOWN, // < Unknown data format + UFBX_CACHE_DATA_FORMAT_REAL_FLOAT, // < `float data[]` + UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT, // < `struct { float x, y, z; } data[]` + UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE, // < `double data[]` + UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE, // < `struct { double x, y, z; } data[]` + + UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_DATA_FORMAT) +} ufbx_cache_data_format; + +UFBX_ENUM_TYPE(ufbx_cache_data_format, UFBX_CACHE_DATA_FORMAT, UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE); + +typedef enum ufbx_cache_data_encoding UFBX_ENUM_REPR { + UFBX_CACHE_DATA_ENCODING_UNKNOWN, // < Unknown data encoding + UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN, // < Contiguous little-endian array + UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN, // < Contiguous big-endian array + + UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_DATA_ENCODING) +} ufbx_cache_data_encoding; + +UFBX_ENUM_TYPE(ufbx_cache_data_encoding, UFBX_CACHE_DATA_ENCODING, UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN); + +// Known interpretations of geometry cache data. +typedef enum ufbx_cache_interpretation UFBX_ENUM_REPR { + // Unknown interpretation, see `ufbx_cache_channel.interpretation_name` for more information. + UFBX_CACHE_INTERPRETATION_UNKNOWN, + + // Generic "points" interpretation, FBX SDK default. Usually fine to interpret + // as vertex positions if no other cache channels are specified. + UFBX_CACHE_INTERPRETATION_POINTS, + + // Vertex positions. + UFBX_CACHE_INTERPRETATION_VERTEX_POSITION, + + // Vertex normals. + UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL, + + UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_INTERPRETATION) +} ufbx_cache_interpretation; + +UFBX_ENUM_TYPE(ufbx_cache_interpretation, UFBX_CACHE_INTERPRETATION, UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL); + +typedef struct ufbx_cache_frame { + + // Name of the channel this frame belongs to. + ufbx_string channel; + + // Time of this frame in seconds. + double time; + + // Name of the file containing the data. + // The specified file may contain multiple frames, use `data_offset` etc. to + // read at the right position. + ufbx_string filename; + + // Format of the wrapper file. + ufbx_cache_file_format file_format; + + // Axis to mirror the read data by. + ufbx_mirror_axis mirror_axis; + + // Factor to scale the geometry by. + ufbx_real scale_factor; + + ufbx_cache_data_format data_format; // < Format of the data in the file + ufbx_cache_data_encoding data_encoding; // < Binary encoding of the data + uint64_t data_offset; // < Byte offset into the file + uint32_t data_count; // < Number of data elements + uint32_t data_element_bytes; // < Size of a single data element in bytes + uint64_t data_total_bytes; // < Size of the whole data blob in bytes +} ufbx_cache_frame; + +UFBX_LIST_TYPE(ufbx_cache_frame_list, ufbx_cache_frame); + +typedef struct ufbx_cache_channel { + + // Name of the geometry cache channel. + ufbx_string name; + + // What does the data in this channel represent. + ufbx_cache_interpretation interpretation; + + // Source name for `interpretation`, especially useful if `interpretation` is + // `UFBX_CACHE_INTERPRETATION_UNKNOWN`. + ufbx_string interpretation_name; + + // List of frames belonging to this channel. + // Sorted by time (`ufbx_cache_frame.time`). + ufbx_cache_frame_list frames; + + // Axis to mirror the frames by. + ufbx_mirror_axis mirror_axis; + + // Factor to scale the geometry by. + ufbx_real scale_factor; + +} ufbx_cache_channel; + +UFBX_LIST_TYPE(ufbx_cache_channel_list, ufbx_cache_channel); + +typedef struct ufbx_geometry_cache { + ufbx_string root_filename; + ufbx_cache_channel_list channels; + ufbx_cache_frame_list frames; + ufbx_string_list extra_info; +} ufbx_geometry_cache; + +struct ufbx_cache_deformer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + ufbx_string channel; + ufbx_nullable ufbx_cache_file *file; + + // Only valid if `ufbx_load_opts.load_external_files` is set! + ufbx_nullable ufbx_geometry_cache *external_cache; + ufbx_nullable ufbx_cache_channel *external_channel; +}; + +struct ufbx_cache_file { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + ufbx_cache_file_format format; + + // Only valid if `ufbx_load_opts.load_external_files` is set! + ufbx_nullable ufbx_geometry_cache *external_cache; +}; + +// -- Materials + +// Material property, either specified with a constant value or a mapped texture +typedef struct ufbx_material_map { + + // Constant value or factor for the map. + // May be specified simultaneously with a texture, in this case most shading models + // use multiplicative tinting of the texture values. + union { + ufbx_real value_real; + ufbx_vec2 value_vec2; + ufbx_vec3 value_vec3; + ufbx_vec4 value_vec4; + }; + int64_t value_int; + + // Texture if connected, otherwise `NULL`. + // May be valid but "disabled" (application specific) if `texture_enabled == false`. + ufbx_nullable ufbx_texture *texture; + + // `true` if the file has specified any of the values above. + // NOTE: The value may be set to a non-zero default even if `has_value == false`, + // for example missing factors are set to `1.0` if a color is defined. + bool has_value; + + // Controls whether shading should use `texture`. + // NOTE: Some shading models allow this to be `true` even if `texture == NULL`. + bool texture_enabled; + + // Set to `true` if this feature should be disabled (specific to shader type). + bool feature_disabled; + + // Number of components in the value from 1 to 4 if defined, 0 if not. + uint8_t value_components; + +} ufbx_material_map; + +// Material feature +typedef struct ufbx_material_feature_info { + + // Whether the material model uses this feature or not. + // NOTE: The feature can be enabled but still not used if eg. the corresponding factor is at zero! + bool enabled; + + // Explicitly enabled/disabled by the material. + bool is_explicit; + +} ufbx_material_feature_info; + +// Texture attached to an FBX property +typedef struct ufbx_material_texture { + ufbx_string material_prop; // < Name of the property in `ufbx_material.props` + ufbx_string shader_prop; // < Shader-specific property mapping name + + // Texture attached to the property. + ufbx_texture *texture; + +} ufbx_material_texture; + +UFBX_LIST_TYPE(ufbx_material_texture_list, ufbx_material_texture); + +// Shading model type +typedef enum ufbx_shader_type UFBX_ENUM_REPR { + // Unknown shading model + UFBX_SHADER_UNKNOWN, + // FBX builtin diffuse material + UFBX_SHADER_FBX_LAMBERT, + // FBX builtin diffuse+specular material + UFBX_SHADER_FBX_PHONG, + // Open Shading Language standard surface + // https://github.com/Autodesk/standard-surface + UFBX_SHADER_OSL_STANDARD_SURFACE, + // Arnold standard surface + // https://docs.arnoldrenderer.com/display/A5AFMUG/Standard+Surface + UFBX_SHADER_ARNOLD_STANDARD_SURFACE, + // 3ds Max Physical Material + // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2022/ENU/3DSMax-Lighting-Shading/files/GUID-C1328905-7783-4917-AB86-FC3CC19E8972-htm.html + UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL, + // 3ds Max PBR (Metal/Rough) material + // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2021/ENU/3DSMax-Lighting-Shading/files/GUID-A16234A5-6500-4662-8B20-A5EC9FE1B255-htm.html + UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH, + // 3ds Max PBR (Spec/Gloss) material + // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2021/ENU/3DSMax-Lighting-Shading/files/GUID-18087194-B2A6-43EF-9B80-8FD1736FAE52-htm.html + UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS, + // 3ds glTF Material + // https://help.autodesk.com/view/3DSMAX/2023/ENU/?guid=GUID-7ABFB805-1D9F-417E-9C22-704BFDF160FA + UFBX_SHADER_GLTF_MATERIAL, + // 3ds OpenPBR Material + // https://help.autodesk.com/view/3DSMAX/2025/ENU/?guid=GUID-CD90329C-1E2B-4BBA-9285-3BB46253B9C2 + UFBX_SHADER_OPENPBR_MATERIAL, + // Stingray ShaderFX shader graph. + // Contains a serialized `"ShaderGraph"` in `ufbx_props`. + UFBX_SHADER_SHADERFX_GRAPH, + // Variation of the FBX phong shader that can recover PBR properties like + // `metalness` or `roughness` from the FBX non-physical values. + // NOTE: Enable `ufbx_load_opts.use_blender_pbr_material`. + UFBX_SHADER_BLENDER_PHONG, + // Wavefront .mtl format shader (used by .obj files) + UFBX_SHADER_WAVEFRONT_MTL, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SHADER_TYPE) +} ufbx_shader_type; + +UFBX_ENUM_TYPE(ufbx_shader_type, UFBX_SHADER_TYPE, UFBX_SHADER_WAVEFRONT_MTL); + +// FBX builtin material properties, matches maps in `ufbx_material_fbx_maps` +typedef enum ufbx_material_fbx_map UFBX_ENUM_REPR { + UFBX_MATERIAL_FBX_DIFFUSE_FACTOR, + UFBX_MATERIAL_FBX_DIFFUSE_COLOR, + UFBX_MATERIAL_FBX_SPECULAR_FACTOR, + UFBX_MATERIAL_FBX_SPECULAR_COLOR, + UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, + UFBX_MATERIAL_FBX_REFLECTION_FACTOR, + UFBX_MATERIAL_FBX_REFLECTION_COLOR, + UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, + UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, + UFBX_MATERIAL_FBX_EMISSION_FACTOR, + UFBX_MATERIAL_FBX_EMISSION_COLOR, + UFBX_MATERIAL_FBX_AMBIENT_FACTOR, + UFBX_MATERIAL_FBX_AMBIENT_COLOR, + UFBX_MATERIAL_FBX_NORMAL_MAP, + UFBX_MATERIAL_FBX_BUMP, + UFBX_MATERIAL_FBX_BUMP_FACTOR, + UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR, + UFBX_MATERIAL_FBX_DISPLACEMENT, + UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR, + UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT, + + UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_FBX_MAP) +} ufbx_material_fbx_map; + +UFBX_ENUM_TYPE(ufbx_material_fbx_map, UFBX_MATERIAL_FBX_MAP, UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT); + +// Known PBR material properties, matches maps in `ufbx_material_pbr_maps` +typedef enum ufbx_material_pbr_map UFBX_ENUM_REPR { + UFBX_MATERIAL_PBR_BASE_FACTOR, + UFBX_MATERIAL_PBR_BASE_COLOR, + UFBX_MATERIAL_PBR_ROUGHNESS, + UFBX_MATERIAL_PBR_METALNESS, + UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, + UFBX_MATERIAL_PBR_SPECULAR_FACTOR, + UFBX_MATERIAL_PBR_SPECULAR_COLOR, + UFBX_MATERIAL_PBR_SPECULAR_IOR, + UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, + UFBX_MATERIAL_PBR_SPECULAR_ROTATION, + UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, + UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, + UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, + UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, + UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, + UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, + UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, + UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, + UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY, + UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV, + UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, + UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, + UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, + UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, + UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, + UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR, + UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, + UFBX_MATERIAL_PBR_SHEEN_FACTOR, + UFBX_MATERIAL_PBR_SHEEN_COLOR, + UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, + UFBX_MATERIAL_PBR_COAT_FACTOR, + UFBX_MATERIAL_PBR_COAT_COLOR, + UFBX_MATERIAL_PBR_COAT_ROUGHNESS, + UFBX_MATERIAL_PBR_COAT_IOR, + UFBX_MATERIAL_PBR_COAT_ANISOTROPY, + UFBX_MATERIAL_PBR_COAT_ROTATION, + UFBX_MATERIAL_PBR_COAT_NORMAL, + UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, + UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, + UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, + UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, + UFBX_MATERIAL_PBR_THIN_FILM_IOR, + UFBX_MATERIAL_PBR_EMISSION_FACTOR, + UFBX_MATERIAL_PBR_EMISSION_COLOR, + UFBX_MATERIAL_PBR_OPACITY, + UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE, + UFBX_MATERIAL_PBR_INDIRECT_SPECULAR, + UFBX_MATERIAL_PBR_NORMAL_MAP, + UFBX_MATERIAL_PBR_TANGENT_MAP, + UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, + UFBX_MATERIAL_PBR_MATTE_FACTOR, + UFBX_MATERIAL_PBR_MATTE_COLOR, + UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, + UFBX_MATERIAL_PBR_GLOSSINESS, + UFBX_MATERIAL_PBR_COAT_GLOSSINESS, + UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS, + + UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_PBR_MAP) +} ufbx_material_pbr_map; + +UFBX_ENUM_TYPE(ufbx_material_pbr_map, UFBX_MATERIAL_PBR_MAP, UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS); + +// Known material features +typedef enum ufbx_material_feature UFBX_ENUM_REPR { + UFBX_MATERIAL_FEATURE_PBR, + UFBX_MATERIAL_FEATURE_METALNESS, + UFBX_MATERIAL_FEATURE_DIFFUSE, + UFBX_MATERIAL_FEATURE_SPECULAR, + UFBX_MATERIAL_FEATURE_EMISSION, + UFBX_MATERIAL_FEATURE_TRANSMISSION, + UFBX_MATERIAL_FEATURE_COAT, + UFBX_MATERIAL_FEATURE_SHEEN, + UFBX_MATERIAL_FEATURE_OPACITY, + UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION, + UFBX_MATERIAL_FEATURE_MATTE, + UFBX_MATERIAL_FEATURE_UNLIT, + UFBX_MATERIAL_FEATURE_IOR, + UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, + UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, + UFBX_MATERIAL_FEATURE_THIN_WALLED, + UFBX_MATERIAL_FEATURE_CAUSTICS, + UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, + UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, + UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, + UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, + UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, + UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, + + UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_FEATURE) +} ufbx_material_feature; + +UFBX_ENUM_TYPE(ufbx_material_feature, UFBX_MATERIAL_FEATURE, UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS); + +typedef struct ufbx_material_fbx_maps { + union { + ufbx_material_map maps[UFBX_MATERIAL_FBX_MAP_COUNT]; + struct { + ufbx_material_map diffuse_factor; + ufbx_material_map diffuse_color; + ufbx_material_map specular_factor; + ufbx_material_map specular_color; + ufbx_material_map specular_exponent; + ufbx_material_map reflection_factor; + ufbx_material_map reflection_color; + ufbx_material_map transparency_factor; + ufbx_material_map transparency_color; + ufbx_material_map emission_factor; + ufbx_material_map emission_color; + ufbx_material_map ambient_factor; + ufbx_material_map ambient_color; + ufbx_material_map normal_map; + ufbx_material_map bump; + ufbx_material_map bump_factor; + ufbx_material_map displacement_factor; + ufbx_material_map displacement; + ufbx_material_map vector_displacement_factor; + ufbx_material_map vector_displacement; + }; + }; +} ufbx_material_fbx_maps; + +typedef struct ufbx_material_pbr_maps { + union { + ufbx_material_map maps[UFBX_MATERIAL_PBR_MAP_COUNT]; + struct { + ufbx_material_map base_factor; + ufbx_material_map base_color; + ufbx_material_map roughness; + ufbx_material_map metalness; + ufbx_material_map diffuse_roughness; + ufbx_material_map specular_factor; + ufbx_material_map specular_color; + ufbx_material_map specular_ior; + ufbx_material_map specular_anisotropy; + ufbx_material_map specular_rotation; + ufbx_material_map transmission_factor; + ufbx_material_map transmission_color; + ufbx_material_map transmission_depth; + ufbx_material_map transmission_scatter; + ufbx_material_map transmission_scatter_anisotropy; + ufbx_material_map transmission_dispersion; + ufbx_material_map transmission_roughness; + ufbx_material_map transmission_extra_roughness; + ufbx_material_map transmission_priority; + ufbx_material_map transmission_enable_in_aov; + ufbx_material_map subsurface_factor; + ufbx_material_map subsurface_color; + ufbx_material_map subsurface_radius; + ufbx_material_map subsurface_scale; + ufbx_material_map subsurface_anisotropy; + ufbx_material_map subsurface_tint_color; + ufbx_material_map subsurface_type; + ufbx_material_map sheen_factor; + ufbx_material_map sheen_color; + ufbx_material_map sheen_roughness; + ufbx_material_map coat_factor; + ufbx_material_map coat_color; + ufbx_material_map coat_roughness; + ufbx_material_map coat_ior; + ufbx_material_map coat_anisotropy; + ufbx_material_map coat_rotation; + ufbx_material_map coat_normal; + ufbx_material_map coat_affect_base_color; + ufbx_material_map coat_affect_base_roughness; + ufbx_material_map thin_film_factor; + ufbx_material_map thin_film_thickness; + ufbx_material_map thin_film_ior; + ufbx_material_map emission_factor; + ufbx_material_map emission_color; + ufbx_material_map opacity; + ufbx_material_map indirect_diffuse; + ufbx_material_map indirect_specular; + ufbx_material_map normal_map; + ufbx_material_map tangent_map; + ufbx_material_map displacement_map; + ufbx_material_map matte_factor; + ufbx_material_map matte_color; + ufbx_material_map ambient_occlusion; + ufbx_material_map glossiness; + ufbx_material_map coat_glossiness; + ufbx_material_map transmission_glossiness; + }; + }; +} ufbx_material_pbr_maps; + +typedef struct ufbx_material_features { + union { + ufbx_material_feature_info features[UFBX_MATERIAL_FEATURE_COUNT]; + struct { + ufbx_material_feature_info pbr; + ufbx_material_feature_info metalness; + ufbx_material_feature_info diffuse; + ufbx_material_feature_info specular; + ufbx_material_feature_info emission; + ufbx_material_feature_info transmission; + ufbx_material_feature_info coat; + ufbx_material_feature_info sheen; + ufbx_material_feature_info opacity; + ufbx_material_feature_info ambient_occlusion; + ufbx_material_feature_info matte; + ufbx_material_feature_info unlit; + ufbx_material_feature_info ior; + ufbx_material_feature_info diffuse_roughness; + ufbx_material_feature_info transmission_roughness; + ufbx_material_feature_info thin_walled; + ufbx_material_feature_info caustics; + ufbx_material_feature_info exit_to_background; + ufbx_material_feature_info internal_reflections; + ufbx_material_feature_info double_sided; + ufbx_material_feature_info roughness_as_glossiness; + ufbx_material_feature_info coat_roughness_as_glossiness; + ufbx_material_feature_info transmission_roughness_as_glossiness; + }; + }; +} ufbx_material_features; + +// Surface material properties such as color, roughness, etc. Each property may +// be optionally bound to an `ufbx_texture`. +struct ufbx_material { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // FBX builtin properties + // NOTE: These may be empty if the material is using a custom shader + ufbx_material_fbx_maps fbx; + + // PBR material properties, defined for all shading models but may be + // somewhat approximate if `shader == NULL`. + ufbx_material_pbr_maps pbr; + + // Material features, primarily applies to `pbr`. + ufbx_material_features features; + + // Shading information + ufbx_shader_type shader_type; // < Always defined + ufbx_nullable ufbx_shader *shader; // < Optional extended shader information + ufbx_string shading_model_name; // < Often one of `{ "lambert", "phong", "unknown" }` + + // Prefix before shader property names with trailing `|`. + // For example `"3dsMax|Parameters|"` where properties would have names like + // `"3dsMax|Parameters|base_color"`. You can ignore this if you use the built-in + // `ufbx_material_fbx_maps fbx` and `ufbx_material_pbr_maps pbr` structures. + ufbx_string shader_prop_prefix; + + // All textures attached to the material, if you want specific maps if might be + // more convenient to use eg. `fbx.diffuse_color.texture` or `pbr.base_color.texture` + ufbx_material_texture_list textures; // < Sorted by `material_prop` +}; + +typedef enum ufbx_texture_type UFBX_ENUM_REPR { + + // Texture associated with an image file/sequence. `texture->filename` and + // and `texture->relative_filename` contain the texture's path. If the file + // has embedded content `texture->content` may hold `texture->content_size` + // bytes of raw image data. + UFBX_TEXTURE_FILE, + + // The texture consists of multiple texture layers blended together. + UFBX_TEXTURE_LAYERED, + + // Reserved as these _should_ exist in FBX files. + UFBX_TEXTURE_PROCEDURAL, + + // Node in a shader graph. + // Use `ufbx_texture.shader` for more information. + UFBX_TEXTURE_SHADER, + + UFBX_ENUM_FORCE_WIDTH(UFBX_TEXTURE_TYPE) +} ufbx_texture_type; + +UFBX_ENUM_TYPE(ufbx_texture_type, UFBX_TEXTURE_TYPE, UFBX_TEXTURE_SHADER); + +// Blend modes to combine layered textures with, compatible with common blend +// mode definitions in many art programs. Simpler blend modes have equations +// specified below where `src` is the layer to composite over `dst`. +// See eg. https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendingseparable +typedef enum ufbx_blend_mode UFBX_ENUM_REPR { + UFBX_BLEND_TRANSLUCENT, // < `src` effects result alpha + UFBX_BLEND_ADDITIVE, // < `src + dst` + UFBX_BLEND_MULTIPLY, // < `src * dst` + UFBX_BLEND_MULTIPLY_2X, // < `2 * src * dst` + UFBX_BLEND_OVER, // < `src * src_alpha + dst * (1-src_alpha)` + UFBX_BLEND_REPLACE, // < `src` Replace the contents + UFBX_BLEND_DISSOLVE, // < `random() + src_alpha >= 1.0 ? src : dst` + UFBX_BLEND_DARKEN, // < `min(src, dst)` + UFBX_BLEND_COLOR_BURN, // < `src > 0 ? 1 - min(1, (1-dst) / src) : 0` + UFBX_BLEND_LINEAR_BURN, // < `src + dst - 1` + UFBX_BLEND_DARKER_COLOR, // < `value(src) < value(dst) ? src : dst` + UFBX_BLEND_LIGHTEN, // < `max(src, dst)` + UFBX_BLEND_SCREEN, // < `1 - (1-src)*(1-dst)` + UFBX_BLEND_COLOR_DODGE, // < `src < 1 ? dst / (1 - src)` : (dst>0?1:0)` + UFBX_BLEND_LINEAR_DODGE, // < `src + dst` + UFBX_BLEND_LIGHTER_COLOR, // < `value(src) > value(dst) ? src : dst` + UFBX_BLEND_SOFT_LIGHT, // < https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendingsoftlight + UFBX_BLEND_HARD_LIGHT, // < https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendinghardlight + UFBX_BLEND_VIVID_LIGHT, // < Combination of `COLOR_DODGE` and `COLOR_BURN` + UFBX_BLEND_LINEAR_LIGHT, // < Combination of `LINEAR_DODGE` and `LINEAR_BURN` + UFBX_BLEND_PIN_LIGHT, // < Combination of `DARKEN` and `LIGHTEN` + UFBX_BLEND_HARD_MIX, // < Produces primary colors depending on similarity + UFBX_BLEND_DIFFERENCE, // < `abs(src - dst)` + UFBX_BLEND_EXCLUSION, // < `dst + src - 2 * src * dst` + UFBX_BLEND_SUBTRACT, // < `dst - src` + UFBX_BLEND_DIVIDE, // < `dst / src` + UFBX_BLEND_HUE, // < Replace hue + UFBX_BLEND_SATURATION, // < Replace saturation + UFBX_BLEND_COLOR, // < Replace hue and saturatio + UFBX_BLEND_LUMINOSITY, // < Replace value + UFBX_BLEND_OVERLAY, // < Same as `HARD_LIGHT` but with `src` and `dst` swapped + + UFBX_ENUM_FORCE_WIDTH(UFBX_BLEND_MODE) +} ufbx_blend_mode; + +UFBX_ENUM_TYPE(ufbx_blend_mode, UFBX_BLEND_MODE, UFBX_BLEND_OVERLAY); + +// Blend modes to combine layered textures with, compatible with common blend +typedef enum ufbx_wrap_mode UFBX_ENUM_REPR { + UFBX_WRAP_REPEAT, // < Repeat the texture past the [0,1] range + UFBX_WRAP_CLAMP, // < Clamp the normalized texture coordinates to [0,1] + + UFBX_ENUM_FORCE_WIDTH(UFBX_WRAP_MODE) +} ufbx_wrap_mode; + +UFBX_ENUM_TYPE(ufbx_wrap_mode, UFBX_WRAP_MODE, UFBX_WRAP_CLAMP); + +// Single layer in a layered texture +typedef struct ufbx_texture_layer { + ufbx_texture *texture; // < The inner texture to evaluate, never `NULL` + ufbx_blend_mode blend_mode; // < Equation to combine the layer to the background + ufbx_real alpha; // < Blend weight of this layer +} ufbx_texture_layer; + +UFBX_LIST_TYPE(ufbx_texture_layer_list, ufbx_texture_layer); + +typedef enum ufbx_shader_texture_type UFBX_ENUM_REPR { + UFBX_SHADER_TEXTURE_UNKNOWN, + + // Select an output of a multi-output shader. + // HINT: If this type is used the `ufbx_shader_texture.main_texture` and + // `ufbx_shader_texture.main_texture_output_index` fields are set. + UFBX_SHADER_TEXTURE_SELECT_OUTPUT, + + // Open Shading Language (OSL) shader. + // https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + UFBX_SHADER_TEXTURE_OSL, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SHADER_TEXTURE_TYPE) +} ufbx_shader_texture_type; + +UFBX_ENUM_TYPE(ufbx_shader_texture_type, UFBX_SHADER_TEXTURE_TYPE, UFBX_SHADER_TEXTURE_OSL); + +// Input to a shader texture, see `ufbx_shader_texture`. +typedef struct ufbx_shader_texture_input { + + // Name of the input. + ufbx_string name; + + // Constant value of the input. + union { + ufbx_real value_real; + ufbx_vec2 value_vec2; + ufbx_vec3 value_vec3; + ufbx_vec4 value_vec4; + }; + int64_t value_int; + ufbx_string value_str; + ufbx_blob value_blob; + + // Texture connected to this input. + ufbx_nullable ufbx_texture *texture; + + // Index of the output to use if `texture` is a multi-output shader node. + int64_t texture_output_index; + + // Controls whether shading should use `texture`. + // NOTE: Some shading models allow this to be `true` even if `texture == NULL`. + bool texture_enabled; + + // Property representing this input. + ufbx_prop *prop; + + // Property representing `texture`. + ufbx_nullable ufbx_prop *texture_prop; + + // Property representing `texture_enabled`. + ufbx_nullable ufbx_prop *texture_enabled_prop; + +} ufbx_shader_texture_input; + +UFBX_LIST_TYPE(ufbx_shader_texture_input_list, ufbx_shader_texture_input); + +// Texture that emulates a shader graph node. +// 3ds Max exports some materials as node graphs serialized to textures. +// ufbx can parse a small subset of these, as normal maps are often hidden behind +// some kind of bump node. +// NOTE: These encode a lot of details of 3ds Max internals, not recommended for direct use. +// HINT: `ufbx_texture.file_textures[]` contains a list of "real" textures that are connected +// to the `ufbx_texture` that is pretending to be a shader node. +typedef struct ufbx_shader_texture { + + // Type of this shader node. + ufbx_shader_texture_type type; + + // Name of the shader to use. + ufbx_string shader_name; + + // 64-bit opaque identifier for the shader type. + uint64_t shader_type_id; + + // Input values/textures (possibly further shader textures) to the shader. + // Sorted by `ufbx_shader_texture_input.name`. + ufbx_shader_texture_input_list inputs; + + // Shader source code if found. + ufbx_string shader_source; + ufbx_blob raw_shader_source; + + // Representative texture for this shader. + // Only specified if `main_texture.outputs[main_texture_output_index]` is semantically + // equivalent to this texture. + ufbx_texture *main_texture; + + // Output index of `main_texture` if it is a multi-output shader. + int64_t main_texture_output_index; + + // Prefix for properties related to this shader in `ufbx_texture`. + // NOTE: Contains the trailing '|' if not empty. + ufbx_string prop_prefix; + +} ufbx_shader_texture; + +// Unique texture within the file. +typedef struct ufbx_texture_file { + + // Index in `ufbx_scene.texture_files[]`. + uint32_t index; + + // Paths to the resource. + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + // Optional embedded content blob, eg. raw .png format data + ufbx_blob content; + +} ufbx_texture_file; + +UFBX_LIST_TYPE(ufbx_texture_file_list, ufbx_texture_file); + +// Texture that controls material appearance +struct ufbx_texture { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Texture type (file / layered / procedural / shader) + ufbx_texture_type type; + + // FILE: Paths to the resource + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + // FILE: Optional embedded content blob, eg. raw .png format data + ufbx_blob content; + + // FILE: Optional video texture + ufbx_nullable ufbx_video *video; + + // FILE: Index into `ufbx_scene.texture_files[]` or `UFBX_NO_INDEX`. + uint32_t file_index; + + // FILE: True if `file_index` has a valid value. + bool has_file; + + // LAYERED: Inner texture layers, ordered from _bottom_ to _top_ + ufbx_texture_layer_list layers; + + // SHADER: Shader information + // NOTE: May be specified even if `type == UFBX_TEXTURE_FILE` if `ufbx_load_opts.disable_quirks` + // is _not_ specified. Some known shaders that represent files are interpreted as `UFBX_TEXTURE_FILE`. + ufbx_nullable ufbx_shader_texture *shader; + + // List of file textures representing this texture. + // Defined even if `type == UFBX_TEXTURE_FILE` in which case the array contains only itself. + ufbx_texture_list file_textures; + + // Name of the UV set to use + ufbx_string uv_set; + + // Wrapping mode + ufbx_wrap_mode wrap_u; + ufbx_wrap_mode wrap_v; + + // UV transform + bool has_uv_transform; // < Has a non-identity `transform` and derived matrices. + ufbx_transform uv_transform; // < Texture transformation in UV space + ufbx_matrix texture_to_uv; // < Matrix representation of `transform` + ufbx_matrix uv_to_texture; // < UV coordinate to normalized texture coordinate matrix +}; + +// TODO: Video textures +struct ufbx_video { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Paths to the resource + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + // Optional embedded content blob + ufbx_blob content; +}; + +// Shader specifies a shading model and contains `ufbx_shader_binding` elements +// that define how to interpret FBX properties in the shader. +struct ufbx_shader { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Known shading model + ufbx_shader_type type; + + // TODO: Expose actual properties here + + // Bindings from FBX properties to the shader + // HINT: `ufbx_find_shader_prop()` translates shader properties to FBX properties + ufbx_shader_binding_list bindings; +}; + +// Binding from a material property to shader implementation +typedef struct ufbx_shader_prop_binding { + ufbx_string shader_prop; // < Property name used by the shader implementation + ufbx_string material_prop; // < Property name inside `ufbx_material.props` +} ufbx_shader_prop_binding; + +UFBX_LIST_TYPE(ufbx_shader_prop_binding_list, ufbx_shader_prop_binding); + +// Shader binding table +struct ufbx_shader_binding { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + ufbx_shader_prop_binding_list prop_bindings; // < Sorted by `shader_prop` +}; + +// -- Animation + +typedef struct ufbx_prop_override { + uint32_t element_id; + + uint32_t _internal_key; + + ufbx_string prop_name; + ufbx_vec4 value; + ufbx_string value_str; + int64_t value_int; +} ufbx_prop_override; + +UFBX_LIST_TYPE(ufbx_prop_override_list, ufbx_prop_override); + +typedef struct ufbx_transform_override { + uint32_t node_id; + ufbx_transform transform; +} ufbx_transform_override; + +UFBX_LIST_TYPE(ufbx_transform_override_list, ufbx_transform_override); + +// Animation descriptor used for evaluating animation. +// Usually obtained from `ufbx_scene` via either global animation `ufbx_scene.anim`, +// per-stack animation `ufbx_anim_stack.anim` or per-layer animation `ufbx_anim_layer.anim`. +// +// For advanced usage you can use `ufbx_create_anim()` to create animation descriptors +// with custom layers, property overrides, special flags, etc. +typedef struct ufbx_anim { + + // Time begin/end for the animation, both may be zero if absent. + double time_begin; + double time_end; + + // List of layers in the animation. + ufbx_anim_layer_list layers; + + // Optional overrides for weights for each layer in `layers[]`. + ufbx_real_list override_layer_weights; + + // Sorted by `element_id, prop_name` + ufbx_prop_override_list prop_overrides; + + // Sorted by `node_id` + ufbx_transform_override_list transform_overrides; + + // Evaluate connected properties as if they would not be connected. + bool ignore_connections; + + // Custom `ufbx_anim` created by `ufbx_create_anim()`. + bool custom; + +} ufbx_anim; + +struct ufbx_anim_stack { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + double time_begin; + double time_end; + + ufbx_anim_layer_list layers; + ufbx_anim *anim; +}; + +typedef struct ufbx_anim_prop { + ufbx_element *element; + + uint32_t _internal_key; + + ufbx_string prop_name; + ufbx_anim_value *anim_value; +} ufbx_anim_prop; + +UFBX_LIST_TYPE(ufbx_anim_prop_list, ufbx_anim_prop); + +struct ufbx_anim_layer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + ufbx_real weight; + bool weight_is_animated; + bool blended; + bool additive; + bool compose_rotation; + bool compose_scale; + + ufbx_anim_value_list anim_values; + ufbx_anim_prop_list anim_props; // < Sorted by `element,prop_name` + + ufbx_anim *anim; + + uint32_t _min_element_id; + uint32_t _max_element_id; + uint32_t _element_id_bitmask[4]; +}; + +struct ufbx_anim_value { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + ufbx_vec3 default_value; + ufbx_nullable ufbx_anim_curve *curves[3]; +}; + +// Animation curve segment interpolation mode between two keyframes +typedef enum ufbx_interpolation UFBX_ENUM_REPR { + UFBX_INTERPOLATION_CONSTANT_PREV, // < Hold previous key value + UFBX_INTERPOLATION_CONSTANT_NEXT, // < Hold next key value + UFBX_INTERPOLATION_LINEAR, // < Linear interpolation between two keys + UFBX_INTERPOLATION_CUBIC, // < Cubic interpolation, see `ufbx_tangent` + + UFBX_ENUM_FORCE_WIDTH(UFBX_INTERPOLATION) +} ufbx_interpolation; + +UFBX_ENUM_TYPE(ufbx_interpolation, UFBX_INTERPOLATION, UFBX_INTERPOLATION_CUBIC); + +typedef enum ufbx_extrapolation_mode UFBX_ENUM_REPR { + UFBX_EXTRAPOLATION_CONSTANT, // < Use the value of the first/last keyframe + UFBX_EXTRAPOLATION_REPEAT, // < Repeat the whole animation curve + UFBX_EXTRAPOLATION_MIRROR, // < Repeat with mirroring + UFBX_EXTRAPOLATION_SLOPE, // < Use the tangent of the last keyframe to linearly extrapolate + UFBX_EXTRAPOLATION_REPEAT_RELATIVE, // < Repeat the animation curve but connect the first and last keyframe values + + UFBX_ENUM_FORCE_WIDTH(UFBX_EXTRAPOLATION) +} ufbx_extrapolation_mode; + +UFBX_ENUM_TYPE(ufbx_extrapolation_mode, UFBX_EXTRAPOLATION_MODE, UFBX_EXTRAPOLATION_REPEAT_RELATIVE); + +typedef struct ufbx_extrapolation { + ufbx_extrapolation_mode mode; + + // Count used for repeating modes. + // Negative values mean infinite repetition. + int32_t repeat_count; +} ufbx_extrapolation; + +// Tangent vector at a keyframe, may be split into left/right +typedef struct ufbx_tangent { + float dx; // < Derivative in the time axis + float dy; // < Derivative in the (curve specific) value axis +} ufbx_tangent; + +// Single real `value` at a specified `time`, interpolation between two keyframes +// is determined by the `interpolation` field of the _previous_ key. +// If `interpolation == UFBX_INTERPOLATION_CUBIC` the span is evaluated as a +// cubic bezier curve through the following points: +// +// (prev->time, prev->value) +// (prev->time + prev->right.dx, prev->value + prev->right.dy) +// (next->time - next->left.dx, next->value - next->left.dy) +// (next->time, next->value) +// +// HINT: You can use `ufbx_evaluate_curve(ufbx_anim_curve *curve, double time)` +// rather than trying to manually handle all the interpolation modes. +typedef struct ufbx_keyframe { + double time; + ufbx_real value; + ufbx_interpolation interpolation; + ufbx_tangent left; + ufbx_tangent right; +} ufbx_keyframe; + +UFBX_LIST_TYPE(ufbx_keyframe_list, ufbx_keyframe); + +struct ufbx_anim_curve { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // List of keyframes that define the curve. + ufbx_keyframe_list keyframes; + + // Extrapolation before the curve. + ufbx_extrapolation pre_extrapolation; + // Extrapolation after the curve. + ufbx_extrapolation post_extrapolation; + + // Value range for all the keyframes. + ufbx_real min_value; + ufbx_real max_value; + + // Time range for all the keyframes. + double min_time; + double max_time; +}; + +// -- Collections + +// Collection of nodes to hide/freeze +struct ufbx_display_layer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Nodes included in the layer (exclusively at most one layer per node) + ufbx_node_list nodes; + + // Layer state + bool visible; // < Contained nodes are visible + bool frozen; // < Contained nodes cannot be edited + + ufbx_vec3 ui_color; // < Visual color for UI +}; + +// Named set of nodes/geometry features to select. +struct ufbx_selection_set { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Included nodes and geometry features + ufbx_selection_node_list nodes; +}; + +// Selection state of a node, potentially contains vertex/edge/face selection as well. +struct ufbx_selection_node { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Selection targets, possibly `NULL` + ufbx_nullable ufbx_node *target_node; + ufbx_nullable ufbx_mesh *target_mesh; + bool include_node; // < Is `target_node` included in the selection + + // Indices to selected components. + // Guaranteed to be valid as per `ufbx_load_opts.index_error_handling` + // if `target_mesh` is not `NULL`. + ufbx_uint32_list vertices; // < Indices to `ufbx_mesh.vertices` + ufbx_uint32_list edges; // < Indices to `ufbx_mesh.edges` + ufbx_uint32_list faces; // < Indices to `ufbx_mesh.faces` +}; + +// -- Constraints + +struct ufbx_character { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; +}; + +// Type of property constrain eg. position or look-at +typedef enum ufbx_constraint_type UFBX_ENUM_REPR { + UFBX_CONSTRAINT_UNKNOWN, + UFBX_CONSTRAINT_AIM, + UFBX_CONSTRAINT_PARENT, + UFBX_CONSTRAINT_POSITION, + UFBX_CONSTRAINT_ROTATION, + UFBX_CONSTRAINT_SCALE, + // Inverse kinematic chain to a single effector `ufbx_constraint.ik_effector` + // `targets` optionally contains a list of pole targets! + UFBX_CONSTRAINT_SINGLE_CHAIN_IK, + + UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_TYPE) +} ufbx_constraint_type; + +UFBX_ENUM_TYPE(ufbx_constraint_type, UFBX_CONSTRAINT_TYPE, UFBX_CONSTRAINT_SINGLE_CHAIN_IK); + +// Target to follow with a constraint +typedef struct ufbx_constraint_target { + ufbx_node *node; // < Target node reference + ufbx_real weight; // < Relative weight to other targets (does not always sum to 1) + ufbx_transform transform; // < Offset from the actual target +} ufbx_constraint_target; + +UFBX_LIST_TYPE(ufbx_constraint_target_list, ufbx_constraint_target); + +// Method to determine the up vector in aim constraints +typedef enum ufbx_constraint_aim_up_type UFBX_ENUM_REPR { + UFBX_CONSTRAINT_AIM_UP_SCENE, // < Align the up vector to the scene global up vector + UFBX_CONSTRAINT_AIM_UP_TO_NODE, // < Aim the up vector at `ufbx_constraint.aim_up_node` + UFBX_CONSTRAINT_AIM_UP_ALIGN_NODE, // < Copy the up vector from `ufbx_constraint.aim_up_node` + UFBX_CONSTRAINT_AIM_UP_VECTOR, // < Use `ufbx_constraint.aim_up_vector` as the up vector + UFBX_CONSTRAINT_AIM_UP_NONE, // < Don't align the up vector to anything + + UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_AIM_UP_TYPE) +} ufbx_constraint_aim_up_type; + +UFBX_ENUM_TYPE(ufbx_constraint_aim_up_type, UFBX_CONSTRAINT_AIM_UP_TYPE, UFBX_CONSTRAINT_AIM_UP_NONE); + +// Method to determine the up vector in aim constraints +typedef enum ufbx_constraint_ik_pole_type UFBX_ENUM_REPR { + UFBX_CONSTRAINT_IK_POLE_VECTOR, // < Use towards calculated from `ufbx_constraint.targets` + UFBX_CONSTRAINT_IK_POLE_NODE, // < Use `ufbx_constraint.ik_pole_vector` directly + + UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_IK_POLE_TYPE) +} ufbx_constraint_ik_pole_type; + +UFBX_ENUM_TYPE(ufbx_constraint_ik_pole_type, UFBX_CONSTRAINT_IK_POLE_TYPE, UFBX_CONSTRAINT_IK_POLE_NODE); + +struct ufbx_constraint { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Type of constraint to use + ufbx_constraint_type type; + ufbx_string type_name; + + // Node to be constrained + ufbx_nullable ufbx_node *node; + + // List of weighted targets for the constraint (pole vectors for IK) + ufbx_constraint_target_list targets; + + // State of the constraint + ufbx_real weight; + bool active; + + // Translation/rotation/scale axes the constraint is applied to + bool constrain_translation[3]; + bool constrain_rotation[3]; + bool constrain_scale[3]; + + // Offset from the constrained position + ufbx_transform transform_offset; + + // AIM: Target and up vectors + ufbx_vec3 aim_vector; + ufbx_constraint_aim_up_type aim_up_type; + ufbx_nullable ufbx_node *aim_up_node; + ufbx_vec3 aim_up_vector; + + // SINGLE_CHAIN_IK: Target for the IK, `targets` contains pole vectors! + ufbx_nullable ufbx_node *ik_effector; + ufbx_nullable ufbx_node *ik_end_node; + ufbx_vec3 ik_pole_vector; +}; + +// -- Audio + +struct ufbx_audio_layer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Clips contained in this layer. + ufbx_audio_clip_list clips; +}; + +struct ufbx_audio_clip { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + // Optional embedded content blob, eg. raw .png format data + ufbx_blob content; +}; + +// -- Miscellaneous + +typedef struct ufbx_bone_pose { + + // Node to apply the pose to. + ufbx_node *bone_node; + + // Matrix from node local space to world space. + ufbx_matrix bone_to_world; + + // Matrix from node local space to parent space. + // NOTE: FBX only stores world transformations so this is approximated from + // the parent world transform. + ufbx_matrix bone_to_parent; + +} ufbx_bone_pose; + +UFBX_LIST_TYPE(ufbx_bone_pose_list, ufbx_bone_pose); + +struct ufbx_pose { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Set if this pose is marked as a bind pose. + bool is_bind_pose; + + // List of bone poses. + // Sorted by `ufbx_node.typed_id`. + ufbx_bone_pose_list bone_poses; +}; + +struct ufbx_metadata_object { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; +}; + +// -- Named elements + +typedef struct ufbx_name_element { + ufbx_string name; + ufbx_element_type type; + + uint32_t _internal_key; + + ufbx_element *element; +} ufbx_name_element; + +UFBX_LIST_TYPE(ufbx_name_element_list, ufbx_name_element); + +// -- Scene + +// Scene is the root object loaded by ufbx that everything is accessed from. + +typedef enum ufbx_exporter UFBX_ENUM_REPR { + UFBX_EXPORTER_UNKNOWN, + UFBX_EXPORTER_FBX_SDK, + UFBX_EXPORTER_BLENDER_BINARY, + UFBX_EXPORTER_BLENDER_ASCII, + UFBX_EXPORTER_MOTION_BUILDER, + + UFBX_ENUM_FORCE_WIDTH(UFBX_EXPORTER) +} ufbx_exporter; + +UFBX_ENUM_TYPE(ufbx_exporter, UFBX_EXPORTER, UFBX_EXPORTER_MOTION_BUILDER); + +typedef struct ufbx_application { + ufbx_string vendor; + ufbx_string name; + ufbx_string version; +} ufbx_application; + +typedef enum ufbx_file_format UFBX_ENUM_REPR { + UFBX_FILE_FORMAT_UNKNOWN, // < Unknown file format + UFBX_FILE_FORMAT_FBX, // < .fbx Kaydara/Autodesk FBX file + UFBX_FILE_FORMAT_OBJ, // < .obj Wavefront OBJ file + UFBX_FILE_FORMAT_MTL, // < .mtl Wavefront MTL (Material template library) file + + UFBX_ENUM_FORCE_WIDTH(UFBX_FILE_FORMAT) +} ufbx_file_format; + +UFBX_ENUM_TYPE(ufbx_file_format, UFBX_FILE_FORMAT, UFBX_FILE_FORMAT_MTL); + +typedef enum ufbx_warning_type UFBX_ENUM_REPR { + // Missing external file file (for example .mtl for Wavefront .obj file or a + // geometry cache) + UFBX_WARNING_MISSING_EXTERNAL_FILE, + + // Loaded a Wavefront .mtl file derived from the filename instead of a proper + // `mtllib` statement. + UFBX_WARNING_IMPLICIT_MTL, + + // Truncated array has been auto-expanded. + UFBX_WARNING_TRUNCATED_ARRAY, + + // Geometry data has been defined but has no data. + UFBX_WARNING_MISSING_GEOMETRY_DATA, + + // Duplicated connection between two elements that shouldn't have. + UFBX_WARNING_DUPLICATE_CONNECTION, + + // Vertex 'W' attribute length differs from main attribute. + UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, + + // Missing polygon mapping type. + UFBX_WARNING_MISSING_POLYGON_MAPPING, + + // Unsupported version, loaded but may be incorrect. + // If the loading fails `UFBX_ERROR_UNSUPPORTED_VERSION` is issued instead. + UFBX_WARNING_UNSUPPORTED_VERSION, + + // Out-of-bounds index has been clamped to be in-bounds. + // HINT: You can use `ufbx_index_error_handling` to adjust behavior. + UFBX_WARNING_INDEX_CLAMPED, + + // Non-UTF8 encoded strings. + // HINT: You can use `ufbx_unicode_error_handling` to adjust behavior. + UFBX_WARNING_BAD_UNICODE, + + // Invalid base64-encoded embedded content ignored. + UFBX_WARNING_BAD_BASE64_CONTENT, + + // Non-node element connected to root. + UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, + + // Duplicated object ID in the file, connections will be wrong. + UFBX_WARNING_DUPLICATE_OBJECT_ID, + + // Empty face has been removed. + // Use `ufbx_load_opts.allow_empty_faces` if you want to allow them. + UFBX_WARNING_EMPTY_FACE_REMOVED, + + // Unknown .obj file directive. + UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, + + // Warnings after this one are deduplicated. + // See `ufbx_warning.count` for how many times they happened. + UFBX_WARNING_TYPE_FIRST_DEDUPLICATED = UFBX_WARNING_INDEX_CLAMPED, + + UFBX_ENUM_FORCE_WIDTH(UFBX_WARNING_TYPE) +} ufbx_warning_type; + +UFBX_ENUM_TYPE(ufbx_warning_type, UFBX_WARNING_TYPE, UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE); + +// Warning about a non-fatal issue in the file. +// Often contains information about issues that ufbx has corrected about the +// file but it might indicate something is not working properly. +typedef struct ufbx_warning { + // Type of the warning. + ufbx_warning_type type; + // Description of the warning. + ufbx_string description; + // The element related to this warning or `UFBX_NO_INDEX` if not related to a specific element. + uint32_t element_id; + // Number of times this warning was encountered. + size_t count; +} ufbx_warning; + +UFBX_LIST_TYPE(ufbx_warning_list, ufbx_warning); + +typedef enum ufbx_thumbnail_format UFBX_ENUM_REPR { + UFBX_THUMBNAIL_FORMAT_UNKNOWN, // < Unknown format + UFBX_THUMBNAIL_FORMAT_RGB_24, // < 8-bit RGB pixels, in memory R,G,B + UFBX_THUMBNAIL_FORMAT_RGBA_32, // < 8-bit RGBA pixels, in memory R,G,B,A + + UFBX_ENUM_FORCE_WIDTH(UFBX_THUMBNAIL_FORMAT) +} ufbx_thumbnail_format; + +UFBX_ENUM_TYPE(ufbx_thumbnail_format, UFBX_THUMBNAIL_FORMAT, UFBX_THUMBNAIL_FORMAT_RGBA_32); + +// Specify how unit / coordinate system conversion should be performed. +// Affects how `ufbx_load_opts.target_axes` and `ufbx_load_opts.target_unit_meters` work, +// has no effect if neither is specified. +typedef enum ufbx_space_conversion UFBX_ENUM_REPR { + + // Store the space conversion transform in the root node. + // Sets `ufbx_node.local_transform` of the root node. + UFBX_SPACE_CONVERSION_TRANSFORM_ROOT, + + // Perform the conversion by using "adjust" transforms. + // Compensates for the transforms using `ufbx_node.adjust_pre_rotation` and + // `ufbx_node.adjust_pre_scale`. You don't need to account for these unless + // you are manually building transforms from `ufbx_props`. + UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS, + + // Perform the conversion by scaling geometry in addition to adjusting transforms. + // Compensates transforms like `UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS` but + // applies scaling to geometry as well. + UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SPACE_CONVERSION) +} ufbx_space_conversion; + +UFBX_ENUM_TYPE(ufbx_space_conversion, UFBX_SPACE_CONVERSION, UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY); + +// How to handle FBX node geometry transforms. +// FBX nodes can have "geometry transforms" that affect only the attached meshes, +// but not the children. This is not allowed in many scene representations so +// ufbx provides some ways to simplify them. +// Geometry transforms can also be used to transform any other attributes such +// as lights or cameras. +typedef enum ufbx_geometry_transform_handling UFBX_ENUM_REPR { + + // Preserve the geometry transforms as-is. + // To be correct for all files you have to use `ufbx_node.geometry_transform`, + // `ufbx_node.geometry_to_node`, or `ufbx_node.geometry_to_world` to compensate + // for any potential geometry transforms. + UFBX_GEOMETRY_TRANSFORM_HANDLING_PRESERVE, + + // Add helper nodes between the nodes and geometry where needed. + // The created nodes have `ufbx_node.is_geometry_transform_helper` set and are + // named `ufbx_load_opts.geometry_transform_helper_name`. + UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES, + + // Modify the geometry of meshes attached to nodes with geometry transforms. + // Will add helper nodes like `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES` if + // necessary, for example if there are multiple instances of the same mesh with + // geometry transforms. + UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY, + + // Modify the geometry of meshes attached to nodes with geometry transforms. + // NOTE: This will not work correctly for instanced geometry. + UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK, + + UFBX_ENUM_FORCE_WIDTH(UFBX_GEOMETRY_TRANSFORM_HANDLING) +} ufbx_geometry_transform_handling; + +UFBX_ENUM_TYPE(ufbx_geometry_transform_handling, UFBX_GEOMETRY_TRANSFORM_HANDLING, UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK); + +// How to handle FBX transform inherit modes. +typedef enum ufbx_inherit_mode_handling UFBX_ENUM_REPR { + + // Preserve inherit mode in `ufbx_node.inherit_mode`. + // NOTE: To correctly handle all scenes you would need to handle the + // non-standard inherit modes. + UFBX_INHERIT_MODE_HANDLING_PRESERVE, + + // Create scale helper nodes parented to nodes that need special inheritance. + // Scale helper nodes will have `ufbx_node.is_scale_helper` and parents of + // scale helpers will have `ufbx_node.scale_helper` pointing to it. + UFBX_INHERIT_MODE_HANDLING_HELPER_NODES, + + // Attempt to compensate for bone scale by inversely scaling children. + // NOTE: This only works for uniform non-animated scaling, if scale is + // non-uniform or animated, ufbx will add scale helpers in the same way + // as `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. + UFBX_INHERIT_MODE_HANDLING_COMPENSATE, + + // Attempt to compensate for bone scale by inversely scaling children. + // Will never create helper nodes. + UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK, + + // Ignore non-standard inheritance modes. + // Forces all nodes to have `UFBX_INHERIT_MODE_NORMAL` regardless of the + // inherit mode specified in the file. This can be useful for emulating + // results from importers/programs that don't support inherit modes. + UFBX_INHERIT_MODE_HANDLING_IGNORE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_INHERIT_MODE_HANDLING) +} ufbx_inherit_mode_handling; + +UFBX_ENUM_TYPE(ufbx_inherit_mode_handling, UFBX_INHERIT_MODE_HANDLING, UFBX_INHERIT_MODE_HANDLING_IGNORE); + +// How to handle FBX transform pivots. +typedef enum ufbx_pivot_handling UFBX_ENUM_REPR { + + // Take pivots into account when computing the transform. + UFBX_PIVOT_HANDLING_RETAIN, + + // Translate objects to be located at their pivot. + // NOTE: Only applied if rotation and scaling pivots are equal. + // NOTE: Results in geometric translation. Use `ufbx_geometry_transform_handling` + // to interpret these in a standard scene graph. + UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT, + + // Translate objects to be located at their rotation pivot. + // NOTE: Results in geometric translation. Use `ufbx_geometry_transform_handling` + // to interpret these in a standard scene graph. + // NOTE: By default the original transforms of empties are not retained when using this, + // use `ufbx_load_opts.pivot_handling_retain_empties` to prevent adjusting these pivots. + UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT, + + UFBX_ENUM_FORCE_WIDTH(UFBX_PIVOT_HANDLING) +} ufbx_pivot_handling; + +UFBX_ENUM_TYPE(ufbx_pivot_handling, UFBX_PIVOT_HANDLING, UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT); + +// Embedded thumbnail in the file, valid if the dimensions are non-zero. +typedef struct ufbx_thumbnail { + ufbx_props props; + + // Extents of the thumbnail + uint32_t width; + uint32_t height; + + // Format of `ufbx_thumbnail.data`. + ufbx_thumbnail_format format; + + // Thumbnail pixel data, layout as contiguous rows from bottom to top. + // See `ufbx_thumbnail.format` for the pixel format. + ufbx_blob data; +} ufbx_thumbnail; + +// Miscellaneous data related to the loaded file +typedef struct ufbx_metadata { + + // List of non-fatal warnings about the file. + // If you need to only check whether a specific warning was triggered you + // can use `ufbx_metadata.has_warning[]`. + ufbx_warning_list warnings; + + // FBX ASCII file format. + bool ascii; + + // FBX version in integer format, eg. 7400 for 7.4. + uint32_t version; + + // File format of the source file. + ufbx_file_format file_format; + + // Index arrays may contain `UFBX_NO_INDEX` instead of a valid index + // to indicate gaps. + bool may_contain_no_index; + + // May contain meshes with no defined vertex position. + // NOTE: `ufbx_mesh.vertex_position.exists` may be `false`! + bool may_contain_missing_vertex_position; + + // Arrays may contain items with `NULL` element references. + // See `ufbx_load_opts.connect_broken_elements`. + bool may_contain_broken_elements; + + // Some API guarantees do not apply (depending on unsafe options used). + // Loaded with `ufbx_load_opts.allow_unsafe` enabled. + bool is_unsafe; + + // Flag for each possible warning type. + // See `ufbx_metadata.warnings[]` for detailed warning information. + bool has_warning[UFBX_WARNING_TYPE_COUNT]; + + ufbx_string creator; + bool big_endian; + + ufbx_string filename; + ufbx_string relative_root; + + ufbx_blob raw_filename; + ufbx_blob raw_relative_root; + + ufbx_exporter exporter; + uint32_t exporter_version; + + ufbx_props scene_props; + + ufbx_application original_application; + ufbx_application latest_application; + + ufbx_thumbnail thumbnail; + + bool geometry_ignored; + bool animation_ignored; + bool embedded_ignored; + + size_t max_face_triangles; + + size_t result_memory_used; + size_t temp_memory_used; + size_t result_allocs; + size_t temp_allocs; + + size_t element_buffer_size; + size_t num_shader_textures; + + ufbx_real bone_prop_size_unit; + bool bone_prop_limb_length_relative; + + ufbx_real ortho_size_unit; + + int64_t ktime_second; // < One second in internal KTime units + + ufbx_string original_file_path; + ufbx_blob raw_original_file_path; + + // Conversion methods applied for the scene. + ufbx_space_conversion space_conversion; + ufbx_geometry_transform_handling geometry_transform_handling; + ufbx_inherit_mode_handling inherit_mode_handling; + ufbx_pivot_handling pivot_handling; + ufbx_mirror_axis handedness_conversion_axis; + + // Transform that has been applied to root for axis/unit conversion. + ufbx_quat root_rotation; + ufbx_real root_scale; + + // Axis that the scene has been mirrored by. + // All geometry has been mirrored in this axis. + ufbx_mirror_axis mirror_axis; + + // Amount geometry has been scaled. + // See `UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY`. + ufbx_real geometry_scale; + +} ufbx_metadata; + +typedef enum ufbx_time_mode UFBX_ENUM_REPR { + UFBX_TIME_MODE_DEFAULT, + UFBX_TIME_MODE_120_FPS, + UFBX_TIME_MODE_100_FPS, + UFBX_TIME_MODE_60_FPS, + UFBX_TIME_MODE_50_FPS, + UFBX_TIME_MODE_48_FPS, + UFBX_TIME_MODE_30_FPS, + UFBX_TIME_MODE_30_FPS_DROP, + UFBX_TIME_MODE_NTSC_DROP_FRAME, + UFBX_TIME_MODE_NTSC_FULL_FRAME, + UFBX_TIME_MODE_PAL, + UFBX_TIME_MODE_24_FPS, + UFBX_TIME_MODE_1000_FPS, + UFBX_TIME_MODE_FILM_FULL_FRAME, + UFBX_TIME_MODE_CUSTOM, + UFBX_TIME_MODE_96_FPS, + UFBX_TIME_MODE_72_FPS, + UFBX_TIME_MODE_59_94_FPS, + + UFBX_ENUM_FORCE_WIDTH(UFBX_TIME_MODE) +} ufbx_time_mode; + +UFBX_ENUM_TYPE(ufbx_time_mode, UFBX_TIME_MODE, UFBX_TIME_MODE_59_94_FPS); + +typedef enum ufbx_time_protocol UFBX_ENUM_REPR { + UFBX_TIME_PROTOCOL_SMPTE, + UFBX_TIME_PROTOCOL_FRAME_COUNT, + UFBX_TIME_PROTOCOL_DEFAULT, + + UFBX_ENUM_FORCE_WIDTH(UFBX_TIME_PROTOCOL) +} ufbx_time_protocol; + +UFBX_ENUM_TYPE(ufbx_time_protocol, UFBX_TIME_PROTOCOL, UFBX_TIME_PROTOCOL_DEFAULT); + +typedef enum ufbx_snap_mode UFBX_ENUM_REPR { + UFBX_SNAP_MODE_NONE, + UFBX_SNAP_MODE_SNAP, + UFBX_SNAP_MODE_PLAY, + UFBX_SNAP_MODE_SNAP_AND_PLAY, + + UFBX_ENUM_FORCE_WIDTH(UFBX_SNAP_MODE) +} ufbx_snap_mode; + +UFBX_ENUM_TYPE(ufbx_snap_mode, UFBX_SNAP_MODE, UFBX_SNAP_MODE_SNAP_AND_PLAY); + +// Global settings: Axes and time/unit scales +typedef struct ufbx_scene_settings { + ufbx_props props; + + // Mapping of X/Y/Z axes to world-space directions. + // HINT: Use `ufbx_load_opts.target_axes` to normalize this. + // NOTE: This contains the _original_ axes even if you supply `ufbx_load_opts.target_axes`. + ufbx_coordinate_axes axes; + + // How many meters does a single world-space unit represent. + // FBX files usually default to centimeters, reported as `0.01` here. + // HINT: Use `ufbx_load_opts.target_unit_meters` to normalize this. + ufbx_real unit_meters; + + // Frames per second the animation is defined at. + double frames_per_second; + + ufbx_vec3 ambient_color; + ufbx_string default_camera; + + // Animation user interface settings. + // HINT: Use `ufbx_scene_settings.frames_per_second` instead of interpreting these yourself. + ufbx_time_mode time_mode; + ufbx_time_protocol time_protocol; + ufbx_snap_mode snap_mode; + + // Original settings (?) + ufbx_coordinate_axis original_axis_up; + ufbx_real original_unit_meters; +} ufbx_scene_settings; + +struct ufbx_scene { + ufbx_metadata metadata; + + // Global settings + ufbx_scene_settings settings; + + // Node instances in the scene + ufbx_node *root_node; + + // Default animation descriptor + ufbx_anim *anim; + + union { + struct { + ufbx_unknown_list unknowns; + + // Nodes + ufbx_node_list nodes; + + // Node attributes (common) + ufbx_mesh_list meshes; + ufbx_light_list lights; + ufbx_camera_list cameras; + ufbx_bone_list bones; + ufbx_empty_list empties; + + // Node attributes (curves/surfaces) + ufbx_line_curve_list line_curves; + ufbx_nurbs_curve_list nurbs_curves; + ufbx_nurbs_surface_list nurbs_surfaces; + ufbx_nurbs_trim_surface_list nurbs_trim_surfaces; + ufbx_nurbs_trim_boundary_list nurbs_trim_boundaries; + + // Node attributes (advanced) + ufbx_procedural_geometry_list procedural_geometries; + ufbx_stereo_camera_list stereo_cameras; + ufbx_camera_switcher_list camera_switchers; + ufbx_marker_list markers; + ufbx_lod_group_list lod_groups; + + // Deformers + ufbx_skin_deformer_list skin_deformers; + ufbx_skin_cluster_list skin_clusters; + ufbx_blend_deformer_list blend_deformers; + ufbx_blend_channel_list blend_channels; + ufbx_blend_shape_list blend_shapes; + ufbx_cache_deformer_list cache_deformers; + ufbx_cache_file_list cache_files; + + // Materials + ufbx_material_list materials; + ufbx_texture_list textures; + ufbx_video_list videos; + ufbx_shader_list shaders; + ufbx_shader_binding_list shader_bindings; + + // Animation + ufbx_anim_stack_list anim_stacks; + ufbx_anim_layer_list anim_layers; + ufbx_anim_value_list anim_values; + ufbx_anim_curve_list anim_curves; + + // Collections + ufbx_display_layer_list display_layers; + ufbx_selection_set_list selection_sets; + ufbx_selection_node_list selection_nodes; + + // Constraints + ufbx_character_list characters; + ufbx_constraint_list constraints; + + // Audio + ufbx_audio_layer_list audio_layers; + ufbx_audio_clip_list audio_clips; + + // Miscellaneous + ufbx_pose_list poses; + ufbx_metadata_object_list metadata_objects; + }; + + ufbx_element_list elements_by_type[UFBX_ELEMENT_TYPE_COUNT]; + }; + + // Unique texture files referenced by the scene. + ufbx_texture_file_list texture_files; + + // All elements and connections in the whole file + ufbx_element_list elements; // < Sorted by `id` + ufbx_connection_list connections_src; // < Sorted by `src,src_prop` + ufbx_connection_list connections_dst; // < Sorted by `dst,dst_prop` + + // Elements sorted by name, type + ufbx_name_element_list elements_by_name; + + // Enabled if `ufbx_load_opts.retain_dom == true`. + ufbx_nullable ufbx_dom_node *dom_root; +}; + +// -- Curves + +typedef struct ufbx_curve_point { + bool valid; + ufbx_vec3 position; + ufbx_vec3 derivative; +} ufbx_curve_point; + +typedef struct ufbx_surface_point { + bool valid; + ufbx_vec3 position; + ufbx_vec3 derivative_u; + ufbx_vec3 derivative_v; +} ufbx_surface_point; + +// -- Mesh topology + +typedef enum ufbx_topo_flags UFBX_FLAG_REPR { + UFBX_TOPO_NON_MANIFOLD = 0x1, // < Edge with three or more faces + + UFBX_FLAG_FORCE_WIDTH(UFBX_TOPO_FLAGS) +} ufbx_topo_flags; + +typedef struct ufbx_topo_edge { + uint32_t index; // < Starting index of the edge, always defined + uint32_t next; // < Ending index of the edge / next per-face `ufbx_topo_edge`, always defined + uint32_t prev; // < Previous per-face `ufbx_topo_edge`, always defined + uint32_t twin; // < `ufbx_topo_edge` on the opposite side, `UFBX_NO_INDEX` if not found + uint32_t face; // < Index into `mesh->faces[]`, always defined + uint32_t edge; // < Index into `mesh->edges[]`, `UFBX_NO_INDEX` if not found + + ufbx_topo_flags flags; +} ufbx_topo_edge; + +// Vertex data array for `ufbx_generate_indices()`. +// NOTE: `ufbx_generate_indices()` compares the vertices using `memcmp()`, so +// any padding should be cleared to zero. +typedef struct ufbx_vertex_stream { + void *data; // < Data pointer of shape `char[vertex_count][vertex_size]`. + size_t vertex_count; // < Number of vertices in this stream, for sanity checking. + size_t vertex_size; // < Size of a vertex in bytes. +} ufbx_vertex_stream; + +// -- Memory callbacks + +// You can optionally provide an allocator to ufbx, the default is to use the +// CRT malloc/realloc/free + +// Allocate `size` bytes, must be at least 8 byte aligned +typedef void *ufbx_alloc_fn(void *user, size_t size); + +// Reallocate `old_ptr` from `old_size` to `new_size` +// NOTE: If omit `alloc_fn` and `free_fn` they will be translated to: +// `alloc(size)` -> `realloc_fn(user, NULL, 0, size)` +// `free_fn(ptr, size)` -> `realloc_fn(user, ptr, size, 0)` +typedef void *ufbx_realloc_fn(void *user, void *old_ptr, size_t old_size, size_t new_size); + +// Free pointer `ptr` (of `size` bytes) returned by `alloc_fn` or `realloc_fn` +typedef void ufbx_free_fn(void *user, void *ptr, size_t size); + +// Free the allocator itself +typedef void ufbx_free_allocator_fn(void *user); + +// Allocator callbacks and user context +// NOTE: The allocator will be stored to the loaded scene and will be called +// again from `ufbx_free_scene()` so make sure `user` outlives that! +// You can use `free_allocator_fn()` to free the allocator yourself. +typedef struct ufbx_allocator { + // Callback functions, see `typedef`s above for information + ufbx_alloc_fn *alloc_fn; + ufbx_realloc_fn *realloc_fn; + ufbx_free_fn *free_fn; + ufbx_free_allocator_fn *free_allocator_fn; + void *user; +} ufbx_allocator; + +typedef struct ufbx_allocator_opts { + // Allocator callbacks + ufbx_allocator allocator; + + // Maximum number of bytes to allocate before failing + size_t memory_limit; + + // Maximum number of allocations to attempt before failing + size_t allocation_limit; + + // Threshold to swap from batched allocations to individual ones + // Defaults to 1MB if set to zero + // NOTE: If set to `1` ufbx will allocate everything in the smallest + // possible chunks which may be useful for debugging (eg. ASAN) + size_t huge_threshold; + + // Maximum size of a single allocation containing sub-allocations. + // Defaults to 16MB if set to zero + // The maximum amount of wasted memory depends on `max_chunk_size` and + // `huge_threshold`: each chunk can waste up to `huge_threshold` bytes + // internally and the last chunk might be incomplete. So for example + // with the defaults we can waste around 1MB/16MB = 6.25% overall plus + // up to 32MB due to the two incomplete blocks. The actual amounts differ + // slightly as the chunks start out at 4kB and double in size each time, + // meaning that the maximum fixed overhead (up to 32MB with defaults) is + // at most ~30% of the total allocation size. + size_t max_chunk_size; + +} ufbx_allocator_opts; + +// -- IO callbacks + +// Try to read up to `size` bytes to `data`, return the amount of read bytes. +// Return `SIZE_MAX` to indicate an IO error. +typedef size_t ufbx_read_fn(void *user, void *data, size_t size); + +// Skip `size` bytes in the file. +typedef bool ufbx_skip_fn(void *user, size_t size); + +// Get the size of the file. +// Return `0` if unknown, `UINT64_MAX` if error. +typedef uint64_t ufbx_size_fn(void *user); + +// Close the file +typedef void ufbx_close_fn(void *user); + +typedef struct ufbx_stream { + ufbx_read_fn *read_fn; // < Required + ufbx_skip_fn *skip_fn; // < Optional: Will use `read_fn()` if missing + ufbx_size_fn *size_fn; // < Optional + ufbx_close_fn *close_fn; // < Optional + + // Context passed to other functions + void *user; +} ufbx_stream; + +typedef enum ufbx_open_file_type UFBX_ENUM_REPR { + UFBX_OPEN_FILE_MAIN_MODEL, // < Main model file + UFBX_OPEN_FILE_GEOMETRY_CACHE, // < Unknown geometry cache file + UFBX_OPEN_FILE_OBJ_MTL, // < .mtl material library file + + UFBX_ENUM_FORCE_WIDTH(UFBX_OPEN_FILE_TYPE) +} ufbx_open_file_type; + +UFBX_ENUM_TYPE(ufbx_open_file_type, UFBX_OPEN_FILE_TYPE, UFBX_OPEN_FILE_OBJ_MTL); + +typedef uintptr_t ufbx_open_file_context; + +typedef struct ufbx_open_file_info { + // Context that can be passed to the following functions to use a shared allocator: + // ufbx_open_file_ctx() + // ufbx_open_memory_ctx() + ufbx_open_file_context context; + + // Kind of file to load. + ufbx_open_file_type type; + + // Original filename in the file, not resolved or UTF-8 encoded. + // NOTE: Not necessarily NULL-terminated! + ufbx_blob original_filename; +} ufbx_open_file_info; + +// Callback for opening an external file from the filesystem +typedef bool ufbx_open_file_fn(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info); + +typedef struct ufbx_open_file_cb { + ufbx_open_file_fn *fn; + void *user; + + UFBX_CALLBACK_IMPL(ufbx_open_file_cb, ufbx_open_file_fn, bool, + (void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info), + (stream, path, path_len, info)) +} ufbx_open_file_cb; + +// Options for `ufbx_open_file()`. +typedef struct ufbx_open_file_opts { + uint32_t _begin_zero; + + // Allocator to allocate the memory with. + ufbx_allocator_opts allocator; + + // The filename is guaranteed to be NULL-terminated. + ufbx_unsafe bool filename_null_terminated; + + uint32_t _end_zero; +} ufbx_open_file_opts; + +// Memory stream options +typedef void ufbx_close_memory_fn(void *user, void *data, size_t data_size); + +typedef struct ufbx_close_memory_cb { + ufbx_close_memory_fn *fn; + void *user; + + UFBX_CALLBACK_IMPL(ufbx_close_memory_cb, ufbx_close_memory_fn, void, + (void *user, void *data, size_t data_size), + (data, data_size)) +} ufbx_close_memory_cb; + +// Options for `ufbx_open_memory()`. +typedef struct ufbx_open_memory_opts { + uint32_t _begin_zero; + + // Allocator to allocate the memory with. + // NOTE: Used even if no copy is made to allocate a small metadata block. + ufbx_allocator_opts allocator; + + // Do not copy the memory. + // You can use `close_cb` to free the memory when the stream is closed. + // NOTE: This means the provided data pointer is referenced after creating + // the memory stream, make sure the data stays valid until the stream is closed! + ufbx_unsafe bool no_copy; + + // Callback to free the memory blob. + ufbx_close_memory_cb close_cb; + + uint32_t _end_zero; +} ufbx_open_memory_opts; + +// Detailed error stack frame. +// NOTE: You must compile `ufbx.c` with `UFBX_ENABLE_ERROR_STACK` to enable the error stack. +typedef struct ufbx_error_frame { + uint32_t source_line; + ufbx_string function; + ufbx_string description; +} ufbx_error_frame; + +// Error causes (and `UFBX_ERROR_NONE` for no error). +typedef enum ufbx_error_type UFBX_ENUM_REPR { + + // No error, operation has been performed successfully. + UFBX_ERROR_NONE, + + // Unspecified error, most likely caused by an invalid FBX file or a file + // that contains something ufbx can't handle. + UFBX_ERROR_UNKNOWN, + + // File not found. + UFBX_ERROR_FILE_NOT_FOUND, + + // Empty file. + UFBX_ERROR_EMPTY_FILE, + + // External file not found. + // See `ufbx_load_opts.load_external_files` for more information. + UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND, + + // Out of memory (allocator returned `NULL`). + UFBX_ERROR_OUT_OF_MEMORY, + + // `ufbx_allocator_opts.memory_limit` exhausted. + UFBX_ERROR_MEMORY_LIMIT, + + // `ufbx_allocator_opts.allocation_limit` exhausted. + UFBX_ERROR_ALLOCATION_LIMIT, + + // File ended abruptly. + UFBX_ERROR_TRUNCATED_FILE, + + // IO read error. + // eg. returning `SIZE_MAX` from `ufbx_stream.read_fn` or stdio `ferror()` condition. + UFBX_ERROR_IO, + + // User cancelled the loading via `ufbx_load_opts.progress_cb` returning `UFBX_PROGRESS_CANCEL`. + UFBX_ERROR_CANCELLED, + + // Could not detect file format from file data or filename. + // HINT: You can supply it manually using `ufbx_load_opts.file_format` or use `ufbx_load_opts.filename` + // when using `ufbx_load_memory()` to let ufbx guess the format from the extension. + UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT, + + // Options struct (eg. `ufbx_load_opts`) is not cleared to zero. + // Make sure you initialize the structure to zero via eg. + // ufbx_load_opts opts = { 0 }; // C + // ufbx_load_opts opts = { }; // C++ + UFBX_ERROR_UNINITIALIZED_OPTIONS, + + // The vertex streams in `ufbx_generate_indices()` are empty. + UFBX_ERROR_ZERO_VERTEX_SIZE, + + // Vertex stream passed to `ufbx_generate_indices()`. + UFBX_ERROR_TRUNCATED_VERTEX_STREAM, + + // Invalid UTF-8 encountered in a file when loading with `UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING`. + UFBX_ERROR_INVALID_UTF8, + + // Feature needed for the operation has been compiled out. + UFBX_ERROR_FEATURE_DISABLED, + + // Attempting to tessellate an invalid NURBS object. + // See `ufbx_nurbs_basis.valid`. + UFBX_ERROR_BAD_NURBS, + + // Out of bounds index in the file when loading with `UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING`. + UFBX_ERROR_BAD_INDEX, + + // Node is deeper than `ufbx_load_opts.node_depth_limit` in the hierarchy. + UFBX_ERROR_NODE_DEPTH_LIMIT, + + // Error parsing ASCII array in a thread. + // Threaded ASCII parsing is slightly more strict than non-threaded, for cursed files, + // set `ufbx_load_opts.force_single_thread_ascii_parsing` to `true`. + UFBX_ERROR_THREADED_ASCII_PARSE, + + // Unsafe options specified without enabling `ufbx_load_opts.allow_unsafe`. + UFBX_ERROR_UNSAFE_OPTIONS, + + // Duplicated override property in `ufbx_create_anim()` + UFBX_ERROR_DUPLICATE_OVERRIDE, + + // Unsupported file format version. + // ufbx still tries to load files with unsupported versions, see `UFBX_WARNING_UNSUPPORTED_VERSION`. + UFBX_ERROR_UNSUPPORTED_VERSION, + + UFBX_ENUM_FORCE_WIDTH(UFBX_ERROR_TYPE) +} ufbx_error_type; + +UFBX_ENUM_TYPE(ufbx_error_type, UFBX_ERROR_TYPE, UFBX_ERROR_UNSUPPORTED_VERSION); + +// Error description with detailed stack trace +// HINT: You can use `ufbx_format_error()` for formatting the error +typedef struct ufbx_error { + + // Type of the error, or `UFBX_ERROR_NONE` if successful. + ufbx_error_type type; + + // Description of the error type. + ufbx_string description; + + // Internal error stack. + // NOTE: You must compile `ufbx.c` with `UFBX_ENABLE_ERROR_STACK` to enable the error stack. + uint32_t stack_size; + ufbx_error_frame stack[UFBX_ERROR_STACK_MAX_DEPTH]; + + // Additional error information, such as missing file filename. + // `info` is a NULL-terminated UTF-8 string containing `info_length` bytes, excluding the trailing `'\0'`. + size_t info_length; + char info[UFBX_ERROR_INFO_LENGTH]; + +} ufbx_error; + +// -- Progress callbacks + +// Loading progress information. +typedef struct ufbx_progress { + uint64_t bytes_read; + uint64_t bytes_total; +} ufbx_progress; + +// Progress result returned from `ufbx_progress_fn()` callback. +// Determines whether ufbx should continue or abort the loading. +typedef enum ufbx_progress_result UFBX_ENUM_REPR { + + // Continue loading the file. + UFBX_PROGRESS_CONTINUE = 0x100, + + // Cancel loading and fail with `UFBX_ERROR_CANCELLED`. + UFBX_PROGRESS_CANCEL = 0x200, + + UFBX_ENUM_FORCE_WIDTH(UFBX_PROGRESS_RESULT) +} ufbx_progress_result; + +// Called periodically with the current progress. +// Return `UFBX_PROGRESS_CANCEL` to cancel further processing. +typedef ufbx_progress_result ufbx_progress_fn(void *user, const ufbx_progress *progress); + +typedef struct ufbx_progress_cb { + ufbx_progress_fn *fn; + void *user; + + UFBX_CALLBACK_IMPL(ufbx_progress_cb, ufbx_progress_fn, ufbx_progress_result, + (void *user, const ufbx_progress *progress), + (progress)) +} ufbx_progress_cb; + +// -- Inflate + +typedef struct ufbx_inflate_input ufbx_inflate_input; +typedef struct ufbx_inflate_retain ufbx_inflate_retain; + +// Source data/stream to decompress with `ufbx_inflate()` +struct ufbx_inflate_input { + // Total size of the data in bytes + size_t total_size; + + // (optional) Initial or complete data chunk + const void *data; + size_t data_size; + + // (optional) Temporary buffer, defaults to 256b stack buffer + void *buffer; + size_t buffer_size; + + // (optional) Streaming read function, concatenated after `data` + ufbx_read_fn *read_fn; + void *read_user; + + // (optional) Progress reporting + ufbx_progress_cb progress_cb; + uint64_t progress_interval_hint; // < Bytes between progress report calls + + // (optional) Change the progress scope + uint64_t progress_size_before; + uint64_t progress_size_after; + + // (optional) No the DEFLATE header + bool no_header; + + // (optional) No the Adler32 checksum + bool no_checksum; + + // (optional) Force internal fast lookup bit amount + size_t internal_fast_bits; +}; + +// Persistent data between `ufbx_inflate()` calls +// NOTE: You must set `initialized` to `false`, but `data` may be uninitialized +struct ufbx_inflate_retain { + bool initialized; + uint64_t data[1024]; +}; + +typedef enum ufbx_index_error_handling UFBX_ENUM_REPR { + // Clamp to a valid value. + UFBX_INDEX_ERROR_HANDLING_CLAMP, + // Set bad indices to `UFBX_NO_INDEX`. + // This is the recommended way if you need to deal with files with gaps in information. + // HINT: If you use this `ufbx_get_vertex_TYPE()` functions will return zero + // on invalid indices instead of failing. + UFBX_INDEX_ERROR_HANDLING_NO_INDEX, + // Fail loading entierely when encountering a bad index. + UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING, + // Pass bad indices through as-is. + // Requires `ufbx_load_opts.allow_unsafe`. + // UNSAFE: Breaks any API guarantees regarding indexes being in bounds and makes + // `ufbx_get_vertex_TYPE()` memory-unsafe to use. + UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_INDEX_ERROR_HANDLING) +} ufbx_index_error_handling; + +UFBX_ENUM_TYPE(ufbx_index_error_handling, UFBX_INDEX_ERROR_HANDLING, UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE); + +typedef enum ufbx_unicode_error_handling UFBX_ENUM_REPR { + // Replace errors with U+FFFD "Replacement Character" + UFBX_UNICODE_ERROR_HANDLING_REPLACEMENT_CHARACTER, + // Replace errors with '_' U+5F "Low Line" + UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE, + // Replace errors with '?' U+3F "Question Mark" + UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK, + // Remove errors from the output + UFBX_UNICODE_ERROR_HANDLING_REMOVE, + // Fail loading on encountering an Unicode error + UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING, + // Ignore and pass-through non-UTF-8 string data. + // Requires `ufbx_load_opts.allow_unsafe`. + // UNSAFE: Breaks API guarantee that `ufbx_string` is UTF-8 encoded. + UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE, + + UFBX_ENUM_FORCE_WIDTH(UFBX_UNICODE_ERROR_HANDLING) +} ufbx_unicode_error_handling; + +UFBX_ENUM_TYPE(ufbx_unicode_error_handling, UFBX_UNICODE_ERROR_HANDLING, UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE); + +typedef enum ufbx_baked_key_flags UFBX_FLAG_REPR { + // This keyframe represents a constant step from the left side + UFBX_BAKED_KEY_STEP_LEFT = 0x1, + // This keyframe represents a constant step from the right side + UFBX_BAKED_KEY_STEP_RIGHT = 0x2, + // This keyframe is the main part of a step + // Bordering either `UFBX_BAKED_KEY_STEP_LEFT` or `UFBX_BAKED_KEY_STEP_RIGHT`. + UFBX_BAKED_KEY_STEP_KEY = 0x4, + // This keyframe is a real keyframe in the source animation + UFBX_BAKED_KEY_KEYFRAME = 0x8, + // This keyframe has been reduced by maximum sample rate. + // See `ufbx_bake_opts.maximum_sample_rate`. + UFBX_BAKED_KEY_REDUCED = 0x10, + + UFBX_FLAG_FORCE_WIDTH(UFBX_BAKED_KEY) +} ufbx_baked_key_flags; + +typedef struct ufbx_baked_vec3 { + double time; // < Time of the keyframe, in seconds + ufbx_vec3 value; // < Value at `time`, can be linearly interpolated + ufbx_baked_key_flags flags; // < Additional information about the keyframe +} ufbx_baked_vec3; + +UFBX_LIST_TYPE(ufbx_baked_vec3_list, ufbx_baked_vec3); + +typedef struct ufbx_baked_quat { + double time; // < Time of the keyframe, in seconds + ufbx_quat value; // < Value at `time`, can be (spherically) linearly interpolated + ufbx_baked_key_flags flags; // < Additional information about the keyframe +} ufbx_baked_quat; + +UFBX_LIST_TYPE(ufbx_baked_quat_list, ufbx_baked_quat); + +// Baked transform animation for a single node. +typedef struct ufbx_baked_node { + + // Typed ID of the node, maps to `ufbx_scene.nodes[]`. + uint32_t typed_id; + // Element ID of the element, maps to `ufbx_scene.elements[]`. + uint32_t element_id; + + // The translation channel has constant values for the whole animation. + bool constant_translation; + // The rotation channel has constant values for the whole animation. + bool constant_rotation; + // The scale channel has constant values for the whole animation. + bool constant_scale; + + // Translation keys for the animation, maps to `ufbx_node.local_transform.translation`. + ufbx_baked_vec3_list translation_keys; + // Rotation keyframes, maps to `ufbx_node.local_transform.rotation`. + ufbx_baked_quat_list rotation_keys; + // Scale keyframes, maps to `ufbx_node.local_transform.scale`. + ufbx_baked_vec3_list scale_keys; + +} ufbx_baked_node; + +UFBX_LIST_TYPE(ufbx_baked_node_list, ufbx_baked_node); + +// Baked property animation. +typedef struct ufbx_baked_prop { + // Name of the property, eg. `"Visibility"`. + ufbx_string name; + // The value of the property is constant for the whole animation. + bool constant_value; + // Property value keys. + ufbx_baked_vec3_list keys; +} ufbx_baked_prop; + +UFBX_LIST_TYPE(ufbx_baked_prop_list, ufbx_baked_prop); + +// Baked property animation for a single element. +typedef struct ufbx_baked_element { + // Element ID of the element, maps to `ufbx_scene.elements[]`. + uint32_t element_id; + // List of properties the animation modifies. + ufbx_baked_prop_list props; +} ufbx_baked_element; + +UFBX_LIST_TYPE(ufbx_baked_element_list, ufbx_baked_element); + +typedef struct ufbx_baked_anim_metadata { + // Memory statistics + size_t result_memory_used; + size_t temp_memory_used; + size_t result_allocs; + size_t temp_allocs; +} ufbx_baked_anim_metadata; + +// Animation baked into linearly interpolated keyframes. +// See `ufbx_bake_anim()`. +typedef struct ufbx_baked_anim { + + // Nodes that are modified by the animation. + // Some nodes may be missing if the specified animation does not transform them. + // Conversely, some non-obviously animated nodes may be included as exporters + // often may add dummy keyframes for objects. + ufbx_baked_node_list nodes; + + // Element properties modified by the animation. + ufbx_baked_element_list elements; + + // Playback time range for the animation. + double playback_time_begin; + double playback_time_end; + double playback_duration; + + // Keyframe time range. + double key_time_min; + double key_time_max; + + // Additional bake information. + ufbx_baked_anim_metadata metadata; + +} ufbx_baked_anim; + +// -- Thread API + +// Internal thread pool handle. +// Passed to `ufbx_thread_pool_run_task()` from an user thread to run ufbx tasks. +// HINT: This context can store a user pointer via `ufbx_thread_pool_set_user_ptr()`. +typedef uintptr_t ufbx_thread_pool_context; + +// Thread pool creation information from ufbx. +typedef struct ufbx_thread_pool_info { + uint32_t max_concurrent_tasks; +} ufbx_thread_pool_info; + +// Initialize the thread pool. +// Return `true` on success. +typedef bool ufbx_thread_pool_init_fn(void *user, ufbx_thread_pool_context ctx, const ufbx_thread_pool_info *info); + +// Run tasks `count` tasks in threads. +// You must call `ufbx_thread_pool_run_task()` with indices `[start_index, start_index + count)`. +// The threads are launched in batches indicated by `group`, see `UFBX_THREAD_GROUP_COUNT` for more information. +// Ideally, you should run all the task indices in parallel within each `ufbx_thread_pool_run_fn()` call. +typedef void ufbx_thread_pool_run_fn(void *user, ufbx_thread_pool_context ctx, uint32_t group, uint32_t start_index, uint32_t count); + +// Wait for previous tasks spawned in `ufbx_thread_pool_run_fn()` to finish. +// `group` specifies the batch to wait for, `max_index` contains `start_index + count` from that group instance. +typedef void ufbx_thread_pool_wait_fn(void *user, ufbx_thread_pool_context ctx, uint32_t group, uint32_t max_index); + +// Free the thread pool. +typedef void ufbx_thread_pool_free_fn(void *user, ufbx_thread_pool_context ctx); + +// Thread pool interface. +// See functions above for more information. +// +// Hypothetical example of calls, where `UFBX_THREAD_GROUP_COUNT=2` for simplicity: +// +// run_fn(group=0, start_index=0, count=4) -> t0 := threaded { ufbx_thread_pool_run_task(0..3) } +// run_fn(group=1, start_index=4, count=10) -> t1 := threaded { ufbx_thread_pool_run_task(4..10) } +// wait_fn(group=0, max_index=4) -> wait_threads(t0) +// run_fn(group=0, start_index=10, count=15) -> t0 := threaded { ufbx_thread_pool_run_task(10..14) } +// wait_fn(group=1, max_index=10) -> wait_threads(t1) +// wait_fn(group=0, max_index=15) -> wait_threads(t0) +// +typedef struct ufbx_thread_pool { + ufbx_thread_pool_init_fn *init_fn; // < Optional + ufbx_thread_pool_run_fn *run_fn; // < Required + ufbx_thread_pool_wait_fn *wait_fn; // < Required + ufbx_thread_pool_free_fn *free_fn; // < Optional + void *user; +} ufbx_thread_pool; + +// Thread pool options. +typedef struct ufbx_thread_opts { + + // Thread pool interface. + // HINT: You can use `extra/ufbx_os.h` to provide a thread pool. + ufbx_thread_pool pool; + + // Maximum of tasks to have in-flight. + // Default: 2048 + size_t num_tasks; + + // Maximum amount of memory to use for batched threaded processing. + // Default: 32MB + // NOTE: The actual used memory usage might be higher, if there are individual tasks + // that rqeuire a high amount of memory. + size_t memory_limit; + +} ufbx_thread_opts; + +// Flags to control nanimation evaluation functions. +typedef enum ufbx_evaluate_flags UFBX_FLAG_REPR { + + // Do not extrapolate past the keyframes. + UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION = 0x1, + + UFBX_FLAG_FORCE_WIDTH(ufbx_evaluate_flags) +} ufbx_evaluate_flags; + +// -- Main API + +// Options for `ufbx_load_file/memory/stream/stdio()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_load_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during loading + ufbx_allocator_opts result_allocator; // < Allocator used for the final scene + ufbx_thread_opts thread_opts; // < Threading options + + // Preferences + bool ignore_geometry; // < Do not load geometry datsa (vertices, indices, etc) + bool ignore_animation; // < Do not load animation curves + bool ignore_embedded; // < Do not load embedded content + bool ignore_all_content; // < Do not load any content (geometry, animation, embedded) + + bool evaluate_skinning; // < Evaluate skinning (see ufbx_mesh.skinned_vertices) + bool evaluate_caches; // < Evaluate vertex caches (see ufbx_mesh.skinned_vertices) + + // Try to open external files referenced by the main file automatically. + // Applies to geometry caches and .mtl files for OBJ. + // NOTE: This may be risky for untrusted data as the input files may contain + // references to arbitrary paths in the filesystem. + // NOTE: This only applies to files *implicitly* referenced by the scene, if + // you request additional files via eg. `ufbx_load_opts.obj_mtl_path` they + // are still loaded. + // NOTE: Will fail loading if any external files are not found by default, use + // `ufbx_load_opts.ignore_missing_external_files` to suppress this, in this case + // you can find the errors at `ufbx_metadata.warnings[]` as `UFBX_WARNING_MISSING_EXTERNAL_FILE`. + bool load_external_files; + + // Don't fail loading if external files are not found. + bool ignore_missing_external_files; + + // Don't compute `ufbx_skin_deformer` `vertices` and `weights` arrays saving + // a bit of memory and time if not needed + bool skip_skin_vertices; + + // Skip computing `ufbx_mesh.material_parts[]` and `ufbx_mesh.face_group_parts[]`. + bool skip_mesh_parts; + + // Clean-up skin weights by removing negative, zero and NAN weights. + bool clean_skin_weights; + + // Read Blender materials as PBR values. + // Blender converts PBR materials to legacy FBX Phong materials in a deterministic way. + // If this setting is enabled, such materials will be read as `UFBX_SHADER_BLENDER_PHONG`, + // which means ufbx will be able to parse roughness and metallic textures. + bool use_blender_pbr_material; + + // Don't adjust reading the FBX file depending on the detected exporter + bool disable_quirks; + + // Don't allow partially broken FBX files to load + bool strict; + + // Force ASCII parsing to use a single thread. + // The multi-threaded ASCII parsing is slightly more lenient as it ignores + // the self-reported size of ASCII arrays, that threaded parsing depends on. + bool force_single_thread_ascii_parsing; + + // UNSAFE: If enabled allows using unsafe options that may fundamentally + // break the API guarantees. + ufbx_unsafe bool allow_unsafe; + + // Specify how to handle broken indices. + ufbx_index_error_handling index_error_handling; + + // Connect related elements even if they are broken. If `false` (default) + // `ufbx_skin_cluster` with a missing `bone` field are _not_ included in + // the `ufbx_skin_deformer.clusters[]` array for example. + bool connect_broken_elements; + + // Allow nodes that are not connected in any way to the root. Conversely if + // disabled, all lone nodes will be parented under `ufbx_scene.root_node`. + bool allow_nodes_out_of_root; + + // Allow meshes with no vertex position attribute. + // NOTE: If this is set `ufbx_mesh.vertex_position.exists` may be `false`. + bool allow_missing_vertex_position; + + // Allow faces with zero indices. + bool allow_empty_faces; + + // Generate vertex normals for a meshes that are missing normals. + // You can see if the normals have been generated from `ufbx_mesh.generated_normals`. + bool generate_missing_normals; + + // Ignore `open_file_cb` when loading the main file. + bool open_main_file_with_default; + + // Path separator character, defaults to '\' on Windows and '/' otherwise. + char path_separator; + + // Maximum depth of the node hirerachy. + // Will fail with `UFBX_ERROR_NODE_DEPTH_LIMIT` if a node is deeper than this limit. + // NOTE: The default of 0 allows arbitrarily deep hierarchies. Be careful if using + // recursive algorithms without setting this limit. + uint32_t node_depth_limit; + + // Estimated file size for progress reporting + uint64_t file_size_estimate; + + // Buffer size in bytes to use for reading from files or IO callbacks + size_t read_buffer_size; + + // Filename to use as a base for relative file paths if not specified using + // `ufbx_load_file()`. Use `length = SIZE_MAX` for NULL-terminated strings. + // `raw_filename` will be derived from this if empty. + ufbx_string filename; + + // Raw non-UTF8 filename. Does not support NULL termination. + // `filename` will be derived from this if empty. + ufbx_blob raw_filename; + + // Progress reporting + ufbx_progress_cb progress_cb; + uint64_t progress_interval_hint; // < Bytes between progress report calls + + // External file callbacks (defaults to stdio.h) + ufbx_open_file_cb open_file_cb; + + // How to handle geometry transforms in the nodes. + // See `ufbx_geometry_transform_handling` for an explanation. + ufbx_geometry_transform_handling geometry_transform_handling; + + // How to handle unconventional transform inherit modes. + // See `ufbx_inherit_mode_handling` for an explanation. + ufbx_inherit_mode_handling inherit_mode_handling; + + // How to perform space conversion by `target_axes` and `target_unit_meters`. + // See `ufbx_space_conversion` for an explanation. + ufbx_space_conversion space_conversion; + + // How to handle pivots. + // See `ufbx_pivot_handling` for an explanation. + ufbx_pivot_handling pivot_handling; + + // Retain the original transforms of empties when converting pivots. + bool pivot_handling_retain_empties; + + // Axis used to mirror for conversion between left-handed and right-handed coordinates. + ufbx_mirror_axis handedness_conversion_axis; + + // Do not change winding of faces when converting handedness. + bool handedness_conversion_retain_winding; + + // Reverse winding of all faces. + // If `handedness_conversion_retain_winding` is not specified, mirrored meshes + // will retain their original winding. + bool reverse_winding; + + // Apply an implicit root transformation to match axes. + // Used if `ufbx_coordinate_axes_valid(target_axes)`. + ufbx_coordinate_axes target_axes; + + // Scale the scene so that one world-space unit is `target_unit_meters` meters. + // By default units are not scaled. + ufbx_real target_unit_meters; + + // Target space for camera. + // By default FBX cameras point towards the positive X axis. + // Used if `ufbx_coordinate_axes_valid(target_camera_axes)`. + ufbx_coordinate_axes target_camera_axes; + + // Target space for directed lights. + // By default FBX lights point towards the negative Y axis. + // Used if `ufbx_coordinate_axes_valid(target_light_axes)`. + ufbx_coordinate_axes target_light_axes; + + // Name for dummy geometry transform helper nodes. + // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. + ufbx_string geometry_transform_helper_name; + + // Name for dummy scale helper nodes. + // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. + ufbx_string scale_helper_name; + + // Normalize vertex normals. + bool normalize_normals; + + // Normalize tangents and bitangents. + bool normalize_tangents; + + // Override for the root transform + bool use_root_transform; + ufbx_transform root_transform; + + // Animation keyframe clamp threshold, only applies to specific interpolation modes. + double key_clamp_threshold; + + // Specify how to handle Unicode errors in strings. + ufbx_unicode_error_handling unicode_error_handling; + + // Retain the 'W' component of mesh normal/tangent/bitangent. + // See `ufbx_vertex_attrib.values_w`. + bool retain_vertex_attrib_w; + + // Retain the raw document structure using `ufbx_dom_node`. + bool retain_dom; + + // Force a specific file format instead of detecting it. + ufbx_file_format file_format; + + // How far to read into the file to determine the file format. + // Default: 16kB + size_t file_format_lookahead; + + // Do not attempt to detect file format from file content. + bool no_format_from_content; + + // Do not attempt to detect file format from filename extension. + // ufbx primarily detects file format from the file header, + // this is just used as a fallback. + bool no_format_from_extension; + + // (.obj) Try to find .mtl file with matching filename as the .obj file. + // Used if the file specified `mtllib` line is not found, eg. for a file called + // `model.obj` that contains the line `usemtl materials.mtl`, ufbx would first + // try to open `materials.mtl` and if that fails it tries to open `model.mtl`. + bool obj_search_mtl_by_filename; + + // (.obj) Don't split geometry into meshes by object. + bool obj_merge_objects; + + // (.obj) Don't split geometry into meshes by groups. + bool obj_merge_groups; + + // (.obj) Force splitting groups even on object boundaries. + bool obj_split_groups; + + // (.obj) Path to the .mtl file. + // Use `length = SIZE_MAX` for NULL-terminated strings. + // NOTE: This is used _instead_ of the one in the file even if not found + // and sidesteps `load_external_files` as it's _explicitly_ requested. + ufbx_string obj_mtl_path; + + // (.obj) Data for the .mtl file. + ufbx_blob obj_mtl_data; + + // The world unit in meters that .obj files are assumed to be in. + // .obj files do not define the working units. By default the unit scale + // is read as zero, and no unit conversion is performed. + ufbx_real obj_unit_meters; + + // Coordinate space .obj files are assumed to be in. + // .obj files do not define the coordinate space they use. By default no + // coordinate space is assumed and no conversion is performed. + ufbx_coordinate_axes obj_axes; + + uint32_t _end_zero; +} ufbx_load_opts; + +// Options for `ufbx_evaluate_scene()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_evaluate_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during evaluation + ufbx_allocator_opts result_allocator; // < Allocator used for the final scene + + bool evaluate_skinning; // < Evaluate skinning (see ufbx_mesh.skinned_vertices) + bool evaluate_caches; // < Evaluate vertex caches (see ufbx_mesh.skinned_vertices) + + // Evaluation flags. + // See `ufbx_evaluate_flags` for information. + uint32_t evaluate_flags; + + // WARNING: Potentially unsafe! Try to open external files such as geometry caches + bool load_external_files; + + // External file callbacks (defaults to stdio.h) + ufbx_open_file_cb open_file_cb; + + uint32_t _end_zero; +} ufbx_evaluate_opts; + +UFBX_LIST_TYPE(ufbx_const_uint32_list, const uint32_t); +UFBX_LIST_TYPE(ufbx_const_real_list, const ufbx_real); + +typedef struct ufbx_prop_override_desc { + // Element (`ufbx_element.element_id`) to override the property from + uint32_t element_id; + + // Property name to override. + ufbx_string prop_name; + + // Override value, use `value.x` for scalars. `value_int` is initialized + // from `value.x` if zero so keep `value` zeroed even if you don't need it! + ufbx_vec4 value; + ufbx_string value_str; + int64_t value_int; +} ufbx_prop_override_desc; + +UFBX_LIST_TYPE(ufbx_const_prop_override_desc_list, const ufbx_prop_override_desc); + +UFBX_LIST_TYPE(ufbx_const_transform_override_list, const ufbx_transform_override); + +typedef struct ufbx_anim_opts { + uint32_t _begin_zero; + + // Animation layers indices. + // Corresponding to `ufbx_scene.anim_layers[]`, aka `ufbx_anim_layer.typed_id`. + ufbx_const_uint32_list layer_ids; + + // Override layer weights, parallel to `ufbx_anim_opts.layer_ids[]`. + ufbx_const_real_list override_layer_weights; + + // Property overrides. + // These allow you to override FBX properties, such as 'UFBX_Lcl_Rotation`. + ufbx_const_prop_override_desc_list prop_overrides; + + // Transform overrides. + // These allow you to override individual nodes' `ufbx_node.local_transform`. + ufbx_const_transform_override_list transform_overrides; + + // Ignore connected properties + bool ignore_connections; + + ufbx_allocator_opts result_allocator; // < Allocator used to create the `ufbx_anim` + + uint32_t _end_zero; +} ufbx_anim_opts; + +// Specifies how to handle stepped tangents. +typedef enum ufbx_bake_step_handling UFBX_ENUM_REPR { + + // One millisecond default step duration, with potential extra slack for converting to `float`. + UFBX_BAKE_STEP_HANDLING_DEFAULT, + + // Use a custom interpolation duration for the constant step. + // See `ufbx_bake_opts.step_custom_duration` and optionally `ufbx_bake_opts.step_custom_epsilon`. + UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION, + + // Stepped keyframes are represented as keyframes at the exact same time. + // Use flags `UFBX_BAKED_KEY_STEP_LEFT` and `UFBX_BAKED_KEY_STEP_RIGHT` to differentiate + // between the primary key and edge limits. + UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME, + + // Represent stepped keyframe times as the previous/next representable `double` value. + // Using this and robust linear interpolation will handle stepped tangents correctly + // without having to look at the key flags. + // NOTE: Casting these values to `float` or otherwise modifying them can collapse + // the keyframes to have the identical time. + UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE, + + // Treat all stepped tangents as linearly interpolated. + UFBX_BAKE_STEP_HANDLING_IGNORE, + + UFBX_ENUM_FORCE_WIDTH(ufbx_bake_step_handling) +} ufbx_bake_step_handling; + +UFBX_ENUM_TYPE(ufbx_bake_step_handling, UFBX_BAKE_STEP_HANDLING, UFBX_BAKE_STEP_HANDLING_IGNORE); + +typedef struct ufbx_bake_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during loading + ufbx_allocator_opts result_allocator; // < Allocator used for the final baked animation + + // Move the keyframe times to start from zero regardless of the animation start time. + // For example, for an animation spanning between frames [30, 60] will be moved to + // [0, 30] in the baked animation. + // NOTE: This is in general not equivalent to subtracting `ufbx_anim.time_begin` + // from each keyframe, as this trimming is done exactly using internal FBX ticks. + bool trim_start_time; + + // Samples per second to use for resampling non-linear animation. + // Default: 30 + double resample_rate; + + // Minimum sample rate to not resample. + // Many exporters resample animation by default. To avoid double-resampling + // keyframe rates higher or equal to this will not be resampled. + // Default: 19.5 + double minimum_sample_rate; + + // Maximum sample rate to use, this will remove keys if they are too close together. + // Default: unlimited + double maximum_sample_rate; + + // Bake the raw versions of properties related to transforms. + bool bake_transform_props; + + // Do not bake node transforms. + bool skip_node_transforms; + + // Do not resample linear rotation keyframes. + // FBX interpolates rotation in Euler angles, so this might cause incorrect interpolation. + bool no_resample_rotation; + + // Ignore layer weight animation. + bool ignore_layer_weight_animation; + + // Maximum number of segments to generate from one keyframe. + // Default: 32 + size_t max_keyframe_segments; + + // How to handle stepped tangents. + ufbx_bake_step_handling step_handling; + + // Interpolation duration used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. + double step_custom_duration; + + // Interpolation epsilon used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. + // Defined as the minimum fractional decrease/increase in key time, ie. + // `time / (1.0 + step_custom_epsilon)` and `time * (1.0 + step_custom_epsilon)`. + double step_custom_epsilon; + + // Flags passed to animation evaluation functions. + // See `ufbx_evaluate_flags`. + uint32_t evaluate_flags; + + // Enable key reduction. + bool key_reduction_enabled; + + // Enable key reduction for non-constant rotations. + // Assumes rotations will be interpolated using a spherical linear interpolation at runtime. + bool key_reduction_rotation; + + // Threshold for reducing keys for linear segments. + // Default `0.000001`, use negative to disable. + double key_reduction_threshold; + + // Maximum passes over the keys to reduce. + // Every pass can potentially halve the the amount of keys. + // Default: `4` + size_t key_reduction_passes; + + uint32_t _end_zero; +} ufbx_bake_opts; + +// Options for `ufbx_tessellate_nurbs_curve()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_tessellate_curve_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during tessellation + ufbx_allocator_opts result_allocator; // < Allocator used for the final line curve + + // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. + size_t span_subdivision; + + uint32_t _end_zero; +} ufbx_tessellate_curve_opts; + +// Options for `ufbx_tessellate_nurbs_surface()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_tessellate_surface_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during tessellation + ufbx_allocator_opts result_allocator; // < Allocator used for the final mesh + + // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. + // NOTE: Default is `4`, _not_ `ufbx_nurbs_surface.span_subdivision_u/v` as that + // would make it easy to create an FBX file with an absurdly high subdivision + // rate (similar to mesh subdivision). Please enforce copy the value yourself + // enforcing whatever limits you deem reasonable. + size_t span_subdivision_u; + size_t span_subdivision_v; + + // Skip computing `ufbx_mesh.material_parts[]` + bool skip_mesh_parts; + + uint32_t _end_zero; +} ufbx_tessellate_surface_opts; + +// Options for `ufbx_subdivide_mesh()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_subdivide_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during subdivision + ufbx_allocator_opts result_allocator; // < Allocator used for the final mesh + + ufbx_subdivision_boundary boundary; + ufbx_subdivision_boundary uv_boundary; + + // Do not generate normals + bool ignore_normals; + + // Interpolate existing normals using the subdivision rules + // instead of generating new normals + bool interpolate_normals; + + // Subdivide also tangent attributes + bool interpolate_tangents; + + // Map subdivided vertices into weighted original vertices. + // NOTE: May be O(n^2) if `max_source_vertices` is not specified! + bool evaluate_source_vertices; + + // Limit source vertices per subdivided vertex. + size_t max_source_vertices; + + // Calculate bone influences over subdivided vertices (if applicable). + // NOTE: May be O(n^2) if `max_skin_weights` is not specified! + bool evaluate_skin_weights; + + // Limit bone influences per subdivided vertex. + size_t max_skin_weights; + + // Index of the skin deformer to use for `evaluate_skin_weights`. + size_t skin_deformer_index; + + uint32_t _end_zero; +} ufbx_subdivide_opts; + +// Options for `ufbx_load_geometry_cache()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_geometry_cache_opts { + uint32_t _begin_zero; + + ufbx_allocator_opts temp_allocator; // < Allocator used during loading + ufbx_allocator_opts result_allocator; // < Allocator used for the final scene + + // External file callbacks (defaults to stdio.h) + ufbx_open_file_cb open_file_cb; + + // FPS value for converting frame times to seconds + double frames_per_second; + + // Axis to mirror the geometry by. + ufbx_mirror_axis mirror_axis; + + // Enable scaling `scale_factor` all geometry by. + bool use_scale_factor; + + // Factor to scale the geometry by. + ufbx_real scale_factor; + + uint32_t _end_zero; +} ufbx_geometry_cache_opts; + +// Options for `ufbx_read_geometry_cache_TYPE()` +// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) +typedef struct ufbx_geometry_cache_data_opts { + uint32_t _begin_zero; + + // External file callbacks (defaults to stdio.h) + ufbx_open_file_cb open_file_cb; + + bool additive; + bool use_weight; + ufbx_real weight; + + // Ignore scene transform. + bool ignore_transform; + + uint32_t _end_zero; +} ufbx_geometry_cache_data_opts; + +typedef struct ufbx_panic { + bool did_panic; + size_t message_length; + char message[UFBX_PANIC_MESSAGE_LENGTH]; +} ufbx_panic; + +// -- API + +#ifdef __cplusplus +extern "C" { +#endif + +// Various zero/empty/identity values +ufbx_abi_data const ufbx_string ufbx_empty_string; +ufbx_abi_data const ufbx_blob ufbx_empty_blob; +ufbx_abi_data const ufbx_matrix ufbx_identity_matrix; +ufbx_abi_data const ufbx_transform ufbx_identity_transform; +ufbx_abi_data const ufbx_vec2 ufbx_zero_vec2; +ufbx_abi_data const ufbx_vec3 ufbx_zero_vec3; +ufbx_abi_data const ufbx_vec4 ufbx_zero_vec4; +ufbx_abi_data const ufbx_quat ufbx_identity_quat; + +// Commonly used coordinate axes. +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_y_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_z_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_y_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_z_up; + +// Sizes of element types. eg `sizeof(ufbx_node)` +ufbx_abi_data const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT]; + +// Version of the source file, comparable to `UFBX_HEADER_VERSION` +ufbx_abi_data const uint32_t ufbx_source_version; + + +// Practically always `true` (see below), if not you need to be careful with threads. +// +// Guaranteed to be `true` in _any_ of the following conditions: +// - ufbx.c has been compiled using: GCC / Clang / MSVC / ICC / EMCC / TCC +// - ufbx.c has been compiled as C++11 or later +// - ufbx.c has been compiled as C11 or later with `` support +// +// If `false` you can't call the following functions concurrently: +// ufbx_evaluate_scene() +// ufbx_free_scene() +// ufbx_subdivide_mesh() +// ufbx_tessellate_nurbs_surface() +// ufbx_free_mesh() +ufbx_abi bool ufbx_is_thread_safe(void); + +// Load a scene from a `size` byte memory buffer at `data` +ufbx_abi ufbx_scene *ufbx_load_memory( + const void *data, size_t data_size, + const ufbx_load_opts *opts, ufbx_error *error); + +// Load a scene by opening a file named `filename` +ufbx_abi ufbx_scene *ufbx_load_file( + const char *filename, + const ufbx_load_opts *opts, ufbx_error *error); +ufbx_abi ufbx_scene *ufbx_load_file_len( + const char *filename, size_t filename_len, + const ufbx_load_opts *opts, ufbx_error *error); + +// Load a scene by reading from an `FILE *file` stream +// NOTE: `file` is passed as a `void` pointer to avoid including +ufbx_abi ufbx_scene *ufbx_load_stdio( + void *file, + const ufbx_load_opts *opts, ufbx_error *error); + +// Load a scene by reading from an `FILE *file` stream with a prefix +// NOTE: `file` is passed as a `void` pointer to avoid including +ufbx_abi ufbx_scene *ufbx_load_stdio_prefix( + void *file, + const void *prefix, size_t prefix_size, + const ufbx_load_opts *opts, ufbx_error *error); + +// Load a scene from a user-specified stream +ufbx_abi ufbx_scene *ufbx_load_stream( + const ufbx_stream *stream, + const ufbx_load_opts *opts, ufbx_error *error); + +// Load a scene from a user-specified stream with a prefix +ufbx_abi ufbx_scene *ufbx_load_stream_prefix( + const ufbx_stream *stream, + const void *prefix, size_t prefix_size, + const ufbx_load_opts *opts, ufbx_error *error); + +// Free a previously loaded or evaluated scene +ufbx_abi void ufbx_free_scene(ufbx_scene *scene); + +// Increment `scene` refcount +ufbx_abi void ufbx_retain_scene(ufbx_scene *scene); + +// Format a textual description of `error`. +// Always produces a NULL-terminated string to `char dst[dst_size]`, truncating if +// necessary. Returns the number of characters written not including the NULL terminator. +ufbx_abi size_t ufbx_format_error(char *dst, size_t dst_size, const ufbx_error *error); + +// Query + +// Find a property `name` from `props`, returns `NULL` if not found. +// Searches through `ufbx_props.defaults` as well. +ufbx_abi ufbx_prop *ufbx_find_prop_len(const ufbx_props *props, const char *name, size_t name_len); +ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name); + +// Utility functions for finding the value of a property, returns `def` if not found. +// NOTE: For `ufbx_string` you need to ensure the lifetime of the default is +// sufficient as no copy is made. +ufbx_abi ufbx_real ufbx_find_real_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_real def); +ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def); +ufbx_abi ufbx_vec3 ufbx_find_vec3_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_vec3 def); +ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def); +ufbx_abi int64_t ufbx_find_int_len(const ufbx_props *props, const char *name, size_t name_len, int64_t def); +ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def); +ufbx_abi bool ufbx_find_bool_len(const ufbx_props *props, const char *name, size_t name_len, bool def); +ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def); +ufbx_abi ufbx_string ufbx_find_string_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_string def); +ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def); +ufbx_abi ufbx_blob ufbx_find_blob_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_blob def); +ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def); + +// Find property in `props` with concatenated `parts[num_parts]`. +ufbx_abi ufbx_prop *ufbx_find_prop_concat(const ufbx_props *props, const ufbx_string *parts, size_t num_parts); + +// Get an element connected to a property. +ufbx_abi ufbx_element *ufbx_get_prop_element(const ufbx_element *element, const ufbx_prop *prop, ufbx_element_type type); + +// Find an element connected to a property by name. +ufbx_abi ufbx_element *ufbx_find_prop_element_len(const ufbx_element *element, const char *name, size_t name_len, ufbx_element_type type); +ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type); + +// Find any element of type `type` in `scene` by `name`. +// For example if you want to find `ufbx_material` named `Mat`: +// (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Mat"); +ufbx_abi ufbx_element *ufbx_find_element_len(const ufbx_scene *scene, ufbx_element_type type, const char *name, size_t name_len); +ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name); + +// Find node in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_NODE)`). +ufbx_abi ufbx_node *ufbx_find_node_len(const ufbx_scene *scene, const char *name, size_t name_len); +ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name); + +// Find an animation stack in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_ANIM_STACK)`) +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack_len(const ufbx_scene *scene, const char *name, size_t name_len); +ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name); + +// Find a material in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_MATERIAL)`). +ufbx_abi ufbx_material *ufbx_find_material_len(const ufbx_scene *scene, const char *name, size_t name_len); +ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name); + +// Find a single animated property `prop` of `element` in `layer`. +// Returns `NULL` if not found. +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop_len(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop, size_t prop_len); +ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop); + +// Find all animated properties of `element` in `layer`. +ufbx_abi ufbx_anim_prop_list ufbx_find_anim_props(const ufbx_anim_layer *layer, const ufbx_element *element); + +// Get a matrix that transforms normals in the same way as Autodesk software. +// NOTE: The resulting normals are slightly incorrect as this function deliberately +// inverts geometric transformation wrong. For better results use +// `ufbx_matrix_for_normals(&node->geometry_to_world)`. +ufbx_abi ufbx_matrix ufbx_get_compatible_matrix_for_normals(const ufbx_node *node); + +// Utility + +// Decompress a DEFLATE compressed buffer. +// Returns the decompressed size or a negative error code (see source for details). +// NOTE: You must supply a valid `retain` with `ufbx_inflate_retain.initialized == false` +// but the rest can be uninitialized. +ufbx_abi ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain); + +// Same as `ufbx_open_file()` but compatible with the callback in `ufbx_open_file_fn`. +// The `user` parameter is actually not used here. +ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info); + +// Open a `ufbx_stream` from a file. +// Use `path_len == SIZE_MAX` for NULL terminated string. +ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); +ufbx_unsafe ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); + +// NOTE: Uses the default ufbx allocator! +ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); +ufbx_unsafe ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); + +// Animation evaluation + +// Evaluate a single animation `curve` at a `time`. +// Returns `default_value` only if `curve == NULL` or it has no keyframes. +ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value); +ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags); + +// Evaluate a value from bundled animation curves. +ufbx_abi ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time); +ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time); +ufbx_abi ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); +ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); + +// Evaluate an animated property `name` from `element` at `time`. +// NOTE: If the property is not found it will have the flag `UFBX_PROP_FLAG_NOT_FOUND`. +ufbx_abi ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time); +ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time); +ufbx_abi ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags); +ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags); + +// Evaluate all _animated_ properties of `element`. +// HINT: This function returns an `ufbx_props` structure with the original properties as +// `ufbx_props.defaults`. This lets you use `ufbx_find_prop/value()` for the results. +ufbx_abi ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size); +ufbx_abi ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags); + +// Flags to control `ufbx_evaluate_transform_flags()`. +typedef enum ufbx_transform_flags UFBX_FLAG_REPR { + + // Ignore parent scale helper. + UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER = 0x1, + + // Ignore componentwise scale. + // Note that if you don't specify this, ufbx will have to potentially + // evaluate the entire parent chain in the worst case. + UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE = 0x2, + + // Require explicit components + UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES = 0x4, + + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.translation`. + UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION = 0x10, + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.rotation`. + UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION = 0x20, + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.scale`. + UFBX_TRANSFORM_FLAG_INCLUDE_SCALE = 0x40, + + // Do not extrapolate keyframes. + // See `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. + UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION = 0x80, + + UFBX_FLAG_FORCE_WIDTH(UFBX_TRANSFORM_FLAGS) +} ufbx_transform_flags; + +// Evaluate the animated transform of a node given a time. +// The returned transform is the local transform of the node (ie. relative to the parent), +// comparable to `ufbx_node.local_transform`. +ufbx_abi ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time); +ufbx_abi ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags); + +// Evaluate the blend shape weight of a blend channel. +// NOTE: Return value uses `1.0` for full weight, instead of `100.0` that the internal property `UFBX_Weight` uses. +ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time); +ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags); + +// Evaluate the whole `scene` at a specific `time` in the animation `anim`. +// The returned scene behaves as if it had been exported at a specific time +// in the specified animation, except that animated elements' properties contain +// only the animated values, the original ones are in `props->defaults`. +// +// NOTE: The returned scene refers to the original `scene` so the original +// scene cannot be freed until all evaluated scenes are freed. +ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error); + +// Create a custom animation descriptor. +// `ufbx_anim_opts` is used to specify animation layers and weights. +// HINT: You can also leave `ufbx_anim_opts.layer_ids[]` empty and only specify +// overrides to evaluate the scene with different properties or local transforms. +ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error); + +// Free an animation returned by `ufbx_create_anim()`. +ufbx_abi void ufbx_free_anim(ufbx_anim *anim); + +// Increase the animation reference count. +ufbx_abi void ufbx_retain_anim(ufbx_anim *anim); + +// Animation baking + +// "Bake" an animation to linearly interpolated keyframes. +// Composites the FBX transformation chain into quaternion rotations. +ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_anim *anim, const ufbx_bake_opts *opts, ufbx_error *error); + +ufbx_abi void ufbx_retain_baked_anim(ufbx_baked_anim *bake); +ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake); + +ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id); +ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node); + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id); +ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element); + +// Evaluate baked animation `keyframes` at `time`. +// Internally linearly interpolates between two adjacent keyframes. +// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. +ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time); + +// Evaluate baked animation `keyframes` at `time`. +// Internally spherically interpolates (`ufbx_quat_slerp()`) between two adjacent keyframes. +// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. +ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time); + +// Poses + +// Retrieve the bone pose for `node`. +// Returns `NULL` if the pose does not contain `node`. +ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node); + +// Materials + +// Find a texture for a given material FBX property. +ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len); +ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name); + +// Find a texture for a given shader property. +ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len); +ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name); + +// Map from a shader property to material property. +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len); +ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name); + +// Find an input in a shader texture. +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len); +ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name); + +// Math + +// Returns `true` if `axes` forms a valid coordinate space. +ufbx_abi bool ufbx_coordinate_axes_valid(ufbx_coordinate_axes axes); + +// Vector math utility functions. +ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v); + +// Quaternion math utility functions. +ufbx_abi ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b); +ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b); +ufbx_abi ufbx_quat ufbx_quat_normalize(ufbx_quat q); +ufbx_abi ufbx_quat ufbx_quat_fix_antipodal(ufbx_quat q, ufbx_quat reference); +ufbx_abi ufbx_quat ufbx_quat_slerp(ufbx_quat a, ufbx_quat b, ufbx_real t); +ufbx_abi ufbx_vec3 ufbx_quat_rotate_vec3(ufbx_quat q, ufbx_vec3 v); +ufbx_abi ufbx_vec3 ufbx_quat_to_euler(ufbx_quat q, ufbx_rotation_order order); +ufbx_abi ufbx_quat ufbx_euler_to_quat(ufbx_vec3 v, ufbx_rotation_order order); + +// Matrix math utility functions. +ufbx_abi ufbx_matrix ufbx_matrix_mul(const ufbx_matrix *a, const ufbx_matrix *b); +ufbx_abi ufbx_real ufbx_matrix_determinant(const ufbx_matrix *m); +ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m); + +// Get a matrix that can be used to transform geometry normals. +// NOTE: You must normalize the normals after transforming them with this matrix, +// eg. using `ufbx_vec3_normalize()`. +// NOTE: This function flips the normals if the determinant is negative. +ufbx_abi ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m); + +// Matrix transformation utilities. +ufbx_abi ufbx_vec3 ufbx_transform_position(const ufbx_matrix *m, ufbx_vec3 v); +ufbx_abi ufbx_vec3 ufbx_transform_direction(const ufbx_matrix *m, ufbx_vec3 v); + +// Conversions between `ufbx_matrix` and `ufbx_transform`. +ufbx_abi ufbx_matrix ufbx_transform_to_matrix(const ufbx_transform *t); +ufbx_abi ufbx_transform ufbx_matrix_to_transform(const ufbx_matrix *m); + +// Skinning + +// Get a matrix representing the deformation for a single vertex. +// Returns `fallback` if the vertex is not skinned. +ufbx_abi ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic *panic, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback); +ufbx_inline ufbx_matrix ufbx_get_skin_vertex_matrix(const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) { + return ufbx_catch_get_skin_vertex_matrix(NULL, skin, vertex, fallback); +} + +// Resolve the index into `ufbx_blend_shape.position_offsets[]` given a vertex. +// Returns `UFBX_NO_INDEX` if the vertex is not included in the blend shape. +ufbx_abi uint32_t ufbx_get_blend_shape_offset_index(const ufbx_blend_shape *shape, size_t vertex); + +// Get the offset for a given vertex in the blend shape. +// Returns `ufbx_zero_vec3` if the vertex is not a included in the blend shape. +ufbx_abi ufbx_vec3 ufbx_get_blend_shape_vertex_offset(const ufbx_blend_shape *shape, size_t vertex); + +// Get the _current_ blend offset given a blend deformer. +// NOTE: This depends on the current animated blend weight of the deformer. +ufbx_abi ufbx_vec3 ufbx_get_blend_vertex_offset(const ufbx_blend_deformer *blend, size_t vertex); + +// Apply the blend shape with `weight` to given vertices. +ufbx_abi void ufbx_add_blend_shape_vertex_offsets(const ufbx_blend_shape *shape, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); + +// Apply the blend deformer with `weight` to given vertices. +// NOTE: This depends on the current animated blend weight of the deformer. +ufbx_abi void ufbx_add_blend_vertex_offsets(const ufbx_blend_deformer *blend, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); + +// Curves/surfaces + +// Low-level utility to evaluate NURBS the basis functions. +ufbx_abi size_t ufbx_evaluate_nurbs_basis(const ufbx_nurbs_basis *basis, ufbx_real u, ufbx_real *weights, size_t num_weights, ufbx_real *derivatives, size_t num_derivatives); + +// Evaluate a point on a NURBS curve given the parameter `u`. +ufbx_abi ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nurbs_curve *curve, ufbx_real u); + +// Evaluate a point on a NURBS surface given the parameter `u` and `v`. +ufbx_abi ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v); + +// Tessellate a NURBS curve into a polyline. +ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error); + +// Tessellate a NURBS surface into a mesh. +ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surface, const ufbx_tessellate_surface_opts *opts, ufbx_error *error); + +// Free a line returned by `ufbx_tessellate_nurbs_curve()`. +ufbx_abi void ufbx_free_line_curve(ufbx_line_curve *curve); + +// Increase the refcount of the line. +ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *curve); + +// Mesh Topology + +// Find the face that contains a given `index`. +// Returns `UFBX_NO_INDEX` if out of bounds. +ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index); + +// Triangulate a mesh face, returning the number of triangles. +// NOTE: You need to space for `(face.num_indices - 2) * 3 - 1` indices! +// HINT: Using `ufbx_mesh.max_face_triangles * 3` is always safe. +ufbx_abi uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); +ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); + +// Generate the half-edge representation of `mesh` to `topo[mesh->num_indices]` +ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); +ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); + +// Get the next/previous edge around a vertex +// NOTE: Does not return the half-edge on the opposite side (ie. `topo[index].twin`) + +// Get the next half-edge in `topo`. +ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); +ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); + +// Get the previous half-edge in `topo`. +ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); +ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); + +// Calculate a normal for a given face. +// The returned normal is weighted by face area. +ufbx_abi ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face); +ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face); + +// Generate indices for normals from the topology. +// Respects smoothing groups. +ufbx_abi size_t ufbx_catch_generate_normal_mapping(ufbx_panic *panic, const ufbx_mesh *mesh, + const ufbx_topo_edge *topo, size_t num_topo, + uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); +ufbx_abi size_t ufbx_generate_normal_mapping(const ufbx_mesh *mesh, + const ufbx_topo_edge *topo, size_t num_topo, + uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); + +// Compute normals given normal indices. +// You can use `ufbx_generate_normal_mapping()` to generate the normal indices. +ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, + const uint32_t *normal_indices, size_t num_normal_indices, + ufbx_vec3 *normals, size_t num_normals); +ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, + const uint32_t *normal_indices, size_t num_normal_indices, + ufbx_vec3 *normals, size_t num_normals); + +// Subdivide a mesh using the Catmull-Clark subdivision `level` times. +ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error); + +// Free a mesh returned from `ufbx_subdivide_mesh()` or `ufbx_tessellate_nurbs_surface()`. +ufbx_abi void ufbx_free_mesh(ufbx_mesh *mesh); + +// Increase the mesh reference count. +ufbx_abi void ufbx_retain_mesh(ufbx_mesh *mesh); + +// Geometry caches + +// Load geometry cache information from a file. +// As geometry caches can be massive, this does not actually read the data, but +// only seeks through the files to form the metadata. +ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache( + const char *filename, + const ufbx_geometry_cache_opts *opts, ufbx_error *error); +ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( + const char *filename, size_t filename_len, + const ufbx_geometry_cache_opts *opts, ufbx_error *error); + +// Free a geometry cache returned from `ufbx_load_geometry_cache()`. +ufbx_abi void ufbx_free_geometry_cache(ufbx_geometry_cache *cache); +// Increase the geometry cache reference count. +ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache); + +// Read a frame from a geometry cache. +ufbx_abi size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); +ufbx_abi size_t ufbx_read_geometry_cache_vec3(const ufbx_cache_frame *frame, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); +// Sample the a geometry cache channel, linearly blending between adjacent frames. +ufbx_abi size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); +ufbx_abi size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channel, double time, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); + +// DOM + +// Find a DOM node given a name. +ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len); +ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name); + +// Utility + +// Generate an index buffer for a flat vertex buffer. +// `streams` specifies one or more vertex data arrays, each stream must contain `num_indices` vertices. +// This function compacts the data within `streams` in-place, writing the deduplicated indices to `indices`. +ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error); + +// Thread pool + +// Run a single thread pool task. +// See `ufbx_thread_pool_run_fn` for more information. +ufbx_unsafe ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx, uint32_t index); + +// Get or set an arbitrary user pointer for the thread pool context. +// `ufbx_thread_pool_get_user_ptr()` returns `NULL` if unset. +ufbx_unsafe ufbx_abi void ufbx_thread_pool_set_user_ptr(ufbx_thread_pool_context ctx, void *user_ptr); +ufbx_unsafe ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx); + +// -- Inline API + +// Utility functions for reading geometry data for a single index. +ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index); +ufbx_abi ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index); +ufbx_abi ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); +ufbx_abi ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index); + +// Utility functions for reading geometry data for a single index. +ufbx_inline ufbx_real ufbx_get_vertex_real(const ufbx_vertex_real *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } +ufbx_inline ufbx_vec2 ufbx_get_vertex_vec2(const ufbx_vertex_vec2 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } +ufbx_inline ufbx_vec3 ufbx_get_vertex_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } +ufbx_inline ufbx_vec4 ufbx_get_vertex_vec4(const ufbx_vertex_vec4 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } + +ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); +ufbx_inline ufbx_real ufbx_get_vertex_w_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values_w.count > 0 ? v->values_w.data[(int32_t)v->indices.data[index]] : 0.0f; } + +// Functions for converting an untyped `ufbx_element` to a concrete type. +// Returns `NULL` if the element is not that type. +ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element); +ufbx_abi ufbx_node *ufbx_as_node(const ufbx_element *element); +ufbx_abi ufbx_mesh *ufbx_as_mesh(const ufbx_element *element); +ufbx_abi ufbx_light *ufbx_as_light(const ufbx_element *element); +ufbx_abi ufbx_camera *ufbx_as_camera(const ufbx_element *element); +ufbx_abi ufbx_bone *ufbx_as_bone(const ufbx_element *element); +ufbx_abi ufbx_empty *ufbx_as_empty(const ufbx_element *element); +ufbx_abi ufbx_line_curve *ufbx_as_line_curve(const ufbx_element *element); +ufbx_abi ufbx_nurbs_curve *ufbx_as_nurbs_curve(const ufbx_element *element); +ufbx_abi ufbx_nurbs_surface *ufbx_as_nurbs_surface(const ufbx_element *element); +ufbx_abi ufbx_nurbs_trim_surface *ufbx_as_nurbs_trim_surface(const ufbx_element *element); +ufbx_abi ufbx_nurbs_trim_boundary *ufbx_as_nurbs_trim_boundary(const ufbx_element *element); +ufbx_abi ufbx_procedural_geometry *ufbx_as_procedural_geometry(const ufbx_element *element); +ufbx_abi ufbx_stereo_camera *ufbx_as_stereo_camera(const ufbx_element *element); +ufbx_abi ufbx_camera_switcher *ufbx_as_camera_switcher(const ufbx_element *element); +ufbx_abi ufbx_marker *ufbx_as_marker(const ufbx_element *element); +ufbx_abi ufbx_lod_group *ufbx_as_lod_group(const ufbx_element *element); +ufbx_abi ufbx_skin_deformer *ufbx_as_skin_deformer(const ufbx_element *element); +ufbx_abi ufbx_skin_cluster *ufbx_as_skin_cluster(const ufbx_element *element); +ufbx_abi ufbx_blend_deformer *ufbx_as_blend_deformer(const ufbx_element *element); +ufbx_abi ufbx_blend_channel *ufbx_as_blend_channel(const ufbx_element *element); +ufbx_abi ufbx_blend_shape *ufbx_as_blend_shape(const ufbx_element *element); +ufbx_abi ufbx_cache_deformer *ufbx_as_cache_deformer(const ufbx_element *element); +ufbx_abi ufbx_cache_file *ufbx_as_cache_file(const ufbx_element *element); +ufbx_abi ufbx_material *ufbx_as_material(const ufbx_element *element); +ufbx_abi ufbx_texture *ufbx_as_texture(const ufbx_element *element); +ufbx_abi ufbx_video *ufbx_as_video(const ufbx_element *element); +ufbx_abi ufbx_shader *ufbx_as_shader(const ufbx_element *element); +ufbx_abi ufbx_shader_binding *ufbx_as_shader_binding(const ufbx_element *element); +ufbx_abi ufbx_anim_stack *ufbx_as_anim_stack(const ufbx_element *element); +ufbx_abi ufbx_anim_layer *ufbx_as_anim_layer(const ufbx_element *element); +ufbx_abi ufbx_anim_value *ufbx_as_anim_value(const ufbx_element *element); +ufbx_abi ufbx_anim_curve *ufbx_as_anim_curve(const ufbx_element *element); +ufbx_abi ufbx_display_layer *ufbx_as_display_layer(const ufbx_element *element); +ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element); +ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element); +ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element); +ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element); +ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element); +ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element); +ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element); +ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element); + +// Functions for interfacing with DOM lists +ufbx_abi bool ufbx_dom_is_array(const ufbx_dom_node *node); +ufbx_abi size_t ufbx_dom_array_size(const ufbx_dom_node *node); +ufbx_abi ufbx_int32_list ufbx_dom_as_int32_list(const ufbx_dom_node *node); +ufbx_abi ufbx_int64_list ufbx_dom_as_int64_list(const ufbx_dom_node *node); +ufbx_abi ufbx_float_list ufbx_dom_as_float_list(const ufbx_dom_node *node); +ufbx_abi ufbx_double_list ufbx_dom_as_double_list(const ufbx_dom_node *node); +ufbx_abi ufbx_real_list ufbx_dom_as_real_list(const ufbx_dom_node *node); +ufbx_abi ufbx_blob_list ufbx_dom_as_blob_list(const ufbx_dom_node *node); + +#ifdef __cplusplus +} +#endif + +// bindgen-disable + +#if UFBX_CPP11 + +struct ufbx_string_view { + const char *data; + size_t length; + + ufbx_string_view() : data(nullptr), length(0) { } + ufbx_string_view(const char *data_, size_t length_) : data(data_), length(length_) { } + UFBX_CONVERSION_TO_IMPL(ufbx_string_view) +}; + +ufbx_inline ufbx_scene *ufbx_load_file(ufbx_string_view filename, const ufbx_load_opts *opts, ufbx_error *error) { return ufbx_load_file_len(filename.data, filename.length, opts, error); } +ufbx_inline ufbx_prop *ufbx_find_prop(const ufbx_props *props, ufbx_string_view name) { return ufbx_find_prop_len(props, name.data, name.length); } +ufbx_inline ufbx_real ufbx_find_real(const ufbx_props *props, ufbx_string_view name, ufbx_real def) { return ufbx_find_real_len(props, name.data, name.length, def); } +ufbx_inline ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, ufbx_string_view name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name.data, name.length, def); } +ufbx_inline int64_t ufbx_find_int(const ufbx_props *props, ufbx_string_view name, int64_t def) { return ufbx_find_int_len(props, name.data, name.length, def); } +ufbx_inline bool ufbx_find_bool(const ufbx_props *props, ufbx_string_view name, bool def) { return ufbx_find_bool_len(props, name.data, name.length, def); } +ufbx_inline ufbx_string ufbx_find_string(const ufbx_props *props, ufbx_string_view name, ufbx_string def) { return ufbx_find_string_len(props, name.data, name.length, def); } +ufbx_inline ufbx_blob ufbx_find_blob(const ufbx_props *props, ufbx_string_view name, ufbx_blob def) { return ufbx_find_blob_len(props, name.data, name.length, def); } +ufbx_inline ufbx_element *ufbx_find_prop_element(const ufbx_element *element, ufbx_string_view name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name.data, name.length, type); } +ufbx_inline ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, ufbx_string_view name) { return ufbx_find_element_len(scene, type, name.data, name.length); } +ufbx_inline ufbx_node *ufbx_find_node(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_node_len(scene, name.data, name.length); } +ufbx_inline ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_anim_stack_len(scene, name.data, name.length); } +ufbx_inline ufbx_material *ufbx_find_material(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_material_len(scene, name.data, name.length); } +ufbx_inline ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, ufbx_string_view prop) { return ufbx_find_anim_prop_len(layer, element, prop.data, prop.length); } +ufbx_inline ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, ufbx_string_view name, double time) { return ufbx_evaluate_prop_len(anim, element, name.data, name.length, time); } +ufbx_inline ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, ufbx_string_view name) { return ufbx_find_prop_texture_len(material, name.data, name.length); } +ufbx_inline ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, ufbx_string_view name) { return ufbx_find_shader_prop_len(shader, name.data, name.length); } +ufbx_inline ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, ufbx_string_view name) { return ufbx_find_shader_prop_bindings_len(shader, name.data, name.length); } +ufbx_inline ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, ufbx_string_view name) { return ufbx_find_shader_texture_input_len(shader, name.data, name.length); } +ufbx_inline ufbx_geometry_cache *ufbx_load_geometry_cache(ufbx_string_view filename, const ufbx_geometry_cache_opts *opts, ufbx_error *error) { return ufbx_load_geometry_cache_len(filename.data, filename.length, opts, error); } +ufbx_inline ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, ufbx_string_view name) { return ufbx_dom_find_len(parent, name.data, name.length); } + +#endif + +#if UFBX_CPP11 + +template +struct ufbx_type_traits { enum { valid = 0 }; }; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_scene *ptr) { ufbx_retain_scene(ptr); } + static void free(ufbx_scene *ptr) { ufbx_free_scene(ptr); } +}; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_mesh *ptr) { ufbx_retain_mesh(ptr); } + static void free(ufbx_mesh *ptr) { ufbx_free_mesh(ptr); } +}; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_line_curve *ptr) { ufbx_retain_line_curve(ptr); } + static void free(ufbx_line_curve *ptr) { ufbx_free_line_curve(ptr); } +}; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_geometry_cache *ptr) { ufbx_retain_geometry_cache(ptr); } + static void free(ufbx_geometry_cache *ptr) { ufbx_free_geometry_cache(ptr); } +}; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_anim *ptr) { ufbx_retain_anim(ptr); } + static void free(ufbx_anim *ptr) { ufbx_free_anim(ptr); } +}; + +template<> struct ufbx_type_traits { + enum { valid = 1 }; + static void retain(ufbx_baked_anim *ptr) { ufbx_retain_baked_anim(ptr); } + static void free(ufbx_baked_anim *ptr) { ufbx_free_baked_anim(ptr); } +}; + +class ufbx_deleter { +public: + template + void operator()(T *ptr) const { + static_assert(ufbx_type_traits::valid, "ufbx_deleter() unsupported for type"); + ufbx_type_traits::free(ptr); + } +}; + +// RAII wrapper over refcounted ufbx types. + +// Behaves like `std::unique_ptr`. +template +class ufbx_unique_ptr { + T *ptr; + using traits = ufbx_type_traits; + static_assert(ufbx_type_traits::valid, "ufbx_unique_ptr unsupported for type"); +public: + ufbx_unique_ptr() noexcept : ptr(nullptr) { } + explicit ufbx_unique_ptr(T *ptr_) noexcept : ptr(ptr_) { } + ufbx_unique_ptr(ufbx_unique_ptr &&ref) noexcept : ptr(ref.ptr) { ref.ptr = nullptr; } + ~ufbx_unique_ptr() { traits::free(ptr); } + + ufbx_unique_ptr &operator=(ufbx_unique_ptr &&ref) noexcept { + if (&ref == this) return *this; + ptr = ref.ptr; + ref.ptr = nullptr; + return *this; + } + + void reset(T *new_ptr=nullptr) noexcept { + traits::free(ptr); + ptr = new_ptr; + } + + void swap(ufbx_unique_ptr &ref) noexcept { + T *tmp = ptr; + ptr = ref.ptr; + ref.ptr = tmp; + } + + T &operator*() const noexcept { return *ptr; } + T *operator->() const noexcept { return ptr; } + T *get() const noexcept { return ptr; } + explicit operator bool() const noexcept { return ptr != nullptr; } +}; + +// Behaves like `std::shared_ptr` except uses ufbx's internal reference counting, +// so it is half the size of a standard `shared_ptr` but might be marginally slower. +template +class ufbx_shared_ptr { + T *ptr; + using traits = ufbx_type_traits; + static_assert(ufbx_type_traits::valid, "ufbx_shared_ptr unsupported for type"); +public: + + ufbx_shared_ptr() noexcept : ptr(nullptr) { } + explicit ufbx_shared_ptr(T *ptr_) noexcept : ptr(ptr_) { } + ufbx_shared_ptr(const ufbx_shared_ptr &ref) noexcept : ptr(ref.ptr) { traits::retain(ref.ptr); } + ufbx_shared_ptr(ufbx_shared_ptr &&ref) noexcept : ptr(ref.ptr) { ref.ptr = nullptr; } + ~ufbx_shared_ptr() { traits::free(ptr); } + + ufbx_shared_ptr &operator=(const ufbx_shared_ptr &ref) noexcept { + if (&ref == this) return *this; + traits::free(ptr); + traits::retain(ref.ptr); + ptr = ref.ptr; + return *this; + } + + ufbx_shared_ptr &operator=(ufbx_shared_ptr &&ref) noexcept { + if (&ref == this) return *this; + ptr = ref.ptr; + ref.ptr = nullptr; + return *this; + } + + void reset(T *new_ptr=nullptr) noexcept { + traits::free(ptr); + ptr = new_ptr; + } + + void swap(ufbx_shared_ptr &ref) noexcept { + T *tmp = ptr; + ptr = ref.ptr; + ref.ptr = tmp; + } + + T &operator*() const noexcept { return *ptr; } + T *operator->() const noexcept { return ptr; } + T *get() const noexcept { return ptr; } + explicit operator bool() const noexcept { return ptr != nullptr; } +}; + +#endif +// bindgen-enable + +// -- Properties + +// Names of common properties in `ufbx_props`. +// Some of these differ from ufbx interpretations. + +// Local translation. +// Used by: `ufbx_node` +#define UFBX_Lcl_Translation "Lcl Translation" + +// Local rotation expressed in Euler degrees. +// Used by: `ufbx_node` +// The rotation order is defined by the `UFBX_RotationOrder` property. +#define UFBX_Lcl_Rotation "Lcl Rotation" + +// Local scaling factor, 3D vector. +// Used by: `ufbx_node` +#define UFBX_Lcl_Scaling "Lcl Scaling" + +// Euler rotation interpretation, used by `UFBX_Lcl_Rotation`. +// Used by: `ufbx_node`, enum value `ufbx_rotation_order`. +#define UFBX_RotationOrder "RotationOrder" + +// Scaling pivot: point around which scaling is performed. +// Used by: `ufbx_node`. +#define UFBX_ScalingPivot "ScalingPivot" + +// Scaling pivot: point around which rotation is performed. +// Used by: `ufbx_node`. +#define UFBX_RotationPivot "RotationPivot" + +// Scaling offset: translation added after scaling is performed. +// Used by: `ufbx_node`. +#define UFBX_ScalingOffset "ScalingOffset" + +// Rotation offset: translation added after rotation is performed. +// Used by: `ufbx_node`. +#define UFBX_RotationOffset "RotationOffset" + +// Pre-rotation: Rotation applied _after_ `UFBX_Lcl_Rotation`. +// Used by: `ufbx_node`. +// Affected by `UFBX_RotationPivot` but not `UFBX_RotationOrder`. +#define UFBX_PreRotation "PreRotation" + +// Post-rotation: Rotation applied _before_ `UFBX_Lcl_Rotation`. +// Used by: `ufbx_node`. +// Affected by `UFBX_RotationPivot` but not `UFBX_RotationOrder`. +#define UFBX_PostRotation "PostRotation" + +// Controls whether the node should be displayed or not. +// Used by: `ufbx_node`. +#define UFBX_Visibility "Visibility" + +// Weight of an animation layer in percentage (100.0 being full). +// Used by: `ufbx_anim_layer`. +#define UFBX_Weight "Weight" + +// Blend shape deformation weight (100.0 being full). +// Used by: `ufbx_blend_channel`. +#define UFBX_DeformPercent "DeformPercent" + +#if defined(_MSC_VER) + #pragma warning(pop) +#elif defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + +#endif diff --git a/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp b/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp index 5c26a32dd..4a27e2769 100644 --- a/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp +++ b/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp @@ -3,6 +3,7 @@ #include "Game/T6/CommonT6.h" #include "Game/T6/T6.h" #include "Image/IwiLoader.h" +#include "Image/IwiTypes.h" #include #include @@ -25,7 +26,12 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { - const auto fileName = std::format("images/{}.iwi", assetName); + auto fileName = std::format("images/{}.iwi", assetName); + + for (size_t i = 0; i < fileName.size(); i++) + if (fileName[i] == '*') + fileName[i] = '_'; + const auto file = m_search_path.Open(fileName); if (!file.IsOpen()) return AssetCreationResult::NoAction(); @@ -53,11 +59,47 @@ namespace image->height = static_cast(texture->GetHeight()); image->depth = static_cast(texture->GetDepth()); - image->streaming = 1; - image->streamedParts[0].levelCount = 1; - image->streamedParts[0].levelSize = static_cast(fileSize); - image->streamedParts[0].hash = dataHash & 0x1FFFFFFF; - image->streamedPartCount = 1; + if (texture->GetTextureType() == TextureType::T_2D) + image->mapType = 3; + else if (texture->GetTextureType() == TextureType::T_3D) + image->mapType = 4; + else if (texture->GetTextureType() == TextureType::T_CUBE) + image->mapType = 5; + else + _ASSERT(false); + + //image->streaming = 1; + //image->streamedParts[0].levelCount = 1; + //image->streamedParts[0].levelSize = static_cast(fileSize); + //image->streamedParts[0].hash = dataHash & 0x1FFFFFFF; + //image->streamedPartCount = 1; + + int mipMapCount = texture->HasMipMaps() ? texture->GetMipMapCount() : 1; + size_t textureSize = 0; + for (int previousMipLevel = 0; previousMipLevel < mipMapCount; previousMipLevel++) + { + textureSize += texture->GetSizeOfMipLevel(previousMipLevel) * texture->GetFaceCount(); + } + + image->streaming = 0; + image->texture.loadDef = (GfxImageLoadDef*)malloc(sizeof(GfxImageLoadDef) + textureSize); + memset(image->texture.loadDef, 0, sizeof(GfxImageLoadDef) + textureSize); + image->texture.loadDef->format = texture->GetFormat()->GetDxgiFormat(); + image->texture.loadDef->levelCount = 1; + image->texture.loadDef->resourceSize = textureSize; + memcpy(image->texture.loadDef->data, + texture->GetBufferForMipLevel(0), + textureSize); // GetBufferForMipLevel(0) returns a pointer to the start of the image + + image->texture.loadDef->flags = 0; + if (image->noPicmip) + image->texture.loadDef->flags |= iwi27::IMG_FLAG_NOMIPMAPS; + + if (texture->GetTextureType() == TextureType::T_3D) + image->texture.loadDef->flags |= iwi27::IMG_FLAG_VOLMAP; + + if (texture->GetTextureType() == TextureType::T_CUBE) + image->texture.loadDef->flags |= iwi27::IMG_FLAG_CUBEMAP; return AssetCreationResult::Success(context.AddAsset(assetName, image)); } diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index c92928011..90adf960e 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -134,8 +134,8 @@ namespace printf("ERROR: Cant find pixel shader %s\n", psFileName.c_str()); return AssetCreationResult::Failure(); } - currPass->pixelShader->prog.loadDef.programSize = psFile.m_length; - currPass->pixelShader->prog.loadDef.program = new char[psFile.m_length]; + currPass->pixelShader->prog.loadDef.programSize = (unsigned int)psFile.m_length; + currPass->pixelShader->prog.loadDef.program = new char[(unsigned int)psFile.m_length]; psFile.m_stream->read(currPass->pixelShader->prog.loadDef.program, psFile.m_length); } @@ -158,8 +158,8 @@ namespace printf("ERROR: Cant find vertex shader %s\n", vsFileName.c_str()); return AssetCreationResult::Failure(); } - currPass->vertexShader->prog.loadDef.programSize = vsFile.m_length; - currPass->vertexShader->prog.loadDef.program = new char[vsFile.m_length]; + currPass->vertexShader->prog.loadDef.programSize = (unsigned int)vsFile.m_length; + currPass->vertexShader->prog.loadDef.program = new char[(unsigned int)vsFile.m_length]; vsFile.m_stream->read(currPass->vertexShader->prog.loadDef.program, vsFile.m_length); } } diff --git a/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp b/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp index 3d0d1d702..3f73d5843 100644 --- a/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp +++ b/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp @@ -14,6 +14,7 @@ #include "Writing/Steps/StepWriteZoneContentToFile.h" #include "Writing/Steps/StepWriteZoneContentToMemory.h" #include "Writing/Steps/StepWriteZoneHeader.h" +#include "Writing/Steps/StepWriteZoneRSA.h" #include "Writing/Steps/StepWriteZoneSizes.h" #include "Zone/XChunk/XChunkProcessorDeflate.h" #include "Zone/XChunk/XChunkProcessorSalsa20Encryption.h" @@ -99,9 +100,9 @@ std::unique_ptr ZoneWriterFactory::CreateWriter(const Zone& zone) co { auto writer = std::make_unique(); - // TODO Support signed fastfiles - bool isSecure = false; + bool isSecure = true; bool isEncrypted = true; + bool isOfficial = true; SetupBlocks(*writer); @@ -111,7 +112,11 @@ std::unique_ptr ZoneWriterFactory::CreateWriter(const Zone& zone) co writer->AddWritingStep(std::move(contentInMemory)); // Write zone header - writer->AddWritingStep(std::make_unique(CreateHeaderForParams(isSecure, false, isEncrypted))); + writer->AddWritingStep(std::make_unique(CreateHeaderForParams(isSecure, isOfficial, isEncrypted))); + + // write RSA + if (isSecure) + writer->AddWritingStep(std::make_unique(zone.m_name)); // Setup loading XChunks from the zone from this point on. ICapturedDataProvider* dataToSignProvider; diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp new file mode 100644 index 000000000..af048dedf --- /dev/null +++ b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp @@ -0,0 +1,35 @@ +#include "StepWriteZoneRSA.h" + +#include "Game/T6/ZoneConstantsT6.h" + +StepWriteZoneRSA::StepWriteZoneRSA(std::string zoneName) + : m_zoneName(zoneName) +{ +} + +void StepWriteZoneRSA::PerformStep(ZoneWriter* zoneWriter, IWritingStream* stream) +{ + stream->Write(T6::ZoneConstants::MAGIC_AUTH_HEADER, strlen(T6::ZoneConstants::MAGIC_AUTH_HEADER)); + + uint32_t loadFlags = 0; + stream->Write(&loadFlags, sizeof(uint32_t)); + + char fileName[32]; + memset(fileName, 0, 32); + strncpy(fileName, m_zoneName.c_str(), 31); + stream->Write(fileName, 32); + + char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, + 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, + 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, + 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, + 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, + 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, + 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, + 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, + 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, + 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, + 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, + 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; + stream->Write(data, 256); +} diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h new file mode 100644 index 000000000..3dbd48222 --- /dev/null +++ b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h @@ -0,0 +1,13 @@ +#pragma once +#include "Writing/IWritingStep.h" + + +class StepWriteZoneRSA final : public IWritingStep +{ + std::string m_zoneName; + +public: + explicit StepWriteZoneRSA(std::string zoneName); + + void PerformStep(ZoneWriter* zoneWriter, IWritingStream* stream) override; +}; From f129874728dd9267984f07e72436ef9cdcd30757 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 6 Oct 2025 15:35:03 +0800 Subject: [PATCH 007/123] V1 release of custom map code --- .gitmodules | 3 + src/Common/Game/T6/T6.h | 5 +- src/Common/Game/T6/T6_Assets.h | 26 +- src/ObjLoading/Asset/AssetCreationContext.cpp | 3 + .../Game/T6/CustomMap/CustomMapConsts.h | 100 +++- .../Game/T6/CustomMap/CustomMapLinker.h | 498 +++++++++++++----- .../Game/T6/CustomMap/ProjectCreator.cpp | 122 ++--- src/ObjLoading/Game/T6/CustomMap/Util.h | 48 +- .../Game/T6/Script/LoaderScriptT6.cpp | 31 +- .../Game/T6/Script/ScriptCompileT6.cpp | 31 ++ .../Game/T6/Script/ScriptCompileT6.h | 3 + .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 1 + .../SequenceZoneDefinitionMetaData.cpp | 6 + .../Parsing/ZoneDefinitionParserState.cpp | 21 + .../Parsing/ZoneDefinitionParserState.h | 2 + .../Zone/Definition/ZoneDefinition.h | 2 + .../Loading/Steps/StepVerifySignature.cpp | 2 +- .../Writing/Steps/StepWriteZoneRSA.cpp | 27 +- thirdparty/gsc-tool | 1 + 19 files changed, 696 insertions(+), 236 deletions(-) create mode 100644 src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp create mode 100644 src/ObjLoading/Game/T6/Script/ScriptCompileT6.h create mode 160000 thirdparty/gsc-tool diff --git a/.gitmodules b/.gitmodules index b283a8c3a..84c41e459 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "thirdparty/lz4"] path = thirdparty/lz4 url = https://github.com/lz4/lz4.git +[submodule "thirdparty/gsc-tool"] + path = thirdparty/gsc-tool + url = https://github.com/xensik/gsc-tool.git diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index d7413f40e..4b13191a5 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -172,9 +172,9 @@ namespace T6 enum CM_MATERIAL_TYPE { - NO_COLOUR_OR_TEXTURE, CM_MATERIAL_COLOUR, - CM_MATERIAL_TEXTURE + CM_MATERIAL_TEXTURE, + CM_MATERIAL_EMPTY }; struct customMapMaterial @@ -185,7 +185,6 @@ namespace T6 struct worldSurface { - char flags; customMapMaterial material; //char lightmapIndex; diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 7e6953011..c8488eef7 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -1278,6 +1278,30 @@ namespace T6 typedef tdef_align32(4) char aligned_byte_pointer; typedef tdef_align32(4) GfxCompressedLightGridCoeffs GfxCompressedLightGridCoeffs_align4; + struct GfxLightGridUnk + { + char unknown1; + char unknown2; + char unknown3; + char unknown4; + char unknown5; + char unknown6; + char unknown7; + char unknown8; + }; + + + struct GfxLightGridRow + { + unsigned __int16 colStart; + unsigned __int16 colCount; + unsigned __int16 zStart; + unsigned __int16 zCount; + unsigned int firstEntry; + GfxLightGridUnk unk; + }; + + struct GfxLightGrid { unsigned int sunPrimaryLightIndex; @@ -1288,7 +1312,7 @@ namespace T6 unsigned int colAxis; uint16_t* rowDataStart; unsigned int rawRowDataSize; - aligned_byte_pointer* rawRowData; + aligned_byte_pointer* rawRowData; // GfxLightGridRow unsigned int entryCount; GfxLightGridEntry* entries; unsigned int colorCount; diff --git a/src/ObjLoading/Asset/AssetCreationContext.cpp b/src/ObjLoading/Asset/AssetCreationContext.cpp index deb08b5a2..a33cebdda 100644 --- a/src/ObjLoading/Asset/AssetCreationContext.cpp +++ b/src/ObjLoading/Asset/AssetCreationContext.cpp @@ -131,6 +131,9 @@ XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_ } else { + if (assetName[0] == ',') + return LoadDefaultAssetDependency(assetType, assetName); + std::cerr << std::format("Missing asset \"{}\" of type \"{}\"\n", assetName, *m_zone.m_pools->GetAssetTypeName(assetType)); } diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index 5c2a1310e..dfa1ac836 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -19,28 +19,108 @@ #define LEAF_TERRAIN_CONTENTS 1 #define WORLD_TERRAIN_CONTENTS 1 -const std::string missingMaterialName = "custom/missing_material"; -const std::string colourOnlyMaterialName = "custom/colour_only_material"; -const std::string templateMaterialName = "custom/material_template"; - -#define DEFAULT_SURFACE_LIGHT 1 -#define DEFAULT_SURFACE_LIGHTMAP 0 -#define DEFAULT_SURFACE_REFLECTION_PROBE 0 +std::string missingImageName = "missing_image"; +std::string colorOnlyImageName = "color_only_image"; #define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf +#define LIGHTGRID_COLOUR 128 // global lighting colour + // do not change -#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of verts a map can have +#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of collision verts a map can have // do not change #define DEFAULT_LIGHT_INDEX 0 #define SUN_LIGHT_INDEX 1 // do not change -#define SMODEL_FLAG_NO_SHADOW 1 -#define SMODEL_FLAG_IS_LIT 2 +enum SMODEL_FLAGS +{ + SMODEL_FLAG_NO_SHADOW = 1, + SMODEL_FLAG_IS_LIT = 2 +}; + +// do not change +enum GFX_SURFACE_FLAGS +{ + GFX_SURFACE_CASTS_SUN_SHADOW = 0x1, + GFX_SURFACE_HAS_PRIMARY_LIGHT_CONFLICT = 0x2, + GFX_SURFACE_IS_SKY = 0x4, + GFX_SURFACE_NO_DRAW = 0x8, + GFX_SURFACE_CASTS_SHADOW = 0x10, + GFX_SURFACE_QUANTIZED = 0x20, + GFX_SURFACE_NO_COLOR = 0x40 +}; + #define DEFAULT_SMODEL_CULL_DIST 10000.0f #define DEFAULT_SMODEL_FLAGS SMODEL_FLAG_NO_SHADOW #define DEFAULT_SMODEL_LIGHT 1 #define DEFAULT_SMODEL_REFLECTION_PROBE 0 + +#define DEFAULT_SURFACE_LIGHT 1 +#define DEFAULT_SURFACE_LIGHTMAP 0 +#define DEFAULT_SURFACE_REFLECTION_PROBE 0 +#define DEFAULT_SURFACE_FLAGS 0 + +const std::vector spawnpointDefenderTypeArray = { + "mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start" +}; + +const std::vector spawnpointAttackerTypeArray = { + "mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start" +}; + +const std::vector spawnpointFFATypeArray = { + "mp_tdm_spawn", + "mp_dm_spawn", + "mp_dom_spawn" +}; + +const std::vector spawnpointObjectiveTypeArray = { + "mp_dom_spawn_flag_c", + "mp_dom_spawn_flag_a" +}; + +const std::string defaultSpawnpointString = "{" + "\"attackers\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]," + "\"defenders\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]," + "\"FFA\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]" + "}"; + + diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 9e43dcf76..6bcffc982 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,5 +1,7 @@ #pragma once +#include "Game/T6/Material/JsonMaterialLoaderT6.h" + #include "BinarySpacePartitionTreePreCalc.h" #include "TriangleSort.h" #include "CustomMapConsts.h" @@ -26,15 +28,21 @@ class CustomMapLinker { _ASSERT(projInfo != NULL); - createGfxWorld(projInfo); + checkAndAddDefaultRequiredAssets(projInfo); + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + createComWorld(projInfo); createMapEnts(projInfo); createGameWorldMp(projInfo); createSkinnedVerts(projInfo); + createGfxWorld(projInfo); // requires mapents asset createClipMap(projInfo); // must go last (requires gfx and mapents asset) - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) { printf("Custom Map link has failed.\n"); @@ -45,12 +53,21 @@ class CustomMapLinker } private: + struct entModelBounds + { + vec3_t mins; + vec3_t maxs; + }; + MemoryManager& m_memory; ISearchPath& m_search_path; Zone& m_zone; AssetCreationContext& m_context; bool hasLinkFailed; + std::vector entityModelList; + + json materialTemplateJson; // TODO vd1: // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour @@ -120,6 +137,26 @@ class CustomMapLinker return surf0.vertexCount > surf1.vertexCount; } + Material* loadImageIntoMaterial(std::string& imageName) + { + Material* material = new Material; + material->info.name = m_memory.Dup(imageName.c_str()); + + // parse the template file and replace the image name + materialTemplateJson["textures"][1]["image"] = imageName; + + AssetRegistration registration(imageName, material); + if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) + { + printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); + return NULL; + } + + m_context.AddAsset(std::move(registration)); + + return material; + } + void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) { bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); @@ -138,7 +175,7 @@ class CustomMapLinker currSurface->primaryLightIndex = DEFAULT_SURFACE_LIGHT; currSurface->lightmapIndex = DEFAULT_SURFACE_LIGHTMAP; currSurface->reflectionProbeIndex = DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = objSurface->flags; + currSurface->flags = DEFAULT_SURFACE_FLAGS; currSurface->tris.triCount = objSurface->triCount; currSurface->tris.baseIndex = objSurface->firstIndex_Index; @@ -154,25 +191,25 @@ class CustomMapLinker break; case CM_MATERIAL_COLOUR: - case NO_COLOUR_OR_TEXTURE: - surfMaterialName = colourOnlyMaterialName; + case CM_MATERIAL_EMPTY: + surfMaterialName = colorOnlyImageName; break; default: _ASSERT(false); } - auto* assetInfo = m_context.LoadDependency(surfMaterialName); - if (assetInfo == NULL) + Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); + if (surfMaterial == NULL) { - assetInfo = m_context.LoadDependency(missingMaterialName); - if (assetInfo == NULL) + surfMaterial = loadImageIntoMaterial(missingImageName); + if (surfMaterial == NULL) { - printf("Error: unable to find the default texture!\n"); + printf("Error: unable to find the missing image texture!\n"); hasLinkFailed = true; return; } } - currSurface->material = assetInfo->Asset(); + currSurface->material = surfMaterial; GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; currSurface->bounds[0].x = firstVert[0].xyz.x; @@ -441,36 +478,70 @@ class CustomMapLinker } } + // the lightgrid is used to light models in a dynamic way and is precomputed void overwriteLightGrid(GfxWorld* gfxWorld) { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values gfxWorld->lightGrid.mins[0] = 0; gfxWorld->lightGrid.mins[1] = 0; gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 0; - gfxWorld->lightGrid.maxs[1] = 0; - gfxWorld->lightGrid.maxs[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; // the sun is always index 1 - gfxWorld->lightGrid.offset = 0.0f; - - // if rowDataStart's first value is -1, it will not look up any lightmap data - gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; - *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); - - gfxWorld->lightGrid.rawRowDataSize = 0; - gfxWorld->lightGrid.rawRowData = NULL; - - gfxWorld->lightGrid.colorCount = 0; - gfxWorld->lightGrid.colors = NULL; + gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value + + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); + + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); + GfxLightGridRow* row = new GfxLightGridRow[1]; + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + // this unknown part is weird, bo2 code uses up to unk5 and possibly onwards but the dumped rawRowData looks like it has a different structure. + // this seems to work though + row->unk.unknown1 = 0; + row->unk.unknown2 = 0; + row->unk.unknown3 = 0; + row->unk.unknown4 = 0; + row->unk.unknown5 = 0; + row->unk.unknown6 = 0; + row->unk.unknown7 = 0; + row->unk.unknown8 = 0; + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + + // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; + memset(gfxWorld->lightGrid.colors, LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; gfxWorld->lightGrid.coeffs = NULL; - - gfxWorld->lightGrid.entryCount = 0; - gfxWorld->lightGrid.entries = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; gfxWorld->lightGrid.skyGridVolumes = NULL; } @@ -490,8 +561,6 @@ class CustomMapLinker gfxWorld->cells = new GfxCell[cellCount]; gfxWorld->cells[0].portalCount = 0; gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].reflectionProbeCount = 0; - gfxWorld->cells[0].reflectionProbes = NULL; gfxWorld->cells[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].mins.y = gfxWorld->mins.y; gfxWorld->cells[0].mins.z = gfxWorld->mins.z; @@ -499,6 +568,12 @@ class CustomMapLinker gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; + reflectionProbeIndexes[0] = DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + // AABB trees are used to detect what should be rendered and what shouldn't // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn gfxWorld->cells[0].aabbTreeCount = 1; @@ -558,7 +633,7 @@ class CustomMapLinker // these models are the collision for the entities defined in the mapents asset // used for triggers and stuff - gfxWorld->modelCount = 1; + gfxWorld->modelCount = entityModelList.size() + 1; gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; // first model is always the world model @@ -572,22 +647,21 @@ class CustomMapLinker gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - // for (int i = 1; i < gfxWorld->modelCount; i++) - //{ - // auto currEntModel = &gfxWorld->models[i]; - // json currEntModelJs = js["entityModels"][i - 1]; - // - // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; - // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; - // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; - // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; - // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; - // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; - // - // currEntModel->surfaceCount = 0; - // currEntModel->startSurfIndex = (unsigned int)(-1); - // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); - // } + for (size_t i = 0; i < entityModelList.size(); i++) + { + auto currEntModel = &gfxWorld->models[i + 1]; + entModelBounds currEntModelBounds = entityModelList[i]; + + currEntModel->startSurfIndex = 0; + currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->bounds[0].x = currEntModelBounds.mins.x; + currEntModel->bounds[0].y = currEntModelBounds.mins.y; + currEntModel->bounds[0].z = currEntModelBounds.mins.z; + currEntModel->bounds[1].x = currEntModelBounds.maxs.x; + currEntModel->bounds[1].y = currEntModelBounds.maxs.y; + currEntModel->bounds[1].z = currEntModelBounds.maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + } } void updateSunData(GfxWorld* gfxWorld) @@ -703,14 +777,14 @@ class CustomMapLinker gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); } - void overwriteSkyBox(GfxWorld* gfxWorld) + void overwriteSkyBox(customMapInfo* projInfo, GfxWorld* gfxWorld) { - const char* skyBoxName = "skybox_mp_dig"; - gfxWorld->skyBoxModel = skyBoxName; + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); if (m_context.LoadDependency(skyBoxName) == NULL) { - printf("WARN: Unable to find the skybox model %s\n", skyBoxName); + printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); } // default skybox values from mp_dig @@ -722,9 +796,12 @@ class CustomMapLinker void updateDynEntData(GfxWorld* gfxWorld) { - gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; + gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; gfxWorld->dpvsDyn.usageCount = 0; @@ -779,7 +856,7 @@ class CustomMapLinker auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); if (outdoorImageAsset == NULL) { - printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); + printf("ERROR! unable to find image $outdoor!\n"); hasLinkFailed = true; return; } @@ -804,7 +881,7 @@ class CustomMapLinker overwriteLightmapData(gfxWorld); - overwriteSkyBox(gfxWorld); + overwriteSkyBox(projInfo, gfxWorld); updateReflectionProbeData(gfxWorld); @@ -1386,8 +1463,8 @@ class CustomMapLinker clipMap->info.brushBounds = NULL; clipMap->info.brushContents = NULL; - clipMap->originalDynEntCount = DYN_ENT_COUNT; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; + clipMap->originalDynEntCount = DYN_ENT_COUNT; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame clipMap->dynEntCount[1] = 0; clipMap->dynEntCount[2] = 0; clipMap->dynEntCount[3] = 0; @@ -1413,49 +1490,47 @@ class CustomMapLinker clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; clipMap->dynEntDefList[1] = NULL; memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - for (int i = 0; i < clipMap->dynEntCount[0]; i++) - { - DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; - currDef->physConstraints[0] = 0x1FF; - currDef->physConstraints[1] = 0x1FF; - currDef->physConstraints[2] = 0x1FF; - currDef->physConstraints[3] = 0x1FF; - } // cmodels is the collision for mapents auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); _ASSERT(gfxWorldAsset != NULL); GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - _ASSERT(gfxWorld->modelCount == 1); - clipMap->numSubModels = 1; + clipMap->numSubModels = gfxWorld->modelCount; clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = NULL; - clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; - clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; - clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; - clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; - clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; - clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; - clipMap->cmodels[0].radius = CMUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - + for (unsigned int i = 0; i < clipMap->numSubModels; i++) + { + // bomb triggers use leafs, not world terrain so that might be an issue + + GfxBrushModel* gfxModel = &gfxWorld->models[i]; + cmodel_t* cmModel = &clipMap->cmodels[i]; + + cmModel->leaf.firstCollAabbIndex = 0; + cmModel->leaf.collAabbCount = 0; + cmModel->leaf.brushContents = 0; + cmModel->leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + cmModel->leaf.mins.x = 0.0f; + cmModel->leaf.mins.y = 0.0f; + cmModel->leaf.mins.z = 0.0f; + cmModel->leaf.maxs.x = 0.0f; + cmModel->leaf.maxs.y = 0.0f; + cmModel->leaf.maxs.z = 0.0f; + cmModel->leaf.leafBrushNode = 0; + cmModel->leaf.cluster = 0; + cmModel->info = NULL; + cmModel->mins.x = gfxModel->bounds[0].x; + cmModel->mins.y = gfxModel->bounds[0].y; + cmModel->mins.z = gfxModel->bounds[0].z; + cmModel->maxs.x = gfxModel->bounds[1].x; + cmModel->maxs.y = gfxModel->bounds[1].y; + cmModel->maxs.z = gfxModel->bounds[1].z; + cmModel->radius = CMUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; + } addXModelsToCollision(projInfo, clipMap); clipMap->info.numMaterials = 1; clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(missingMaterialName.c_str()); + clipMap->info.materials[0].name = _strdup(missingImageName.c_str()); clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; @@ -1559,34 +1634,12 @@ class CustomMapLinker m_context.AddAsset(comWorld->name, comWorld); } - void createMapEnts(customMapInfo* projInfo) + void parseMapEntsJSON(json& entArrayJs, std::string& entityString) { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - const auto file = m_search_path.Open("custom_map/entities.json"); - if (!file.IsOpen()) - { - printf("WARN: can't find entity json!\n"); - return; - } - - json entJs = json::parse(*file.m_stream); - - std::string entityString; - int entityCount = entJs["entityCount"]; + int entityCount = entArrayJs.size(); for (int i = 0; i < entityCount; i++) { - auto currEntity = entJs["entities"][i]; + auto currEntity = entArrayJs[i]; if (i == 0) { @@ -1610,7 +1663,186 @@ class CustomMapLinker entityString.append("}\n"); } - entityString.pop_back(); // remove newline character from the end of the string + } + + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, std::vector spawnpointTypeArray) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + std::string origin = currEntity["origin"]; + std::string angles = currEntity["angles"]; + + for (std::string& spawnType : spawnpointTypeArray) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", origin)); + entityString.append(std::format("\"angles\" \"{}\"\n", angles)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnType)); + entityString.append("}\n"); + } + } + } + + void parseBombJSON(json& bombJs, std::string& entityString) + { + // add the bomb model + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + entityString.append("{\n"); + entityString.append("\"classname\" \"script_model\"\n"); + entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); + entityString.append("\"targetname\" \"sd_bomb\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append("\"spawnflags\" \"4\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append("}\n"); + } + if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) + { + hasLinkFailed = true; + printf("ERROR: unable to find s&d bomb xmodel\n"); + return; + } + + // add the bomb pickup trigger + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + vec3_t bomboriginV3 = CMUtil::convertStringToVec3(bombOriginStr); + entModelBounds bounds; + bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig + bounds.mins.y = bomboriginV3.y - 32.0f; + bounds.mins.z = bomboriginV3.z - 8.0f; + bounds.maxs.x = bomboriginV3.x + 32.0f; + bounds.maxs.y = bomboriginV3.y + 32.0f; + bounds.maxs.z = bomboriginV3.z + 28.0f; + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_multiple\"\n"); + entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add A site bomb + { + std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; + std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; + vec3_t siteAPoint1V3 = CMUtil::convertStringToVec3(siteAPoint1Str); + vec3_t siteAPoint2V3 = CMUtil::convertStringToVec3(siteAPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteAPoint1V3.x; + bounds.mins.y = siteAPoint1V3.y; + bounds.mins.z = siteAPoint1V3.z; + bounds.maxs.x = siteAPoint1V3.x; + bounds.maxs.y = siteAPoint1V3.y; + bounds.maxs.z = siteAPoint1V3.z; + CMUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_a\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add B site bomb + { + std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; + std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; + vec3_t siteBPoint1V3 = CMUtil::convertStringToVec3(siteBPoint1Str); + vec3_t siteBPoint2V3 = CMUtil::convertStringToVec3(siteBPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteBPoint1V3.x; + bounds.mins.y = siteBPoint1V3.y; + bounds.mins.z = siteBPoint1V3.z; + bounds.maxs.x = siteBPoint1V3.x; + bounds.maxs.y = siteBPoint1V3.y; + bounds.maxs.z = siteBPoint1V3.z; + CMUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_b\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + } + + void createMapEnts(customMapInfo* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + std::string entityString; + + const auto entFile = m_search_path.Open("entities.json"); + if (!entFile.IsOpen()) + { + printf("ERROR: can't find entity json!\n"); + return; + } + json entJs = json::parse(*entFile.m_stream); + parseMapEntsJSON(entJs["entities"], entityString); + + const auto spawnFile = m_search_path.Open("spawns.json"); + json spawnJs; + if (!spawnFile.IsOpen()) + { + printf("WARN: no spawn points given, setting spawns to 0 0 0\n"); + spawnJs = json::parse(defaultSpawnpointString); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + + parseSpawnpointJSON(spawnJs["attackers"], entityString, spawnpointDefenderTypeArray); + parseSpawnpointJSON(spawnJs["defenders"], entityString, spawnpointAttackerTypeArray); + parseSpawnpointJSON(spawnJs["FFA"], entityString, spawnpointFFATypeArray); + + //const auto objectiveFile = m_search_path.Open("objectives.json"); + //if (!spawnFile.IsOpen()) + //{ + // printf("WARN: no objectives given\n"); + //} + //else + //{ + // json objectiveJs = json::parse(*objectiveFile.m_stream); + // parseBombJSON(objectiveJs, entityString); + //} mapEnts->entityString = _strdup(entityString.c_str()); mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character @@ -1668,42 +1900,42 @@ class CustomMapLinker void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) { - if (m_context.LoadDependency("maps/mp/mod.gsc") == NULL) + auto templateFile = m_search_path.Open("materials/material_template.json"); + if (!templateFile.IsOpen()) { + printf("ERROR: failed to open materials/material_template.json\n"); hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mod_amb.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/mod_fx.gsc") == NULL) + materialTemplateJson = json::parse(*templateFile.m_stream); + + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/createfx/mod_fx.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mod.csc") == NULL) + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mod_amb.csc") == NULL) + + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mod_fx.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/createfx/mod_fx.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) { hasLinkFailed = true; return; @@ -1715,5 +1947,11 @@ class CustomMapLinker addEmptyFootstepTableAsset("default_3rd_person_quiet"); addEmptyFootstepTableAsset("default_3rd_person_loud"); addEmptyFootstepTableAsset("default_ai"); + + if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) + { + hasLinkFailed = true; + return; + } } }; diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp index 7682fdef9..ec982b7b6 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -18,7 +18,7 @@ bool loadFBXMesh(ufbx_node* node) ufbx_mesh* mesh = node->mesh; if (mesh->instances.count != 1) - printf("node %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); + printf("mesh %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); if (mesh->num_triangles == 0) { @@ -41,12 +41,6 @@ bool loadFBXMesh(ufbx_node* node) } } - _ASSERT(mesh->vertex_position.unique_per_vertex == true); - _ASSERT(mesh->vertex_color.unique_per_vertex == false); - _ASSERT(mesh->vertex_uv.unique_per_vertex == false); - _ASSERT(mesh->vertex_normal.unique_per_vertex == false); - _ASSERT(mesh->vertex_tangent.unique_per_vertex == false); - if (mesh->vertex_tangent.exists == false) hasTangentSpace = false; @@ -60,29 +54,9 @@ bool loadFBXMesh(ufbx_node* node) origTransform.scale.z /= 100.0f; ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - CM_MATERIAL_TYPE meshMaterialType; - if (mesh->materials.count == 0) - { - if (!mesh->vertex_color.exists) - { - printf("mesh with no colour/texture data: %s\n", node->name.data); - meshMaterialType = NO_COLOUR_OR_TEXTURE; - } - else - { - printf("colour only mesh %s\n", node->name.data); - meshMaterialType = CM_MATERIAL_COLOUR; - } - } - else - { - meshMaterialType = CM_MATERIAL_TEXTURE; - } - - // FBX loading code modified from https://ufbx.github.io/elements/meshes/ // Seems like I have to use this exact way of loading, otherwise some values become incorrect - for (int i = 0; i < mesh->material_parts.count; i++) + for (size_t i = 0; i < mesh->material_parts.count; i++) { ufbx_mesh_part* meshPart = &mesh->material_parts.data[i]; @@ -90,26 +64,31 @@ bool loadFBXMesh(ufbx_node* node) continue; worldSurface surface; - surface.flags = 0; surface.triCount = meshPart->num_triangles; surface.firstVertexIndex = vertexVec.size(); surface.firstIndex_Index = indexVec.size(); - surface.material.materialType = meshMaterialType; - switch (meshMaterialType) + CM_MATERIAL_TYPE meshMaterialType; + if (mesh->materials.count == 0) + { + meshMaterialType = CM_MATERIAL_EMPTY; + } + //else if (mesh->materials.data[i]->textures.count != 0) + //{ + // meshMaterialType = CM_MATERIAL_TEXTURE; + // surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); + //} + //else + //{ + // meshMaterialType = CM_MATERIAL_COLOUR; + // surface.material.materialName = ""; + //} + else { - case CM_MATERIAL_TEXTURE: + meshMaterialType = CM_MATERIAL_TEXTURE; surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - break; - - case CM_MATERIAL_COLOUR: - case NO_COLOUR_OR_TEXTURE: - surface.material.materialName = ""; - break; - - default: - _ASSERT(false); } + surface.material.materialType = meshMaterialType; size_t num_triangles = meshPart->num_triangles; customMapVertex* vertices = (customMapVertex*)calloc(num_triangles * 3, sizeof(customMapVertex)); @@ -135,31 +114,34 @@ bool loadFBXMesh(ufbx_node* node) customMapVertex* vertex = &vertices[num_vertices++]; ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex->pos.x = transformedPos.x; - vertex->pos.y = transformedPos.y; - vertex->pos.z = transformedPos.z; + vertex->pos.x = static_cast(transformedPos.x); + vertex->pos.y = static_cast(transformedPos.y); + vertex->pos.z = static_cast(transformedPos.z); - // textured and missing materials are set to white + switch (meshMaterialType) { case CM_MATERIAL_TEXTURE: - case NO_COLOUR_OR_TEXTURE: + case CM_MATERIAL_EMPTY: vertex->color[0] = 1.0f; vertex->color[1] = 1.0f; vertex->color[2] = 1.0f; vertex->color[3] = 1.0f; break; - case CM_MATERIAL_COLOUR: - vertex->color[0] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).x; - vertex->color[1] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).y; - vertex->color[2] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).z; - vertex->color[3] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).w; + { + float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); + vertex->color[0] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.x * factor); + vertex->color[1] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.y * factor); + vertex->color[2] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.z * factor); + vertex->color[3] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec4.w * factor); break; + } default: _ASSERT(false); } + // 1.0f - uv.v: @@ -167,15 +149,15 @@ bool loadFBXMesh(ufbx_node* node) vertex->texCoord[0] = (float)(ufbx_get_vertex_vec2(&mesh->vertex_uv, index).x); vertex->texCoord[1] = (float)(1.0f - ufbx_get_vertex_vec2(&mesh->vertex_uv, index).y); - vertex->normal.x = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x; - vertex->normal.y = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y; - vertex->normal.z = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z; + vertex->normal.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x); + vertex->normal.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y); + vertex->normal.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z); if (mesh->vertex_tangent.exists) { - vertex->tangent.x = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x; - vertex->tangent.y = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y; - vertex->tangent.z = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z; + vertex->tangent.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x); + vertex->tangent.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y); + vertex->tangent.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z); } else { @@ -235,13 +217,13 @@ bool loadFBXModel(ufbx_node* node) origTransform.scale.z /= 100.0f; ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - model.origin.x = node->local_transform.translation.x / 100.0f; - model.origin.y = node->local_transform.translation.y / 100.0f; - model.origin.z = node->local_transform.translation.z / 100.0f; - model.rotation.x = node->euler_rotation.x; - model.rotation.y = node->euler_rotation.y; - model.rotation.z = node->euler_rotation.z; - model.scale = node->local_transform.scale.x / 100.0f; + model.origin.x = static_cast(node->local_transform.translation.x) / 100.0f; + model.origin.y = static_cast(node->local_transform.translation.y) / 100.0f; + model.origin.z = static_cast(node->local_transform.translation.z) / 100.0f; + model.rotation.x = static_cast(node->euler_rotation.x); + model.rotation.y = static_cast(node->euler_rotation.y); + model.rotation.z = static_cast(node->euler_rotation.z); + model.scale = static_cast(node->local_transform.scale.x) / 100.0f; if (model.scale == 0.0f) { @@ -277,7 +259,7 @@ void parseGFXData(ufbx_scene* scene, customMapInfo* projInfo) // loadFBXModel(node); break; default: - printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); break; } } @@ -321,7 +303,7 @@ void parseCollisionData(ufbx_scene* scene, customMapInfo* projInfo) // loadFBXModel(node); break; default: - printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); break; } } @@ -355,7 +337,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea return NULL; } - char* gfxMapData = new char[gfxFile.m_length]; + char* gfxMapData = new char[static_cast(gfxFile.m_length)]; gfxFile.m_stream->seekg(0); gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); @@ -364,7 +346,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea opts.target_axes = ufbx_axes_right_handed_y_up; opts.generate_missing_normals = true; opts.allow_missing_vertex_position = false; - gfxScene = ufbx_load_memory(gfxMapData, gfxFile.m_length, NULL, &error); + gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), NULL, &error); if (!gfxScene) { fprintf(stderr, "Failed to load map gfx fbx file: %s\n", error.description.data); @@ -380,7 +362,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea } else { - char* colMapData = new char[colFile.m_length]; + char* colMapData = new char[static_cast(colFile.m_length)]; colFile.m_stream->seekg(0); colFile.m_stream->read(colMapData, colFile.m_length); @@ -389,7 +371,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea opts.target_axes = ufbx_axes_right_handed_y_up; opts.generate_missing_normals = true; opts.allow_missing_vertex_position = false; - colScene = ufbx_load_memory(colMapData, colFile.m_length, NULL, &error); + colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), NULL, &error); if (!colScene) { fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h index a30ad7adb..1d23a831d 100644 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -70,6 +70,19 @@ class CMUtil currmaxs->z = point->z; } + static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + temp.x *= 0.5f; + temp.y *= 0.5f; + temp.z *= 0.5f; + return temp; + } + static int allignBy128(int size) { return ((size + 127) & 0xFFFFFF80); @@ -86,9 +99,9 @@ class CMUtil // angles are in euler degrees static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis) { - float xRadians = angles->x * 0.017453292; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292; // M_PI / 180.0f + float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f + float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f + float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f float cosX = cos(xRadians); float sinX = sin(xRadians); @@ -120,4 +133,33 @@ class CMUtil out[2].y = in[1].z; out[2].z = in[2].z; } + + static vec3_t convertStringToVec3(std::string str) + { + std::string v1Str = str; + + int nextValIndex = 0; + while (v1Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v2Str = &v1Str[nextValIndex]; + + nextValIndex = 0; + while (v2Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v3Str = &v2Str[nextValIndex]; + + vec3_t result; + result.x = static_cast(atof(v1Str.c_str())); + result.y = static_cast(atof(v2Str.c_str())); + result.z = static_cast(atof(v3Str.c_str())); + return result; + } + + static std::string convertVec3ToString(vec3_t vec) + { + std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + return result; + } }; diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp index 5d30b473d..60982351a 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp @@ -4,6 +4,8 @@ #include +#include "ScriptCompileT6.h" + using namespace T6; namespace @@ -17,23 +19,40 @@ namespace { } + const unsigned char T6GSCMagic[8] = {0x80, 0x47, 0x53, 0x43, 0x0D, 0x0A, 0x00, 0x06 }; + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { const auto file = m_search_path.Open(assetName); if (!file.IsOpen()) return AssetCreationResult::NoAction(); - auto* scriptParseTree = m_memory.Alloc(); - scriptParseTree->name = m_memory.Dup(assetName.c_str()); - scriptParseTree->len = static_cast(file.m_length); + auto* fileBuffer = m_memory.Alloc(static_cast(file.m_length + 1)); file.m_stream->read(fileBuffer, file.m_length); - if (file.m_stream->gcount() != file.m_length) + if (file.m_stream->gcount() != file.m_length || file.m_length < 0x10) return AssetCreationResult::Failure(); - fileBuffer[scriptParseTree->len] = '\0'; + fileBuffer[file.m_length] = '\0'; + + auto* scriptParseTree = m_memory.Alloc(); + scriptParseTree->name = m_memory.Dup(assetName.c_str()); + + if (memcmp(fileBuffer, T6GSCMagic, 8) == 0) + { + scriptParseTree->len = static_cast(file.m_length); + scriptParseTree->buffer = static_cast(fileBuffer); + } + else + { + size_t compiledSize; + char* result = compileScriptT6(assetName, fileBuffer, static_cast(file.m_length), &compiledSize); + if (result == NULL) + return AssetCreationResult::Failure(); - scriptParseTree->buffer = static_cast(fileBuffer); + scriptParseTree->buffer = result; + scriptParseTree->len = static_cast(compiledSize); + } return AssetCreationResult::Success(context.AddAsset(assetName, scriptParseTree)); } diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp new file mode 100644 index 000000000..db74229b0 --- /dev/null +++ b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp @@ -0,0 +1,31 @@ +#include "ScriptCompileT6.h" + +#include + + +char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize) +{ + try + { + xsk::arc::t6::pc::context context = xsk::arc::t6::pc::context(xsk::arc::instance::server); + + std::vector scriptBuffer; + scriptBuffer.resize(gscCodeSize); + memcpy(&scriptBuffer[0], gscCode, gscCodeSize); + + auto outasm = context.compiler().compile(gscName, scriptBuffer); + auto outbin = context.assembler().assemble(*outasm); + + char* compiledBuffer = new char[outbin.first.size]; + memcpy(compiledBuffer, outbin.first.data, outbin.first.size); + + *out_CompiledSize = outbin.first.size; + return compiledBuffer; + + } + catch (std::exception const& e) + { + printf(std::format("GSC Compile failed: {}\n", e.what()).c_str()); + return NULL; + } +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h new file mode 100644 index 000000000..522858a50 --- /dev/null +++ b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h @@ -0,0 +1,3 @@ +#include + +char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize); diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index 90adf960e..b2e973709 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -112,6 +112,7 @@ namespace { currPass->vertexDecl->routing.data[i].source = (unsigned char)passJs["vertexDecl"]["routing"][i]["source"]; currPass->vertexDecl->routing.data[i].dest = (unsigned char)passJs["vertexDecl"]["routing"][i]["dest"]; + currPass->vertexDecl->routing.decl[i] = NULL; } } diff --git a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp index 68e8d3c16..1ffa80d82 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp @@ -9,6 +9,8 @@ namespace { + constexpr auto METADATA_CUSTOM_MAP = "custom_map"; + constexpr auto METADATA_GAME = "game"; constexpr auto METADATA_GDT = "gdt"; constexpr auto METADATA_NAME = "name"; @@ -133,6 +135,10 @@ void SequenceZoneDefinitionMetaData::ProcessMatch(ZoneDefinitionParserState* sta { ProcessMetaDataGame(state, valueToken, value); } + else if (key == METADATA_CUSTOM_MAP) + { + state->SetCustomMap(); + } else if (key == METADATA_GDT) { state->m_definition->m_gdts.emplace_back(value); diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp index af7a1a622..a5ed3bf04 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp @@ -19,6 +19,27 @@ void ZoneDefinitionParserState::SetGame(const GameId game) m_asset_name_resolver = IAssetNameResolver::GetResolverForGame(game); } +void ZoneDefinitionParserState::SetCustomMap() +{ + if (m_definition->is_custom_map == false) + { + m_definition->is_custom_map = true; + if (m_definition->m_game != GameId::T6) + { + printf("ERROR: Custom map linking is only supported on BO2 (T6).\n"); + return; + } + + printf("Processing zone as a custom map zone.\n"); + const auto gfxWorldAssetType = m_asset_name_resolver->GetAssetTypeByName("gfxworld"); + _ASSERT(gfxWorldAssetType); + + m_definition->m_assets.emplace_back(*gfxWorldAssetType, "gfxworld", false); + } + + +} + namespace { void AddCurrentObjContainerToDefinitionIfNecessary(ZoneDefinition& zoneDefinition, std::optional& maybeObjContainer) diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h index 4fa605152..1be1bd843 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h @@ -17,6 +17,8 @@ class ZoneDefinitionParserState void SetGame(GameId game); + void SetCustomMap(); + void StartIPak(std::string ipakName); void StartIwd(std::string iwdName); diff --git a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h index 0d64f161d..e87f52cb2 100644 --- a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h +++ b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h @@ -69,4 +69,6 @@ class ZoneDefinition std::vector m_gdts; std::vector m_assets; std::vector m_obj_containers; + + bool is_custom_map = false; }; diff --git a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp index 6fb7780fc..e4093d85e 100644 --- a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp +++ b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp @@ -34,7 +34,7 @@ namespace if (!m_algorithm->Verify(signatureData, signatureDataSize, signature, signatureSize)) { - throw InvalidSignatureException(); + //throw InvalidSignatureException(); } } diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp index af048dedf..96dde6483 100644 --- a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp +++ b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp @@ -19,17 +19,20 @@ void StepWriteZoneRSA::PerformStep(ZoneWriter* zoneWriter, IWritingStream* strea strncpy(fileName, m_zoneName.c_str(), 31); stream->Write(fileName, 32); - char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, - 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, - 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, - 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, - 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, - 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, - 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, - 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, - 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, - 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, - 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, - 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; + //char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, + // 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, + // 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, + // 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, + // 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, + // 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, + // 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, + // 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, + // 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, + // 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, + // 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, + // 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; + + char data[256]; + memset(data, 0, 256); stream->Write(data, 256); } diff --git a/thirdparty/gsc-tool b/thirdparty/gsc-tool new file mode 160000 index 000000000..c9bd8e5c6 --- /dev/null +++ b/thirdparty/gsc-tool @@ -0,0 +1 @@ +Subproject commit c9bd8e5c6aec6ec77f7089208892871c9eb8597a From abf15e861ee3d18d3211ac8c2b9fb88a8d0b554d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 13:47:08 +0800 Subject: [PATCH 008/123] Failed test to make shadows work --- .../Game/T6/CustomMap/CustomMapConsts.h | 2 +- .../Game/T6/CustomMap/CustomMapLinker.h | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index dfa1ac836..6d5df10dd 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -61,7 +61,7 @@ enum GFX_SURFACE_FLAGS #define DEFAULT_SURFACE_LIGHT 1 #define DEFAULT_SURFACE_LIGHTMAP 0 #define DEFAULT_SURFACE_REFLECTION_PROBE 0 -#define DEFAULT_SURFACE_FLAGS 0 +#define DEFAULT_SURFACE_FLAGS (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW) const std::vector spawnpointDefenderTypeArray = { "mp_ctf_spawn_allies", diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 6bcffc982..a544b40dc 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -258,8 +258,19 @@ class CustomMapLinker memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); + + for (unsigned int i = 0; i < surfaceCount; i++) + { + if ((gfxWorld->dpvs.surfaces[i].flags & GFX_SURFACE_CASTS_SHADOW) == 0) + gfxWorld->dpvs.surfaceCastsShadow[i] = 0; + else + gfxWorld->dpvs.surfaceCastsShadow[i] = 1; + + if ((gfxWorld->dpvs.surfaces[i].flags & GFX_SURFACE_CASTS_SUN_SHADOW) == 0) + gfxWorld->dpvs.surfaceCastsSunShadow[i] = 0; + else + gfxWorld->dpvs.surfaceCastsSunShadow[i] = 1; + } gfxWorld->dpvs.litSurfsBegin = 0; gfxWorld->dpvs.litSurfsEnd = surfaceCount; @@ -454,9 +465,9 @@ class CustomMapLinker for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) { gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = gfxWorld->surfaceCount; gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = gfxWorld->dpvs.sortedSurfIndex; } gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; @@ -470,7 +481,7 @@ class CustomMapLinker if (lightEntShadowVisSize != 0) { gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); + memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); } else { From dd460a65c8010d02d2784bc2bf7cdf8a8baf930d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 19:42:11 +0800 Subject: [PATCH 009/123] Second failed attempt at making shadows work --- .../Game/T6/CustomMap/CustomMapConsts.h | 2 +- .../Game/T6/CustomMap/CustomMapLinker.h | 20 +++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index 6d5df10dd..6acc66fc9 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -58,7 +58,7 @@ enum GFX_SURFACE_FLAGS #define DEFAULT_SMODEL_LIGHT 1 #define DEFAULT_SMODEL_REFLECTION_PROBE 0 -#define DEFAULT_SURFACE_LIGHT 1 +#define DEFAULT_SURFACE_LIGHT SUN_LIGHT_INDEX #define DEFAULT_SURFACE_LIGHTMAP 0 #define DEFAULT_SURFACE_REFLECTION_PROBE 0 #define DEFAULT_SURFACE_FLAGS (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW) diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index a544b40dc..61ec5b681 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -258,19 +258,8 @@ class CustomMapLinker memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - - for (unsigned int i = 0; i < surfaceCount; i++) - { - if ((gfxWorld->dpvs.surfaces[i].flags & GFX_SURFACE_CASTS_SHADOW) == 0) - gfxWorld->dpvs.surfaceCastsShadow[i] = 0; - else - gfxWorld->dpvs.surfaceCastsShadow[i] = 1; - - if ((gfxWorld->dpvs.surfaces[i].flags & GFX_SURFACE_CASTS_SUN_SHADOW) == 0) - gfxWorld->dpvs.surfaceCastsSunShadow[i] = 0; - else - gfxWorld->dpvs.surfaceCastsSunShadow[i] = 1; - } + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); gfxWorld->dpvs.litSurfsBegin = 0; gfxWorld->dpvs.litSurfsEnd = surfaceCount; @@ -465,9 +454,10 @@ class CustomMapLinker for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) { gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->shadowGeom[i].surfaceCount = 0; gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = gfxWorld->dpvs.sortedSurfIndex; + gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; + memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); } gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; From b1670445f53f47b70a5751b9f99249ac6b5a7708 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 19:47:46 +0800 Subject: [PATCH 010/123] remove GSC-Tool use --- .../Game/T6/Script/LoaderScriptT6.cpp | 37 +++++-------------- .../Game/T6/Script/LoaderScriptT6.h | 6 +-- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp index 60982351a..a7284bc73 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp @@ -4,8 +4,6 @@ #include -#include "ScriptCompileT6.h" - using namespace T6; namespace @@ -19,40 +17,23 @@ namespace { } - const unsigned char T6GSCMagic[8] = {0x80, 0x47, 0x53, 0x43, 0x0D, 0x0A, 0x00, 0x06 }; - AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { const auto file = m_search_path.Open(assetName); if (!file.IsOpen()) return AssetCreationResult::NoAction(); - + auto* scriptParseTree = m_memory.Alloc(); + scriptParseTree->name = m_memory.Dup(assetName.c_str()); + scriptParseTree->len = static_cast(file.m_length); auto* fileBuffer = m_memory.Alloc(static_cast(file.m_length + 1)); file.m_stream->read(fileBuffer, file.m_length); - if (file.m_stream->gcount() != file.m_length || file.m_length < 0x10) + if (file.m_stream->gcount() != file.m_length) return AssetCreationResult::Failure(); - fileBuffer[file.m_length] = '\0'; - - auto* scriptParseTree = m_memory.Alloc(); - scriptParseTree->name = m_memory.Dup(assetName.c_str()); - - if (memcmp(fileBuffer, T6GSCMagic, 8) == 0) - { - scriptParseTree->len = static_cast(file.m_length); - scriptParseTree->buffer = static_cast(fileBuffer); - } - else - { - size_t compiledSize; - char* result = compileScriptT6(assetName, fileBuffer, static_cast(file.m_length), &compiledSize); - if (result == NULL) - return AssetCreationResult::Failure(); + fileBuffer[scriptParseTree->len] = '\0'; - scriptParseTree->buffer = result; - scriptParseTree->len = static_cast(compiledSize); - } + scriptParseTree->buffer = static_cast(fileBuffer); return AssetCreationResult::Success(context.AddAsset(assetName, scriptParseTree)); } @@ -63,10 +44,10 @@ namespace }; } // namespace -namespace T6 +namespace script { - std::unique_ptr> CreateScriptLoader(MemoryManager& memory, ISearchPath& searchPath) + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath) { return std::make_unique(memory, searchPath); } -} // namespace T6 +} // namespace script diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h index 754da2ecf..ebaab5dae 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h @@ -7,7 +7,7 @@ #include -namespace T6 +namespace script { - std::unique_ptr> CreateScriptLoader(MemoryManager& memory, ISearchPath& searchPath); -} // namespace T6 + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath); +} // namespace script From 94ccd961cb41808965e4a3a1f35c63fd05be77c2 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 19:54:01 +0800 Subject: [PATCH 011/123] Remove GSC-Tool 2 --- thirdparty/gsc-tool | 1 - 1 file changed, 1 deletion(-) delete mode 160000 thirdparty/gsc-tool diff --git a/thirdparty/gsc-tool b/thirdparty/gsc-tool deleted file mode 160000 index c9bd8e5c6..000000000 --- a/thirdparty/gsc-tool +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c9bd8e5c6aec6ec77f7089208892871c9eb8597a From 0662f2adbec6cb1188a1ec8ba95cf12a68128bde Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 21:09:15 +0800 Subject: [PATCH 012/123] Another refactor to clean up comments and code --- .../BinarySpacePartitionTreePreCalc.h | 4 +- .../Game/T6/CustomMap/CustomMapConsts.h | 24 +- .../Game/T6/CustomMap/CustomMapLinker.cpp | 1648 ----------------- .../Game/T6/CustomMap/CustomMapLinker.h | 9 +- .../Game/T6/CustomMap/CustomMapOptions.h | 17 + .../Game/T6/CustomMap/LoaderCustomMapT6.cpp | 12 +- src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h | 1153 ------------ .../Game/T6/CustomMap/ProjectCreator.cpp | 68 +- .../Game/T6/CustomMap/ProjectCreator.h | 2 +- .../Game/T6/CustomMap/TriangleSort.h | 99 - src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 3 +- .../Game/T6/Script/LoaderScriptT6.cpp | 6 +- .../Game/T6/Script/LoaderScriptT6.h | 6 +- 13 files changed, 70 insertions(+), 2981 deletions(-) delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp create mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/TriangleSort.h diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h index 3c02217c2..eda7bca0a 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h @@ -5,7 +5,7 @@ Heavily modified version of https://github.com/sudeshnapal12/Space-Partitioning- Credit to sudeshnapal12 -Precalculated, evenly sized BSPs are much more efficient and smaller compared to dynamically creating them +BSP leaf sizes are precalculated, evenly sized BSPs are much more efficient and smaller compared to dynamically creating them */ #include @@ -16,7 +16,7 @@ Precalculated, evenly sized BSPs are much more efficient and smaller compared to #include #include -#include "CustomMapConsts.h" +#define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf enum PlaneAxis { diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index 6acc66fc9..c8ce1d0dd 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -1,46 +1,26 @@ #pragma once - #include -#define DYN_ENT_COUNT 0 - -// the clipMap->cmodels[0].leaf.terrainContents takes precendence over leaf and material terrain contents +// do not change any values in this header file -// material flags determine the features of the surface -// unsure which flag type changes what right now -// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast -// 1 results in: normal surface features, grenades work, seems normal -#define MATERIAL_SURFACE_FLAGS 1 -#define MATERIAL_CONTENT_FLAGS 1 - -// terrain/world flags: does not change the type of terrain or what features they have -// from testing, as long at it isn't 0 things will work correctly -#define LEAF_TERRAIN_CONTENTS 1 -#define WORLD_TERRAIN_CONTENTS 1 +#define DYN_ENT_COUNT 0 std::string missingImageName = "missing_image"; std::string colorOnlyImageName = "color_only_image"; -#define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf - -#define LIGHTGRID_COLOUR 128 // global lighting colour -// do not change #define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of collision verts a map can have -// do not change #define DEFAULT_LIGHT_INDEX 0 #define SUN_LIGHT_INDEX 1 -// do not change enum SMODEL_FLAGS { SMODEL_FLAG_NO_SHADOW = 1, SMODEL_FLAG_IS_LIT = 2 }; -// do not change enum GFX_SURFACE_FLAGS { GFX_SURFACE_CASTS_SUN_SHADOW = 0x1, diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp deleted file mode 100644 index e865f6ba7..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp +++ /dev/null @@ -1,1648 +0,0 @@ -//#include "CustomMapLinker.h" -// -//#include "Utils/Pack.h" -//#include "Util.h" -//#include "CustomMapConsts.h" -//#include "BinarySpacePartitionTreePreCalc.h" -// -//#include -//#include -//using json = nlohmann::json; -// -//class CustomMapLinker -//{ -//public: -// CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) -// : m_memory(memory), -// m_search_path(searchPath), -// m_zone(zone), -// m_context(context) -// { -// hasLinkFailed = false; -// } -// -// bool linkCustomMap(customMapInfo* projInfo) -// { -// _ASSERT(projInfo != NULL); -// -// createGfxWorld(projInfo); -// createComWorld(projInfo); -// createMapEnts(projInfo); -// createGameWorldMp(projInfo); -// createSkinnedVerts(projInfo); -// createClipMap(projInfo); // must go last (requires gfx and mapents asset) -// -// checkAndAddDefaultRequiredAssets(projInfo); -// -// if (hasLinkFailed) -// { -// printf("Custom Map link has failed.\n"); -// return false; -// } -// -// return true; -// } -// -//private: -// MemoryManager& m_memory; -// ISearchPath& m_search_path; -// Zone& m_zone; -// AssetCreationContext& m_context; -// -// bool hasLinkFailed; -// -// // TODO vd1: -// // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour -// // could fix by removing sub textures or figure out how they are created and redo that -// // its not an important issue though -// bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) -// { -// int vertexCount = projInfo->gfxInfo.vertexCount; -// customMapVertex* worldVertices = projInfo->gfxInfo.vertices; -// -// gfxWorld->draw.vertexCount = vertexCount; -// gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); -// GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; -// for (int i = 0; i < vertexCount; i++) -// { -// customMapVertex* WorldVertex = &worldVertices[i]; -// GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; -// -// GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); -// -// GfxVertex->binormalSign = WorldVertex->binormalSign; -// -// GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); -// -// GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); -// -// GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); -// -// GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); -// -// GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; -// } -// gfxWorld->draw.vd0.data = (char*)vertexBuffer; -// -// // we don't use vd1 but still needs to be initialised -// // the data type varies and 0x20 is enough for all types -// gfxWorld->draw.vertexDataSize1 = 0x20; -// gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; -// memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); -// -// int indexCount = projInfo->gfxInfo.indexCount; -// _ASSERT(indexCount % 3 == 0); -// gfxWorld->draw.indexCount = indexCount; -// gfxWorld->draw.indices = new uint16_t[indexCount]; -// for (int i = 0; i < indexCount; i += 3) -// { -// // the editor orders their vertices opposite to bo2, so its converted here -// gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; -// gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; -// gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; -// } -// -// return true; -// } -// -// struct s_sortedSurf -// { -// int surfaceIndex; -// int vertexCount; -// }; -// -// bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) -// { -// return surf0.vertexCount > surf1.vertexCount; -// } -// -// void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) -// { -// bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); -// if (!overwriteResult) -// return; -// -// unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; -// gfxWorld->surfaceCount = surfaceCount; -// gfxWorld->dpvs.staticSurfaceCount = surfaceCount; -// gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; -// for (int i = 0; i < surfaceCount; i++) -// { -// auto currSurface = &gfxWorld->dpvs.surfaces[i]; -// auto objSurface = &projInfo->gfxInfo.surfaces[i]; -// -// currSurface->lightmapIndex = objSurface->lightmapIndex; -// currSurface->primaryLightIndex = objSurface->primaryLightIndex; -// currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; -// currSurface->flags = objSurface->flags; -// -// currSurface->tris.triCount = objSurface->triCount; -// currSurface->tris.baseIndex = objSurface->firstIndex_Index; -// -// currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); -// currSurface->tris.vertexDataOffset1 = 0; -// -// auto* assetInfo = m_context.LoadDependency(objSurface->materialName); -// if (assetInfo == NULL) -// { -// printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); -// assetInfo = m_context.LoadDependency(defaultMaterialName); -// _ASSERT(assetInfo != NULL); -// } -// currSurface->material = assetInfo->Asset(); -// -// GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; -// currSurface->bounds[0].x = firstVert[0].xyz.x; -// currSurface->bounds[0].y = firstVert[0].xyz.y; -// currSurface->bounds[0].z = firstVert[0].xyz.z; -// currSurface->bounds[1].x = firstVert[0].xyz.x; -// currSurface->bounds[1].y = firstVert[0].xyz.y; -// currSurface->bounds[1].z = firstVert[0].xyz.z; -// for (int k = 0; k < currSurface->tris.triCount * 3; k++) -// { -// CMUtil::calcNewBoundsWithPoint(&firstVert[k].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); -// } -// -// // unused values -// currSurface->tris.mins.x = 0.0f; -// currSurface->tris.mins.y = 0.0f; -// currSurface->tris.mins.z = 0.0f; -// currSurface->tris.maxs.x = 0.0f; -// currSurface->tris.maxs.y = 0.0f; -// currSurface->tris.maxs.z = 0.0f; -// currSurface->tris.himipRadiusInvSq = 0.0f; -// currSurface->tris.vertexCount = 0; -// currSurface->tris.firstVertex = 0; -// } -// -// // sort all vertexes by vertex count -// // unsure if this does anything -// s_sortedSurf* sortedSurfs = new s_sortedSurf[surfaceCount]; -// for (int i = 0; i < surfaceCount; i++) -// { -// sortedSurfs[i].surfaceIndex = i; -// sortedSurfs[i].vertexCount = gfxWorld->dpvs.surfaces[i].tris.triCount * 3; -// } -// std::sort(sortedSurfs, &sortedSurfs[surfaceCount], compareSurfaces); -// -// gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; -// for (int i = 0; i < surfaceCount; i++) -// { -// gfxWorld->dpvs.sortedSurfIndex[i] = sortedSurfs[i].surfaceIndex; -// } -// -// gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; -// memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); -// -// // all visdata is alligned by 128 -// gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); -// gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; -// gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; -// gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; -// gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; -// gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; -// gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; -// memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); -// memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); -// memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); -// memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); -// memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); -// memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); -// -// gfxWorld->dpvs.litSurfsBegin = 0; -// gfxWorld->dpvs.litSurfsEnd = surfaceCount; -// gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; -// gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; -// gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; -// gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; -// gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; -// gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; -// } -// -//#define SMODEL_FLAG_NO_SHADOW 1 -//#define SMODEL_FLAG_IS_LIT 2 -// -// void overwriteMapSModels(GfxWorld* gfxWorld) -// { -// unsigned int modelCount = 0; -// gfxWorld->dpvs.smodelCount = modelCount; -// -// gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; -// gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; -// /* -// for (unsigned int i = 0; i < modelCount; i++) -// { -// auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; -// auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; -// json currModelJs = js["models"][i]; -// -// // TODO: load custom xmodels -// std::string modelName = currModelJs["model"]; -// auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); -// if (xModelAsset == NULL) -// { -// printf("Custom model (%s) not supported!\n", modelName.c_str()); -// hasLinkFailed = true; -// return; -// } -// currModel->model = (XModel*)xModelAsset->m_ptr; -// -// currModel->placement.origin.x = currModelJs["origin"]["x"]; -// currModel->placement.origin.y = currModelJs["origin"]["y"]; -// currModel->placement.origin.z = currModelJs["origin"]["z"]; -// currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); -// currModel->placement.axis[0].x = currModelJs["forward"]["x"]; -// currModel->placement.axis[0].y = currModelJs["forward"]["y"]; -// currModel->placement.axis[0].z = currModelJs["forward"]["z"]; -// currModel->placement.axis[1].x = currModelJs["right"]["x"]; -// currModel->placement.axis[1].y = currModelJs["right"]["y"]; -// currModel->placement.axis[1].z = currModelJs["right"]["z"]; -// currModel->placement.axis[2].x = currModelJs["up"]["x"]; -// currModel->placement.axis[2].y = currModelJs["up"]["y"]; -// currModel->placement.axis[2].z = currModelJs["up"]["z"]; -// currModel->placement.scale = currModelJs["scale"]; -// -// // mins and maxs are calculated in world space not local space -// // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame -// currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; -// currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; -// currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; -// currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; -// currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; -// currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; -// -// currModel->cullDist = currModelJs["cullDist"]; -// currModel->flags = currModelJs["flags"]; -// currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; -// currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; -// -// // unknown use / unused -// currModel->smid = i; -// memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); -// currModel->invScaleSq = 0.0f; -// currModel->lightingHandle = 0; -// currModel->colorsIndex = 0; -// currModel->visibility = 0; -// -// // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow -// // TODO: calculate lighting and store it here -// currModel->lmapVertexInfo[0].numLmapVertexColors = 0; -// currModel->lmapVertexInfo[0].lmapVertexColors = NULL; -// currModel->lmapVertexInfo[1].numLmapVertexColors = 0; -// currModel->lmapVertexInfo[1].lmapVertexColors = NULL; -// currModel->lmapVertexInfo[2].numLmapVertexColors = 0; -// currModel->lmapVertexInfo[2].lmapVertexColors = NULL; -// currModel->lmapVertexInfo[3].numLmapVertexColors = 0; -// currModel->lmapVertexInfo[3].lmapVertexColors = NULL; -// } -// */ -// -// // all visdata is alligned by 128 -// gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); -// gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; -// gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; -// gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; -// gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; -// gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; -// for (unsigned int i = 0; i < modelCount; i++) -// { -// if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) -// gfxWorld->dpvs.smodelCastsShadow[i] = 1; -// else -// gfxWorld->dpvs.smodelCastsShadow[i] = 0; -// } -// memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); -// memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); -// memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); -// memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); -// -// gfxWorld->dpvs.usageCount = 0; -// } -// -// void cleanGfxWorld(GfxWorld* gfxWorld) -// { -// gfxWorld->checksum = 0; -// -// // Remove Coronas -// gfxWorld->coronaCount = 0; -// gfxWorld->coronas = NULL; -// -// // Remove exposure volumes -// gfxWorld->exposureVolumeCount = 0; -// gfxWorld->exposureVolumes = NULL; -// gfxWorld->exposureVolumePlaneCount = 0; -// gfxWorld->exposureVolumePlanes = NULL; -// -// // Remove hero lights -// gfxWorld->heroLightCount = 0; -// gfxWorld->heroLights = NULL; -// gfxWorld->heroLightTreeCount = 0; -// gfxWorld->heroLightTree = NULL; -// -// // remove LUT data -// gfxWorld->lutVolumeCount = 0; -// gfxWorld->lutVolumes = NULL; -// gfxWorld->lutVolumePlaneCount = 0; -// gfxWorld->lutVolumePlanes = NULL; -// -// // remove occluders -// gfxWorld->numOccluders = 0; -// gfxWorld->occluders = NULL; -// -// // remove Siege Skins -// gfxWorld->numSiegeSkinInsts = 0; -// gfxWorld->siegeSkinInsts = NULL; -// -// // remove outdoor bounds -// gfxWorld->numOutdoorBounds = 0; -// gfxWorld->outdoorBounds = NULL; -// -// // remove materials -// gfxWorld->ropeMaterial = NULL; -// gfxWorld->lutMaterial = NULL; -// gfxWorld->waterMaterial = NULL; -// gfxWorld->coronaMaterial = NULL; -// -// // remove shadow maps -// gfxWorld->shadowMapVolumeCount = 0; -// gfxWorld->shadowMapVolumes = NULL; -// gfxWorld->shadowMapVolumePlaneCount = 0; -// gfxWorld->shadowMapVolumePlanes = NULL; -// -// // remove stream info -// gfxWorld->streamInfo.aabbTreeCount = 0; -// gfxWorld->streamInfo.aabbTrees = NULL; -// gfxWorld->streamInfo.leafRefCount = 0; -// gfxWorld->streamInfo.leafRefs = NULL; -// -// // remove sun data -// memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); -// gfxWorld->sun.hasValidData = false; -// -// // Remove Water -// gfxWorld->waterDirection = 0.0f; -// gfxWorld->waterBuffers[0].buffer = NULL; -// gfxWorld->waterBuffers[0].bufferSize = NULL; -// gfxWorld->waterBuffers[1].buffer = NULL; -// gfxWorld->waterBuffers[1].bufferSize = NULL; -// -// // Remove Fog -// gfxWorld->worldFogModifierVolumeCount = 0; -// gfxWorld->worldFogModifierVolumes = NULL; -// gfxWorld->worldFogModifierVolumePlaneCount = 0; -// gfxWorld->worldFogModifierVolumePlanes = NULL; -// gfxWorld->worldFogVolumeCount = 0; -// gfxWorld->worldFogVolumes = NULL; -// gfxWorld->worldFogVolumePlaneCount = 0; -// gfxWorld->worldFogVolumePlanes = NULL; -// -// // materialMemory is unused -// gfxWorld->materialMemoryCount = 0; -// gfxWorld->materialMemory = NULL; -// -// // sunLight is overwritten by the game, just needs to be a valid pointer -// gfxWorld->sunLight = new GfxLight; -// memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); -// } -// -// void overwriteGfxLights(GfxWorld* gfxWorld) -// { -// // there must be 2 or more lights, first is the default light and second is the sun -// gfxWorld->primaryLightCount = 2; -// gfxWorld->sunPrimaryLightIndex = 1; // the sun is always index 1 -// -// gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; -// for (int i = 0; i < gfxWorld->primaryLightCount; i++) -// { -// gfxWorld->shadowGeom[i].smodelCount = 0; -// gfxWorld->shadowGeom[i].surfaceCount = 0; -// gfxWorld->shadowGeom[i].smodelIndex = NULL; -// gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; -// } -// -// gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; -// for (int i = 0; i < gfxWorld->primaryLightCount; i++) -// { -// gfxWorld->lightRegion[i].hullCount = 0; -// gfxWorld->lightRegion[i].hulls = NULL; -// } -// -// int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; -// if (lightEntShadowVisSize != 0) -// { -// gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; -// memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); -// } -// else -// { -// gfxWorld->primaryLightEntityShadowVis = NULL; -// } -// } -// -// void overwriteLightGrid(GfxWorld* gfxWorld) -// { -// gfxWorld->lightGrid.mins[0] = 0; -// gfxWorld->lightGrid.mins[1] = 0; -// gfxWorld->lightGrid.mins[2] = 0; -// gfxWorld->lightGrid.maxs[0] = 0; -// gfxWorld->lightGrid.maxs[1] = 0; -// gfxWorld->lightGrid.maxs[2] = 0; -// -// gfxWorld->lightGrid.rowAxis = 0; // default value -// gfxWorld->lightGrid.colAxis = 1; // default value -// gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 -// gfxWorld->lightGrid.offset = 0.0f; -// -// // if rowDataStart's first value is -1, it will not look up any lightmap data -// gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; -// *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); -// -// gfxWorld->lightGrid.rawRowDataSize = 0; -// gfxWorld->lightGrid.rawRowData = NULL; -// -// gfxWorld->lightGrid.colorCount = 0; -// gfxWorld->lightGrid.colors = NULL; -// -// gfxWorld->lightGrid.coeffCount = 0; -// gfxWorld->lightGrid.coeffs = NULL; -// -// gfxWorld->lightGrid.entryCount = 0; -// gfxWorld->lightGrid.entries = NULL; -// -// gfxWorld->lightGrid.skyGridVolumeCount = 0; -// gfxWorld->lightGrid.skyGridVolumes = NULL; -// } -// -// void updateGfxCells(GfxWorld* gfxWorld) -// { -// // Cells are basically data used to determine what can be seen and what cant be seen -// // Right now custom maps have no optimisation so there is only 1 cell -// int cellCount = 1; -// -// gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; -// -// int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); -// gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; -// memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); -// -// gfxWorld->cells = new GfxCell[cellCount]; -// gfxWorld->cells[0].portalCount = 0; -// gfxWorld->cells[0].portals = NULL; -// gfxWorld->cells[0].reflectionProbeCount = 0; -// gfxWorld->cells[0].reflectionProbes = NULL; -// gfxWorld->cells[0].mins.x = gfxWorld->mins.x; -// gfxWorld->cells[0].mins.y = gfxWorld->mins.y; -// gfxWorld->cells[0].mins.z = gfxWorld->mins.z; -// gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; -// gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; -// gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; -// -// // AABB trees are used to detect what should be rendered and what shouldn't -// // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn -// gfxWorld->cells[0].aabbTreeCount = 1; -// gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; -// gfxWorld->cells[0].aabbTree[0].childCount = 0; -// gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; -// gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; -// gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; -// gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; -// gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; -// for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) -// { -// gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; -// } -// gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; -// gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; -// gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; -// gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; -// gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; -// gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; -// -// gfxWorld->dpvsPlanes.cellCount = cellCount; -// -// int sceneEntCellBitsCount = cellCount * 512; -// gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; -// memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); -// -// // nodes have the struct mnode_t, and there must be at least 1 node -// // Nodes mnode_t.cellIndex indexes gfxWorld->cells -// // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes -// gfxWorld->nodeCount = 1; -// gfxWorld->planeCount = 0; -// gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; -// gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 -// gfxWorld->dpvsPlanes.planes = NULL; -// } -// -// void updateWorldBounds(GfxWorld* gfxWorld) -// { -// gfxWorld->mins.x = 0.0f; -// gfxWorld->mins.y = 0.0f; -// gfxWorld->mins.z = 0.0f; -// gfxWorld->maxs.x = 0.0f; -// gfxWorld->maxs.y = 0.0f; -// gfxWorld->maxs.z = 0.0f; -// -// for (int i = 0; i < gfxWorld->surfaceCount; i++) -// { -// CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); -// } -// } -// -// void overwriteModels(GfxWorld* gfxWorld) -// { -// gfxWorld->modelCount = 1; -// gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; -// -// // first model is the world model, the world json doesn't include it so its added here -// gfxWorld->models[0].startSurfIndex = 0; -// gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; -// gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; -// gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; -// gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; -// gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; -// gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; -// gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; -// memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); -// -// // for (int i = 1; i < gfxWorld->modelCount; i++) -// //{ -// // auto currEntModel = &gfxWorld->models[i]; -// // json currEntModelJs = js["entityModels"][i - 1]; -// // -// // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; -// // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; -// // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; -// // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; -// // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; -// // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; -// // -// // currEntModel->surfaceCount = 0; -// // currEntModel->startSurfIndex = (unsigned int)(-1); -// // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); -// // } -// } -// -// void updateSunData(GfxWorld* gfxWorld) -// { -// // default values taken from mp_dig -// gfxWorld->sunParse.fogTransitionTime = 0.001; -// gfxWorld->sunParse.name[0] = 0x00; -// -// gfxWorld->sunParse.initWorldSun->control = 0; -// gfxWorld->sunParse.initWorldSun->exposure = 2.5f; -// gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; -// gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; -// gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; -// gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; -// gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; -// gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; -// gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; -// gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; -// gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; -// gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; -// gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; -// gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; -// gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; -// gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; -// gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; -// gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; -// gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; -// gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; -// gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; -// -// gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; -// gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; -// gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; -// gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; -// gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; -// gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; -// gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; -// gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; -// gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; -// gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; -// gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; -// gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; -// gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; -// gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; -// gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; -// gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; -// } -// -// void updateReflectionProbeData(GfxWorld* gfxWorld) -// { -// gfxWorld->draw.reflectionProbeCount = 1; -// -// gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; -// memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); -// -// gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; -// -// // default values taken from mp_dig -// gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; -// -// gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; -// gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; -// gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; -// -// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; -// gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; -// -// gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; -// gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; -// -// std::string probeImageName = "*reflection_probe0"; -// auto probeImageAsset = m_context.LoadDependency(probeImageName); -// if (probeImageAsset == NULL) -// { -// printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); -// hasLinkFailed = true; -// return; -// } -// gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); -// } -// -// void overwriteLightmapData(GfxWorld* gfxWorld) -// { -// gfxWorld->draw.lightmapCount = 1; -// -// gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; -// gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; -// gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; -// -// // always set to 0 -// memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); -// memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); -// -// std::string secondaryTexture = "*lightmap0_secondary"; -// auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); -// if (secondaryTextureAsset == NULL) -// { -// printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); -// hasLinkFailed = true; -// return; -// } -// gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL -// gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); -// } -// -// void overwriteSkyBox(GfxWorld* gfxWorld) -// { -// const char* skyBoxName = "skybox_mp_dig"; -// gfxWorld->skyBoxModel = skyBoxName; -// -// if (m_context.LoadDependency(skyBoxName) == NULL) -// { -// printf("WARN: Unable to find the skybox model %s\n", skyBoxName); -// } -// -// // default skybox values from mp_dig -// gfxWorld->skyDynIntensity.angle0 = 0.0f; -// gfxWorld->skyDynIntensity.angle1 = 0.0f; -// gfxWorld->skyDynIntensity.factor0 = 1.0f; -// gfxWorld->skyDynIntensity.factor1 = 1.0f; -// } -// -// void updateDynEntData(GfxWorld* gfxWorld) -// { -// gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; -// gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; -// gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 -// gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; -// gfxWorld->dpvsDyn.usageCount = 0; -// -// int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; -// gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; -// gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; -// memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); -// -// int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; -// gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; -// gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; -// gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; -// gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; -// gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; -// gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; -// memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); -// memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); -// memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); -// -// int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); -// gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; -// gfxWorld->primaryLightDynEntShadowVis[1] = NULL; -// memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); -// -// gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; -// gfxWorld->sceneDynBrush = NULL; -// memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); -// } -// -// void updateOutdoors(GfxWorld* gfxWorld) -// { -// float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); -// float xScale = -(xRecip * gfxWorld->mins.x); -// -// float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); -// float yScale = -(yRecip * gfxWorld->mins.y); -// -// float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); -// float zScale = -(zRecip * gfxWorld->mins.z); -// -// memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); -// -// gfxWorld->outdoorLookupMatrix[0].x = xRecip; -// gfxWorld->outdoorLookupMatrix[1].y = yRecip; -// gfxWorld->outdoorLookupMatrix[2].z = zRecip; -// gfxWorld->outdoorLookupMatrix[3].x = xScale; -// gfxWorld->outdoorLookupMatrix[3].y = yScale; -// gfxWorld->outdoorLookupMatrix[3].z = zScale; -// gfxWorld->outdoorLookupMatrix[3].w = 1.0f; -// -// std::string outdoorImageName = std::string("$outdoor"); -// auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); -// if (outdoorImageAsset == NULL) -// { -// printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); -// hasLinkFailed = true; -// return; -// } -// gfxWorld->outdoorImage = outdoorImageAsset->Asset(); -// } -// -// void createGfxWorld(customMapInfo* projInfo) -// { -// GfxWorld* gfxWorld = new GfxWorld; -// gfxWorld->baseName = _strdup(projInfo->name.c_str()); -// gfxWorld->name = _strdup(projInfo->bspName.c_str()); -// -// // Default values taken from mp_dig -// gfxWorld->lightingFlags = 0; -// gfxWorld->lightingQuality = 4096; -// -// cleanGfxWorld(gfxWorld); -// -// overwriteMapSurfaces(projInfo, gfxWorld); -// -// overwriteMapSModels(gfxWorld); -// -// overwriteLightmapData(gfxWorld); -// -// overwriteSkyBox(gfxWorld); -// -// updateReflectionProbeData(gfxWorld); -// -// // world bounds are based on surface mins/maxs -// // Other update functions depend on the bounds being set first -// updateWorldBounds(gfxWorld); -// -// updateOutdoors(gfxWorld); -// -// // gfx cells depend on surface/smodel count -// updateGfxCells(gfxWorld); -// -// overwriteLightGrid(gfxWorld); -// -// overwriteGfxLights(gfxWorld); -// -// overwriteModels(gfxWorld); -// -// updateSunData(gfxWorld); -// -// updateDynEntData(gfxWorld); -// -// m_context.AddAsset(gfxWorld->name, gfxWorld); -// } -// -// void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) -// { -// // Origin is the midpoint: (min + max) / 2 -// vec3_t temp; -// temp.x = mins->x + maxs->x; -// temp.y = mins->y + maxs->y; -// temp.z = mins->z + maxs->z; -// out_origin->x = temp.x * 0.5f; -// out_origin->y = temp.y * 0.5f; -// out_origin->z = temp.z * 0.5f; -// -// // Half-size is half the difference: (max - min) / 2 -// temp.x = maxs->x - mins->x; -// temp.y = maxs->y - mins->y; -// temp.z = maxs->z - mins->z; -// out_halfSize->x = temp.x * 0.5f; -// out_halfSize->y = temp.y * 0.5f; -// out_halfSize->z = temp.z * 0.5f; -// } -// -// bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) -// { -// -// unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); -// -// return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; -// } -// -// float distBetweenPoints(vec3_t p1, vec3_t p2) -// { -// float x = p2.x - p1.x; -// float y = p2.y - p1.y; -// float z = p2.z - p1.z; -// return sqrtf((x * x) + (y * y) + (z * z)); -// } -// -// -// -// void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) -// { -// if (node->isLeaf) -// { -// (*numLeafs)++; -// // there won't be an AABB tree when objectList is empty -// if (node->u.leaf->getObjectCount() > 0) -// { -// *numAABBTrees += node->u.leaf->getObjectCount() + 1; -// -// if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) -// *maxObjsPerLeaf = node->u.leaf->getObjectCount(); -// } -// } -// else -// { -// (*numPlanes)++; -// (*numNodes)++; -// traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); -// traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); -// } -// } -// -// vec3_t normalX = {1.0f, 0.0f, 0.0f}; -// vec3_t normalY = {0.0f, 1.0f, 0.0f}; -// vec3_t normalZ = {0.0f, 0.0f, 1.0f}; -// -// int currPlaneCount = 0; -// int currNodeCount = 0; -// int currLeafCount = 0; -// int currAABBCount = 0; -// -// int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) -// { -// _ASSERT(node->isLeaf); -// -// int objectCount = node->u.leaf->getObjectCount(); -// int firstAABBIndex = currAABBCount; -// currAABBCount += objectCount + 1; -// -// // calculate root AABB node mins and maxs -// // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs -// // so we have to recompute every min and max, not hard just tedious -// int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; -// auto firstPartition = &clipMap->partitions[firstPartitionIndex]; -// uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; -// vec3_t* firstVert = &clipMap->verts[firstTri[0]]; -// vec3_t aabbMins; -// vec3_t aabbMaxs; -// aabbMins.x = firstVert->x; -// aabbMins.y = firstVert->y; -// aabbMins.z = firstVert->z; -// aabbMaxs.x = firstVert->x; -// aabbMaxs.y = firstVert->y; -// aabbMaxs.z = firstVert->z; -// for (int i = 0; i < objectCount; i++) -// { -// int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; -// auto currPartition = &clipMap->partitions[currPartitionIndex]; -// -// for (int k = 0; k < currPartition->triCount; k++) -// { -// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; -// for (int l = 0; l < 3; l++) -// { -// uint16_t vertIndex = tri[l]; -// vec3_t vertCoord = clipMap->verts[vertIndex]; -// CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); -// } -// } -// } -// // create root AABB node -// CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; -// aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); -// rootAABB->materialIndex = 0; -// rootAABB->childCount = objectCount; -// rootAABB->u.firstChildIndex = firstAABBIndex + 1; -// -// // populate child AABB nodes -// for (int i = 0; i < objectCount; i++) -// { -// CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; -// int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; -// -// currAabbTree->materialIndex = 0; -// currAabbTree->childCount = 0; -// currAabbTree->u.partitionIndex = currPartitionIndex; -// -// // calculate partition origin and half size -// CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; -// uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; -// vec3_t* firstVertex = &clipMap->verts[firstUind]; -// vec3_t mins; -// vec3_t maxs; -// mins.x = firstVertex->x; -// mins.y = firstVertex->y; -// mins.z = firstVertex->z; -// maxs.x = firstVertex->x; -// maxs.y = firstVertex->y; -// maxs.z = firstVertex->z; -// for (int i = 1; i < aabbPartition->nuinds; i++) -// { -// uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; -// vec3_t* currVertex = &clipMap->verts[currUind]; -// -// CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); -// } -// -// aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); -// -// } -// -// return firstAABBIndex; -// } -// -// // returns the index corresponding to the BSPTree* node parsed -// int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) -// { -// new cplane_s; -// new cNode_t; -// new cLeaf_s; -// new CollisionAabbTree; -// -// if (node->isLeaf) -// { -// int currLeafIndex = currLeafCount; -// currLeafCount++; -// cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; -// -// currLeaf->cluster = 0; -// currLeaf->brushContents = 0; // no brushes used so contents is 0 -// currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence -// -// // unused when leafBrushNode == 0 -// currLeaf->mins.x = 0.0f; -// currLeaf->mins.y = 0.0f; -// currLeaf->mins.z = 0.0f; -// currLeaf->maxs.x = 0.0f; -// currLeaf->maxs.y = 0.0f; -// currLeaf->maxs.z = 0.0f; -// currLeaf->leafBrushNode = 0; -// -// if (node->u.leaf->getObjectCount() > 0) -// { -// currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); -// currLeaf->collAabbCount = 1; -// } -// else -// { -// currLeaf->firstCollAabbIndex = 0; -// currLeaf->collAabbCount = 0; -// } -// -// return -1 - currLeafIndex; -// } -// else -// { -// cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; -// currPlaneCount++; -// -// if (node->u.node->axis == AXIS_X) -// { -// // X is unchanged when going from OGL x -> BO2 x -// currPlane->normal = normalX; -// -// // converting OGL -> BO2 X coords doesn't change the x coords at all, so -// // the dist stays the same -// currPlane->dist = node->u.node->distance; -// } -// else -// { -// // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. -// // convert the z normal to the y normal, but don't negate it. Negative normals don't do -// // what is expected when the game uses them -// _ASSERT(node->u.node->axis == AXIS_Z); -// currPlane->normal = normalY; -// -// // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. -// // just negate it here as it is just the distance from the orgin along the axis -// currPlane->dist = -node->u.node->distance; -// } -// -// bool foundType = false; -// if (currPlane->normal.x == 1.0f) -// { -// _ASSERT(!foundType); -// foundType = true; -// currPlane->type = 0; -// } -// else if (currPlane->normal.y == 1.0f) -// { -// _ASSERT(!foundType); -// foundType = true; -// currPlane->type = 1; -// } -// else if (currPlane->normal.z == 1.0f) -// { -// _ASSERT(!foundType); -// foundType = true; -// currPlane->type = 2; -// } -// else -// _ASSERT(foundType); -// -// currPlane->signbits = 0; -// if (currPlane->normal.x < 0.0f) -// currPlane->signbits |= 1; -// if (currPlane->normal.y < 0.0f) -// currPlane->signbits |= 2; -// if (currPlane->normal.z < 0.0f) -// currPlane->signbits |= 4; -// -// currPlane->pad[0] = 0; -// currPlane->pad[1] = 0; -// -// int currNodeIndex = currNodeCount; -// currNodeCount++; -// cNode_t* currNode = &clipMap->nodes[currNodeIndex]; -// -// currNode->plane = currPlane; -// // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): -// // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, -// // the normal vector flips and objects behind the plane are now in front, and vise versa -// // so the back node now represents the front, and the front node represents the back. -// // Do the OGL -> Bo2 coord change on paper and it will make sense -// if (currPlane->type == 1) -// { -// currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); -// currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); -// } -// else -// { -// currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); -// currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); -// } -// -// return currNodeIndex; -// } -// } -// -// void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) -// { -// int numPlanes = 0; -// int numNodes = 0; -// int numLeafs = 0; -// int numAABBTrees = 0; -// int maxObjsPerLeaf = 0; -// -// traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); -// -// printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); -// -// clipMap->info.planeCount = numPlanes; -// clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; -// clipMap->numNodes = numNodes; -// clipMap->nodes = new cNode_t[clipMap->numNodes]; -// // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. -// // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. -// clipMap->aabbTreeCount = numAABBTrees; -// clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; -// -// currPlaneCount = 0; -// currNodeCount = 0; -// currAABBCount = 0; -// -// // first leaf is always empty -// clipMap->numLeafs = numLeafs + 1; -// clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; -// memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); -// currLeafCount = 1; -// -// populateBSPTree_r(clipMap, tree); -// -// _ASSERT(clipMap->info.planeCount == currPlaneCount); -// _ASSERT(clipMap->numNodes == currNodeCount); -// _ASSERT(clipMap->numLeafs == currLeafCount); -// _ASSERT(clipMap->aabbTreeCount == currAABBCount); -// } -// -// void createClipMap(customMapInfo* projInfo) -// { -// clipMap_t* clipMap = new clipMap_t; -// -// clipMap->name = _strdup(projInfo->bspName.c_str()); -// -// std::string mapEntsName = projInfo->bspName.c_str(); -// auto mapEntsAsset = m_context.LoadDependency(mapEntsName); -// _ASSERT(mapEntsAsset != NULL); -// clipMap->mapEnts = mapEntsAsset->Asset(); -// -// clipMap->pInfo = NULL; -// -// clipMap->box_model.mins.x = 0.0f; -// clipMap->box_model.mins.y = 0.0f; -// clipMap->box_model.mins.z = 0.0f; -// clipMap->box_model.maxs.x = 0.0f; -// clipMap->box_model.maxs.y = 0.0f; -// clipMap->box_model.maxs.z = 0.0f; -// clipMap->box_model.radius = 0.0f; -// clipMap->box_model.info = NULL; -// clipMap->box_model.leaf.mins.x = 3.4028235e38; -// clipMap->box_model.leaf.mins.y = 3.4028235e38; -// clipMap->box_model.leaf.mins.z = 3.4028235e38; -// clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive -// clipMap->box_model.leaf.maxs.y = -3.4028235e38; -// clipMap->box_model.leaf.maxs.z = -3.4028235e38; -// clipMap->box_model.leaf.brushContents = -1; -// clipMap->box_model.leaf.terrainContents = 0; -// clipMap->box_model.leaf.cluster = 0; -// clipMap->box_model.leaf.collAabbCount = 0; -// clipMap->box_model.leaf.firstCollAabbIndex = 0; -// clipMap->box_model.leaf.leafBrushNode = 0; -// -// clipMap->box_brush = new cbrush_t; -// clipMap->box_brush->axial_sflags[0][0] = -1; -// clipMap->box_brush->axial_sflags[0][1] = -1; -// clipMap->box_brush->axial_sflags[0][2] = -1; -// clipMap->box_brush->axial_sflags[1][0] = -1; -// clipMap->box_brush->axial_sflags[1][1] = -1; -// clipMap->box_brush->axial_sflags[1][2] = -1; -// clipMap->box_brush->axial_cflags[0][0] = -1; -// clipMap->box_brush->axial_cflags[0][1] = -1; -// clipMap->box_brush->axial_cflags[0][2] = -1; -// clipMap->box_brush->axial_cflags[1][0] = -1; -// clipMap->box_brush->axial_cflags[1][1] = -1; -// clipMap->box_brush->axial_cflags[1][2] = -1; -// clipMap->box_brush->contents = -1; -// clipMap->box_brush->mins.x = 0.0f; -// clipMap->box_brush->mins.y = 0.0f; -// clipMap->box_brush->mins.z = 0.0f; -// clipMap->box_brush->maxs.x = 0.0f; -// clipMap->box_brush->maxs.y = 0.0f; -// clipMap->box_brush->maxs.z = 0.0f; -// clipMap->box_brush->numsides = 0; -// clipMap->box_brush->numverts = 0; -// clipMap->box_brush->sides = NULL; -// clipMap->box_brush->verts = NULL; -// -// clipMap->numClusters = 1; -// clipMap->vised = 0; -// clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; -// clipMap->visibility = new char[clipMap->clusterBytes]; -// memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); -// -// clipMap->isInUse = true; -// clipMap->checksum = 0; -// -// clipMap->num_constraints = 0; -// clipMap->constraints = NULL; -// clipMap->max_ropes = 32; -// clipMap->ropes = new rope_t[clipMap->max_ropes]; -// memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); -// -// clipMap->info.numBrushSides = 0; -// clipMap->info.brushsides = NULL; -// clipMap->info.leafbrushNodesCount = 0; -// clipMap->info.leafbrushNodes = NULL; -// clipMap->info.numLeafBrushes = 0; -// clipMap->info.leafbrushes = NULL; -// clipMap->info.numBrushVerts = 0; -// clipMap->info.brushVerts = NULL; -// clipMap->info.numBrushes = NULL; -// clipMap->info.brushes = NULL; -// clipMap->info.brushBounds = NULL; -// clipMap->info.brushContents = NULL; -// -// clipMap->originalDynEntCount = DYN_ENT_COUNT; -// clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; -// clipMap->dynEntCount[1] = 0; -// clipMap->dynEntCount[2] = 0; -// clipMap->dynEntCount[3] = 0; -// -// // assume that there are 0 dyn ents from here on -// clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; -// clipMap->dynEntClientList[1] = NULL; -// memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); -// -// clipMap->dynEntServerList[0] = NULL; -// clipMap->dynEntServerList[1] = NULL; -// -// clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; -// clipMap->dynEntCollList[1] = NULL; -// clipMap->dynEntCollList[2] = NULL; -// clipMap->dynEntCollList[3] = NULL; -// memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); -// -// clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; -// clipMap->dynEntPoseList[1] = NULL; -// memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); -// -// clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; -// clipMap->dynEntDefList[1] = NULL; -// memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); -// for (int i = 0; i < clipMap->dynEntCount[0]; i++) -// { -// DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; -// currDef->physConstraints[0] = 0x1FF; -// currDef->physConstraints[1] = 0x1FF; -// currDef->physConstraints[2] = 0x1FF; -// currDef->physConstraints[3] = 0x1FF; -// } -// -// clipMap->numSubModels = 1; -// clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; -// clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; -// clipMap->cmodels[0].leaf.collAabbCount = 0; -// clipMap->cmodels[0].leaf.brushContents = 0; -// clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; -// clipMap->cmodels[0].leaf.mins.x = 0.0f; -// clipMap->cmodels[0].leaf.mins.y = 0.0f; -// clipMap->cmodels[0].leaf.mins.z = 0.0f; -// clipMap->cmodels[0].leaf.maxs.x = 0.0f; -// clipMap->cmodels[0].leaf.maxs.y = 0.0f; -// clipMap->cmodels[0].leaf.maxs.z = 0.0f; -// clipMap->cmodels[0].leaf.leafBrushNode = 0; -// clipMap->cmodels[0].leaf.cluster = 0; -// clipMap->cmodels[0].info = NULL; -// -// auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); -// _ASSERT(gfxWorldAsset != NULL); -// GfxWorld* gfxWorld = gfxWorldAsset->Asset(); -// -// clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; -// clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; -// clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; -// clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; -// clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; -// clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; -// clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; -// -// clipMap->numStaticModels = 0; -// clipMap->staticModelList = NULL; -// -// clipMap->info.numMaterials = 1; -// clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; -// clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); -// clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; -// clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; -// -// int collisionVertexCount = projInfo->gfxInfo.vertexCount; -// vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; -// for (int i = 0; i < collisionVertexCount; i++) -// { -// convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); -// } -// clipMap->vertCount = collisionVertexCount; -// clipMap->verts = convertedCollisionVerts; -// -// // due to tris using uint16_t as the type for indexing the vert array, -// // any vertex count over the uint16_t max means the vertices above it can't be indexed -// const int maxVerts = UINT16_MAX - 1; -// if (collisionVertexCount > maxVerts) -// { -// printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); -// hasLinkFailed = true; -// return; -// } -// -// clipMap->triCount = projInfo->gfxInfo.indexCount / 3; -// clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri -// int triIndex = 0; -// for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) -// { -// clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; -// clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; -// clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; -// triIndex++; -// } -// -// int partitionCount = 0; -// for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) -// { -// int triCount = projInfo->gfxInfo.surfaces[i].triCount; -// int subdividedSize = triCount / 16; // integer division used -// partitionCount += subdividedSize; -// -// // add another partition if the triangle count doesn't add up to a multiple of 16 -// if (triCount % 16 != 0) -// partitionCount++; -// } -// clipMap->partitionCount = partitionCount; -// clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; -// -// int partitionIndex = 0; -// for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) -// { -// int triCount = projInfo->gfxInfo.surfaces[i].triCount; -// int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; -// int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; -// int firstTri = firstIndex / 3; -// -// //////////////////////////////////////////////////////////////////////////////////////////////////// -// // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. -// // NEED TO FIX -// // fixup the tri indices. -// // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] -// // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] -// // this loop fixes the index buffer by adding the firstVertex to each index -// for (int k = 0; k < triCount; k++) -// { -// clipMap->triIndices[k + firstTri][0] += firstVertex; -// clipMap->triIndices[k + firstTri][1] += firstVertex; -// clipMap->triIndices[k + firstTri][2] += firstVertex; -// } -// -// while (triCount > 0) -// { -// if (triCount > 16) -// clipMap->partitions[partitionIndex].triCount = 16; -// else -// clipMap->partitions[partitionIndex].triCount = triCount; -// -// clipMap->partitions[partitionIndex].firstTri = firstTri; -// -// triCount -= 16; -// firstIndex += 16 * 3; -// partitionIndex++; -// } -// } -// -// int uindCount = 0; -// clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition -// for (int i = 0; i < clipMap->partitionCount; i++) -// { -// auto currPartition = &clipMap->partitions[i]; -// -// int uniqueVertBufferCount = 0; -// uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris -// -// for (int k = 0; k < currPartition->triCount; k++) -// { -// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; -// for (int l = 0; l < 3; l++) -// { -// bool isVertexIndexUnique = true; -// uint16_t vertIndex = tri[l]; -// -// for (int m = 0; m < uniqueVertBufferCount; m++) -// { -// if (uniqueVertBuffer[m] == vertIndex) -// { -// isVertexIndexUnique = false; -// break; -// } -// } -// -// if (isVertexIndexUnique) -// { -// uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; -// uniqueVertBufferCount++; -// } -// } -// } -// -// _ASSERT(uniqueVertBufferCount <= 3 * 16); -// -// currPartition->fuind = uindCount; -// currPartition->nuinds = uniqueVertBufferCount; -// memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); -// uindCount += uniqueVertBufferCount; -// } -// _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); -// clipMap->info.nuinds = uindCount; -// -// // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility -// // might do weird stuff on walls -// int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; -// clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; -// memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); -// -// // BSP creation must go last as it depends on unids, tris and verts already being populated -// // HACK: -// // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. -// // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, -// // and then convert them back when it is being parsed into the clipmap. Requires some hacky -// // logic, check populateBSPTree_r and addAABBTreeFromLeaf -// -// vec3_t* firstVert = &clipMap->verts[0]; -// vec3_t clipMins; -// vec3_t clipMaxs; -// clipMins.x = firstVert->x; -// clipMins.y = firstVert->y; -// clipMins.z = firstVert->z; -// clipMaxs.x = firstVert->x; -// clipMaxs.y = firstVert->y; -// clipMaxs.z = firstVert->z; -// clipMins = CMUtil::convertFromBO2Coords(clipMins); -// clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); -// for (int i = 1; i < clipMap->vertCount; i++) -// { -// vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); -// CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); -// } -// -// BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); -// for (int i = 0; i < clipMap->partitionCount; i++) -// { -// auto currPartition = &clipMap->partitions[i]; -// -// uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; -// vec3_t* firstVert = &clipMap->verts[firstTri[0]]; -// vec3_t mins; -// vec3_t maxs; -// mins.x = firstVert->x; -// mins.y = firstVert->y; -// mins.z = firstVert->z; -// maxs.x = firstVert->x; -// maxs.y = firstVert->y; -// maxs.z = firstVert->z; -// mins = CMUtil::convertFromBO2Coords(mins); -// maxs = CMUtil::convertFromBO2Coords(maxs); -// for (int k = 0; k < currPartition->triCount; k++) -// { -// uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; -// for (int l = 0; l < 3; l++) -// { -// uint16_t vertIndex = tri[l]; -// vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); -// CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); -// } -// } -// -// Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); -// -// tree->addObject(currObject); -// } -// -// populateBSPTree(clipMap, tree); -// -// m_context.AddAsset(clipMap->name, clipMap); -// } -// -// void createComWorld(customMapInfo* projInfo) -// { -// // all lights that aren't the sunlight or default light need their own GfxLightDef asset -// ComWorld* comWorld = new ComWorld; -// comWorld->name = _strdup(projInfo->bspName.c_str()); -// comWorld->isInUse = 1; -// comWorld->primaryLightCount = 2; -// comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; -// -// ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; -// memset(defaultLight, 0, sizeof(ComPrimaryLight)); -// -// ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; -// memset(sunLight, 0, sizeof(ComPrimaryLight)); -// sunLight->type = 1; -// // Below are default values taken from mp_dig -// sunLight->diffuseColor.r = 1.0f; -// sunLight->diffuseColor.g = 1.0f; -// sunLight->diffuseColor.b = 1.0f; -// sunLight->diffuseColor.a = 0.0f; -// sunLight->dir.x = -0.2410777360200882; -// sunLight->dir.y = -0.8407384753227234; -// sunLight->dir.z = 0.48480960726737976; -// -// m_context.AddAsset(comWorld->name, comWorld); -// } -// -// void createMapEnts(customMapInfo* projInfo) -// { -// MapEnts* mapEnts = new MapEnts; -// -// mapEnts->name = _strdup(projInfo->bspName.c_str()); -// -// // don't need these -// mapEnts->trigger.count = 0; -// mapEnts->trigger.models = NULL; -// mapEnts->trigger.hullCount = 0; -// mapEnts->trigger.hulls = NULL; -// mapEnts->trigger.slabCount = 0; -// mapEnts->trigger.slabs = NULL; -// -// const auto file = m_search_path.Open("custom_map/entities.json"); -// if (!file.IsOpen()) -// { -// printf("WARN: can't find entity json!\n"); -// return; -// } -// -// json entJs = json::parse(*file.m_stream); -// -// std::string entityString; -// int entityCount = entJs["entityCount"]; -// for (int i = 0; i < entityCount; i++) -// { -// auto currEntity = entJs["entities"][i]; -// -// if (i == 0) -// { -// std::string className = currEntity["classname"]; -// if (className.compare("worldspawn") != 0) -// { -// printf("ERROR: first entity in the map entity string must be the worldspawn class!"); -// hasLinkFailed = true; -// return; -// } -// } -// -// entityString.append("{\n"); -// -// for (auto& element : currEntity.items()) -// { -// std::string key = element.key(); -// std::string value = element.value(); -// entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); -// } -// -// entityString.append("}\n"); -// } -// entityString.pop_back(); // remove newline character from the end of the string -// -// mapEnts->entityString = _strdup(entityString.c_str()); -// mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character -// -// m_context.AddAsset(mapEnts->name, mapEnts); -// } -// -// void createGameWorldMp(customMapInfo* projInfo) -// { -// GameWorldMp* gameWorldMp = new GameWorldMp; -// -// gameWorldMp->name = _strdup(projInfo->bspName.c_str()); -// -// gameWorldMp->path.nodeCount = 0; -// gameWorldMp->path.originalNodeCount = 0; -// gameWorldMp->path.visBytes = 0; -// gameWorldMp->path.smoothBytes = 0; -// gameWorldMp->path.nodeTreeCount = 0; -// -// int nodeCount = gameWorldMp->path.nodeCount + 128; -// gameWorldMp->path.nodes = new pathnode_t[nodeCount]; -// gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; -// memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); -// memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); -// -// gameWorldMp->path.pathVis = NULL; -// gameWorldMp->path.smoothCache = NULL; -// gameWorldMp->path.nodeTree = NULL; -// -// m_context.AddAsset(gameWorldMp->name, gameWorldMp); -// } -// -// void createSkinnedVerts(customMapInfo* projInfo) -// { -// SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; -// skinnedVerts->name = "skinnedverts"; -// skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; -// -// m_context.AddAsset("skinnedverts", skinnedVerts); -// } -// -// FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) -// { -// if (assetName.length() == 0) -// return NULL; -// -// FootstepTableDef* footstepTable = new FootstepTableDef; -// footstepTable->name = _strdup(assetName.c_str()); -// memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); -// -// m_context.AddAsset(assetName, footstepTable); -// -// return footstepTable; -// } -// -// void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) -// { -// if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } -// if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } -// -// addEmptyFootstepTableAsset("default_1st_person"); -// addEmptyFootstepTableAsset("default_3rd_person"); -// addEmptyFootstepTableAsset("default_1st_person_quiet"); -// addEmptyFootstepTableAsset("default_3rd_person_quiet"); -// addEmptyFootstepTableAsset("default_3rd_person_loud"); -// addEmptyFootstepTableAsset("default_ai"); -// } -//}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 61ec5b681..0ba12b452 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,16 +1,15 @@ #pragma once -#include "Game/T6/Material/JsonMaterialLoaderT6.h" - #include "BinarySpacePartitionTreePreCalc.h" -#include "TriangleSort.h" #include "CustomMapConsts.h" +#include "CustomMapOptions.h" #include "Util.h" + +#include "Game/T6/Material/JsonMaterialLoaderT6.h" #include "Utils/Pack.h" -#include #include -using json = nlohmann::json; +using namespace nlohmann; class CustomMapLinker { diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h new file mode 100644 index 000000000..b18a97c68 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h @@ -0,0 +1,17 @@ +#pragma once + +// These options can be edited + +// material flags determine the features of the surface +// unsure which flag type changes what right now +// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast +// 1 results in: normal surface features, grenades work, seems normal +#define MATERIAL_SURFACE_FLAGS 1 +#define MATERIAL_CONTENT_FLAGS 1 + +// terrain/world flags: does not change the type of terrain or what features they have +// from testing, as long at it isn't 0 things will work correctly +#define LEAF_TERRAIN_CONTENTS 1 +#define WORLD_TERRAIN_CONTENTS 1 + +#define LIGHTGRID_COLOUR 128 // global lighting colour diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp index f780448cc..29983f784 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -22,26 +22,20 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { + // custom maps must have a map_gfx file auto mapGfxFile = m_search_path.Open("custom_map/map_gfx.fbx"); if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); - printf("Loading map data...\n"); - - // create map info from the obj file - customMapInfo* mapInfo = CustomMapInfo::createCustomMapInfo(m_zone.m_name, m_search_path); + // create map info from the fbx file + customMapInfo* mapInfo = ProjectCreator::createCustomMapInfo(m_zone.m_name, m_search_path); if (mapInfo == NULL) return AssetCreationResult::Failure(); - - printf("Creating map from data...\n"); // linker will add all the assets needed CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); bool result = linker->linkCustomMap(mapInfo); - //auto gfxWorldAsset = context.LoadDependency("default_1st_person"); - //return AssetCreationResult::Success(gfxWorldAsset); - if (result) { auto gfxWorldAsset = context.LoadDependency(mapInfo->bspName); diff --git a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h deleted file mode 100644 index 8ee1a62da..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h +++ /dev/null @@ -1,1153 +0,0 @@ -// OBJ_Loader.h - A Single Header OBJ Model Loader -// modified to work better with OpenAssetTools - -#pragma once - -// Iostream - STD I/O Library -#include - -// Vector - STD Vector/Array Library -#include - -// String - STD String Library -#include - -// fStream - STD File I/O Library -#include - -// Math.h - STD math Library -#include - -#include "SearchPath/ISearchPath.h" - -// Print progress to console while loading (large models) -// #define OBJL_CONSOLE_OUTPUT - -// Namespace: OBJL -// -// Description: The namespace that holds eveyrthing that -// is needed and used for the OBJ Model Loader -namespace objl -{ - // Structure: Vector2 - // - // Description: A 2D Vector that Holds Positional Data - struct Vector2 - { - // Default Constructor - Vector2() - { - X = 0.0f; - Y = 0.0f; - } - - // Variable Set Constructor - Vector2(float X_, float Y_) - { - X = X_; - Y = Y_; - } - - // Bool Equals Operator Overload - bool operator==(const Vector2& other) const - { - return (this->X == other.X && this->Y == other.Y); - } - - // Bool Not Equals Operator Overload - bool operator!=(const Vector2& other) const - { - return !(this->X == other.X && this->Y == other.Y); - } - - // Addition Operator Overload - Vector2 operator+(const Vector2& right) const - { - return Vector2(this->X + right.X, this->Y + right.Y); - } - - // Subtraction Operator Overload - Vector2 operator-(const Vector2& right) const - { - return Vector2(this->X - right.X, this->Y - right.Y); - } - - // Float Multiplication Operator Overload - Vector2 operator*(const float& other) const - { - return Vector2(this->X * other, this->Y * other); - } - - // Positional Variables - float X; - float Y; - }; - - // Structure: Vector3 - // - // Description: A 3D Vector that Holds Positional Data - struct Vector3 - { - // Default Constructor - Vector3() - { - X = 0.0f; - Y = 0.0f; - Z = 0.0f; - } - - // Variable Set Constructor - Vector3(float X_, float Y_, float Z_) - { - X = X_; - Y = Y_; - Z = Z_; - } - - // Bool Equals Operator Overload - bool operator==(const Vector3& other) const - { - return (this->X == other.X && this->Y == other.Y && this->Z == other.Z); - } - - // Bool Not Equals Operator Overload - bool operator!=(const Vector3& other) const - { - return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z); - } - - // Addition Operator Overload - Vector3 operator+(const Vector3& right) const - { - return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z); - } - - // Subtraction Operator Overload - Vector3 operator-(const Vector3& right) const - { - return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z); - } - - // Float Multiplication Operator Overload - Vector3 operator*(const float& other) const - { - return Vector3(this->X * other, this->Y * other, this->Z * other); - } - - // Float Division Operator Overload - Vector3 operator/(const float& other) const - { - return Vector3(this->X / other, this->Y / other, this->Z / other); - } - - // Positional Variables - float X; - float Y; - float Z; - }; - - // Structure: Vertex - // - // Description: Model Vertex object that holds - // a Position, Normal, and Texture Coordinate - struct Vertex - { - // Position Vector - Vector3 Position; - - // Normal Vector - Vector3 Normal; - - // Texture Coordinate Vector - Vector2 TextureCoordinate; - }; - - struct Material - { - Material() - { - name; - Ns = 0.0f; - Ni = 0.0f; - d = 0.0f; - illum = 0; - } - - // Material Name - std::string name; - // Ambient Color - Vector3 Ka; - // Diffuse Color - Vector3 Kd; - // Specular Color - Vector3 Ks; - // Specular Exponent - float Ns; - // Optical Density - float Ni; - // Dissolve - float d; - // Illumination - int illum; - // Ambient Texture Map - std::string map_Ka; - // Diffuse Texture Map - std::string map_Kd; - // Specular Texture Map - std::string map_Ks; - // Specular Hightlight Map - std::string map_Ns; - // Alpha Texture Map - std::string map_d; - // Bump Map - std::string map_bump; - }; - - // Structure: Mesh - // - // Description: A Simple Mesh Object that holds - // a name, a vertex list, and an index list - struct Mesh - { - // Default Constructor - Mesh() {} - - // Variable Set Constructor - Mesh(std::vector& _Vertices, std::vector& _Indices) - { - Vertices = _Vertices; - Indices = _Indices; - } - - // Mesh Name - std::string MeshName; - // Vertex List - std::vector Vertices; - // Index List - std::vector Indices; - - // Material - Material MeshMaterial; - }; - - // Namespace: Math - // - // Description: The namespace that holds all of the math - // functions need for OBJL - namespace math - { - // Vector3 Cross Product - Vector3 CrossV3(const Vector3 a, const Vector3 b) - { - return Vector3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); - } - - // Vector3 Magnitude Calculation - float MagnitudeV3(const Vector3 in) - { - return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2))); - } - - // Vector3 DotProduct - float DotV3(const Vector3 a, const Vector3 b) - { - return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); - } - - // Angle between 2 Vector3 Objects - float AngleBetweenV3(const Vector3 a, const Vector3 b) - { - float angle = DotV3(a, b); - angle /= (MagnitudeV3(a) * MagnitudeV3(b)); - return angle = acosf(angle); - } - - // Projection Calculation of a onto b - Vector3 ProjV3(const Vector3 a, const Vector3 b) - { - Vector3 bn = b / MagnitudeV3(b); - return bn * DotV3(a, bn); - } - } // namespace math - - // Namespace: Algorithm - // - // Description: The namespace that holds all of the - // Algorithms needed for OBJL - namespace algorithm - { - // Vector3 Multiplication Opertor Overload - Vector3 operator*(const float& left, const Vector3& right) - { - return Vector3(right.X * left, right.Y * left, right.Z * left); - } - - // A test to see if P1 is on the same side as P2 of a line segment ab - bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) - { - Vector3 cp1 = math::CrossV3(b - a, p1 - a); - Vector3 cp2 = math::CrossV3(b - a, p2 - a); - - if (math::DotV3(cp1, cp2) >= 0) - return true; - else - return false; - } - - // Generate a cross produect normal for a triangle - Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3) - { - Vector3 u = t2 - t1; - Vector3 v = t3 - t1; - - Vector3 normal = math::CrossV3(u, v); - - return normal; - } - - // Check to see if a Vector3 Point is within a 3 Vector3 Triangle - bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3) - { - // Test to see if it is within an infinite prism that the triangle outlines. - bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && SameSide(point, tri2, tri1, tri3) && SameSide(point, tri3, tri1, tri2); - - // If it isn't it will never be on the triangle - if (!within_tri_prisim) - return false; - - // Calulate Triangle's Normal - Vector3 n = GenTriNormal(tri1, tri2, tri3); - - // Project the point onto this normal - Vector3 proj = math::ProjV3(point, n); - - // If the distance from the triangle to the point is 0 - // it lies on the triangle - if (math::MagnitudeV3(proj) == 0) - return true; - else - return false; - } - - // Split a String into a string array at a given token - inline void split(const std::string& in, std::vector& out, std::string token) - { - out.clear(); - - std::string temp; - - for (int i = 0; i < int(in.size()); i++) - { - std::string test = in.substr(i, token.size()); - - if (test == token) - { - if (!temp.empty()) - { - out.push_back(temp); - temp.clear(); - i += (int)token.size() - 1; - } - else - { - out.push_back(""); - } - } - else if (i + token.size() >= in.size()) - { - temp += in.substr(i, token.size()); - out.push_back(temp); - break; - } - else - { - temp += in[i]; - } - } - } - - // Get tail of string after first token and possibly following spaces - inline std::string tail(const std::string& in) - { - size_t token_start = in.find_first_not_of(" \t"); - size_t space_start = in.find_first_of(" \t", token_start); - size_t tail_start = in.find_first_not_of(" \t", space_start); - size_t tail_end = in.find_last_not_of(" \t"); - if (tail_start != std::string::npos && tail_end != std::string::npos) - { - return in.substr(tail_start, tail_end - tail_start + 1); - } - else if (tail_start != std::string::npos) - { - return in.substr(tail_start); - } - return ""; - } - - // Get first token of string - inline std::string firstToken(const std::string& in) - { - if (!in.empty()) - { - size_t token_start = in.find_first_not_of(" \t"); - size_t token_end = in.find_first_of(" \t", token_start); - if (token_start != std::string::npos && token_end != std::string::npos) - { - return in.substr(token_start, token_end - token_start); - } - else if (token_start != std::string::npos) - { - return in.substr(token_start); - } - } - return ""; - } - - // Get element at given index position - template inline const T& getElement(const std::vector& elements, std::string& index) - { - int idx = std::stoi(index); - if (idx < 0) - idx = int(elements.size()) + idx; - else - idx--; - return elements[idx]; - } - } // namespace algorithm - - // Class: Loader - // - // Description: The OBJ Model Loader - class Loader - { - public: - // Default Constructor - Loader() {} - - ~Loader() - { - LoadedMeshes.clear(); - } - - // Load a file into the loader - // - // If file is loaded return true - // - // If the file is unable to be found - // or unable to be loaded return false - bool LoadFile(ISearchPath& searchPath, std::string fileName) - { - auto file = searchPath.Open(fileName); - - if (!file.IsOpen()) - return false; - - LoadedMeshes.clear(); - LoadedVertices.clear(); - LoadedIndices.clear(); - - std::vector Positions; - std::vector TCoords; - std::vector Normals; - - std::vector Vertices; - std::vector Indices; - - std::vector MeshMatNames; - - bool listening = false; - std::string meshname; - - Mesh tempMesh; - -#ifdef OBJL_CONSOLE_OUTPUT - const unsigned int outputEveryNth = 1000; - unsigned int outputIndicator = outputEveryNth; -#endif - - std::string curline; - while (std::getline(*file.m_stream, curline)) - { -#ifdef OBJL_CONSOLE_OUTPUT - if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) - { - if (!meshname.empty()) - { - std::cout << "\r- " << meshname << "\t| vertices > " << Positions.size() << "\t| texcoords > " << TCoords.size() << "\t| normals > " - << Normals.size() << "\t| triangles > " << (Vertices.size() / 3) - << (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() : ""); - } - } -#endif - - // Generate a Mesh Object or Prepare for an object to be created - if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || curline[0] == 'g') - { - if (!listening) - { - listening = true; - - if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") - { - meshname = algorithm::tail(curline); - } - else - { - meshname = "unnamed"; - } - } - else - { - // Generate the mesh to put into the array - - if (!Indices.empty() && !Vertices.empty()) - { - // Create Mesh - tempMesh = Mesh(Vertices, Indices); - tempMesh.MeshName = meshname; - - // Insert Mesh - LoadedMeshes.push_back(tempMesh); - - // Cleanup - Vertices.clear(); - Indices.clear(); - meshname.clear(); - - meshname = algorithm::tail(curline); - } - else - { - if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") - { - meshname = algorithm::tail(curline); - } - else - { - meshname = "unnamed"; - } - } - } -#ifdef OBJL_CONSOLE_OUTPUT - std::cout << std::endl; - outputIndicator = 0; -#endif - } - // Generate a Vertex Position - if (algorithm::firstToken(curline) == "v") - { - std::vector spos; - Vector3 vpos; - algorithm::split(algorithm::tail(curline), spos, " "); - - vpos.X = std::stof(spos[0]); - vpos.Y = std::stof(spos[1]); - vpos.Z = std::stof(spos[2]); - - Positions.push_back(vpos); - } - // Generate a Vertex Texture Coordinate - if (algorithm::firstToken(curline) == "vt") - { - std::vector stex; - Vector2 vtex; - algorithm::split(algorithm::tail(curline), stex, " "); - - vtex.X = std::stof(stex[0]); - vtex.Y = std::stof(stex[1]); - - TCoords.push_back(vtex); - } - // Generate a Vertex Normal; - if (algorithm::firstToken(curline) == "vn") - { - std::vector snor; - Vector3 vnor; - algorithm::split(algorithm::tail(curline), snor, " "); - - vnor.X = std::stof(snor[0]); - vnor.Y = std::stof(snor[1]); - vnor.Z = std::stof(snor[2]); - - Normals.push_back(vnor); - } - // Generate a Face (vertices & indices) - if (algorithm::firstToken(curline) == "f") - { - // Generate the vertices - std::vector vVerts; - GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline); - - // Add Vertices - for (int i = 0; i < int(vVerts.size()); i++) - { - Vertices.push_back(vVerts[i]); - - LoadedVertices.push_back(vVerts[i]); - } - - std::vector iIndices; - - VertexTriangluation(iIndices, vVerts); - - // Add Indices - for (int i = 0; i < int(iIndices.size()); i++) - { - unsigned int indnum = (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i]; - Indices.push_back(indnum); - - indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i]; - LoadedIndices.push_back(indnum); - } - } - // Get Mesh Material Name - if (algorithm::firstToken(curline) == "usemtl") - { - MeshMatNames.push_back(algorithm::tail(curline)); - - // Create new Mesh, if Material changes within a group - if (!Indices.empty() && !Vertices.empty()) - { - // Create Mesh - tempMesh = Mesh(Vertices, Indices); - tempMesh.MeshName = meshname; - int i = 2; - while (1) - { - tempMesh.MeshName = meshname + "_" + std::to_string(i); - - for (auto& m : LoadedMeshes) - if (m.MeshName == tempMesh.MeshName) - continue; - break; - } - - // Insert Mesh - LoadedMeshes.push_back(tempMesh); - - // Cleanup - Vertices.clear(); - Indices.clear(); - } - -#ifdef OBJL_CONSOLE_OUTPUT - outputIndicator = 0; -#endif - } - // Load Materials - if (algorithm::firstToken(curline) == "mtllib") - { - // Generate LoadedMaterial - - // Generate a path to the material file - std::vector temp; - algorithm::split(fileName, temp, "/"); // update: use windows file seperators - - std::string pathtomat = ""; - - if (temp.size() != 1) - { - for (size_t i = 0; i < temp.size() - 1; i++) - { - pathtomat += temp[i] + "/"; - } - } - - pathtomat += algorithm::tail(curline); - -#ifdef OBJL_CONSOLE_OUTPUT - std::cout << std::endl << "- find materials in: " << pathtomat << std::endl; -#endif - - // Load Materials - LoadMaterials(searchPath, pathtomat); - } - } - -#ifdef OBJL_CONSOLE_OUTPUT - std::cout << std::endl; -#endif - - // Deal with last mesh - - if (!Indices.empty() && !Vertices.empty()) - { - // Create Mesh - tempMesh = Mesh(Vertices, Indices); - tempMesh.MeshName = meshname; - - // Insert Mesh - LoadedMeshes.push_back(tempMesh); - } - - // Set Materials for each Mesh - for (size_t i = 0; i < MeshMatNames.size(); i++) - { - std::string matname = MeshMatNames[i]; - - // Find corresponding material name in loaded materials - // when found copy material variables into mesh material - for (size_t j = 0; j < LoadedMaterials.size(); j++) - { - if (LoadedMaterials[j].name == matname) - { - LoadedMeshes[i].MeshMaterial = LoadedMaterials[j]; - break; - } - } - } - - if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty()) - { - return false; - } - else - { - return true; - } - } - - // Loaded Mesh Objects - std::vector LoadedMeshes; - // Loaded Vertex Objects - std::vector LoadedVertices; - // Loaded Index Positions - std::vector LoadedIndices; - // Loaded Material Objects - std::vector LoadedMaterials; - - private: - // Generate vertices from a list of positions, - // tcoords, normals and a face line - void GenVerticesFromRawOBJ(std::vector& oVerts, - const std::vector& iPositions, - const std::vector& iTCoords, - const std::vector& iNormals, - std::string icurline) - { - std::vector sface, svert; - Vertex vVert; - algorithm::split(algorithm::tail(icurline), sface, " "); - - bool noNormal = false; - - // For every given vertex do this - for (int i = 0; i < int(sface.size()); i++) - { - // See What type the vertex is. - int vtype; - - algorithm::split(sface[i], svert, "/"); - - // Check for just position - v1 - if (svert.size() == 1) - { - // Only position - vtype = 1; - } - - // Check for position & texture - v1/vt1 - if (svert.size() == 2) - { - // Position & Texture - vtype = 2; - } - - // Check for Position, Texture and Normal - v1/vt1/vn1 - // or if Position and Normal - v1//vn1 - if (svert.size() == 3) - { - if (svert[1] != "") - { - // Position, Texture, and Normal - vtype = 4; - } - else - { - // Position & Normal - vtype = 3; - } - } - - // Calculate and store the vertex - switch (vtype) - { - case 1: // P - { - vVert.Position = algorithm::getElement(iPositions, svert[0]); - vVert.TextureCoordinate = Vector2(0, 0); - noNormal = true; - oVerts.push_back(vVert); - break; - } - case 2: // P/T - { - vVert.Position = algorithm::getElement(iPositions, svert[0]); - vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); - noNormal = true; - oVerts.push_back(vVert); - break; - } - case 3: // P//N - { - vVert.Position = algorithm::getElement(iPositions, svert[0]); - vVert.TextureCoordinate = Vector2(0, 0); - vVert.Normal = algorithm::getElement(iNormals, svert[2]); - oVerts.push_back(vVert); - break; - } - case 4: // P/T/N - { - vVert.Position = algorithm::getElement(iPositions, svert[0]); - vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); - vVert.Normal = algorithm::getElement(iNormals, svert[2]); - oVerts.push_back(vVert); - break; - } - default: - { - break; - } - } - } - - // take care of missing normals - // these may not be truly acurate but it is the - // best they get for not compiling a mesh with normals - if (noNormal) - { - Vector3 A = oVerts[0].Position - oVerts[1].Position; - Vector3 B = oVerts[2].Position - oVerts[1].Position; - - Vector3 normal = math::CrossV3(A, B); - - for (int i = 0; i < int(oVerts.size()); i++) - { - oVerts[i].Normal = normal; - } - } - } - - // Triangulate a list of vertices into a face by printing - // inducies corresponding with triangles within it - void VertexTriangluation(std::vector& oIndices, const std::vector& iVerts) - { - // If there are 2 or less verts, - // no triangle can be created, - // so exit - if (iVerts.size() < 3) - { - return; - } - // If it is a triangle no need to calculate it - if (iVerts.size() == 3) - { - oIndices.push_back(0); - oIndices.push_back(1); - oIndices.push_back(2); - return; - } - - // Create a list of vertices - std::vector tVerts = iVerts; - - while (true) - { - // For every vertex - for (int i = 0; i < int(tVerts.size()); i++) - { - // pPrev = the previous vertex in the list - Vertex pPrev; - if (i == 0) - { - pPrev = tVerts[tVerts.size() - 1]; - } - else - { - pPrev = tVerts[i - 1]; - } - - // pCur = the current vertex; - Vertex pCur = tVerts[i]; - - // pNext = the next vertex in the list - Vertex pNext; - if (i == tVerts.size() - 1) - { - pNext = tVerts[0]; - } - else - { - pNext = tVerts[i + 1]; - } - - // Check to see if there are only 3 verts left - // if so this is the last triangle - if (tVerts.size() == 3) - { - // Create a triangle from pCur, pPrev, pNext - for (int j = 0; j < int(tVerts.size()); j++) - { - if (iVerts[j].Position == pCur.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pPrev.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pNext.Position) - oIndices.push_back(j); - } - - tVerts.clear(); - break; - } - if (tVerts.size() == 4) - { - // Create a triangle from pCur, pPrev, pNext - for (int j = 0; j < int(iVerts.size()); j++) - { - if (iVerts[j].Position == pCur.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pPrev.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pNext.Position) - oIndices.push_back(j); - } - - Vector3 tempVec; - for (int j = 0; j < int(tVerts.size()); j++) - { - if (tVerts[j].Position != pCur.Position && tVerts[j].Position != pPrev.Position && tVerts[j].Position != pNext.Position) - { - tempVec = tVerts[j].Position; - break; - } - } - - // Create a triangle from pCur, pPrev, pNext - for (int j = 0; j < int(iVerts.size()); j++) - { - if (iVerts[j].Position == pPrev.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pNext.Position) - oIndices.push_back(j); - if (iVerts[j].Position == tempVec) - oIndices.push_back(j); - } - - tVerts.clear(); - break; - } - - // If Vertex is not an interior vertex - float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (float)(180 / 3.14159265359); - if (angle <= 0 && angle >= 180) - continue; - - // If any vertices are within this triangle - bool inTri = false; - for (int j = 0; j < int(iVerts.size()); j++) - { - if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, pNext.Position) && iVerts[j].Position != pPrev.Position - && iVerts[j].Position != pCur.Position && iVerts[j].Position != pNext.Position) - { - inTri = true; - break; - } - } - if (inTri) - continue; - - // Create a triangle from pCur, pPrev, pNext - for (int j = 0; j < int(iVerts.size()); j++) - { - if (iVerts[j].Position == pCur.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pPrev.Position) - oIndices.push_back(j); - if (iVerts[j].Position == pNext.Position) - oIndices.push_back(j); - } - - // Delete pCur from the list - for (int j = 0; j < int(tVerts.size()); j++) - { - if (tVerts[j].Position == pCur.Position) - { - tVerts.erase(tVerts.begin() + j); - break; - } - } - - // reset i to the start - // -1 since loop will add 1 to it - i = -1; - } - - // if no triangles were created - if (oIndices.size() == 0) - break; - - // if no more vertices - if (tVerts.size() == 0) - break; - } - } - - // Load Materials from .mtl file - bool LoadMaterials(ISearchPath& searchPath, std::string fileName) - { - auto file = searchPath.Open(fileName); - - // If the file is not found return false - if (!file.IsOpen()) - return false; - - Material tempMaterial; - - bool listening = false; - - // Go through each line looking for material variables - std::string curline; - while (std::getline(*file.m_stream, curline)) - { - // new material and material name - if (algorithm::firstToken(curline) == "newmtl") - { - if (!listening) - { - listening = true; - - if (curline.size() > 7) - { - tempMaterial.name = algorithm::tail(curline); - } - else - { - tempMaterial.name = "none"; - } - } - else - { - // Generate the material - - // Push Back loaded Material - LoadedMaterials.push_back(tempMaterial); - - // Clear Loaded Material - tempMaterial = Material(); - - if (curline.size() > 7) - { - tempMaterial.name = algorithm::tail(curline); - } - else - { - tempMaterial.name = "none"; - } - } - } - // Ambient Color - if (algorithm::firstToken(curline) == "Ka") - { - std::vector temp; - algorithm::split(algorithm::tail(curline), temp, " "); - - if (temp.size() != 3) - continue; - - tempMaterial.Ka.X = std::stof(temp[0]); - tempMaterial.Ka.Y = std::stof(temp[1]); - tempMaterial.Ka.Z = std::stof(temp[2]); - } - // Diffuse Color - if (algorithm::firstToken(curline) == "Kd") - { - std::vector temp; - algorithm::split(algorithm::tail(curline), temp, " "); - - if (temp.size() != 3) - continue; - - tempMaterial.Kd.X = std::stof(temp[0]); - tempMaterial.Kd.Y = std::stof(temp[1]); - tempMaterial.Kd.Z = std::stof(temp[2]); - } - // Specular Color - if (algorithm::firstToken(curline) == "Ks") - { - std::vector temp; - algorithm::split(algorithm::tail(curline), temp, " "); - - if (temp.size() != 3) - continue; - - tempMaterial.Ks.X = std::stof(temp[0]); - tempMaterial.Ks.Y = std::stof(temp[1]); - tempMaterial.Ks.Z = std::stof(temp[2]); - } - // Specular Exponent - if (algorithm::firstToken(curline) == "Ns") - { - tempMaterial.Ns = std::stof(algorithm::tail(curline)); - } - // Optical Density - if (algorithm::firstToken(curline) == "Ni") - { - tempMaterial.Ni = std::stof(algorithm::tail(curline)); - } - // Dissolve - if (algorithm::firstToken(curline) == "d") - { - tempMaterial.d = std::stof(algorithm::tail(curline)); - } - // Illumination - if (algorithm::firstToken(curline) == "illum") - { - tempMaterial.illum = std::stoi(algorithm::tail(curline)); - } - // Ambient Texture Map - if (algorithm::firstToken(curline) == "map_Ka") - { - tempMaterial.map_Ka = algorithm::tail(curline); - } - // Diffuse Texture Map - if (algorithm::firstToken(curline) == "map_Kd") - { - tempMaterial.map_Kd = algorithm::tail(curline); - } - // Specular Texture Map - if (algorithm::firstToken(curline) == "map_Ks") - { - tempMaterial.map_Ks = algorithm::tail(curline); - } - // Specular Hightlight Map - if (algorithm::firstToken(curline) == "map_Ns") - { - tempMaterial.map_Ns = algorithm::tail(curline); - } - // Alpha Texture Map - if (algorithm::firstToken(curline) == "map_d") - { - tempMaterial.map_d = algorithm::tail(curline); - } - // Bump Map - if (algorithm::firstToken(curline) == "map_Bump" || algorithm::firstToken(curline) == "map_bump" || algorithm::firstToken(curline) == "bump") - { - tempMaterial.map_bump = algorithm::tail(curline); - } - } - - // Deal with last material - - // Push Back loaded Material - LoadedMaterials.push_back(tempMaterial); - - // Test to see if anything was loaded - // If not return false - if (LoadedMaterials.empty()) - return false; - // If so return true - else - return true; - } - }; -} // namespace objl diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp index ec982b7b6..0f00ed15e 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -44,6 +44,8 @@ bool loadFBXMesh(ufbx_node* node) if (mesh->vertex_tangent.exists == false) hasTangentSpace = false; + + // Fix the target_unit_meters opt not working // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back ufbx_transform origTransform = node->local_transform; origTransform.translation.x /= 100.0f; @@ -113,6 +115,11 @@ bool loadFBXMesh(ufbx_node* node) customMapVertex* vertex = &vertices[num_vertices++]; + //ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, index); + //vertex->pos.x = static_cast(pos.x); + //vertex->pos.y = static_cast(pos.y); + //vertex->pos.z = static_cast(pos.z); + // Fix the target_unit_meters opt not working ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); vertex->pos.x = static_cast(transformedPos.x); vertex->pos.y = static_cast(transformedPos.y); @@ -131,10 +138,11 @@ bool loadFBXMesh(ufbx_node* node) case CM_MATERIAL_COLOUR: { float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); - vertex->color[0] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.x * factor); - vertex->color[1] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.y * factor); - vertex->color[2] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.z * factor); - vertex->color[3] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec4.w * factor); + ufbx_vec4 diffuse = mesh->materials.data[i]->fbx.diffuse_color.value_vec4; + vertex->color[0] = static_cast(diffuse.x * factor); + vertex->color[1] = static_cast(diffuse.y * factor); + vertex->color[2] = static_cast(diffuse.z * factor); + vertex->color[3] = static_cast(diffuse.w * factor); break; } @@ -144,20 +152,23 @@ bool loadFBXMesh(ufbx_node* node) - // 1.0f - uv.v: + // 1.0f - uv.y reason: // https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange - vertex->texCoord[0] = (float)(ufbx_get_vertex_vec2(&mesh->vertex_uv, index).x); - vertex->texCoord[1] = (float)(1.0f - ufbx_get_vertex_vec2(&mesh->vertex_uv, index).y); + ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); + vertex->texCoord[0] = (float)(uv.x); + vertex->texCoord[1] = (float)(1.0f - uv.y); - vertex->normal.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x); - vertex->normal.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y); - vertex->normal.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z); + ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); + vertex->normal.x = static_cast(normal.x); + vertex->normal.y = static_cast(normal.y); + vertex->normal.z = static_cast(normal.z); if (mesh->vertex_tangent.exists) { - vertex->tangent.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x); - vertex->tangent.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y); - vertex->tangent.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z); + ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); + vertex->tangent.x = static_cast(tangent.x); + vertex->tangent.y = static_cast(tangent.y); + vertex->tangent.z = static_cast(tangent.z); } else { @@ -208,22 +219,13 @@ bool loadFBXModel(ufbx_node* node) model.name = node->name.data; - ufbx_transform origTransform = node->local_transform; - origTransform.translation.x /= 100.0f; - origTransform.translation.y /= 100.0f; - origTransform.translation.z /= 100.0f; - origTransform.scale.x /= 100.0f; - origTransform.scale.y /= 100.0f; - origTransform.scale.z /= 100.0f; - ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - - model.origin.x = static_cast(node->local_transform.translation.x) / 100.0f; - model.origin.y = static_cast(node->local_transform.translation.y) / 100.0f; - model.origin.z = static_cast(node->local_transform.translation.z) / 100.0f; + model.origin.x = static_cast(node->local_transform.translation.x); + model.origin.y = static_cast(node->local_transform.translation.y); + model.origin.z = static_cast(node->local_transform.translation.z); model.rotation.x = static_cast(node->euler_rotation.x); model.rotation.y = static_cast(node->euler_rotation.y); model.rotation.z = static_cast(node->euler_rotation.z); - model.scale = static_cast(node->local_transform.scale.x) / 100.0f; + model.scale = static_cast(node->local_transform.scale.x); if (model.scale == 0.0f) { @@ -324,7 +326,7 @@ void parseCollisionData(ufbx_scene* scene, customMapInfo* projInfo) printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); } -customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +customMapInfo* ProjectCreator::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) { ufbx_scene* gfxScene; ufbx_scene* colScene; @@ -342,10 +344,13 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); ufbx_error error; - ufbx_load_opts opts; + ufbx_load_opts opts; // IDK why but opts don't seem to be working correctly, target_unit_meters isn't being used + memset(&opts, 0, sizeof(ufbx_load_opts)); opts.target_axes = ufbx_axes_right_handed_y_up; opts.generate_missing_normals = true; opts.allow_missing_vertex_position = false; + opts.target_unit_meters = 100.0f; + //gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), &opts, &error); gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), NULL, &error); if (!gfxScene) { @@ -366,12 +371,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea colFile.m_stream->seekg(0); colFile.m_stream->read(colMapData, colFile.m_length); - ufbx_error error; - ufbx_load_opts opts; - opts.target_axes = ufbx_axes_right_handed_y_up; - opts.generate_missing_normals = true; - opts.allow_missing_vertex_position = false; - colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), NULL, &error); + colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), &opts, &error); if (!colScene) { fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h index a75d041ac..e60b6b53b 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h @@ -5,7 +5,7 @@ #include "Game/T6/T6.h" using namespace T6; -class CustomMapInfo +class ProjectCreator { public: static customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath); diff --git a/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h b/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h deleted file mode 100644 index 4e417c412..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/TriangleSort.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "Util.h" -#include "CustomMapConsts.h" - -using TriangleGroup = std::vector; // Group of triangle indices -using AdjacencyList = std::vector; // Triangle-to-triangle connections - -class TriangleSort -{ -private: - static void computeTriCentroid(vec3_t& result, std::vector& vertices, uint16_t i0, uint16_t i1, uint16_t i2) - { - result.x = 0.0f; - result.y = 0.0f; - result.z = 0.0f; - - result.x += vertices[i0].x; - result.y += vertices[i0].y; - result.z += vertices[i0].z; - result.x += vertices[i1].x; - result.y += vertices[i1].y; - result.z += vertices[i1].z; - result.x += vertices[i2].x; - result.y += vertices[i2].y; - result.z += vertices[i2].z; - - result.x /= 3.0f; - result.y /= 3.0f; - result.z /= 3.0f; - } - - static AdjacencyList buildAdjacencyList(const std::vector& indices, std::vector& vertices, int maxTrianglesPerGroup) - { - size_t numTriangles = indices.size() / 3; - AdjacencyList adjList(numTriangles); - - std::vector centroids; - for (size_t i = 0; i < numTriangles; i++) - { - vec3_t center; - computeTriCentroid(center, vertices, indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]); - centroids.push_back(center); - } - - std::vector visited(numTriangles, false); - for (size_t i = 0; i < numTriangles; ++i) - { - _ASSERT(i < MAX_COL_VERTS); - - if (visited[i] == true) - continue; - visited[i] = true; - - // Store distances to other triangles - std::vector> distances; - for (size_t j = 0; j < numTriangles; ++j) - { - if (visited[j] == true) - continue; - - if (i != j) - { - float dist = CMUtil::distBetweenPoints(centroids[i], centroids[j]); - distances.emplace_back(dist, j); - } - } - - // Sort by distance and take the closest maxNeighbors - std::sort(distances.begin(), distances.end()); - for (size_t k = 0; k < std::min((size_t)maxTrianglesPerGroup, distances.size()); ++k) - { - unsigned int neighbour = distances[k].second; - adjList[i].push_back(neighbour); - visited[neighbour] = true; - } - } - - for (size_t i = 0; i < visited.size(); i++) - { - if (visited[i] == false) - printf("WARN: missing triangle: %i\n", i); - } - - return adjList; - } - -public: - static AdjacencyList groupTriangles(std::vector& indices, std::vector& vertices, int maxTrianglesPerGroup) - { - return buildAdjacencyList(indices, vertices, maxTrianglesPerGroup); - } -}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index e1bcc7f1e..18f79214f 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -336,7 +336,6 @@ namespace T6 collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); - // custom maps have no default } void ConfigureGlobalAssetPoolsLoaders(AssetCreatorCollection& collection, Zone& zone) @@ -389,7 +388,6 @@ namespace T6 collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); - //collection.AddAssetCreator(std::make_unique>(zone)); } void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) @@ -451,6 +449,7 @@ namespace T6 collection.AddAssetCreator(CreateRawZBarrierLoader(memory, searchPath, zone)); collection.AddAssetCreator(CreateGdtZBarrierLoader(memory, searchPath, gdt, zone)); + // The Custom Map Loader uses gfxworld as the starting asset collection.AddAssetCreator(CreateCustomMapLoader(memory, searchPath, zone)); } } // namespace diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp index a7284bc73..5d30b473d 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp @@ -44,10 +44,10 @@ namespace }; } // namespace -namespace script +namespace T6 { - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath) + std::unique_ptr> CreateScriptLoader(MemoryManager& memory, ISearchPath& searchPath) { return std::make_unique(memory, searchPath); } -} // namespace script +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h index ebaab5dae..754da2ecf 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.h @@ -7,7 +7,7 @@ #include -namespace script +namespace T6 { - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath); -} // namespace script + std::unique_ptr> CreateScriptLoader(MemoryManager& memory, ISearchPath& searchPath); +} // namespace T6 From 7dced5f9d54bb4b846d36263fed8df965eab9e38 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 21:10:07 +0800 Subject: [PATCH 013/123] Update T6 json material loading to include JSON being passed directy instead of a stream. --- .../Material/JsonMaterialLoader.cpp.template | 39 +++++++++++-------- .../Material/JsonMaterialLoader.h.template | 6 +++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template index 65687955b..9ff75e422 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template @@ -32,9 +32,7 @@ #include #include -#include -using namespace nlohmann; using namespace GAME; namespace @@ -42,19 +40,17 @@ namespace class JsonLoader { public: - JsonLoader(std::istream& stream, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - : m_stream(stream), - m_memory(memory), + JsonLoader(MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + : m_memory(memory), m_context(context), m_registration(registration) { } - bool Load(Material& material) const - { - const auto jRoot = json::parse(m_stream); - std::string type; - unsigned version; + bool Load(json& jRoot, Material& material) const + { + std::string type; + unsigned version; jRoot.at("_type").get_to(type); jRoot.at("_version").get_to(version); @@ -505,7 +501,6 @@ namespace return true; } - std::istream& m_stream; MemoryManager& m_memory; AssetCreationContext& m_context; AssetRegistration& m_registration; @@ -515,10 +510,20 @@ namespace namespace GAME { bool LoadMaterialAsJson( - std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - { - const JsonLoader loader(stream, memory, context, registration); - - return loader.Load(material); - } + std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + { + const JsonLoader loader(memory, context, registration); + + auto jRoot = json::parse(stream); + + return loader.Load(jRoot, material); + } + + bool LoadMaterialAsJson( + json& json, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + { + const JsonLoader loader(memory, context, registration); + + return loader.Load(json, material); + } } // namespace GAME diff --git a/src/ObjLoading/Material/JsonMaterialLoader.h.template b/src/ObjLoading/Material/JsonMaterialLoader.h.template index ae57b3b64..8a9dd63ed 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.h.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.h.template @@ -15,9 +15,15 @@ #include "Utils/MemoryManager.h" #include +#include + +using namespace nlohmann; namespace GAME { bool LoadMaterialAsJson( std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration); + + bool LoadMaterialAsJson( + json& json, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration); } // namespace GAME From d30207307445c914e1dec4bb0edb57c8a71ee12f Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 8 Oct 2025 21:33:13 +0800 Subject: [PATCH 014/123] Another refactor to remove leacy code that isn't used anymore --- .gitmodules | 3 -- src/Common/Game/T6/T6.h | 1 - src/Common/Game/T6/T6_Assets.h | 2 - src/ObjLoading/Asset/AssetCreationContext.cpp | 3 -- .../Game/T6/Script/ScriptCompileT6.cpp | 31 --------------- .../Game/T6/Script/ScriptCompileT6.h | 3 -- .../Game/T6/AssetNameResolverT6.cpp | 2 - .../Loading/Steps/StepVerifySignature.cpp | 2 +- .../Game/T6/ZoneWriterFactoryT6.cpp | 11 ++---- .../Writing/Steps/StepWriteZoneRSA.cpp | 38 ------------------- .../Writing/Steps/StepWriteZoneRSA.h | 13 ------- 11 files changed, 4 insertions(+), 105 deletions(-) delete mode 100644 src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp delete mode 100644 src/ObjLoading/Game/T6/Script/ScriptCompileT6.h delete mode 100644 src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp delete mode 100644 src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h diff --git a/.gitmodules b/.gitmodules index 84c41e459..b283a8c3a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,6 +19,3 @@ [submodule "thirdparty/lz4"] path = thirdparty/lz4 url = https://github.com/lz4/lz4.git -[submodule "thirdparty/gsc-tool"] - path = thirdparty/gsc-tool - url = https://github.com/xensik/gsc-tool.git diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index 4b13191a5..fbaa88db6 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -302,7 +302,6 @@ namespace T6 using AssetFootstepTable = Asset; using AssetFootstepFxTable = Asset; using AssetZBarrier = Asset; - using AssetCustomMap = Asset; } // namespace T6 DEFINE_ASSET_NAME_ACCESSOR(T6::AssetPhysPreset, name); diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index c8488eef7..c830fc6a1 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -304,8 +304,6 @@ namespace T6 ASSET_TYPE_REPORT = 0x3E, ASSET_TYPE_DEPEND = 0x3F, ASSET_TYPE_FULL_COUNT = 0x40, - - ASSET_TYPE_CUSTOM_MAP = 0x41 }; enum XFileBlock diff --git a/src/ObjLoading/Asset/AssetCreationContext.cpp b/src/ObjLoading/Asset/AssetCreationContext.cpp index a33cebdda..deb08b5a2 100644 --- a/src/ObjLoading/Asset/AssetCreationContext.cpp +++ b/src/ObjLoading/Asset/AssetCreationContext.cpp @@ -131,9 +131,6 @@ XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_ } else { - if (assetName[0] == ',') - return LoadDefaultAssetDependency(assetType, assetName); - std::cerr << std::format("Missing asset \"{}\" of type \"{}\"\n", assetName, *m_zone.m_pools->GetAssetTypeName(assetType)); } diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp deleted file mode 100644 index db74229b0..000000000 --- a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "ScriptCompileT6.h" - -#include - - -char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize) -{ - try - { - xsk::arc::t6::pc::context context = xsk::arc::t6::pc::context(xsk::arc::instance::server); - - std::vector scriptBuffer; - scriptBuffer.resize(gscCodeSize); - memcpy(&scriptBuffer[0], gscCode, gscCodeSize); - - auto outasm = context.compiler().compile(gscName, scriptBuffer); - auto outbin = context.assembler().assemble(*outasm); - - char* compiledBuffer = new char[outbin.first.size]; - memcpy(compiledBuffer, outbin.first.data, outbin.first.size); - - *out_CompiledSize = outbin.first.size; - return compiledBuffer; - - } - catch (std::exception const& e) - { - printf(std::format("GSC Compile failed: {}\n", e.what()).c_str()); - return NULL; - } -} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h deleted file mode 100644 index 522858a50..000000000 --- a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h +++ /dev/null @@ -1,3 +0,0 @@ -#include - -char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize); diff --git a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp index 46c0a3faa..765172d66 100644 --- a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp +++ b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp @@ -9,8 +9,6 @@ AssetNameResolver::AssetNameResolver() { for (auto assetType = 0; assetType < ASSET_TYPE_COUNT; assetType++) AddAssetTypeName(assetType, *GameAssetPoolT6::AssetTypeNameByType(assetType)); - - AddAssetTypeName(ASSET_TYPE_CUSTOM_MAP, "custom_map"); } GameId AssetNameResolver::GetGameId() const diff --git a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp index e4093d85e..6fb7780fc 100644 --- a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp +++ b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp @@ -34,7 +34,7 @@ namespace if (!m_algorithm->Verify(signatureData, signatureDataSize, signature, signatureSize)) { - //throw InvalidSignatureException(); + throw InvalidSignatureException(); } } diff --git a/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp b/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp index 3f73d5843..3d0d1d702 100644 --- a/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp +++ b/src/ZoneWriting/Game/T6/ZoneWriterFactoryT6.cpp @@ -14,7 +14,6 @@ #include "Writing/Steps/StepWriteZoneContentToFile.h" #include "Writing/Steps/StepWriteZoneContentToMemory.h" #include "Writing/Steps/StepWriteZoneHeader.h" -#include "Writing/Steps/StepWriteZoneRSA.h" #include "Writing/Steps/StepWriteZoneSizes.h" #include "Zone/XChunk/XChunkProcessorDeflate.h" #include "Zone/XChunk/XChunkProcessorSalsa20Encryption.h" @@ -100,9 +99,9 @@ std::unique_ptr ZoneWriterFactory::CreateWriter(const Zone& zone) co { auto writer = std::make_unique(); - bool isSecure = true; + // TODO Support signed fastfiles + bool isSecure = false; bool isEncrypted = true; - bool isOfficial = true; SetupBlocks(*writer); @@ -112,11 +111,7 @@ std::unique_ptr ZoneWriterFactory::CreateWriter(const Zone& zone) co writer->AddWritingStep(std::move(contentInMemory)); // Write zone header - writer->AddWritingStep(std::make_unique(CreateHeaderForParams(isSecure, isOfficial, isEncrypted))); - - // write RSA - if (isSecure) - writer->AddWritingStep(std::make_unique(zone.m_name)); + writer->AddWritingStep(std::make_unique(CreateHeaderForParams(isSecure, false, isEncrypted))); // Setup loading XChunks from the zone from this point on. ICapturedDataProvider* dataToSignProvider; diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp deleted file mode 100644 index 96dde6483..000000000 --- a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "StepWriteZoneRSA.h" - -#include "Game/T6/ZoneConstantsT6.h" - -StepWriteZoneRSA::StepWriteZoneRSA(std::string zoneName) - : m_zoneName(zoneName) -{ -} - -void StepWriteZoneRSA::PerformStep(ZoneWriter* zoneWriter, IWritingStream* stream) -{ - stream->Write(T6::ZoneConstants::MAGIC_AUTH_HEADER, strlen(T6::ZoneConstants::MAGIC_AUTH_HEADER)); - - uint32_t loadFlags = 0; - stream->Write(&loadFlags, sizeof(uint32_t)); - - char fileName[32]; - memset(fileName, 0, 32); - strncpy(fileName, m_zoneName.c_str(), 31); - stream->Write(fileName, 32); - - //char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, - // 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, - // 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, - // 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, - // 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, - // 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, - // 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, - // 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, - // 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, - // 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, - // 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, - // 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; - - char data[256]; - memset(data, 0, 256); - stream->Write(data, 256); -} diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h deleted file mode 100644 index 3dbd48222..000000000 --- a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "Writing/IWritingStep.h" - - -class StepWriteZoneRSA final : public IWritingStep -{ - std::string m_zoneName; - -public: - explicit StepWriteZoneRSA(std::string zoneName); - - void PerformStep(ZoneWriter* zoneWriter, IWritingStream* stream) override; -}; From 249e6426ffa3c9577a78c5aa31e03cad036ce086 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Thu, 9 Oct 2025 00:31:37 +0800 Subject: [PATCH 015/123] Updated custom map objloading code to match new structure. --- src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp | 6 +++--- src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h | 6 +++--- src/ObjLoading/Game/T6/CustomMap/fbx/{ufbx.c => ufbx.cpp} | 0 src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 3 +++ 4 files changed, 9 insertions(+), 6 deletions(-) rename src/ObjLoading/Game/T6/CustomMap/fbx/{ufbx.c => ufbx.cpp} (100%) diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp index 29983f784..75f86c03e 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -53,10 +53,10 @@ namespace }; } // namespace -namespace T6 +namespace custom_map { - std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) { return std::make_unique(memory, searchPath, zone); } -} // namespace T6 +} // namespace custom_map diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h index 78a388fce..5cf0e007f 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h @@ -7,7 +7,7 @@ #include -namespace T6 +namespace custom_map { - std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); -} // namespace T6 + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); +} // namespace custom_map diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c b/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.c rename to src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 796278d6b..cb804970d 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -42,6 +42,7 @@ #include "Weapon/WeaponRawLoaderT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" +#include "CustomMap/LoaderCustomMapT6.h" #include #include @@ -439,6 +440,8 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(z_barrier::CreateRawLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(z_barrier::CreateGdtLoaderT6(memory, searchPath, gdt, zone)); + + collection.AddAssetCreator(custom_map::CreateLoaderT6(memory, searchPath, zone)); } } // namespace From 8737fdf2e765b07bbd736558e19b069a7a9ecab6 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Thu, 9 Oct 2025 01:10:21 +0800 Subject: [PATCH 016/123] Updated Json Material loader to support passing JSON instead of a stream, and added techset loading code --- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 3 +- .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 6 +- .../T6/TechniqueSet/LoaderTechniqueSetT6.h | 6 +- .../Material/JsonMaterialLoader.cpp.template | 86 ++++++++++--------- 4 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index cb804970d..6abdaaabf 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -43,6 +43,7 @@ #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" #include "CustomMap/LoaderCustomMapT6.h" +#include "TechniqueSet/LoaderTechniqueSetT6.h" #include #include @@ -393,7 +394,7 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(xmodel::CreateLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(material::CreateLoaderT6(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(technique_set::CreateLoaderT6(memory, searchPath)); collection.AddAssetCreator(image::CreateLoaderT6(memory, searchPath)); collection.AddAssetCreator(sound::CreateSoundBankLoaderT6(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index b2e973709..a0e9501ca 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -175,10 +175,10 @@ namespace }; } // namespace -namespace T6 +namespace technique_set { - std::unique_ptr> CreateTechniqueSetLoader(MemoryManager& memory, ISearchPath& searchPath) + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath) { return std::make_unique(memory, searchPath); } -} // namespace T6 +} // namespace technique_set diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h index 78081a86e..35b2b7108 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.h @@ -7,7 +7,7 @@ #include -namespace T6 +namespace technique_set { - std::unique_ptr> CreateTechniqueSetLoader(MemoryManager& memory, ISearchPath& searchPath); -} // namespace T6 + std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath); +} // namespace technique_set diff --git a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template index 248d12ccd..cf8a6b495 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template @@ -52,50 +52,39 @@ namespace class JsonLoader { public: - JsonLoader(std::istream& stream, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - : m_stream(stream), - m_memory(memory), + JsonLoader(MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + : m_memory(memory), m_context(context), m_registration(registration) { } - bool Load(Material& material) const + bool Load(json& jRoot, Material& material) const { - try - { - const auto jRoot = json::parse(m_stream); - std::string type; - unsigned version; - - jRoot.at("_type").get_to(type); - jRoot.at("_version").get_to(version); - - if (type != "material" || version != 1u) - { - con::error("Tried to load material \"{}\" but did not find expected type material of version 1", material.info.name); - return false; - } + std::string type; + unsigned version; + + jRoot.at("_type").get_to(type); + jRoot.at("_version").get_to(version); + + if (type != "material" || version != 1u) + { + con::error("Tried to load material \"{}\" but did not find expected type material of version 1", material.info.name); + return false; + } #ifndef FEATURE_T6 // T6 did not have this check in version 1, so to stay backwards compatible, let it stay that way - std::string game; - jRoot.at("_game").get_to(game); - if (game != GAME_LOWER) - { - con::error("Tried to load material \"{}\" but \"_game\" did not find expected type value {}", material.info.name, GAME_LOWER); - return false; - } -#endif - - const auto jMaterial = jRoot.get(); - return CreateMaterialFromJson(jMaterial, material); - } - catch (const json::exception& e) + std::string game; + jRoot.at("_game").get_to(game); + if (game != GAME_LOWER) { - con::error("Failed to parse json of material: {}", e.what()); + con::error("Tried to load material \"{}\" but \"_game\" did not find expected type value {}", material.info.name, GAME_LOWER); + return false; } +#endif - return false; + const auto jMaterial = jRoot.get(); + return CreateMaterialFromJson(jMaterial, material); } private: @@ -519,7 +508,6 @@ namespace return true; } - std::istream& m_stream; MemoryManager& m_memory; AssetCreationContext& m_context; AssetRegistration& m_registration; @@ -529,10 +517,28 @@ namespace namespace GAME { bool LoadMaterialAsJson( - std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - { - const JsonLoader loader(stream, memory, context, registration); - - return loader.Load(material); - } + std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + { + const JsonLoader loader(memory, context, registration); + + try + { + auto jRoot = json::parse(stream); + + return loader.Load(jRoot, material); + } + catch (const json::exception& e) + { + con::error("Failed to parse json of material: {}", e.what()); + return false; + } + } + + bool LoadMaterialAsJson( + json& json, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + { + const JsonLoader loader(memory, context, registration); + + return loader.Load(json, material); + } } // namespace GAME From 2906a9cf929a57f605de2693cd0495871e43553a Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 11 Oct 2025 18:42:09 +0800 Subject: [PATCH 017/123] FIxed techset dumping and loading to be the same, as well as added a missing image check to the BSP compiler to reduce the output prints when an image is missing. --- src/ObjCommon/Shader/ShaderCommon.cpp | 4 +- .../Game/T6/CustomMap/CustomMapLinker.h | 12 +- .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 5 +- .../Game/T6/Techset/TechsetDumperT6.cpp | 112 ++++++++++++++++-- 4 files changed, 117 insertions(+), 16 deletions(-) diff --git a/src/ObjCommon/Shader/ShaderCommon.cpp b/src/ObjCommon/Shader/ShaderCommon.cpp index 7dc6c7bbd..1703d376a 100644 --- a/src/ObjCommon/Shader/ShaderCommon.cpp +++ b/src/ObjCommon/Shader/ShaderCommon.cpp @@ -6,11 +6,11 @@ namespace shader { std::string GetFileNameForPixelShaderAssetName(const std::string& assetName) { - return std::format("shader_bin/ps_{}.cso", assetName); + return std::format("techniquesets/shader_bin/ps_{}.cso", assetName); } std::string GetFileNameForVertexShaderAssetName(const std::string& assetName) { - return std::format("shader_bin/vs_{}.cso", assetName); + return std::format("techniquesets/shader_bin/vs_{}.cso", assetName); } } // namespace shader diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 0ba12b452..708b53e79 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -138,6 +138,14 @@ class CustomMapLinker Material* loadImageIntoMaterial(std::string& imageName) { + std::string imagePath = std::format("images/{}.iwi", imageName); + auto imageFile = m_search_path.Open(imagePath); + if (!imageFile.IsOpen()) + { + printf("WARN: failed to find image %s.\n", imageName.c_str()); + return NULL; + } + Material* material = new Material; material->info.name = m_memory.Dup(imageName.c_str()); @@ -1808,7 +1816,7 @@ class CustomMapLinker std::string entityString; - const auto entFile = m_search_path.Open("entities.json"); + const auto entFile = m_search_path.Open("custom_map/entities.json"); if (!entFile.IsOpen()) { printf("ERROR: can't find entity json!\n"); @@ -1817,7 +1825,7 @@ class CustomMapLinker json entJs = json::parse(*entFile.m_stream); parseMapEntsJSON(entJs["entities"], entityString); - const auto spawnFile = m_search_path.Open("spawns.json"); + const auto spawnFile = m_search_path.Open("custom_map/spawns.json"); json spawnJs; if (!spawnFile.IsOpen()) { diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index a0e9501ca..c2eb7fa92 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -1,5 +1,6 @@ #include "Game/T6/T6.h" #include "LoaderTechniqueSetT6.h" +#include "Shader/ShaderCommon.h" #include #include @@ -128,7 +129,7 @@ namespace currPass->pixelShader->name = _strdup(pixelName.c_str()); currPass->pixelShader->prog.ps = NULL; - const auto psFileName = std::format("techniquesets/shader_bin/ps_{}", pixelName); + const auto psFileName = shader::GetFileNameForPixelShaderAssetName(pixelName); const auto psFile = m_search_path.Open(psFileName); if (!psFile.IsOpen()) { @@ -152,7 +153,7 @@ namespace currPass->vertexShader->name = _strdup(vertexName.c_str()); currPass->vertexShader->prog.vs = NULL; - const auto vsFileName = std::format("techniquesets/shader_bin/vs_{}", vertexName); + const auto vsFileName = shader::GetFileNameForVertexShaderAssetName(vertexName); const auto vsFile = m_search_path.Open(vsFileName); if (!vsFile.IsOpen()) { diff --git a/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp b/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp index a2d7273ce..f2cc2fc0a 100644 --- a/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp +++ b/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp @@ -4,7 +4,9 @@ #include #include +#include +using namespace nlohmann; using namespace T6; namespace @@ -87,21 +89,111 @@ namespace techset const auto* techniqueSet = asset->Asset(); auto* shaderState = context.GetZoneAssetDumperState(); + const auto assetFile = context.OpenAssetFile(std::format("techniquesets/{}.json", techniqueSet->name)); + if (!assetFile) + return; + + json js; + + js["name"] = techniqueSet->name; + js["worldVertFormat"] = techniqueSet->worldVertFormat; + + js["techniques"] = json::array(); for (const auto* technique : techniqueSet->techniques) { - if (!technique || !shaderState->ShouldDumpTechnique(technique)) - continue; + json techniqueJs = json::object(); - for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + if (technique != NULL) { - const auto* pixelShader = technique->passArray[passIndex].pixelShader; - if (pixelShader && shaderState->ShouldDumpPixelShader(pixelShader)) - DumpPixelShader(context, *pixelShader); - - const auto* vertexShader = technique->passArray[passIndex].vertexShader; - if (vertexShader && shaderState->ShouldDumpVertexShader(vertexShader)) - DumpVertexShader(context, *vertexShader); + techniqueJs["name"] = technique->name; + techniqueJs["flags"] = technique->flags; + techniqueJs["passCount"] = technique->passCount; + + _ASSERT(technique->passCount == 1); + + techniqueJs["passArray"] = json::array(); + for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + { + const MaterialPass* currPass = &technique->passArray[passIndex]; + json passJs = json::object(); + + passJs["perPrimArgCount"] = currPass->perPrimArgCount; + passJs["perObjArgCount"] = currPass->perObjArgCount; + passJs["stableArgCount"] = currPass->stableArgCount; + passJs["customSamplerFlags"] = currPass->customSamplerFlags; + passJs["precompiledIndex"] = currPass->precompiledIndex; + passJs["materialType"] = currPass->materialType; + + json vertDeclJs = json::object(); + if (currPass->vertexDecl != NULL) + { + vertDeclJs["streamCount"] = currPass->vertexDecl->streamCount; + vertDeclJs["hasOptionalSource"] = currPass->vertexDecl->hasOptionalSource; + vertDeclJs["isLoaded"] = currPass->vertexDecl->isLoaded; + for (int i = 0; i < 16; i++) + { + vertDeclJs["routing"][i]["source"] = currPass->vertexDecl->routing.data[i].source; + vertDeclJs["routing"][i]["dest"] = currPass->vertexDecl->routing.data[i].dest; + + _ASSERT(currPass->vertexDecl->routing.decl[i] == NULL); + } + } + passJs["vertexDecl"] = vertDeclJs; + + passJs["args"] = json::array(); + if (currPass->args != NULL) + { + for (int i = 0; i < currPass->perPrimArgCount + currPass->perObjArgCount + currPass->stableArgCount; i++) + { + json argsJs = json::object(); + MaterialShaderArgument* currArg = &currPass->args[i]; + + argsJs["type"] = currArg->type; + argsJs["location"] = currArg->location.offset; + argsJs["size"] = currArg->size; + argsJs["buffer"] = currArg->buffer; + if (currArg->type == MTL_ARG_LITERAL_VERTEX_CONST || currArg->type == MTL_ARG_LITERAL_PIXEL_CONST) + { + argsJs["u"]["const0"] = currArg->u.literalConst[0]; + argsJs["u"]["const1"] = currArg->u.literalConst[1]; + argsJs["u"]["const2"] = currArg->u.literalConst[2]; + argsJs["u"]["const3"] = currArg->u.literalConst[3]; + } + else + { + argsJs["u"]["value"] = currArg->u.nameHash; + } + + passJs["args"].push_back(argsJs); + } + } + + json pixelJs = json::object(); + if (currPass->pixelShader != NULL) + { + pixelJs["name"] = currPass->pixelShader->name; + if (shaderState->ShouldDumpPixelShader(currPass->pixelShader)) + DumpPixelShader(context, *currPass->pixelShader); + } + passJs["pixelShader"] = pixelJs; + + json vertexJs = json::object(); + if (currPass->vertexShader != NULL) + { + vertexJs["name"] = currPass->vertexShader->name; + if (shaderState->ShouldDumpVertexShader(currPass->vertexShader)) + DumpVertexShader(context, *currPass->vertexShader); + } + passJs["vertexShader"] = vertexJs; + + techniqueJs["passArray"].push_back(passJs); + } } + + js["techniques"].push_back(techniqueJs); } + + std::string jsonString = js.dump(4); + assetFile->write(jsonString.c_str(), jsonString.size()); } } // namespace techset From 15918506c37c53fb918bfc09c142c614a3dbbebb Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 11 Oct 2025 19:01:48 +0800 Subject: [PATCH 018/123] Added a default entity string if the entity file isn't given --- .../Game/T6/CustomMap/CustomMapConsts.h | 17 +++++++++++++++++ .../Game/T6/CustomMap/CustomMapLinker.h | 12 ++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index c8ce1d0dd..e65152f43 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -104,3 +104,20 @@ const std::string defaultSpawnpointString = "{" "}"; +const std::string defaultMapEntsString = "{" + "\"entities\": [" + "{" + "\"classname\": \"worldspawn\"" + "}," + "{" + "\"angles\": \"0 0 0\"," + "\"classname\": \"info_player_start\"," + "\"origin\": \"0 0 0\"" + "}," + "{" + "\"angles\": \"0 0 0\"," + "\"classname\": \"mp_global_intermission\"," + "\"origin\": \"0 0 0\"" + "}" + "]" +"}"; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 708b53e79..9f0de39a7 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1817,19 +1817,23 @@ class CustomMapLinker std::string entityString; const auto entFile = m_search_path.Open("custom_map/entities.json"); + json entJs; if (!entFile.IsOpen()) { - printf("ERROR: can't find entity json!\n"); - return; + printf("WARN: can't find entity json custom_map/entities.json, using default entities\n"); + entJs = json::parse(defaultMapEntsString); + } + else + { + entJs = json::parse(*entFile.m_stream); } - json entJs = json::parse(*entFile.m_stream); parseMapEntsJSON(entJs["entities"], entityString); const auto spawnFile = m_search_path.Open("custom_map/spawns.json"); json spawnJs; if (!spawnFile.IsOpen()) { - printf("WARN: no spawn points given, setting spawns to 0 0 0\n"); + printf("WARN: cant find custom_map/spawns.json, setting spawns to 0 0 0\n"); spawnJs = json::parse(defaultSpawnpointString); } else From e19a3a142ab0738e6b5624aa85ee66bbd15886c1 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 20 Oct 2025 18:04:03 +0800 Subject: [PATCH 019/123] WIP: Updating code to follow laupentin's code review. Done: - Moved custom map structures to their own objcommon header file - Updated GfxLightGridRow struct - Reverted shader_bin file path - Renamed Project Creator to BSP Creator - Removed model loading from BSP creator - Cleaned up BSP Creator and updated the names of structs WIP: - Update BSP calculation code to be more readable and use unique/shared ptrs --- src/Common/Game/T6/T6.h | 94 --------- src/Common/Game/T6/T6_Assets.h | 24 +-- src/ObjCommon/Game/T6/Maps/CustomMaps.h | 53 +++++ src/ObjCommon/Shader/ShaderCommon.cpp | 4 +- .../{ProjectCreator.cpp => BSPCreator.cpp} | 183 +++++------------- src/ObjLoading/Game/T6/CustomMap/BSPCreator.h | 10 + .../T6/CustomMap/BinarySpacePartitionTree.cpp | 0 ...eePreCalc.h => BinarySpacePartitionTree.h} | 108 ++++------- .../Game/T6/CustomMap/LoaderCustomMapT6.cpp | 14 +- .../Game/T6/CustomMap/ProjectCreator.h | 12 -- 10 files changed, 164 insertions(+), 338 deletions(-) create mode 100644 src/ObjCommon/Game/T6/Maps/CustomMaps.h rename src/ObjLoading/Game/T6/CustomMap/{ProjectCreator.cpp => BSPCreator.cpp} (60%) create mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCreator.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp rename src/ObjLoading/Game/T6/CustomMap/{BinarySpacePartitionTreePreCalc.h => BinarySpacePartitionTree.h} (67%) delete mode 100644 src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index fbaa88db6..72cd851be 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -158,100 +158,6 @@ namespace T6 AUFT_NUM_FIELD_TYPES, }; - - struct customMapVertex - { - vec3_t pos; - float binormalSign; - float color[4]; - float texCoord[2]; - vec3_t normal; - vec3_t tangent; - unsigned int packedLmapCoord; - }; - - enum CM_MATERIAL_TYPE - { - CM_MATERIAL_COLOUR, - CM_MATERIAL_TEXTURE, - CM_MATERIAL_EMPTY - }; - - struct customMapMaterial - { - CM_MATERIAL_TYPE materialType; - const char* materialName; - }; - - struct worldSurface - { - customMapMaterial material; - - //char lightmapIndex; - //char primaryLightIndex; - //char reflectionProbeIndex; - - int triCount; - int firstVertexIndex; - int firstIndex_Index; - }; - - struct customMapGfx - { - int vertexCount; - customMapVertex* vertices; - - int indexCount; - uint16_t* indices; - - int surfaceCount; - worldSurface* surfaces; - }; - - struct customMapColVertex - { - vec3_t pos; - }; - - struct collisionSurface - { - int triCount; - int firstVertexIndex; - int firstIndex_Index; - }; - - struct customMapCol - { - int vertexCount; - customMapColVertex* vertices; - - int indexCount; - uint16_t* indices; - - int surfaceCount; - collisionSurface* surfaces; - }; - - struct customMapModel - { - std::string name; - - vec3_t origin; - vec3_t rotation; // euler rotation in degrees - float scale; - }; - - struct customMapInfo - { - std::string name; - std::string bspName; - - customMapGfx gfxInfo; - customMapGfx colInfo; - - size_t modelCount; - customMapModel* models; - }; using AssetPhysPreset = Asset; using AssetPhysConstraints = Asset; diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 52a25695a..f50c72fc6 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -1278,30 +1278,16 @@ namespace T6 typedef tdef_align32(4) char aligned_byte_pointer; typedef tdef_align32(4) GfxCompressedLightGridCoeffs GfxCompressedLightGridCoeffs_align4; - struct GfxLightGridUnk - { - char unknown1; - char unknown2; - char unknown3; - char unknown4; - char unknown5; - char unknown6; - char unknown7; - char unknown8; - }; - - struct GfxLightGridRow { - unsigned __int16 colStart; - unsigned __int16 colCount; - unsigned __int16 zStart; - unsigned __int16 zCount; + uint16_t colStart; + uint16_t colCount; + uint16_t zStart; + uint16_t zCount; unsigned int firstEntry; - GfxLightGridUnk unk; + char lookupTable[1]; // The lookup table has a variable length }; - struct GfxLightGrid { unsigned int sunPrimaryLightIndex; diff --git a/src/ObjCommon/Game/T6/Maps/CustomMaps.h b/src/ObjCommon/Game/T6/Maps/CustomMaps.h new file mode 100644 index 000000000..9ed6c7530 --- /dev/null +++ b/src/ObjCommon/Game/T6/Maps/CustomMaps.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include "Game/T6/T6.h" +using namespace T6; + +struct CustomMapVertex +{ + vec3_t pos; + vec4_t color; + vec2_t texCoord; + vec3_t normal; + vec3_t tangent; +}; + +enum CustomMapMaterialType +{ + MATERIAL_TYPE_COLOUR, + MATERIAL_TYPE_TEXTURE, + MATERIAL_TYPE_EMPTY +}; + +struct CustomMapMaterial +{ + CustomMapMaterialType materialType; + std::string materialName; +}; + +struct CustomMapSurface +{ + CustomMapMaterial material; + int triCount; + int indexOfFirstVertex; + int indexOfFirstIndex; +}; + +struct CustomMapWorld +{ + std::vector surfaces; + std::vector vertices; + std::vector indices; +}; + +struct CustomMapBSP +{ + std::string name; + std::string bspName; + + CustomMapWorld gfxWorld; + CustomMapWorld colWorld; +}; diff --git a/src/ObjCommon/Shader/ShaderCommon.cpp b/src/ObjCommon/Shader/ShaderCommon.cpp index 1703d376a..7dc6c7bbd 100644 --- a/src/ObjCommon/Shader/ShaderCommon.cpp +++ b/src/ObjCommon/Shader/ShaderCommon.cpp @@ -6,11 +6,11 @@ namespace shader { std::string GetFileNameForPixelShaderAssetName(const std::string& assetName) { - return std::format("techniquesets/shader_bin/ps_{}.cso", assetName); + return std::format("shader_bin/ps_{}.cso", assetName); } std::string GetFileNameForVertexShaderAssetName(const std::string& assetName) { - return std::format("techniquesets/shader_bin/vs_{}.cso", assetName); + return std::format("shader_bin/vs_{}.cso", assetName); } } // namespace shader diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp similarity index 60% rename from src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp rename to src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp index 0f00ed15e..8b47fd2a5 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp @@ -1,14 +1,14 @@ -#include "ProjectCreator.h" +#include "BSPCreator.h" #include "fbx/ufbx.h" - -std::vector vertexVec; -std::vector indexVec; -std::vector surfaceVec; -std::vector modelVec; -bool hasTangentSpace = true; - -bool loadFBXMesh(ufbx_node* node) +#include "Game/T6/T6.h" +using namespace T6; + +bool addFBXMeshToWorld(ufbx_node* node, + std::vector& surfaceVec, + std::vector& vertexVec, + std::vector& indexVec, + bool& hasTangentSpace) { if (node->attrib_type != UFBX_ELEMENT_MESH) { @@ -65,15 +65,15 @@ bool loadFBXMesh(ufbx_node* node) if (meshPart->num_faces == 0) continue; - worldSurface surface; + CustomMapSurface surface; surface.triCount = meshPart->num_triangles; - surface.firstVertexIndex = vertexVec.size(); - surface.firstIndex_Index = indexVec.size(); + surface.indexOfFirstVertex = vertexVec.size(); + surface.indexOfFirstIndex = indexVec.size(); - CM_MATERIAL_TYPE meshMaterialType; + CustomMapMaterialType meshMaterialType; if (mesh->materials.count == 0) { - meshMaterialType = CM_MATERIAL_EMPTY; + meshMaterialType = MATERIAL_TYPE_EMPTY; } //else if (mesh->materials.data[i]->textures.count != 0) //{ @@ -87,13 +87,13 @@ bool loadFBXMesh(ufbx_node* node) //} else { - meshMaterialType = CM_MATERIAL_TEXTURE; + meshMaterialType = MATERIAL_TYPE_TEXTURE; surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); } surface.material.materialType = meshMaterialType; size_t num_triangles = meshPart->num_triangles; - customMapVertex* vertices = (customMapVertex*)calloc(num_triangles * 3, sizeof(customMapVertex)); + CustomMapVertex* vertices = (CustomMapVertex*)calloc(num_triangles * 3, sizeof(CustomMapVertex)); size_t num_vertices = 0; // Reserve space for the maximum triangle indices. @@ -113,7 +113,7 @@ bool loadFBXMesh(ufbx_node* node) { uint32_t index = tri_indices[q]; - customMapVertex* vertex = &vertices[num_vertices++]; + CustomMapVertex* vertex = &vertices[num_vertices++]; //ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, index); //vertex->pos.x = static_cast(pos.x); @@ -128,21 +128,21 @@ bool loadFBXMesh(ufbx_node* node) switch (meshMaterialType) { - case CM_MATERIAL_TEXTURE: - case CM_MATERIAL_EMPTY: - vertex->color[0] = 1.0f; - vertex->color[1] = 1.0f; - vertex->color[2] = 1.0f; - vertex->color[3] = 1.0f; + case MATERIAL_TYPE_TEXTURE: + case MATERIAL_TYPE_EMPTY: + vertex->color.x = 1.0f; + vertex->color.y = 1.0f; + vertex->color.z = 1.0f; + vertex->color.w = 1.0f; break; - case CM_MATERIAL_COLOUR: + case MATERIAL_TYPE_COLOUR: { float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); ufbx_vec4 diffuse = mesh->materials.data[i]->fbx.diffuse_color.value_vec4; - vertex->color[0] = static_cast(diffuse.x * factor); - vertex->color[1] = static_cast(diffuse.y * factor); - vertex->color[2] = static_cast(diffuse.z * factor); - vertex->color[3] = static_cast(diffuse.w * factor); + vertex->color.x = static_cast(diffuse.x * factor); + vertex->color.y = static_cast(diffuse.y * factor); + vertex->color.z = static_cast(diffuse.z * factor); + vertex->color.w = static_cast(diffuse.w * factor); break; } @@ -155,8 +155,8 @@ bool loadFBXMesh(ufbx_node* node) // 1.0f - uv.y reason: // https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); - vertex->texCoord[0] = (float)(uv.x); - vertex->texCoord[1] = (float)(1.0f - uv.y); + vertex->texCoord.x = (float)(uv.x); + vertex->texCoord.y = (float)(1.0f - uv.y); ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); vertex->normal.x = static_cast(normal.x); @@ -176,11 +176,6 @@ bool loadFBXMesh(ufbx_node* node) vertex->tangent.y = 0.0f; vertex->tangent.z = 0.0f; } - - vertex->packedLmapCoord = 0; - - // possibly bitangent, unsure what the sign part means though - vertex->binormalSign = 0.0f; } } @@ -188,7 +183,7 @@ bool loadFBXMesh(ufbx_node* node) // Generate the index buffer. ufbx_vertex_stream streams[1] = { - {vertices, num_vertices, sizeof(customMapVertex)}, + {vertices, num_vertices, sizeof(CustomMapVertex)}, }; size_t num_indices = num_triangles * 3; uint32_t* indices = (uint32_t*)calloc(num_indices, sizeof(uint32_t)); @@ -213,120 +208,32 @@ bool loadFBXMesh(ufbx_node* node) return true; } -bool loadFBXModel(ufbx_node* node) -{ - customMapModel model; - - model.name = node->name.data; - - model.origin.x = static_cast(node->local_transform.translation.x); - model.origin.y = static_cast(node->local_transform.translation.y); - model.origin.z = static_cast(node->local_transform.translation.z); - model.rotation.x = static_cast(node->euler_rotation.x); - model.rotation.y = static_cast(node->euler_rotation.y); - model.rotation.z = static_cast(node->euler_rotation.z); - model.scale = static_cast(node->local_transform.scale.x); - - if (model.scale == 0.0f) - { - printf("WARN: Ignoring model %s: has a scale of 0!\n", node->name.data); - return false; - } - - if (node->local_transform.scale.x != node->local_transform.scale.y || node->local_transform.scale.x != node->local_transform.scale.z) - printf("WARNING: model %s uses non-uniform scaling! Only the X axis will be used for the scale value.\n", node->name.data); - modelVec.push_back(model); - - return true; -} - -void parseGFXData(ufbx_scene* scene, customMapInfo* projInfo) +void loadWorldData(ufbx_scene* scene, CustomMapBSP* bsp, bool isGfxData) { - vertexVec.clear(); - indexVec.clear(); - surfaceVec.clear(); - modelVec.clear(); - hasTangentSpace = true; + bool hasTangentSpace = true; for (size_t i = 0; i < scene->nodes.count; i++) { ufbx_node* node = scene->nodes.data[i]; - switch (node->attrib_type) + if (node->attrib_type == UFBX_ELEMENT_MESH) { - case UFBX_ELEMENT_MESH: - loadFBXMesh(node); - break; - case UFBX_ELEMENT_EMPTY: - // loadFBXModel(node); - break; - default: - //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); - break; + if (isGfxData) + addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); + else + addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); } - } - projInfo->gfxInfo.surfaceCount = surfaceVec.size(); - projInfo->gfxInfo.vertexCount = vertexVec.size(); - projInfo->gfxInfo.indexCount = indexVec.size(); - projInfo->modelCount = modelVec.size(); - projInfo->gfxInfo.surfaces = new worldSurface[surfaceVec.size()]; - projInfo->gfxInfo.vertices = new customMapVertex[vertexVec.size()]; - projInfo->gfxInfo.indices = new uint16_t[indexVec.size()]; - projInfo->models = new customMapModel[modelVec.size()]; - memcpy(projInfo->gfxInfo.surfaces, &surfaceVec[0], surfaceVec.size() * sizeof(worldSurface)); - memcpy(projInfo->gfxInfo.vertices, &vertexVec[0], vertexVec.size() * sizeof(customMapVertex)); - memcpy(projInfo->gfxInfo.indices, &indexVec[0], indexVec.size() * sizeof(uint16_t)); - // memcpy(projInfo->models, &modelVec[0], modelVec.size() * sizeof(customMapModel)); - - if (hasTangentSpace == false) - printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); -} - -void parseCollisionData(ufbx_scene* scene, customMapInfo* projInfo) -{ - // hack: cbf changing the code for collision data, so just load collision dada as if it was gfx data - - vertexVec.clear(); - indexVec.clear(); - surfaceVec.clear(); - modelVec.clear(); - hasTangentSpace = true; - - for (size_t i = 0; i < scene->nodes.count; i++) - { - ufbx_node* node = scene->nodes.data[i]; - - switch (node->attrib_type) + else { - case UFBX_ELEMENT_MESH: - loadFBXMesh(node); - break; - case UFBX_ELEMENT_EMPTY: - // loadFBXModel(node); - break; - default: //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); - break; } } - projInfo->colInfo.surfaceCount = surfaceVec.size(); - projInfo->colInfo.vertexCount = vertexVec.size(); - projInfo->colInfo.indexCount = indexVec.size(); - projInfo->modelCount = modelVec.size(); - projInfo->colInfo.surfaces = new worldSurface[surfaceVec.size()]; - projInfo->colInfo.vertices = new customMapVertex[vertexVec.size()]; - projInfo->colInfo.indices = new uint16_t[indexVec.size()]; - projInfo->models = new customMapModel[modelVec.size()]; - memcpy(projInfo->colInfo.surfaces, &surfaceVec[0], surfaceVec.size() * sizeof(worldSurface)); - memcpy(projInfo->colInfo.vertices, &vertexVec[0], vertexVec.size() * sizeof(customMapVertex)); - memcpy(projInfo->colInfo.indices, &indexVec[0], indexVec.size() * sizeof(uint16_t)); - // memcpy(projInfo->models, &modelVec[0], modelVec.size() * sizeof(customMapModel)); if (hasTangentSpace == false) printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); } -customMapInfo* ProjectCreator::createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) +CustomMapBSP* BSPCreator::createCustomMapBSP(std::string& mapName, ISearchPath& searchPath) { ufbx_scene* gfxScene; ufbx_scene* colScene; @@ -379,13 +286,13 @@ customMapInfo* ProjectCreator::createCustomMapInfo(std::string& projectName, ISe } } - customMapInfo* projInfo = new customMapInfo; + CustomMapBSP* projInfo = new CustomMapBSP; - projInfo->name = projectName; - projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + projInfo->name = mapName; + projInfo->bspName = "maps/mp/" + mapName + ".d3dbsp"; - parseGFXData(gfxScene, projInfo); - parseCollisionData(colScene, projInfo); + loadWorldData(gfxScene, projInfo, true); + loadWorldData(colScene, projInfo, false); ufbx_free_scene(gfxScene); if (gfxScene != colScene) diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h new file mode 100644 index 000000000..2ba851711 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Game/T6/Maps/CustomMaps.h" +#include "SearchPath/ISearchPath.h" + +class BSPCreator +{ +public: + static CustomMapBSP* createCustomMapBSP(std::string& mapName, ISearchPath& searchPath); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h similarity index 67% rename from src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h rename to src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h index eda7bca0a..52ea1e0c3 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h @@ -1,22 +1,12 @@ #pragma once -/* -Heavily modified version of https://github.com/sudeshnapal12/Space-Partitioning-Algorithms BSP implementation -Credit to sudeshnapal12 - - -BSP leaf sizes are precalculated, evenly sized BSPs are much more efficient and smaller compared to dynamically creating them -*/ - -#include -#include -#include -#include -#include -#include #include +#include + +#include "Game/T6/T6.h" +using namespace T6; -#define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf +constexpr int MAX_NODE_SIZE = 512; // maximum size a BSP node can be before it becomes a leaf enum PlaneAxis { @@ -28,56 +18,50 @@ enum PlaneAxis class Object { public: - double low[3]; - double high[3]; + vec3_t min; + vec3_t max; - // custom data - int partitionIndex; // index of the partition the object is based on (custom) + int partitionIndex; // index of the partition the object is contained in - Object(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int partition_Index) + Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) { - low[0] = min_x; - low[1] = min_y, low[2] = min_z; - high[0] = max_x; - high[1] = max_y; - high[2] = max_z; - partitionIndex = partition_Index; + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + partitionIndex = objPartitionIndex; } }; -union u_BSPNode; +union u_BSPNode +{ + BSPLeaf* leaf; + BSPNode* node; +}; class BSPTree; class BSPLeaf { private: - std::vector objectList; + std::vector> objectList; public: - BSPLeaf() - { - objectList = std::vector(); - } - - ~BSPLeaf() + void addObject(std::unique_ptr object) { - objectList.clear(); + objectList.push_back(std::move(object)); } - void addToList(Object* object) + std::unique_ptr getObject(int index) { - objectList.push_back(object); + return std::move(objectList.at(index)); } int getObjectCount() { return objectList.size(); } - - Object* getObject(int index) - { - return objectList.at(index); - } }; enum objectPlaneSide @@ -106,25 +90,23 @@ class BSPNode objectPlaneSide objectIsInfront(Object* object) { - double minCoord, maxCoord; + float minCoord, maxCoord; // Select the relevant coordinate based on the plane's axis - switch (axis) + if (axis == AXIS_X) + { + minCoord = object->min.x; + maxCoord = object->max.x; + } + else if (axis == AXIS_Y) { - case AXIS_X: - minCoord = object->low[0]; - maxCoord = object->high[0]; - break; - case AXIS_Y: - minCoord = object->low[1]; - maxCoord = object->high[1]; - break; - case AXIS_Z: - minCoord = object->low[2]; - maxCoord = object->high[2]; - break; - default: - _ASSERT(false); // this should never be executed + minCoord = object->min.y; + maxCoord = object->max.y; + } + else // axis == AXIS_Z + { + minCoord = object->min.z; + maxCoord = object->max.z; } // Compare with the plane's distance @@ -143,12 +125,6 @@ class BSPNode } }; -union u_BSPNode -{ - BSPLeaf* leaf; - BSPNode* node; -}; - class BSPTree { public: @@ -178,7 +154,7 @@ class BSPTree BSPTree* back; double halfLength; - if (high[0] - low[0] > MAX_AABB_SIZE) + if (high[0] - low[0] > MAX_NODE_SIZE) { // split along the x axis halfLength = (low[0] + high[0]) * 0.5f; @@ -188,7 +164,7 @@ class BSPTree isLeaf = false; u.node = new BSPNode(front, back, AXIS_X, halfLength); } - else if (high[2] - low[2] > MAX_AABB_SIZE) + else if (high[2] - low[2] > MAX_NODE_SIZE) { // split along the z axis halfLength = (low[2] + high[2]) * 0.5f; diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp index 75f86c03e..58e4bd55c 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -1,5 +1,7 @@ +#include "Game/T6/Maps/CustomMaps.h" + #include "LoaderCustomMapT6.h" -#include "ProjectCreator.h" +#include "BSPCreator.h" #include "CustomMapLinker.h" #include "Game/T6/T6.h" @@ -27,18 +29,16 @@ namespace if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); - // create map info from the fbx file - customMapInfo* mapInfo = ProjectCreator::createCustomMapInfo(m_zone.m_name, m_search_path); - if (mapInfo == NULL) + CustomMapBSP* mapBSP = BSPCreator::createCustomMapBSP(m_zone.m_name, m_search_path); + if (mapBSP == NULL) return AssetCreationResult::Failure(); - // linker will add all the assets needed CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); - bool result = linker->linkCustomMap(mapInfo); + bool result = linker->linkCustomMap(mapBSP); if (result) { - auto gfxWorldAsset = context.LoadDependency(mapInfo->bspName); + auto gfxWorldAsset = context.LoadDependency(mapBSP->bspName); _ASSERT(gfxWorldAsset != NULL); return AssetCreationResult::Success(gfxWorldAsset); } diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h deleted file mode 100644 index e60b6b53b..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "SearchPath/ISearchPath.h" -#include "Utils/MemoryManager.h" -#include "Game/T6/T6.h" -using namespace T6; - -class ProjectCreator -{ -public: - static customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath); -}; From 7d09877d02afce02097c243383592f3ccc3213e7 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Tue, 21 Oct 2025 17:37:56 +0800 Subject: [PATCH 020/123] WIP: - Converted BSP tree generation to use unique ptrs - Updated the linker to use BSP tree unique ptrs and use the new structure of custom map structs --- .../T6/CustomMap/BinarySpacePartitionTree.cpp | 147 +++++++++ .../T6/CustomMap/BinarySpacePartitionTree.h | 195 ++--------- .../Game/T6/CustomMap/CustomMapLinker.h | 307 +++++++++--------- src/ObjLoading/Game/T6/CustomMap/Util.h | 1 + 4 files changed, 337 insertions(+), 313 deletions(-) diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp index e69de29bb..644f86a76 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp @@ -0,0 +1,147 @@ +#include "BinarySpacePartitionTree.h" + + +Object::Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) +{ + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + partitionIndex = objPartitionIndex; +} + +void BSPLeaf::addObject(std::shared_ptr object) +{ + objectList.emplace_back(std::move(object)); +} + +Object* BSPLeaf::getObject(int index) +{ + return objectList.at(index).get(); +} + +int BSPLeaf::getObjectCount() +{ + return objectList.size(); +} + +BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance) +{ + front = std::move(frontTree); + back = std::move(backTree); + axis = nodeAxis; + distance = nodeDistance; +} + +PlaneSide BSPNode::objectIsInfront(Object* object) +{ + float minCoord, maxCoord; + + // Select the relevant coordinate based on the plane's axis + if (axis == AXIS_X) + { + minCoord = object->min.x; + maxCoord = object->max.x; + } + else if (axis == AXIS_Y) + { + minCoord = object->min.y; + maxCoord = object->max.y; + } + else // axis == AXIS_Z + { + minCoord = object->min.z; + maxCoord = object->max.z; + } + + // Compare with the plane's distance + if (maxCoord < distance) + { + return SIDE_BACK; // Object is entirely on the negative side + } + else if (minCoord > distance) + { + return SIDE_FRONT; // Object is entirely on the positive side + } + else + { + return SIDE_INTERSECTS; + } +} + +BSPTree::BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel) +{ + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + level = treeLevel; + splitTree(); +} + +// For simplicity, only split across the X and Z axis. +// It is unlikely that there are many layers to a map, and is instead mostly flat +void BSPTree::splitTree() +{ + std::unique_ptr front; + std::unique_ptr back; + float halfLength; + + if (max.x - min.x > MAX_NODE_SIZE) + { + // split along the x axis + halfLength = (min.x + max.x) * 0.5f; + front = std::make_unique(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); + leaf = nullptr; + } + else if (max.z - min.z > MAX_NODE_SIZE) + { + // split along the z axis + halfLength = (min.z + max.z) * 0.5f; + front = std::make_unique(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_Z, halfLength); + leaf = nullptr; + } + else + { + isLeaf = true; + node = nullptr; + leaf = std::make_unique(); + } +} + +void BSPTree::addObjectToTree(std::shared_ptr object) +{ + if (isLeaf) + { + leaf->addObject(std::move(object)); + } + else + { + PlaneSide side = node->objectIsInfront(object.get()); + if (side == SIDE_FRONT) + { + node->front->addObjectToTree(std::move(object)); + } + else if (side == SIDE_BACK) + { + node->back->addObjectToTree(std::move(object)); + } + else // intersects + { + node->front->addObjectToTree(object); + node->back->addObjectToTree(object); + } + } +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h index 52ea1e0c3..7a63478a2 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include "Game/T6/T6.h" using namespace T6; @@ -15,194 +15,61 @@ enum PlaneAxis AXIS_Z }; +enum PlaneSide +{ + SIDE_FRONT, + SIDE_BACK, + SIDE_INTERSECTS +}; + class Object { public: vec3_t min; vec3_t max; - int partitionIndex; // index of the partition the object is contained in - Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) - { - min.x = xMin; - min.y = yMin; - min.z = zMin; - max.x = xMax; - max.y = yMax; - max.z = zMax; - partitionIndex = objPartitionIndex; - } + Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); }; -union u_BSPNode -{ - BSPLeaf* leaf; - BSPNode* node; -}; -class BSPTree; class BSPLeaf { -private: - std::vector> objectList; - public: - void addObject(std::unique_ptr object) - { - objectList.push_back(std::move(object)); - } - - std::unique_ptr getObject(int index) - { - return std::move(objectList.at(index)); - } - - int getObjectCount() - { - return objectList.size(); - } -}; + std::vector> objectList; -enum objectPlaneSide -{ - SIDE_FRONT, - SIDE_BACK, - SIDE_INTERSECTS + void addObject(std::shared_ptr object); + Object* getObject(int index); + int getObjectCount(); }; +class BSPTree; + class BSPNode { public: - BSPTree* front; - BSPTree* back; + std::unique_ptr front; + std::unique_ptr back; PlaneAxis axis; // axis that the split plane is on - double distance; // distance from the origin (0, 0, 0) to the plane - - BSPNode(BSPTree* _front, BSPTree* _back, PlaneAxis _axis, double _distance) - { - front = _front; - back = _back; - axis = _axis; - distance = _distance; - } - - objectPlaneSide objectIsInfront(Object* object) - { - float minCoord, maxCoord; - - // Select the relevant coordinate based on the plane's axis - if (axis == AXIS_X) - { - minCoord = object->min.x; - maxCoord = object->max.x; - } - else if (axis == AXIS_Y) - { - minCoord = object->min.y; - maxCoord = object->max.y; - } - else // axis == AXIS_Z - { - minCoord = object->min.z; - maxCoord = object->max.z; - } - - // Compare with the plane's distance - if (maxCoord < distance) - { - return SIDE_BACK; // Object is entirely on the negative side - } - else if (minCoord > distance) - { - return SIDE_FRONT; // Object is entirely on the positive side - } - else - { - return SIDE_INTERSECTS; - } - } + float distance; // distance from the origin (0, 0, 0) to the plane + + BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); + PlaneSide objectIsInfront(Object* object); }; class BSPTree { public: bool isLeaf; - u_BSPNode u; - - int level; // level in the BSP tree - double low[3]; // mins - double high[3]; // maxs - - BSPTree(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int _level) - { - low[0] = min_x; - low[1] = min_y, low[2] = min_z; - high[0] = max_x; - high[1] = max_y; - high[2] = max_z; - level = _level; - splitTree(); - } - - // For simplicity, only split across the X and Z axis. - // It is unlikely that there are many layers to a map, and is instead mostly flat - void splitTree() - { - BSPTree* front; - BSPTree* back; - double halfLength; - - if (high[0] - low[0] > MAX_NODE_SIZE) - { - // split along the x axis - halfLength = (low[0] + high[0]) * 0.5f; - front = new BSPTree(halfLength, low[1], low[2], high[0], high[1], high[2], level + 1); - back = new BSPTree(low[0], low[1], low[2], halfLength, high[1], high[2], level + 1); - - isLeaf = false; - u.node = new BSPNode(front, back, AXIS_X, halfLength); - } - else if (high[2] - low[2] > MAX_NODE_SIZE) - { - // split along the z axis - halfLength = (low[2] + high[2]) * 0.5f; - front = new BSPTree(low[0], low[1], halfLength, high[0], high[1], high[2], level + 1); - back = new BSPTree(low[0], low[1], low[2], high[0], high[1], halfLength, level + 1); - - isLeaf = false; - u.node = new BSPNode(front, back, AXIS_Z, halfLength); - } - else - { - isLeaf = true; - u.leaf = new BSPLeaf(); - } - } - - void addObject(Object* object) - { - if (isLeaf) - { - u.leaf->addToList(object); - } - else - { - objectPlaneSide side = u.node->objectIsInfront(object); - if (side == SIDE_FRONT) - { - u.node->front->addObject(object); - } - else if (side == SIDE_BACK) - { - u.node->back->addObject(object); - } - else // intersects - { - u.node->front->addObject(object); - u.node->back->addObject(object); - } - } - } + std::unique_ptr leaf; + std::unique_ptr node; + + int level; // level in the BSP tree + vec3_t min; + vec3_t max; + + BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); + void splitTree(); + void addObjectToTree(std::shared_ptr object); }; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 9f0de39a7..4361f8349 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,6 +1,6 @@ #pragma once -#include "BinarySpacePartitionTreePreCalc.h" +#include "BinarySpacePartitionTree.h" #include "CustomMapConsts.h" #include "CustomMapOptions.h" #include "Util.h" @@ -23,7 +23,7 @@ class CustomMapLinker hasLinkFailed = false; } - bool linkCustomMap(customMapInfo* projInfo) + bool linkCustomMap(CustomMapBSP* projInfo) { _ASSERT(projInfo != NULL); @@ -72,27 +72,24 @@ class CustomMapLinker // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour // could fix by removing sub textures or figure out how they are created and redo that // its not an important issue though - bool overwriteDrawData(customMapInfo* projInfo, GfxWorld* gfxWorld) + bool overwriteDrawData(CustomMapBSP* projInfo, GfxWorld* gfxWorld) { - int vertexCount = projInfo->gfxInfo.vertexCount; - customMapVertex* worldVertices = projInfo->gfxInfo.vertices; + int vertexCount = projInfo->gfxWorld.vertices.size(); gfxWorld->draw.vertexCount = vertexCount; gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; for (int i = 0; i < vertexCount; i++) { - customMapVertex* WorldVertex = &worldVertices[i]; + CustomMapVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); //GfxVertex->xyz = WorldVertex->pos; - GfxVertex->binormalSign = WorldVertex->binormalSign; + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); @@ -100,7 +97,11 @@ class CustomMapLinker GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; + // unknown use variables + // binormalSign may be bitangent of the vertex + // lmapCoord may be the lightmap coordinate of the vertex + GfxVertex->binormalSign = 0.0f; + GfxVertex->lmapCoord.packed = 0; } gfxWorld->draw.vd0.data = (char*)vertexBuffer; @@ -110,16 +111,16 @@ class CustomMapLinker gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - int indexCount = projInfo->gfxInfo.indexCount; + int indexCount = projInfo->gfxWorld.indices.size(); _ASSERT(indexCount % 3 == 0); gfxWorld->draw.indexCount = indexCount; gfxWorld->draw.indices = new uint16_t[indexCount]; for (int i = 0; i < indexCount; i += 3) { // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxInfo.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.indices[i + 2]; + gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; } return true; @@ -164,20 +165,20 @@ class CustomMapLinker return material; } - void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) + void overwriteMapSurfaces(CustomMapBSP* projInfo, GfxWorld* gfxWorld) { bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); if (!overwriteResult) return; - unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; + unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); gfxWorld->surfaceCount = surfaceCount; gfxWorld->dpvs.staticSurfaceCount = surfaceCount; gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; for (unsigned int i = 0; i < surfaceCount; i++) { auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxInfo.surfaces[i]; + auto objSurface = &projInfo->gfxWorld.surfaces[i]; currSurface->primaryLightIndex = DEFAULT_SURFACE_LIGHT; currSurface->lightmapIndex = DEFAULT_SURFACE_LIGHTMAP; @@ -185,20 +186,20 @@ class CustomMapLinker currSurface->flags = DEFAULT_SURFACE_FLAGS; currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->firstIndex_Index; + currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; - currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); currSurface->tris.vertexDataOffset1 = 0; std::string surfMaterialName; switch (objSurface->material.materialType) { - case CM_MATERIAL_TEXTURE: + case MATERIAL_TYPE_TEXTURE: surfMaterialName = objSurface->material.materialName; break; - case CM_MATERIAL_COLOUR: - case CM_MATERIAL_EMPTY: + case MATERIAL_TYPE_COLOUR: + case MATERIAL_TYPE_EMPTY: surfMaterialName = colorOnlyImageName; break; @@ -278,78 +279,92 @@ class CustomMapLinker gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; } - void overwriteMapSModels(customMapInfo* projInfo, GfxWorld* gfxWorld) + void overwriteMapSModels(CustomMapBSP* projInfo, GfxWorld* gfxWorld) { - unsigned int modelCount = projInfo->modelCount; + /* + Models are unsupported right now + Code is left in in case it is supported later on + */ + //unsigned int modelCount = projInfo->modelCount; + //gfxWorld->dpvs.smodelCount = modelCount; + //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + // + //for (unsigned int i = 0; i < modelCount; i++) + //{ + // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + // customMapModel* inModel = &projInfo->models[i]; + // + // auto xModelAsset = m_context.LoadDependency(inModel->name); + // if (xModelAsset == NULL) + // { + // printf("XModel %s not found!\n", inModel->name.c_str()); + // currModel->model = NULL; + // } + // else + // currModel->model = (XModel*)xModelAsset->Asset(); + // + // currModel->placement.origin.x = inModel->origin.x; + // currModel->placement.origin.y = inModel->origin.y; + // currModel->placement.origin.z = inModel->origin.z; + // currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.scale = inModel->scale; + // + // CMUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // + // // mins and maxs are calculated in world space not local space + // // TODO: this does not account for model rotation or scale + // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + // + // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + // currModel->flags = DEFAULT_SMODEL_FLAGS; + // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + // + // // unknown use / unused + // currModel->smid = i; + // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + // currModel->invScaleSq = 0.0f; + // currModel->lightingHandle = 0; + // currModel->colorsIndex = 0; + // currModel->visibility = 0; + // + // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // // TODO: calculate lighting and store it here + // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + //} + + unsigned int modelCount = 0; gfxWorld->dpvs.smodelCount = modelCount; gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - - for (unsigned int i = 0; i < modelCount; i++) - { - auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - customMapModel* inModel = &projInfo->models[i]; - - auto xModelAsset = m_context.LoadDependency(inModel->name); - if (xModelAsset == NULL) - { - printf("XModel %s not found!\n", inModel->name.c_str()); - currModel->model = NULL; - } - else - currModel->model = (XModel*)xModelAsset->Asset(); - - currModel->placement.origin.x = inModel->origin.x; - currModel->placement.origin.y = inModel->origin.y; - currModel->placement.origin.z = inModel->origin.z; - currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); - currModel->placement.scale = inModel->scale; - - CMUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - - // mins and maxs are calculated in world space not local space - // TODO: this does not account for model rotation or scale - currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - - currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - currModel->flags = DEFAULT_SMODEL_FLAGS; - currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - - // unknown use / unused - currModel->smid = i; - memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - currModel->invScaleSq = 0.0f; - currModel->lightingHandle = 0; - currModel->colorsIndex = 0; - currModel->visibility = 0; - - // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // TODO: calculate lighting and store it here - currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - } - // all visdata is alligned by 128 - gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; + int allignedModelCount = CMUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; + memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); for (unsigned int i = 0; i < modelCount; i++) { if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) @@ -357,16 +372,14 @@ class CustomMapLinker else gfxWorld->dpvs.smodelCastsShadow[i] = 0; } - memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); - + + // always set to 0 gfxWorld->dpvs.usageCount = 0; } void cleanGfxWorld(GfxWorld* gfxWorld) { + // checksum is generated by the game gfxWorld->checksum = 0; // Remove Coronas @@ -513,22 +526,16 @@ class CustomMapLinker memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); - GfxLightGridRow* row = new GfxLightGridRow[1]; + GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); row->colStart = 0; row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game row->zStart = 0; row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks row->firstEntry = 0; - // this unknown part is weird, bo2 code uses up to unk5 and possibly onwards but the dumped rawRowData looks like it has a different structure. - // this seems to work though - row->unk.unknown1 = 0; - row->unk.unknown2 = 0; - row->unk.unknown3 = 0; - row->unk.unknown4 = 0; - row->unk.unknown5 = 0; - row->unk.unknown6 = 0; - row->unk.unknown7 = 0; - row->unk.unknown8 = 0; + for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 + { + row->lookupTable[i] = 0; + } gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow @@ -785,7 +792,7 @@ class CustomMapLinker gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); } - void overwriteSkyBox(customMapInfo* projInfo, GfxWorld* gfxWorld) + void overwriteSkyBox(CustomMapBSP* projInfo, GfxWorld* gfxWorld) { std::string skyBoxName = "skybox_" + projInfo->name; gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); @@ -871,7 +878,7 @@ class CustomMapLinker gfxWorld->outdoorImage = outdoorImageAsset->Asset(); } - void createGfxWorld(customMapInfo* projInfo) + void createGfxWorld(CustomMapBSP* projInfo) { GfxWorld* gfxWorld = new GfxWorld; gfxWorld->baseName = _strdup(projInfo->name.c_str()); @@ -915,7 +922,7 @@ class CustomMapLinker m_context.AddAsset(gfxWorld->name, gfxWorld); } - void addXModelsToCollision(customMapInfo* projInfo, clipMap_t* clipMap) + void addXModelsToCollision(CustomMapBSP* projInfo, clipMap_t* clipMap) { auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); _ASSERT(gfxWorldAsset != NULL); @@ -992,20 +999,20 @@ class CustomMapLinker { (*numLeafs)++; // there won't be an AABB tree when objectList is empty - if (node->u.leaf->getObjectCount() > 0) + if (node->leaf->getObjectCount() > 0) { - *numAABBTrees += node->u.leaf->getObjectCount() + 1; + *numAABBTrees += node->leaf->getObjectCount() + 1; - if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->u.leaf->getObjectCount(); + if (node->leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->leaf->getObjectCount(); } } else { (*numPlanes)++; (*numNodes)++; - traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->u.node->back, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); } } @@ -1022,14 +1029,14 @@ class CustomMapLinker { _ASSERT(node->isLeaf); - int objectCount = node->u.leaf->getObjectCount(); + int objectCount = node->leaf->getObjectCount(); int firstAABBIndex = currAABBCount; currAABBCount += objectCount + 1; // calculate root AABB node mins and maxs // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->u.leaf->getObject(0)->partitionIndex; + int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; auto firstPartition = &clipMap->partitions[firstPartitionIndex]; uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; vec3_t* firstVert = &clipMap->verts[firstTri[0]]; @@ -1043,7 +1050,7 @@ class CustomMapLinker aabbMaxs.z = firstVert->z; for (int i = 0; i < objectCount; i++) { - int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; auto currPartition = &clipMap->partitions[currPartitionIndex]; for (int k = 0; k < currPartition->triCount; k++) @@ -1067,7 +1074,7 @@ class CustomMapLinker for (int i = 0; i < objectCount; i++) { CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->u.leaf->getObject(i)->partitionIndex; + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; currAabbTree->materialIndex = 0; currAabbTree->childCount = 0; @@ -1126,7 +1133,7 @@ class CustomMapLinker currLeaf->maxs.z = 0.0f; currLeaf->leafBrushNode = 0; - if (node->u.leaf->getObjectCount() > 0) + if (node->leaf->getObjectCount() > 0) { currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); currLeaf->collAabbCount = 1; @@ -1144,14 +1151,14 @@ class CustomMapLinker cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; currPlaneCount++; - if (node->u.node->axis == AXIS_X) + if (node->node->axis == AXIS_X) { // X is unchanged when going from OGL x -> BO2 x currPlane->normal = normalX; // converting OGL -> BO2 X coords doesn't change the x coords at all, so // the dist stays the same - currPlane->dist = (float)node->u.node->distance; + currPlane->dist = (float)node->node->distance; } else { @@ -1163,7 +1170,7 @@ class CustomMapLinker // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = (float)(-node->u.node->distance); + currPlane->dist = (float)(-node->node->distance); } bool foundType = false; @@ -1211,13 +1218,13 @@ class CustomMapLinker // Do the OGL -> Bo2 coord change on paper and it will make sense if (currPlane->type == 1) { - currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->front); - currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); + currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); } else { - currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); - currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); + currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); } return currNodeIndex; @@ -1263,14 +1270,14 @@ class CustomMapLinker _ASSERT(clipMap->aabbTreeCount == currAABBCount); } - void createPartitions(customMapInfo* projInfo, clipMap_t* clipMap) + void createPartitions(CustomMapBSP* projInfo, clipMap_t* clipMap) { - int collisionVertexCount = projInfo->colInfo.vertexCount; + int collisionVertexCount = projInfo->colWorld.vertices.size(); std::vector collisionVertVec; for (int i = 0; i < collisionVertexCount; i++) { - collisionVertVec.push_back(CMUtil::convertToBO2Coords(projInfo->colInfo.vertices[i].pos)); - //collisionVertVec.push_back(projInfo->colInfo.vertices[i].pos); + collisionVertVec.push_back(CMUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); + //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); } clipMap->vertCount = collisionVertexCount; clipMap->verts = new vec3_t[collisionVertexCount]; @@ -1286,21 +1293,21 @@ class CustomMapLinker } std::vector triIndexVec; - for (int i = 0; i < projInfo->colInfo.surfaceCount; i++) + for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) { - worldSurface* currSurface = &projInfo->colInfo.surfaces[i]; + CustomMapSurface* currSurface = &projInfo->colWorld.surfaces[i]; int triCount = currSurface->triCount; for (int k = 0; k < triCount * 3; k += 3) { - int firstIndex_Index = currSurface->firstIndex_Index; - int firstVertexIndex = currSurface->firstVertexIndex; + int firstIndex_Index = currSurface->indexOfFirstIndex; + int firstVertexIndex = currSurface->indexOfFirstVertex; // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire // clipmap verts buffer, so this code updates the indexes to follow that. - int triIndex0 = projInfo->colInfo.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = projInfo->colInfo.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = projInfo->colInfo.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + int triIndex0 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = projInfo->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; // triangle index ordering is opposite to blenders, so its converted here triIndexVec.push_back(triIndex2); @@ -1317,10 +1324,10 @@ class CustomMapLinker // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower std::vector partitionVec; - for (int i = 0; i < projInfo->colInfo.surfaceCount; i++) + for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) { - int triCount = projInfo->colInfo.surfaces[i].triCount; - int firstTriIndex = projInfo->colInfo.surfaces[i].firstIndex_Index / 3; + int triCount = projInfo->colWorld.surfaces[i].triCount; + int firstTriIndex = projInfo->colWorld.surfaces[i].indexOfFirstIndex / 3; for (int k = 0; k < triCount; k++) { CollisionPartition newPartition; @@ -1375,7 +1382,7 @@ class CustomMapLinker memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); } - void createClipMap(customMapInfo* projInfo) + void createClipMap(CustomMapBSP* projInfo) { clipMap_t* clipMap = new clipMap_t; @@ -1605,9 +1612,9 @@ class CustomMapLinker } } - Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - tree->addObject(currObject); + tree->addObjectToTree(std::move(currObject)); } populateBSPTree(clipMap, tree); @@ -1615,7 +1622,7 @@ class CustomMapLinker m_context.AddAsset(clipMap->name, clipMap); } - void createComWorld(customMapInfo* projInfo) + void createComWorld(CustomMapBSP* projInfo) { // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = new ComWorld; @@ -1800,7 +1807,7 @@ class CustomMapLinker } } - void createMapEnts(customMapInfo* projInfo) + void createMapEnts(CustomMapBSP* projInfo) { MapEnts* mapEnts = new MapEnts; @@ -1862,7 +1869,7 @@ class CustomMapLinker m_context.AddAsset(mapEnts->name, mapEnts); } - void createGameWorldMp(customMapInfo* projInfo) + void createGameWorldMp(CustomMapBSP* projInfo) { GameWorldMp* gameWorldMp = new GameWorldMp; @@ -1887,11 +1894,13 @@ class CustomMapLinker m_context.AddAsset(gameWorldMp->name, gameWorldMp); } - void createSkinnedVerts(customMapInfo* projInfo) + void createSkinnedVerts(CustomMapBSP* projInfo) { SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; + skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); + // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // But setting it to the world vertex count seems to work m_context.AddAsset("skinnedverts", skinnedVerts); } @@ -1910,7 +1919,7 @@ class CustomMapLinker return footstepTable; } - void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) + void checkAndAddDefaultRequiredAssets(CustomMapBSP* projectInfo) { auto templateFile = m_search_path.Open("materials/material_template.json"); if (!templateFile.IsOpen()) diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h index 1d23a831d..e8292aba3 100644 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "Game/T6/T6.h" using namespace T6; From b4073d74d9ea19390c1c2427621df0efe07319b9 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Tue, 21 Oct 2025 18:54:26 +0800 Subject: [PATCH 021/123] WIP: - Updated names to allign with the BSP naming structure - Updated constants file and assoicated uses --- src/Common/Game/T6/T6_Assets.h | 17 ++ ...cePartitionTree.cpp => BSPCalculation.cpp} | 12 +- ...ySpacePartitionTree.h => BSPCalculation.h} | 14 +- .../Game/T6/CustomMap/BSPConstants.h | 127 ++++++++++++++ .../Game/T6/CustomMap/BSPCreator.cpp | 1 - src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp | 161 +++++++++++++++++ src/ObjLoading/Game/T6/CustomMap/BSPUtil.h | 22 +++ .../Game/T6/CustomMap/CustomMapConsts.h | 123 ------------- .../Game/T6/CustomMap/CustomMapLinker.h | 137 ++++++++------- src/ObjLoading/Game/T6/CustomMap/Util.h | 166 ------------------ 10 files changed, 411 insertions(+), 369 deletions(-) rename src/ObjLoading/Game/T6/CustomMap/{BinarySpacePartitionTree.cpp => BSPCalculation.cpp} (90%) rename src/ObjLoading/Game/T6/CustomMap/{BinarySpacePartitionTree.h => BSPCalculation.h} (75%) create mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPConstants.h create mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp create mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPUtil.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/Util.h diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index f50c72fc6..0a33bf519 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -3908,6 +3908,17 @@ namespace T6 int baseIndex; }; + enum GfxSurfaceFlags + { + GFX_SURFACE_CASTS_SUN_SHADOW = 0x1, + GFX_SURFACE_HAS_PRIMARY_LIGHT_CONFLICT = 0x2, + GFX_SURFACE_IS_SKY = 0x4, + GFX_SURFACE_NO_DRAW = 0x8, + GFX_SURFACE_CASTS_SHADOW = 0x10, + GFX_SURFACE_QUANTIZED = 0x20, + GFX_SURFACE_NO_COLOR = 0x40 + }; + struct type_align32(16) GfxSurface { srfTriangles_t tris; @@ -3940,6 +3951,12 @@ namespace T6 uint16_t numLmapVertexColors; }; + enum StaticModelFlag + { + STATIC_MODEL_FLAG_NO_SHADOW = 0x1, + STATIC_MODEL_FLAG_LIGHTMAP_VC = 0x2, + }; + struct GfxStaticModelDrawInst { float cullDist; diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp similarity index 90% rename from src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp rename to src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp index 644f86a76..56396ab51 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp @@ -1,7 +1,7 @@ -#include "BinarySpacePartitionTree.h" +#include "BSPCalculation.h" -Object::Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) +BSPObject::BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) { min.x = xMin; min.y = yMin; @@ -12,12 +12,12 @@ Object::Object(float xMin, float yMin, float zMin, float xMax, float yMax, float partitionIndex = objPartitionIndex; } -void BSPLeaf::addObject(std::shared_ptr object) +void BSPLeaf::addObject(std::shared_ptr object) { objectList.emplace_back(std::move(object)); } -Object* BSPLeaf::getObject(int index) +BSPObject* BSPLeaf::getObject(int index) { return objectList.at(index).get(); } @@ -35,7 +35,7 @@ BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr ba distance = nodeDistance; } -PlaneSide BSPNode::objectIsInfront(Object* object) +PlaneSide BSPNode::objectIsInfront(BSPObject* object) { float minCoord, maxCoord; @@ -121,7 +121,7 @@ void BSPTree::splitTree() } } -void BSPTree::addObjectToTree(std::shared_ptr object) +void BSPTree::addObjectToTree(std::shared_ptr object) { if (isLeaf) { diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h similarity index 75% rename from src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h rename to src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h index 7a63478a2..8f15fee73 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTree.h +++ b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h @@ -22,24 +22,24 @@ enum PlaneSide SIDE_INTERSECTS }; -class Object +class BSPObject { public: vec3_t min; vec3_t max; int partitionIndex; // index of the partition the object is contained in - Object(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); + BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); }; class BSPLeaf { public: - std::vector> objectList; + std::vector> objectList; - void addObject(std::shared_ptr object); - Object* getObject(int index); + void addObject(std::shared_ptr object); + BSPObject* getObject(int index); int getObjectCount(); }; @@ -55,7 +55,7 @@ class BSPNode float distance; // distance from the origin (0, 0, 0) to the plane BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); - PlaneSide objectIsInfront(Object* object); + PlaneSide objectIsInfront(BSPObject* object); }; class BSPTree @@ -71,5 +71,5 @@ class BSPTree BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); void splitTree(); - void addObjectToTree(std::shared_ptr object); + void addObjectToTree(std::shared_ptr object); }; diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h b/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h new file mode 100644 index 000000000..959ca376a --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h @@ -0,0 +1,127 @@ +#pragma once + +// BSPGameConstants: +// These values are hardcoded ingame and will break the map if they are changed +namespace BSPGameConstants +{ + constexpr int MAX_COLLISION_VERTS = UINT16_MAX; + + constexpr int STATIC_LIGHT_INDEX = 0; + constexpr int SUN_LIGHT_INDEX = 1; + + inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start" + }; + + inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start" + }; + + inline const char* FFA_SPAWN_POINT_NAMES[] = { + "mp_tdm_spawn", + "mp_dm_spawn", + "mp_dom_spawn" + }; +} + +// BSPLinkingConstants: +// These values are BSP linking constants that are required for the link to be successful +namespace BSPLinkingConstants +{ + constexpr const char* MISSING_IMAGE_NAME = "missing_image"; + constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; + + constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ + "attackers": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "defenders": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "FFA": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ] + })"; + + + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ + "entities": [ + { + "classname": "worldspawn" + }, + { + "angles": "0 0 0", + "classname": "info_player_start", + "origin": "0 0 0" + }, + { + "angles": "0 0 0", + "classname": "mp_global_intermission", + "origin": "0 0 0" + } + ] + })"; +} + +// BSPEditableConstants: +// These values are BSP constants that can be edited and won't break the linker/game if changed +namespace BSPEditableConstants +{ + // Default xmodel values + // Unused as there is no support for xmodels right now + constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; + constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; + constexpr int DEFAULT_SMODEL_LIGHT = 1; + constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; + + // Default surface values + constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; + constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; + constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; + constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); + + // material flags determine the features of a surface + // unsure which flag type changes what right now + // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast + // 1 results in: normal surface features, grenades work, seems normal + constexpr int MATERIAL_SURFACE_FLAGS = 1; + constexpr int MATERIAL_CONTENT_FLAGS = 1; + + // terrain/world flags: does not change the type of terrain or what features they have + // from testing, as long at it isn't 0 things will work correctly + constexpr int LEAF_TERRAIN_CONTENTS = 1; + constexpr int WORLD_TERRAIN_CONTENTS = 1; + + // lightgrid (global) lighting colour + // since lightgrids are not well understood, this colour is used for the R, G and B values right now + constexpr unsigned char LIGHTGRID_COLOUR = 128; +}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp index 8b47fd2a5..64aad2b2c 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp @@ -43,7 +43,6 @@ bool addFBXMeshToWorld(ufbx_node* node, if (mesh->vertex_tangent.exists == false) hasTangentSpace = false; - // Fix the target_unit_meters opt not working // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp new file mode 100644 index 000000000..a6a1b8f43 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include + +#include "BSPUtil.h" + +// BO2 uses a different coordinate system, so this converts it back from OpenGLs default +vec3_t BSPUtil::convertToBO2Coords(vec3_t OGL_coordinate) +{ + vec3_t result; + result.x = OGL_coordinate.x; + result.y = -OGL_coordinate.z; + result.z = OGL_coordinate.y; + return result; +} + +// BO2 uses a weird coordinate system, so this converts it to OpenGLs default +vec3_t BSPUtil::convertFromBO2Coords(vec3_t bo2_coordinate) +{ + vec3_t result; + result.x = bo2_coordinate.x; + result.y = bo2_coordinate.z; + result.z = -bo2_coordinate.y; + return result; +} + +void BSPUtil::calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs) +{ + if (currmins->x > newmins->x) + currmins->x = newmins->x; + + if (newmaxs->x > currmaxs->x) + currmaxs->x = newmaxs->x; + + if (currmins->y > newmins->y) + currmins->y = newmins->y; + + if (newmaxs->y > currmaxs->y) + currmaxs->y = newmaxs->y; + + if (currmins->z > newmins->z) + currmins->z = newmins->z; + + if (newmaxs->z > currmaxs->z) + currmaxs->z = newmaxs->z; +} + +void BSPUtil::calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs) +{ + if (currmins->x > point->x) + currmins->x = point->x; + + if (point->x > currmaxs->x) + currmaxs->x = point->x; + + if (currmins->y > point->y) + currmins->y = point->y; + + if (point->y > currmaxs->y) + currmaxs->y = point->y; + + if (currmins->z > point->z) + currmins->z = point->z; + + if (point->z > currmaxs->z) + currmaxs->z = point->z; +} + +vec3_t BSPUtil::calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) +{ + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + temp.x *= 0.5f; + temp.y *= 0.5f; + temp.z *= 0.5f; + return temp; +} + +int BSPUtil::allignBy128(int size) +{ + return ((size + 127) & 0xFFFFFF80); +} + +float BSPUtil::distBetweenPoints(vec3_t p1, vec3_t p2) +{ + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); +} + +// angles are in euler degrees +void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) +{ + float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f + float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f + float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f + + float cosX = cos(xRadians); + float sinX = sin(xRadians); + float cosY = cos(yRadians); + float sinY = sin(yRadians); + float cosZ = cos(zRadians); + float sinZ = sin(zRadians); + + axis[0].x = cosX * cosY; + axis[0].y = cosX * sinY; + axis[0].z = -sinX; + axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); + axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); + axis[1].z = sinZ * cosX; + axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); + axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); + axis[2].z = cosZ * cosX; +} + +void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) +{ + out[0].x = in[0].x; + out[0].y = in[1].x; + out[0].z = in[2].x; + out[1].x = in[0].y; + out[1].y = in[1].y; + out[1].z = in[2].y; + out[2].x = in[0].z; + out[2].y = in[1].z; + out[2].z = in[2].z; +} + +vec3_t BSPUtil::convertStringToVec3(std::string str) +{ + std::string v1Str = str; + + int nextValIndex = 0; + while (v1Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v2Str = &v1Str[nextValIndex]; + + nextValIndex = 0; + while (v2Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v3Str = &v2Str[nextValIndex]; + + vec3_t result; + result.x = static_cast(atof(v1Str.c_str())); + result.y = static_cast(atof(v2Str.c_str())); + result.z = static_cast(atof(v3Str.c_str())); + return result; +} + +std::string BSPUtil::convertVec3ToString(vec3_t vec) +{ + std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + return result; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h b/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h new file mode 100644 index 000000000..639619864 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "Game/T6/T6.h" +using namespace T6; + +class BSPUtil +{ +public: + static vec3_t convertToBO2Coords(vec3_t OGL_coordinate); + static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate); + static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs); + static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs); + static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs); + static int allignBy128(int size); + static float distBetweenPoints(vec3_t p1, vec3_t p2); + static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); + static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); + static vec3_t convertStringToVec3(std::string str); + static std::string convertVec3ToString(vec3_t vec); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h deleted file mode 100644 index e65152f43..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include - -// do not change any values in this header file - -#define DYN_ENT_COUNT 0 - -std::string missingImageName = "missing_image"; -std::string colorOnlyImageName = "color_only_image"; - - -#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of collision verts a map can have - -#define DEFAULT_LIGHT_INDEX 0 -#define SUN_LIGHT_INDEX 1 - -enum SMODEL_FLAGS -{ - SMODEL_FLAG_NO_SHADOW = 1, - SMODEL_FLAG_IS_LIT = 2 -}; - -enum GFX_SURFACE_FLAGS -{ - GFX_SURFACE_CASTS_SUN_SHADOW = 0x1, - GFX_SURFACE_HAS_PRIMARY_LIGHT_CONFLICT = 0x2, - GFX_SURFACE_IS_SKY = 0x4, - GFX_SURFACE_NO_DRAW = 0x8, - GFX_SURFACE_CASTS_SHADOW = 0x10, - GFX_SURFACE_QUANTIZED = 0x20, - GFX_SURFACE_NO_COLOR = 0x40 -}; - - -#define DEFAULT_SMODEL_CULL_DIST 10000.0f -#define DEFAULT_SMODEL_FLAGS SMODEL_FLAG_NO_SHADOW -#define DEFAULT_SMODEL_LIGHT 1 -#define DEFAULT_SMODEL_REFLECTION_PROBE 0 - -#define DEFAULT_SURFACE_LIGHT SUN_LIGHT_INDEX -#define DEFAULT_SURFACE_LIGHTMAP 0 -#define DEFAULT_SURFACE_REFLECTION_PROBE 0 -#define DEFAULT_SURFACE_FLAGS (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW) - -const std::vector spawnpointDefenderTypeArray = { - "mp_ctf_spawn_allies", - "mp_ctf_spawn_allies_start", - "mp_sd_spawn_defender", - "mp_dom_spawn_allies_start", - "mp_dem_spawn_defender_start", - "mp_dem_spawn_defenderOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_allies_start", - "mp_tdm_spawn_team1_start", - "mp_tdm_spawn_team2_start", - "mp_tdm_spawn_team3_start" -}; - -const std::vector spawnpointAttackerTypeArray = { - "mp_ctf_spawn_axis", - "mp_ctf_spawn_axis_start", - "mp_sd_spawn_attacker", - "mp_dom_spawn_axis_start", - "mp_dem_spawn_attacker_start", - "mp_dem_spawn_attackerOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_axis_start", - "mp_tdm_spawn_team4_start", - "mp_tdm_spawn_team5_start", - "mp_tdm_spawn_team6_start" -}; - -const std::vector spawnpointFFATypeArray = { - "mp_tdm_spawn", - "mp_dm_spawn", - "mp_dom_spawn" -}; - -const std::vector spawnpointObjectiveTypeArray = { - "mp_dom_spawn_flag_c", - "mp_dom_spawn_flag_a" -}; - -const std::string defaultSpawnpointString = "{" - "\"attackers\": [" - " {" - " \"origin\": \"0 0 0\"," - " \"angles\": \"0 0 0\"" - " }" - "]," - "\"defenders\": [" - " {" - " \"origin\": \"0 0 0\"," - " \"angles\": \"0 0 0\"" - " }" - "]," - "\"FFA\": [" - " {" - " \"origin\": \"0 0 0\"," - " \"angles\": \"0 0 0\"" - " }" - "]" - "}"; - - -const std::string defaultMapEntsString = "{" - "\"entities\": [" - "{" - "\"classname\": \"worldspawn\"" - "}," - "{" - "\"angles\": \"0 0 0\"," - "\"classname\": \"info_player_start\"," - "\"origin\": \"0 0 0\"" - "}," - "{" - "\"angles\": \"0 0 0\"," - "\"classname\": \"mp_global_intermission\"," - "\"origin\": \"0 0 0\"" - "}" - "]" -"}"; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 4361f8349..c46af5f7b 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,9 +1,8 @@ #pragma once -#include "BinarySpacePartitionTree.h" -#include "CustomMapConsts.h" -#include "CustomMapOptions.h" -#include "Util.h" +#include "BSPCalculation.h" +#include "BSPConstants.h" +#include "BSPUtil.h" #include "Game/T6/Material/JsonMaterialLoaderT6.h" #include "Utils/Pack.h" @@ -84,17 +83,17 @@ class CustomMapLinker CustomMapVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); + GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); //GfxVertex->xyz = WorldVertex->pos; GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); // unknown use variables @@ -180,10 +179,10 @@ class CustomMapLinker auto currSurface = &gfxWorld->dpvs.surfaces[i]; auto objSurface = &projInfo->gfxWorld.surfaces[i]; - currSurface->primaryLightIndex = DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = DEFAULT_SURFACE_FLAGS; + currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; currSurface->tris.triCount = objSurface->triCount; currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; @@ -200,7 +199,7 @@ class CustomMapLinker case MATERIAL_TYPE_COLOUR: case MATERIAL_TYPE_EMPTY: - surfMaterialName = colorOnlyImageName; + surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; break; default: @@ -209,6 +208,7 @@ class CustomMapLinker Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); if (surfMaterial == NULL) { + std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); surfMaterial = loadImageIntoMaterial(missingImageName); if (surfMaterial == NULL) { @@ -229,7 +229,7 @@ class CustomMapLinker for (int k = 0; k < currSurface->tris.triCount * 3; k++) { uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - CMUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); } // unused values @@ -255,7 +255,7 @@ class CustomMapLinker memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = CMUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; @@ -308,10 +308,10 @@ class CustomMapLinker // currModel->placement.origin.x = inModel->origin.x; // currModel->placement.origin.y = inModel->origin.y; // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); // currModel->placement.scale = inModel->scale; // - // CMUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); // // // mins and maxs are calculated in world space not local space // // TODO: this does not account for model rotation or scale @@ -353,7 +353,7 @@ class CustomMapLinker gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; // all visdata is alligned by 128 - int allignedModelCount = CMUtil::allignBy128(modelCount); + int allignedModelCount = BSPUtil::allignBy128(modelCount); gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; @@ -367,7 +367,7 @@ class CustomMapLinker memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); for (unsigned int i = 0; i < modelCount; i++) { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) gfxWorld->dpvs.smodelCastsShadow[i] = 1; else gfxWorld->dpvs.smodelCastsShadow[i] = 0; @@ -468,7 +468,7 @@ class CustomMapLinker { // there must be 2 or more lights, first is the default light and second is the sun gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX; // the sun is always index 1 + gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) @@ -517,7 +517,7 @@ class CustomMapLinker gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; + gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->lightGrid.offset = 0.0f; // default value // this will make the lookup into rawRowData always return the first row @@ -544,7 +544,7 @@ class CustomMapLinker for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; entryArray[i].visibility = 0; } gfxWorld->lightGrid.entries = entryArray; @@ -552,7 +552,7 @@ class CustomMapLinker // colours are looked up by an entries colourindex gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; - memset(gfxWorld->lightGrid.colors, LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; @@ -586,7 +586,7 @@ class CustomMapLinker // there is only 1 reflection probe gfxWorld->cells[0].reflectionProbeCount = 1; char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; - reflectionProbeIndexes[0] = DEFAULT_SURFACE_REFLECTION_PROBE; + reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; // AABB trees are used to detect what should be rendered and what shouldn't @@ -639,7 +639,7 @@ class CustomMapLinker for (int i = 0; i < gfxWorld->surfaceCount; i++) { - CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); } } @@ -811,7 +811,8 @@ class CustomMapLinker void updateDynEntData(GfxWorld* gfxWorld) { - gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; // the game allocs 256 empty dynents, as they may be used ingame + int dynEntCount = 0; + gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. @@ -952,7 +953,7 @@ class CustomMapLinker currModel->absmax.y = gfxModelInst->maxs.y; currModel->absmax.z = gfxModelInst->maxs.z; - CMUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); + BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; @@ -1060,7 +1061,7 @@ class CustomMapLinker { uint16_t vertIndex = tri[l]; vec3_t vertCoord = clipMap->verts[vertIndex]; - CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); } } } @@ -1097,7 +1098,7 @@ class CustomMapLinker uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; vec3_t* currVertex = &clipMap->verts[currUind]; - CMUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); } aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); @@ -1122,7 +1123,7 @@ class CustomMapLinker currLeaf->cluster = 0; currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence // unused when leafBrushNode == 0 currLeaf->mins.x = 0.0f; @@ -1276,7 +1277,7 @@ class CustomMapLinker std::vector collisionVertVec; for (int i = 0; i < collisionVertexCount; i++) { - collisionVertVec.push_back(CMUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); + collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); } clipMap->vertCount = collisionVertexCount; @@ -1285,9 +1286,9 @@ class CustomMapLinker // due to tris using uint16_t as the type for indexing the vert array, // any vertex count over the uint16_t max means the vertices above it can't be indexed - if (collisionVertexCount > MAX_COL_VERTS) + if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, MAX_COL_VERTS); + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); hasLinkFailed = true; return; } @@ -1478,7 +1479,8 @@ class CustomMapLinker clipMap->info.brushBounds = NULL; clipMap->info.brushContents = NULL; - clipMap->originalDynEntCount = DYN_ENT_COUNT; + int dynEntCount = 0; + clipMap->originalDynEntCount = dynEntCount; clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame clipMap->dynEntCount[1] = 0; clipMap->dynEntCount[2] = 0; @@ -1522,7 +1524,7 @@ class CustomMapLinker cmModel->leaf.firstCollAabbIndex = 0; cmModel->leaf.collAabbCount = 0; cmModel->leaf.brushContents = 0; - cmModel->leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; cmModel->leaf.mins.x = 0.0f; cmModel->leaf.mins.y = 0.0f; cmModel->leaf.mins.z = 0.0f; @@ -1538,16 +1540,16 @@ class CustomMapLinker cmModel->maxs.x = gfxModel->bounds[1].x; cmModel->maxs.y = gfxModel->bounds[1].y; cmModel->maxs.z = gfxModel->bounds[1].z; - cmModel->radius = CMUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; + cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; } addXModelsToCollision(projInfo, clipMap); clipMap->info.numMaterials = 1; clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(missingImageName.c_str()); - clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; + clipMap->info.materials[0].name = _strdup(BSPLinkingConstants::MISSING_IMAGE_NAME); + clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility // might do weird stuff on walls, but from testing doesnt seem to do much @@ -1573,12 +1575,12 @@ class CustomMapLinker clipMaxs.x = firstVert->x; clipMaxs.y = firstVert->y; clipMaxs.z = firstVert->z; - clipMins = CMUtil::convertFromBO2Coords(clipMins); - clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); + clipMins = BSPUtil::convertFromBO2Coords(clipMins); + clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); for (unsigned int i = 1; i < clipMap->vertCount; i++) { - vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); - CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); } BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); @@ -1599,20 +1601,20 @@ class CustomMapLinker maxs.x = firstVert->x; maxs.y = firstVert->y; maxs.z = firstVert->z; - mins = CMUtil::convertFromBO2Coords(mins); - maxs = CMUtil::convertFromBO2Coords(maxs); + mins = BSPUtil::convertFromBO2Coords(mins); + maxs = BSPUtil::convertFromBO2Coords(maxs); for (int k = 0; k < currPartition->triCount; k++) { uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; for (int l = 0; l < 3; l++) { uint16_t vertIndex = tri[l]; - vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); } } - std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); tree->addObjectToTree(std::move(currObject)); } @@ -1680,7 +1682,7 @@ class CustomMapLinker } } - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, std::vector spawnpointTypeArray) + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) { int entityCount = entArrayJs.size(); for (int i = 0; i < entityCount; i++) @@ -1690,12 +1692,12 @@ class CustomMapLinker std::string origin = currEntity["origin"]; std::string angles = currEntity["angles"]; - for (std::string& spawnType : spawnpointTypeArray) + for (int k = 0; k < nameCount; k++) { entityString.append("{\n"); entityString.append(std::format("\"origin\" \"{}\"\n", origin)); entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnType)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); entityString.append("}\n"); } } @@ -1725,7 +1727,7 @@ class CustomMapLinker // add the bomb pickup trigger { std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - vec3_t bomboriginV3 = CMUtil::convertStringToVec3(bombOriginStr); + vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); entModelBounds bounds; bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig bounds.mins.y = bomboriginV3.y - 32.0f; @@ -1748,8 +1750,8 @@ class CustomMapLinker { std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; - vec3_t siteAPoint1V3 = CMUtil::convertStringToVec3(siteAPoint1Str); - vec3_t siteAPoint2V3 = CMUtil::convertStringToVec3(siteAPoint2Str); + vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); + vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); entModelBounds bounds; bounds.mins.x = siteAPoint1V3.x; bounds.mins.y = siteAPoint1V3.y; @@ -1757,12 +1759,12 @@ class CustomMapLinker bounds.maxs.x = siteAPoint1V3.x; bounds.maxs.y = siteAPoint1V3.y; bounds.maxs.z = siteAPoint1V3.z; - CMUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); + BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model entityModelList.push_back(bounds); - vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); entityString.append("{\n"); entityString.append("\"classname\" \"trigger_use_touch\"\n"); @@ -1779,8 +1781,8 @@ class CustomMapLinker { std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; - vec3_t siteBPoint1V3 = CMUtil::convertStringToVec3(siteBPoint1Str); - vec3_t siteBPoint2V3 = CMUtil::convertStringToVec3(siteBPoint2Str); + vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); + vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); entModelBounds bounds; bounds.mins.x = siteBPoint1V3.x; bounds.mins.y = siteBPoint1V3.y; @@ -1788,12 +1790,12 @@ class CustomMapLinker bounds.maxs.x = siteBPoint1V3.x; bounds.maxs.y = siteBPoint1V3.y; bounds.maxs.z = siteBPoint1V3.z; - CMUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); + BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model entityModelList.push_back(bounds); - vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); entityString.append("{\n"); entityString.append("\"classname\" \"trigger_use_touch\"\n"); @@ -1828,7 +1830,7 @@ class CustomMapLinker if (!entFile.IsOpen()) { printf("WARN: can't find entity json custom_map/entities.json, using default entities\n"); - entJs = json::parse(defaultMapEntsString); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); } else { @@ -1841,16 +1843,19 @@ class CustomMapLinker if (!spawnFile.IsOpen()) { printf("WARN: cant find custom_map/spawns.json, setting spawns to 0 0 0\n"); - spawnJs = json::parse(defaultSpawnpointString); + spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); } else { spawnJs = json::parse(*spawnFile.m_stream); } - parseSpawnpointJSON(spawnJs["attackers"], entityString, spawnpointDefenderTypeArray); - parseSpawnpointJSON(spawnJs["defenders"], entityString, spawnpointAttackerTypeArray); - parseSpawnpointJSON(spawnJs["FFA"], entityString, spawnpointFFATypeArray); + int defenderNameCount = std::extent::value; + int attackerNameCount = std::extent::value; + int ffaNameCount = std::extent::value; + parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); + parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); + parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); //const auto objectiveFile = m_search_path.Open("objectives.json"); //if (!spawnFile.IsOpen()) diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h deleted file mode 100644 index e8292aba3..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ /dev/null @@ -1,166 +0,0 @@ -#pragma once - -#include -#include - -#include "Game/T6/T6.h" -using namespace T6; - -class CMUtil -{ -public: - // BO2 uses a different coordinate system, so this converts it back from OpenGLs default - static vec3_t convertToBO2Coords(vec3_t OGL_coordinate) - { - vec3_t result; - result.x = OGL_coordinate.x; - result.y = -OGL_coordinate.z; - result.z = OGL_coordinate.y; - return result; - } - - // BO2 uses a weird coordinate system, so this converts it to OpenGLs default - static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate) - { - vec3_t result; - result.x = bo2_coordinate.x; - result.y = bo2_coordinate.z; - result.z = -bo2_coordinate.y; - return result; - } - - static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs) - { - if (currmins->x > newmins->x) - currmins->x = newmins->x; - - if (newmaxs->x > currmaxs->x) - currmaxs->x = newmaxs->x; - - if (currmins->y > newmins->y) - currmins->y = newmins->y; - - if (newmaxs->y > currmaxs->y) - currmaxs->y = newmaxs->y; - - if (currmins->z > newmins->z) - currmins->z = newmins->z; - - if (newmaxs->z > currmaxs->z) - currmaxs->z = newmaxs->z; - } - - static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs) - { - if (currmins->x > point->x) - currmins->x = point->x; - - if (point->x > currmaxs->x) - currmaxs->x = point->x; - - if (currmins->y > point->y) - currmins->y = point->y; - - if (point->y > currmaxs->y) - currmaxs->y = point->y; - - if (currmins->z > point->z) - currmins->z = point->z; - - if (point->z > currmaxs->z) - currmaxs->z = point->z; - } - - static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) - { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - temp.x *= 0.5f; - temp.y *= 0.5f; - temp.z *= 0.5f; - return temp; - } - - static int allignBy128(int size) - { - return ((size + 127) & 0xFFFFFF80); - } - - static float distBetweenPoints(vec3_t p1, vec3_t p2) - { - float x = p2.x - p1.x; - float y = p2.y - p1.y; - float z = p2.z - p1.z; - return sqrtf((x * x) + (y * y) + (z * z)); - } - - // angles are in euler degrees - static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis) - { - float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f - - float cosX = cos(xRadians); - float sinX = sin(xRadians); - float cosY = cos(yRadians); - float sinY = sin(yRadians); - float cosZ = cos(zRadians); - float sinZ = sin(zRadians); - - axis[0].x = cosX * cosY; - axis[0].y = cosX * sinY; - axis[0].z = -sinX; - axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); - axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); - axis[1].z = sinZ * cosX; - axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); - axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); - axis[2].z = cosZ * cosX; - } - - static void matrixTranspose3x3(const vec3_t* in, vec3_t* out) - { - out[0].x = in[0].x; - out[0].y = in[1].x; - out[0].z = in[2].x; - out[1].x = in[0].y; - out[1].y = in[1].y; - out[1].z = in[2].y; - out[2].x = in[0].z; - out[2].y = in[1].z; - out[2].z = in[2].z; - } - - static vec3_t convertStringToVec3(std::string str) - { - std::string v1Str = str; - - int nextValIndex = 0; - while (v1Str[nextValIndex] != ' ') - nextValIndex++; - nextValIndex++; // skip past space - std::string v2Str = &v1Str[nextValIndex]; - - nextValIndex = 0; - while (v2Str[nextValIndex] != ' ') - nextValIndex++; - nextValIndex++; // skip past space - std::string v3Str = &v2Str[nextValIndex]; - - vec3_t result; - result.x = static_cast(atof(v1Str.c_str())); - result.y = static_cast(atof(v2Str.c_str())); - result.z = static_cast(atof(v3Str.c_str())); - return result; - } - - static std::string convertVec3ToString(vec3_t vec) - { - std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); - return result; - } -}; From e53779517d5d5d18c4b7f5b3c4c8db707c3a461d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 22 Oct 2025 16:06:59 +0800 Subject: [PATCH 022/123] WIP: - Updated file structure to use BSP naming scheme - Re-wrote BSP creator to use c++ more efficiently --- src/ObjCommon/Game/T6/Maps/CustomMaps.h | 53 - src/ObjLoading/Game/T6/BSP/BSP.h | 185 ++ src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp | 151 ++ src/ObjLoading/Game/T6/BSP/BSPCalculation.h | 72 + src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 269 +++ src/ObjLoading/Game/T6/BSP/BSPCreator.h | 9 + .../Game/T6/{CustomMap => BSP}/BSPUtil.cpp | 0 .../Game/T6/{CustomMap => BSP}/BSPUtil.h | 4 +- src/ObjLoading/Game/T6/BSP/CustomMapLinker.h | 1987 +++++++++++++++++ .../LoaderBSP_T6.cpp} | 36 +- .../LoaderBSP_T6.h} | 4 +- .../Game/T6/{CustomMap => BSP}/fbx/ufbx.cpp | 0 .../Game/T6/{CustomMap => BSP}/fbx/ufbx.h | 0 .../Game/T6/CustomMap/BSPCalculation.cpp | 147 -- .../Game/T6/CustomMap/BSPCalculation.h | 75 - .../Game/T6/CustomMap/BSPConstants.h | 127 -- .../Game/T6/CustomMap/BSPCreator.cpp | 301 --- src/ObjLoading/Game/T6/CustomMap/BSPCreator.h | 10 - .../Game/T6/CustomMap/CustomMapLinker.h | 1983 ---------------- .../Game/T6/CustomMap/CustomMapOptions.h | 17 - src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 4 +- 21 files changed, 2693 insertions(+), 2741 deletions(-) delete mode 100644 src/ObjCommon/Game/T6/Maps/CustomMaps.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSP.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCalculation.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCreator.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCreator.h rename src/ObjLoading/Game/T6/{CustomMap => BSP}/BSPUtil.cpp (100%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/BSPUtil.h (92%) create mode 100644 src/ObjLoading/Game/T6/BSP/CustomMapLinker.h rename src/ObjLoading/Game/T6/{CustomMap/LoaderCustomMapT6.cpp => BSP/LoaderBSP_T6.cpp} (55%) rename src/ObjLoading/Game/T6/{CustomMap/LoaderCustomMapT6.h => BSP/LoaderBSP_T6.h} (86%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/fbx/ufbx.cpp (100%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/fbx/ufbx.h (100%) delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPConstants.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCreator.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h diff --git a/src/ObjCommon/Game/T6/Maps/CustomMaps.h b/src/ObjCommon/Game/T6/Maps/CustomMaps.h deleted file mode 100644 index 9ed6c7530..000000000 --- a/src/ObjCommon/Game/T6/Maps/CustomMaps.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include - -#include "Game/T6/T6.h" -using namespace T6; - -struct CustomMapVertex -{ - vec3_t pos; - vec4_t color; - vec2_t texCoord; - vec3_t normal; - vec3_t tangent; -}; - -enum CustomMapMaterialType -{ - MATERIAL_TYPE_COLOUR, - MATERIAL_TYPE_TEXTURE, - MATERIAL_TYPE_EMPTY -}; - -struct CustomMapMaterial -{ - CustomMapMaterialType materialType; - std::string materialName; -}; - -struct CustomMapSurface -{ - CustomMapMaterial material; - int triCount; - int indexOfFirstVertex; - int indexOfFirstIndex; -}; - -struct CustomMapWorld -{ - std::vector surfaces; - std::vector vertices; - std::vector indices; -}; - -struct CustomMapBSP -{ - std::string name; - std::string bspName; - - CustomMapWorld gfxWorld; - CustomMapWorld colWorld; -}; diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h new file mode 100644 index 000000000..d2b1dfb19 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include + +#include "Utils/Logging/Log.h" + +#include "Game/T6/T6.h" +using namespace T6; + +namespace BSP +{ + enum BSPMaterialType + { + MATERIAL_TYPE_COLOUR, + MATERIAL_TYPE_TEXTURE, + MATERIAL_TYPE_EMPTY + }; + + struct BSPVertex + { + vec3_t pos; + vec4_t color; + vec2_t texCoord; + vec3_t normal; + vec3_t tangent; + }; + + struct BSPMaterial + { + BSPMaterialType materialType; + std::string materialName; + }; + + struct BSPSurface + { + BSPMaterial material; + int triCount; + int indexOfFirstVertex; + int indexOfFirstIndex; + }; + + struct BSPWorld + { + std::vector surfaces; + std::vector vertices; + std::vector indices; + }; + + struct BSPData + { + std::string name; + std::string bspName; + + BSPWorld gfxWorld; + BSPWorld colWorld; + }; + + // BSPGameConstants: + // These values are hardcoded ingame and will break the map if they are changed + namespace BSPGameConstants + { + constexpr int MAX_COLLISION_VERTS = UINT16_MAX; + + constexpr int STATIC_LIGHT_INDEX = 0; + constexpr int SUN_LIGHT_INDEX = 1; + + inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start" + }; + + inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start" + }; + + inline const char* FFA_SPAWN_POINT_NAMES[] = { + "mp_tdm_spawn", + "mp_dm_spawn", + "mp_dom_spawn" + }; + } + + // BSPLinkingConstants: + // These values are BSP linking constants that are required for the link to be successful + namespace BSPLinkingConstants + { + constexpr const char* MISSING_IMAGE_NAME = "missing_image"; + constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; + + constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ + "attackers": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "defenders": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "FFA": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ] + })"; + + + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ + "entities": [ + { + "classname": "worldspawn" + }, + { + "angles": "0 0 0", + "classname": "info_player_start", + "origin": "0 0 0" + }, + { + "angles": "0 0 0", + "classname": "mp_global_intermission", + "origin": "0 0 0" + } + ] + })"; + } + + // BSPEditableConstants: + // These values are BSP constants that can be edited and won't break the linker/game if changed + namespace BSPEditableConstants + { + // Default xmodel values + // Unused as there is no support for xmodels right now + constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; + constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; + constexpr int DEFAULT_SMODEL_LIGHT = 1; + constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; + + // Default surface values + constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; + constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; + constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; + constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); + + // material flags determine the features of a surface + // unsure which flag type changes what right now + // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast + // 1 results in: normal surface features, grenades work, seems normal + constexpr int MATERIAL_SURFACE_FLAGS = 1; + constexpr int MATERIAL_CONTENT_FLAGS = 1; + + // terrain/world flags: does not change the type of terrain or what features they have + // from testing, as long at it isn't 0 things will work correctly + constexpr int LEAF_TERRAIN_CONTENTS = 1; + constexpr int WORLD_TERRAIN_CONTENTS = 1; + + // lightgrid (global) lighting colour + // since lightgrids are not well understood, this colour is used for the R, G and B values right now + constexpr unsigned char LIGHTGRID_COLOUR = 128; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp new file mode 100644 index 000000000..60ee89cd1 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp @@ -0,0 +1,151 @@ +#include "BSPCalculation.h" + +namespace BSP +{ + constexpr int MAX_NODE_SIZE = 512; // maximum size a BSP node can be before it becomes a leaf + + BSPObject::BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) + { + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + partitionIndex = objPartitionIndex; + } + + void BSPLeaf::addObject(std::shared_ptr object) + { + objectList.emplace_back(std::move(object)); + } + + BSPObject* BSPLeaf::getObject(int index) + { + return objectList.at(index).get(); + } + + size_t BSPLeaf::getObjectCount() + { + return objectList.size(); + } + + BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance) + { + front = std::move(frontTree); + back = std::move(backTree); + axis = nodeAxis; + distance = nodeDistance; + } + + PlaneSide BSPNode::objectIsInFront(BSPObject* object) + { + float minCoord, maxCoord; + + // Select the relevant coordinate based on the plane's axis + if (axis == AXIS_X) + { + minCoord = object->min.x; + maxCoord = object->max.x; + } + else if (axis == AXIS_Y) + { + minCoord = object->min.y; + maxCoord = object->max.y; + } + else // axis == AXIS_Z + { + minCoord = object->min.z; + maxCoord = object->max.z; + } + + // Compare with the plane's distance + if (maxCoord < distance) + { + return SIDE_BACK; // Object is entirely on the negative side + } + else if (minCoord > distance) + { + return SIDE_FRONT; // Object is entirely on the positive side + } + else + { + return SIDE_INTERSECTS; + } + } + + BSPTree::BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel) + { + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + level = treeLevel; + splitTree(); + } + + // For simplicity, only split across the X and Z axis. + // It is unlikely that there are many layers to a map, and is instead mostly flat + void BSPTree::splitTree() + { + std::unique_ptr front; + std::unique_ptr back; + float halfLength; + + if (max.x - min.x > MAX_NODE_SIZE) + { + // split along the x axis + halfLength = (min.x + max.x) * 0.5f; + front = std::make_unique(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); + leaf = nullptr; + } + else if (max.z - min.z > MAX_NODE_SIZE) + { + // split along the z axis + halfLength = (min.z + max.z) * 0.5f; + front = std::make_unique(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_Z, halfLength); + leaf = nullptr; + } + else + { + isLeaf = true; + node = nullptr; + leaf = std::make_unique(); + } + } + + void BSPTree::addObjectToTree(std::shared_ptr object) + { + if (isLeaf) + { + leaf->addObject(std::move(object)); + } + else + { + PlaneSide side = node->objectIsInFront(object.get()); + if (side == SIDE_FRONT) + { + node->front->addObjectToTree(std::move(object)); + } + else if (side == SIDE_BACK) + { + node->back->addObjectToTree(std::move(object)); + } + else // intersects + { + node->front->addObjectToTree(object); + node->back->addObjectToTree(object); + } + } + } +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h new file mode 100644 index 000000000..61b8de07a --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h @@ -0,0 +1,72 @@ +#pragma once + +#include "BSP.h" + +namespace BSP +{ + enum PlaneAxis + { + AXIS_X, + AXIS_Y, + AXIS_Z + }; + + enum PlaneSide + { + SIDE_FRONT, + SIDE_BACK, + SIDE_INTERSECTS + }; + + class BSPObject + { + public: + vec3_t min; + vec3_t max; + int partitionIndex; // index of the partition the object is contained in + + BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); + }; + + + class BSPLeaf + { + public: + std::vector> objectList; + + void addObject(std::shared_ptr object); + BSPObject* getObject(int index); + size_t getObjectCount(); + }; + + class BSPTree; + + class BSPNode + { + public: + std::unique_ptr front; + std::unique_ptr back; + + PlaneAxis axis; // axis that the split plane is on + float distance; // distance from the origin (0, 0, 0) to the plane + + BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); + PlaneSide objectIsInFront(BSPObject* object); + }; + + class BSPTree + { + public: + bool isLeaf; + std::unique_ptr leaf; + std::unique_ptr node; + + int level; // level in the BSP tree + vec3_t min; + vec3_t max; + + BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); + void splitTree(); + void addObjectToTree(std::shared_ptr object); + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp new file mode 100644 index 000000000..322d940fa --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -0,0 +1,269 @@ +#include "BSPCreator.h" +#include "fbx/ufbx.h" + +namespace +{ + using namespace BSP; + + void addFBXMeshToWorld(ufbx_node* node, + std::vector& surfaceVec, + std::vector& vertexVec, + std::vector& indexVec, + bool& hasTangentSpace) + { + ufbx_mesh* mesh = node->mesh; + + assert(node->attrib_type == UFBX_ELEMENT_MESH); + + if (mesh->instances.count != 1) + con::warn("mesh {} has {} instances, only the 1st instace will be used.", node->name.data, mesh->instances.count); + + if (mesh->num_triangles == 0) + { + con::warn("ignoring mesh {}: triangle count is 0.", node->name.data); + return; + } + + if (mesh->num_indices % 3 != 0) + { + con::warn("ignoring mesh {}: it is not triangulated.", node->name.data); + return; + } + + for (size_t k = 0; k < mesh->num_indices; k++) + { + if (mesh->vertex_indices[k] > UINT16_MAX) + { + con::warn("ignoring mesh {}, it has more than {} indices.", node->name.data, UINT16_MAX); + return; + } + } + + if (mesh->vertex_tangent.exists == false) + hasTangentSpace = false; + + // Fix the target_unit_meters ufbx opt not working + // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back + ufbx_transform origTransform = node->local_transform; + origTransform.translation.x /= 100.0f; + origTransform.translation.y /= 100.0f; + origTransform.translation.z /= 100.0f; + origTransform.scale.x /= 100.0f; + origTransform.scale.y /= 100.0f; + origTransform.scale.z /= 100.0f; + ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); + + for (ufbx_mesh_part& meshPart : mesh->material_parts) + { + if (meshPart.num_faces == 0) + continue; + + surfaceVec.emplace_back(); + BSPSurface* surface = &surfaceVec[surfaceVec.size() - 1]; + + size_t partTriangleNum = meshPart.num_triangles; + surface->triCount = static_cast(partTriangleNum); + surface->indexOfFirstVertex = vertexVec.size(); + surface->indexOfFirstIndex = indexVec.size(); + + if (mesh->materials.count == 0) + { + surface->material.materialType = MATERIAL_TYPE_EMPTY; + surface->material.materialName = ""; + } + else + { + surface->material.materialType = MATERIAL_TYPE_TEXTURE; + surface->material.materialName = mesh->materials.data[meshPart.index]->name.data; + } + + std::vector vertices; + std::vector indices; + indices.resize(mesh->max_face_triangles * 3); + for (uint32_t faceIndex : meshPart.face_indices) + { + ufbx_face* face = &mesh->faces.data[faceIndex]; + + // Triangulate the face into the indices vector + uint32_t triangluatedTriCount = ufbx_triangulate_face(indices.data(), indices.size(), mesh, *face); + + // Iterate over each triangle corner contiguously. + for (uint32_t idxOfIndex = 0; idxOfIndex < triangluatedTriCount * 3; idxOfIndex++) + { + vertices.emplace_back(); + BSPVertex* vertex = &vertices[vertices.size() - 1]; + uint32_t index = indices[idxOfIndex]; + + ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); + vertex->pos.x = static_cast(transformedPos.x); + vertex->pos.y = static_cast(transformedPos.y); + vertex->pos.z = static_cast(transformedPos.z); + + if (surface->material.materialType == MATERIAL_TYPE_TEXTURE || surface->material.materialType == MATERIAL_TYPE_EMPTY) + { + vertex->color.x = 1.0f; + vertex->color.y = 1.0f; + vertex->color.z = 1.0f; + vertex->color.w = 1.0f; + } + else // surface->material.materialType == MATERIAL_TYPE_COLOUR + { + float factor = static_cast(mesh->materials.data[meshPart.index]->fbx.diffuse_factor.value_real); + ufbx_vec4 diffuse = mesh->materials.data[meshPart.index]->fbx.diffuse_color.value_vec4; + vertex->color.x = static_cast(diffuse.x * factor); + vertex->color.y = static_cast(diffuse.y * factor); + vertex->color.z = static_cast(diffuse.z * factor); + vertex->color.w = static_cast(diffuse.w * factor); + } + + // 1.0f - uv.y reason: https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange + ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); + vertex->texCoord.x = static_cast(uv.x); + vertex->texCoord.y = static_cast(1.0f - uv.y); + + ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); + vertex->normal.x = static_cast(normal.x); + vertex->normal.y = static_cast(normal.y); + vertex->normal.z = static_cast(normal.z); + + if (mesh->vertex_tangent.exists) + { + ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); + vertex->tangent.x = static_cast(tangent.x); + vertex->tangent.y = static_cast(tangent.y); + vertex->tangent.z = static_cast(tangent.z); + } + else + { + vertex->tangent.x = 0.0f; + vertex->tangent.y = 0.0f; + vertex->tangent.z = 0.0f; + } + } + } + + // Generate the index buffer. + // ufbx_generate_indices will deduplicate vertices, modifying the arrays passed in streams, + // indices are written to outIndices and the number of unique vertices is returned. + ufbx_vertex_stream streams[1] = { + {vertices.data(), vertices.size(), sizeof(BSPVertex)}, + }; + std::vector outIndices; + outIndices.resize(partTriangleNum * 3); + size_t numGeneratedVertices = ufbx_generate_indices(streams, 1, outIndices.data(), outIndices.size(), nullptr, nullptr); + assert(numGeneratedVertices != 0); + + // trim non-unique vertexes and add to the world vertex vector + vertices.resize(numGeneratedVertices); + vertexVec.insert(vertexVec.end(), vertices.begin(), vertices.end()); + + // T6 uses unsigned shorts as their index type so we have to loop and convert them from an unsigned int + for (size_t idx = 0; idx < outIndices.size(); idx++) + { + indexVec.emplace_back(static_cast(outIndices[idx])); + } + } + } + + void loadWorldData(ufbx_scene* scene, BSPData* bsp, bool isGfxData) + { + bool hasTangentSpace = true; + for (ufbx_node* node : scene->nodes) + { + if (node->attrib_type == UFBX_ELEMENT_MESH) + { + if (isGfxData) + addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); + else + addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); + } + else + { + con::debug("ignoring node type {}: {}", static_cast(node->attrib_type), node->name.data); + } + } + + if (hasTangentSpace == false) + con::warn("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX."); + } +} + +namespace BSP +{ + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) + { + std::string gfxFbxPath = "BSP/map_gfx.fbx"; + auto gfxFile = searchPath.Open(gfxFbxPath); + if (!gfxFile.IsOpen()) + { + con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); + gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); + if (gfxFile.m_stream->gcount() != gfxFile.m_length) + { + con::error("Read error of gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + ufbx_error errorGfx; + ufbx_load_opts optsGfx {}; + optsGfx.target_axes = ufbx_axes_right_handed_y_up; + optsGfx.generate_missing_normals = true; + optsGfx.allow_missing_vertex_position = false; + ufbx_scene* gfxScene = ufbx_load_memory(gfxMapData.get(), static_cast(gfxFile.m_length), &optsGfx, &errorGfx); + if (!gfxScene) + { + con::error("Failed to load map gfx fbx file: {}", errorGfx.description.data); + return nullptr; + } + + ufbx_scene* colScene; + std::string colFbxPath = "BSP/map_col.fbx"; + auto colFile = searchPath.Open(colFbxPath); + if (!colFile.IsOpen()) + { + con::warn("Failed to open map collison fbx file: {}. map gfx will be used for collision instead.", colFbxPath); + colScene = gfxScene; + } + else + { + std::unique_ptr colMapData(new char[static_cast(colFile.m_length)]); + colFile.m_stream->seekg(0); + colFile.m_stream->read(colMapData.get(), colFile.m_length); + if (colFile.m_stream->gcount() != colFile.m_length) + { + con::error("Read error of collision fbx file: {}", colFbxPath); + return nullptr; + } + + ufbx_error errorCol; + ufbx_load_opts optsCol {}; + optsCol.target_axes = ufbx_axes_right_handed_y_up; + optsCol.generate_missing_normals = true; + optsCol.allow_missing_vertex_position = false; + colScene = ufbx_load_memory(colMapData.get(), static_cast(colFile.m_length), &optsCol, &errorCol); + if (!colScene) + { + con::error("Failed to load map collision fbx file: {}", errorCol.description.data); + return nullptr; + } + } + + std::unique_ptr bsp; + + bsp->name = mapName; + bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + + loadWorldData(gfxScene, bsp.get(), true); + loadWorldData(colScene, bsp.get(), false); + + ufbx_free_scene(gfxScene); + if (gfxScene != colScene) + ufbx_free_scene(colScene); + + return bsp; + } +} // namespace BSP \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.h b/src/ObjLoading/Game/T6/BSP/BSPCreator.h new file mode 100644 index 000000000..7a8395da0 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.h @@ -0,0 +1,9 @@ +#pragma once + +#include "BSP.h" +#include "SearchPath/ISearchPath.h" + +namespace BSP +{ + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath); +}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp rename to src/ObjLoading/Game/T6/BSP/BSPUtil.cpp diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h similarity index 92% rename from src/ObjLoading/Game/T6/CustomMap/BSPUtil.h rename to src/ObjLoading/Game/T6/BSP/BSPUtil.h index 639619864..ddbcd58e1 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -1,9 +1,7 @@ #pragma once -#include +#include "BSP.h" -#include "Game/T6/T6.h" -using namespace T6; class BSPUtil { diff --git a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h new file mode 100644 index 000000000..9b11e3480 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h @@ -0,0 +1,1987 @@ +#pragma once + +#include "BSP.h" +#include "BSPCalculation.h" +#include "BSPUtil.h" + +#include "Game/T6/Material/JsonMaterialLoaderT6.h" +#include "Utils/Pack.h" + +#include +using namespace nlohmann; + +namespace BSP +{ + class CustomMapLinker + { + public: + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone), + m_context(context) + { + hasLinkFailed = false; + } + + bool linkCustomMap(BSPData* projInfo) + { + _ASSERT(projInfo != NULL); + + checkAndAddDefaultRequiredAssets(projInfo); + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + + createComWorld(projInfo); + createMapEnts(projInfo); + createGameWorldMp(projInfo); + createSkinnedVerts(projInfo); + createGfxWorld(projInfo); // requires mapents asset + createClipMap(projInfo); // must go last (requires gfx and mapents asset) + + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + return true; + } + + private: + struct entModelBounds + { + vec3_t mins; + vec3_t maxs; + }; + + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + AssetCreationContext& m_context; + + bool hasLinkFailed; + std::vector entityModelList; + + json materialTemplateJson; + + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + bool overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxWorld.vertices.size(); + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); + //GfxVertex->xyz = WorldVertex->pos; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); + //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); + //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); + + // unknown use variables + // binormalSign may be bitangent of the vertex + // lmapCoord may be the lightmap coordinate of the vertex + GfxVertex->binormalSign = 0.0f; + GfxVertex->lmapCoord.packed = 0; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxWorld.indices.size(); + _ASSERT(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = new uint16_t[indexCount]; + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; + } + + return true; + } + + struct s_sortedSurf + { + int surfaceIndex; + int vertexCount; + }; + + bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) + { + return surf0.vertexCount > surf1.vertexCount; + } + + Material* loadImageIntoMaterial(std::string& imageName) + { + std::string imagePath = std::format("images/{}.iwi", imageName); + auto imageFile = m_search_path.Open(imagePath); + if (!imageFile.IsOpen()) + { + printf("WARN: failed to find image %s.\n", imageName.c_str()); + return NULL; + } + + Material* material = new Material; + material->info.name = m_memory.Dup(imageName.c_str()); + + // parse the template file and replace the image name + materialTemplateJson["textures"][1]["image"] = imageName; + + AssetRegistration registration(imageName, material); + if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) + { + printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); + return NULL; + } + + m_context.AddAsset(std::move(registration)); + + return material; + } + + void overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) + { + bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); + if (!overwriteResult) + return; + + unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; + for (unsigned int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxWorld.surfaces[i]; + + currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; + + currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + std::string surfMaterialName; + switch (objSurface->material.materialType) + { + case MATERIAL_TYPE_TEXTURE: + surfMaterialName = objSurface->material.materialName; + break; + + case MATERIAL_TYPE_COLOUR: + case MATERIAL_TYPE_EMPTY: + surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + break; + + default: + _ASSERT(false); + } + Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); + if (surfMaterial == NULL) + { + std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); + surfMaterial = loadImageIntoMaterial(missingImageName); + if (surfMaterial == NULL) + { + printf("Error: unable to find the missing image texture!\n"); + hasLinkFailed = true; + return; + } + } + currSurface->material = surfMaterial; + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; + BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // doesn't seem to matter what order the sorted surfs go in + gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; + for (unsigned int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = i; + } + + gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // all visdata is alligned by 128 + gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; + memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); + + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + } + + void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) + { + /* + Models are unsupported right now + Code is left in in case it is supported later on + */ + //unsigned int modelCount = projInfo->modelCount; + //gfxWorld->dpvs.smodelCount = modelCount; + //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + // + //for (unsigned int i = 0; i < modelCount; i++) + //{ + // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + // customMapModel* inModel = &projInfo->models[i]; + // + // auto xModelAsset = m_context.LoadDependency(inModel->name); + // if (xModelAsset == NULL) + // { + // printf("XModel %s not found!\n", inModel->name.c_str()); + // currModel->model = NULL; + // } + // else + // currModel->model = (XModel*)xModelAsset->Asset(); + // + // currModel->placement.origin.x = inModel->origin.x; + // currModel->placement.origin.y = inModel->origin.y; + // currModel->placement.origin.z = inModel->origin.z; + // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.scale = inModel->scale; + // + // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // + // // mins and maxs are calculated in world space not local space + // // TODO: this does not account for model rotation or scale + // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + // + // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + // currModel->flags = DEFAULT_SMODEL_FLAGS; + // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + // + // // unknown use / unused + // currModel->smid = i; + // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + // currModel->invScaleSq = 0.0f; + // currModel->lightingHandle = 0; + // currModel->colorsIndex = 0; + // currModel->visibility = 0; + // + // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // // TODO: calculate lighting and store it here + // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + //} + + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + + // all visdata is alligned by 128 + int allignedModelCount = BSPUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; + memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + + // always set to 0 + gfxWorld->dpvs.usageCount = 0; + } + + void cleanGfxWorld(GfxWorld* gfxWorld) + { + // checksum is generated by the game + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = new GfxLight; + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + + gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; + memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); + } + + gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; + memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + // the lightgrid is used to light models in a dynamic way and is precomputed + void overwriteLightGrid(GfxWorld* gfxWorld) + { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value + + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); + + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); + GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 + { + row->lookupTable[i] = 0; + } + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + + // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; + memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + + // we use the colours array instead of coeffs array + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = new GfxCell[cellCount]; + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; + reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + + // planes are overwritten by the clipmap loading code ingame + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void overwriteModels(GfxWorld* gfxWorld) + { + // these models are the collision for the entities defined in the mapents asset + // used for triggers and stuff + + gfxWorld->modelCount = entityModelList.size() + 1; + gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; + + // first model is always the world model + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + for (size_t i = 0; i < entityModelList.size(); i++) + { + auto currEntModel = &gfxWorld->models[i + 1]; + entModelBounds currEntModelBounds = entityModelList[i]; + + currEntModel->startSurfIndex = 0; + currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->bounds[0].x = currEntModelBounds.mins.x; + currEntModel->bounds[0].y = currEntModelBounds.mins.y; + currEntModel->bounds[0].z = currEntModelBounds.mins.z; + currEntModel->bounds[1].x = currEntModelBounds.maxs.x; + currEntModel->bounds[1].y = currEntModelBounds.maxs.y; + currEntModel->bounds[1].z = currEntModelBounds.maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + } + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = (float)0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + void updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + } + + void overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + } + + void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) + { + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void updateDynEntData(GfxWorld* gfxWorld) + { + int dynEntCount = 0; + gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + void updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + printf("ERROR! unable to find image $outdoor!\n"); + hasLinkFailed = true; + return; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + } + + void createGfxWorld(BSPData* projInfo) + { + GfxWorld* gfxWorld = new GfxWorld; + gfxWorld->baseName = _strdup(projInfo->name.c_str()); + gfxWorld->name = _strdup(projInfo->bspName.c_str()); + + // Default values taken from mp_dig + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + overwriteMapSurfaces(projInfo, gfxWorld); + + overwriteMapSModels(projInfo, gfxWorld); + + overwriteLightmapData(gfxWorld); + + overwriteSkyBox(projInfo, gfxWorld); + + updateReflectionProbeData(gfxWorld); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + updateOutdoors(gfxWorld); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + m_context.AddAsset(gfxWorld->name, gfxWorld); + } + + void addXModelsToCollision(BSPData* projInfo, clipMap_t* clipMap) + { + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; + clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; + + for (unsigned int i = 0; i < clipMap->numStaticModels; i++) + { + GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; + GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; + cStaticModel_s* currModel = &clipMap->staticModelList[i]; + + memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); + currModel->xmodel = gfxModelDrawInst->model; + currModel->contents = gfxModelDrawInst->model->contents; + currModel->origin.x = gfxModelDrawInst->placement.origin.x; + currModel->origin.y = gfxModelDrawInst->placement.origin.y; + currModel->origin.z = gfxModelDrawInst->placement.origin.z; + + // TODO: this does not account for model rotation or scale + currModel->absmin.x = gfxModelInst->mins.x; + currModel->absmin.y = gfxModelInst->mins.y; + currModel->absmin.z = gfxModelInst->mins.z; + currModel->absmax.x = gfxModelInst->maxs.x; + currModel->absmax.y = gfxModelInst->maxs.y; + currModel->absmax.z = gfxModelInst->maxs.z; + + BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); + currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; + currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; + currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; + currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; + currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; + currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; + currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; + currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; + currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; + } + } + + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + out_origin->x = temp.x * 0.5f; + out_origin->y = temp.y * 0.5f; + out_origin->z = temp.z * 0.5f; + + // Half-size is half the difference: (max - min) / 2 + temp.x = maxs->x - mins->x; + temp.y = maxs->y - mins->y; + temp.z = maxs->z - mins->z; + out_halfSize->x = temp.x * 0.5f; + out_halfSize->y = temp.y * 0.5f; + out_halfSize->z = temp.z * 0.5f; + } + + bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) + { + + unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); + + return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; + } + + void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->leaf->getObjectCount() > 0) + { + *numAABBTrees += node->leaf->getObjectCount() + 1; + + if (node->leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + } + } + + vec3_t normalX = { 1.0f, 0.0f, 0.0f }; + vec3_t normalY = { 0.0f, 1.0f, 0.0f }; + vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; + + int currPlaneCount = 0; + int currNodeCount = 0; + int currLeafCount = 0; + int currAABBCount = 0; + + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + { + _ASSERT(node->isLeaf); + + int objectCount = node->leaf->getObjectCount(); + int firstAABBIndex = currAABBCount; + currAABBCount += objectCount + 1; + + // calculate root AABB node mins and maxs + // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs + // so we have to recompute every min and max, not hard just tedious + int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; + auto firstPartition = &clipMap->partitions[firstPartitionIndex]; + uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t aabbMins; + vec3_t aabbMaxs; + aabbMins.x = firstVert->x; + aabbMins.y = firstVert->y; + aabbMins.z = firstVert->z; + aabbMaxs.x = firstVert->x; + aabbMaxs.y = firstVert->y; + aabbMaxs.z = firstVert->z; + for (int i = 0; i < objectCount; i++) + { + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + auto currPartition = &clipMap->partitions[currPartitionIndex]; + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = clipMap->verts[vertIndex]; + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; + aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); + rootAABB->materialIndex = 0; + rootAABB->childCount = objectCount; + rootAABB->u.firstChildIndex = firstAABBIndex + 1; + + // populate child AABB nodes + for (int i = 0; i < objectCount; i++) + { + CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + + currAabbTree->materialIndex = 0; + currAabbTree->childCount = 0; + currAabbTree->u.partitionIndex = currPartitionIndex; + + // calculate partition origin and half size + CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; + uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; + vec3_t* firstVertex = &clipMap->verts[firstUind]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVertex->x; + mins.y = firstVertex->y; + mins.z = firstVertex->z; + maxs.x = firstVertex->x; + maxs.y = firstVertex->y; + maxs.z = firstVertex->z; + for (int i = 1; i < aabbPartition->nuinds; i++) + { + uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; + vec3_t* currVertex = &clipMap->verts[currUind]; + + BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + } + + aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + } + + return firstAABBIndex; + } + + // returns the index corresponding to the BSPTree* node parsed + int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + { + new cplane_s; + new cNode_t; + new cLeaf_s; + new CollisionAabbTree; + + if (node->isLeaf) + { + int currLeafIndex = currLeafCount; + currLeafCount++; + cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + + currLeaf->cluster = 0; + currLeaf->brushContents = 0; // no brushes used so contents is 0 + currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + + // unused when leafBrushNode == 0 + currLeaf->mins.x = 0.0f; + currLeaf->mins.y = 0.0f; + currLeaf->mins.z = 0.0f; + currLeaf->maxs.x = 0.0f; + currLeaf->maxs.y = 0.0f; + currLeaf->maxs.z = 0.0f; + currLeaf->leafBrushNode = 0; + + if (node->leaf->getObjectCount() > 0) + { + currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); + currLeaf->collAabbCount = 1; + } + else + { + currLeaf->firstCollAabbIndex = 0; + currLeaf->collAabbCount = 0; + } + + return -1 - currLeafIndex; + } + else + { + cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; + currPlaneCount++; + + if (node->node->axis == AXIS_X) + { + // X is unchanged when going from OGL x -> BO2 x + currPlane->normal = normalX; + + // converting OGL -> BO2 X coords doesn't change the x coords at all, so + // the dist stays the same + currPlane->dist = (float)node->node->distance; + } + else + { + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // convert the z normal to the y normal, but don't negate it. Negative normals don't do + // what is expected when the game uses them + _ASSERT(node->u.node->axis == AXIS_Z); + currPlane->normal = normalY; + + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // just negate it here as it is just the distance from the orgin along the axis + currPlane->dist = (float)(-node->node->distance); + } + + bool foundType = false; + if (currPlane->normal.x == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 0; + } + else if (currPlane->normal.y == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 1; + } + else if (currPlane->normal.z == 1.0f) + { + _ASSERT(!foundType); + foundType = true; + currPlane->type = 2; + } + else + _ASSERT(foundType); + + currPlane->signbits = 0; + if (currPlane->normal.x < 0.0f) + currPlane->signbits |= 1; + if (currPlane->normal.y < 0.0f) + currPlane->signbits |= 2; + if (currPlane->normal.z < 0.0f) + currPlane->signbits |= 4; + + currPlane->pad[0] = 0; + currPlane->pad[1] = 0; + + int currNodeIndex = currNodeCount; + currNodeCount++; + cNode_t* currNode = &clipMap->nodes[currNodeIndex]; + + currNode->plane = currPlane; + // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): + // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, + // the normal vector flips and objects behind the plane are now in front, and vise versa + // so the back node now represents the front, and the front node represents the back. + // Do the OGL -> Bo2 coord change on paper and it will make sense + if (currPlane->type == 1) + { + currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); + } + + return currNodeIndex; + } + } + + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + size_t numPlanes = 0; + size_t numNodes = 0; + size_t numLeafs = 0; + size_t numAABBTrees = 0; + size_t maxObjsPerLeaf = 0; + + traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); + + printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); + + clipMap->info.planeCount = numPlanes; + clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; + clipMap->numNodes = numNodes; + clipMap->nodes = new cNode_t[clipMap->numNodes]; + // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. + // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. + clipMap->aabbTreeCount = numAABBTrees; + clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; + memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); + currLeafCount = 1; + + populateBSPTree_r(clipMap, tree); + + _ASSERT(clipMap->info.planeCount == currPlaneCount); + _ASSERT(clipMap->numNodes == currNodeCount); + _ASSERT(clipMap->numLeafs == currLeafCount); + _ASSERT(clipMap->aabbTreeCount == currAABBCount); + } + + void createPartitions(BSPData* projInfo, clipMap_t* clipMap) + { + int collisionVertexCount = projInfo->colWorld.vertices.size(); + std::vector collisionVertVec; + for (int i = 0; i < collisionVertexCount; i++) + { + collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); + //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = new vec3_t[collisionVertexCount]; + memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); + hasLinkFailed = true; + return; + } + + std::vector triIndexVec; + for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) + { + BSPSurface* currSurface = &projInfo->colWorld.surfaces[i]; + int triCount = currSurface->triCount; + + for (int k = 0; k < triCount * 3; k += 3) + { + int firstIndex_Index = currSurface->indexOfFirstIndex; + int firstVertexIndex = currSurface->indexOfFirstVertex; + + // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire + // clipmap verts buffer, so this code updates the indexes to follow that. + int triIndex0 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = projInfo->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + + // triangle index ordering is opposite to blenders, so its converted here + triIndexVec.push_back(triIndex2); + triIndexVec.push_back(triIndex1); + triIndexVec.push_back(triIndex0); + } + } + _ASSERT(triIndexVec.size() % 3 == 0); + clipMap->triCount = triIndexVec.size() / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[triIndexVec.size()]); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + std::vector partitionVec; + for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) + { + int triCount = projInfo->colWorld.surfaces[i].triCount; + int firstTriIndex = projInfo->colWorld.surfaces[i].indexOfFirstIndex / 3; + for (int k = 0; k < triCount; k++) + { + CollisionPartition newPartition; + newPartition.nuinds = 0; // initialised later + newPartition.fuind = 0; // initialised later + newPartition.triCount = 1; + newPartition.firstTri = firstTriIndex; + firstTriIndex += 1; + + partitionVec.push_back(newPartition); + } + } + clipMap->partitionCount = partitionVec.size(); + clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; + memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); + + int totalUindCount = 0; + std::vector uindVec; + for (int i = 0; i < clipMap->partitionCount; i++) + { + CollisionPartition* currPartition = &clipMap->partitions[i]; + std::vector uniqueVertVec; + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (size_t m = 0; m < uniqueVertVec.size(); m++) + { + if (uniqueVertVec[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + uniqueVertVec.push_back(vertIndex); + } + } + + currPartition->fuind = totalUindCount; + currPartition->nuinds = (int)uniqueVertVec.size(); + uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); + totalUindCount += currPartition->nuinds; + } + clipMap->info.nuinds = totalUindCount; + clipMap->info.uinds = new uint16_t[totalUindCount]; + memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); + } + + void createClipMap(BSPData* projInfo) + { + clipMap_t* clipMap = new clipMap_t; + + clipMap->name = _strdup(projInfo->bspName.c_str()); + + std::string mapEntsName = projInfo->bspName.c_str(); + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + _ASSERT(mapEntsAsset != NULL); + clipMap->mapEnts = mapEntsAsset->Asset(); + + clipMap->pInfo = NULL; + + clipMap->box_model.mins.x = 0.0f; + clipMap->box_model.mins.y = 0.0f; + clipMap->box_model.mins.z = 0.0f; + clipMap->box_model.maxs.x = 0.0f; + clipMap->box_model.maxs.y = 0.0f; + clipMap->box_model.maxs.z = 0.0f; + clipMap->box_model.radius = 0.0f; + clipMap->box_model.info = NULL; + + // for some reason the maxs are negative, and mins are positive + // float box_mins = 3.4028235e38; + // float box_maxs = -3.4028235e38; + // hack: the floats above can't be converted to 32 bit floats, and the game requires them to be exact + // so we use the hex representation and set it using int pointers + unsigned int box_mins = 0x7F7FFFFF; + unsigned int box_maxs = 0xFF7FFFFF; + *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; + *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; + *((unsigned int*)&clipMap->box_model.leaf.maxs.z) = box_maxs; + + clipMap->box_model.leaf.brushContents = -1; + clipMap->box_model.leaf.terrainContents = 0; + clipMap->box_model.leaf.cluster = 0; + clipMap->box_model.leaf.collAabbCount = 0; + clipMap->box_model.leaf.firstCollAabbIndex = 0; + clipMap->box_model.leaf.leafBrushNode = 0; + + clipMap->box_brush = new cbrush_t; + clipMap->box_brush->axial_sflags[0][0] = -1; + clipMap->box_brush->axial_sflags[0][1] = -1; + clipMap->box_brush->axial_sflags[0][2] = -1; + clipMap->box_brush->axial_sflags[1][0] = -1; + clipMap->box_brush->axial_sflags[1][1] = -1; + clipMap->box_brush->axial_sflags[1][2] = -1; + clipMap->box_brush->axial_cflags[0][0] = -1; + clipMap->box_brush->axial_cflags[0][1] = -1; + clipMap->box_brush->axial_cflags[0][2] = -1; + clipMap->box_brush->axial_cflags[1][0] = -1; + clipMap->box_brush->axial_cflags[1][1] = -1; + clipMap->box_brush->axial_cflags[1][2] = -1; + clipMap->box_brush->contents = -1; + clipMap->box_brush->mins.x = 0.0f; + clipMap->box_brush->mins.y = 0.0f; + clipMap->box_brush->mins.z = 0.0f; + clipMap->box_brush->maxs.x = 0.0f; + clipMap->box_brush->maxs.y = 0.0f; + clipMap->box_brush->maxs.z = 0.0f; + clipMap->box_brush->numsides = 0; + clipMap->box_brush->numverts = 0; + clipMap->box_brush->sides = NULL; + clipMap->box_brush->verts = NULL; + + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = new char[clipMap->clusterBytes]; + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + + clipMap->isInUse = true; + clipMap->checksum = 0; + + clipMap->num_constraints = 0; + clipMap->constraints = NULL; + clipMap->max_ropes = 32; + clipMap->ropes = new rope_t[clipMap->max_ropes]; + memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); + + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = NULL; + clipMap->info.leafbrushNodesCount = 0; + clipMap->info.leafbrushNodes = NULL; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = NULL; + clipMap->info.numBrushVerts = 0; + clipMap->info.brushVerts = NULL; + clipMap->info.numBrushes = NULL; + clipMap->info.brushes = NULL; + clipMap->info.brushBounds = NULL; + clipMap->info.brushContents = NULL; + + int dynEntCount = 0; + clipMap->originalDynEntCount = dynEntCount; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + clipMap->dynEntCount[1] = 0; + clipMap->dynEntCount[2] = 0; + clipMap->dynEntCount[3] = 0; + + // assume that there are 0 dyn ents from here on + clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; + clipMap->dynEntClientList[1] = NULL; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = NULL; + clipMap->dynEntServerList[1] = NULL; + + clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; + clipMap->dynEntCollList[1] = NULL; + clipMap->dynEntCollList[2] = NULL; + clipMap->dynEntCollList[3] = NULL; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; + clipMap->dynEntPoseList[1] = NULL; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; + clipMap->dynEntDefList[1] = NULL; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + + // cmodels is the collision for mapents + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + clipMap->numSubModels = gfxWorld->modelCount; + clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; + for (unsigned int i = 0; i < clipMap->numSubModels; i++) + { + // bomb triggers use leafs, not world terrain so that might be an issue + + GfxBrushModel* gfxModel = &gfxWorld->models[i]; + cmodel_t* cmModel = &clipMap->cmodels[i]; + + cmModel->leaf.firstCollAabbIndex = 0; + cmModel->leaf.collAabbCount = 0; + cmModel->leaf.brushContents = 0; + cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; + cmModel->leaf.mins.x = 0.0f; + cmModel->leaf.mins.y = 0.0f; + cmModel->leaf.mins.z = 0.0f; + cmModel->leaf.maxs.x = 0.0f; + cmModel->leaf.maxs.y = 0.0f; + cmModel->leaf.maxs.z = 0.0f; + cmModel->leaf.leafBrushNode = 0; + cmModel->leaf.cluster = 0; + cmModel->info = NULL; + cmModel->mins.x = gfxModel->bounds[0].x; + cmModel->mins.y = gfxModel->bounds[0].y; + cmModel->mins.z = gfxModel->bounds[0].z; + cmModel->maxs.x = gfxModel->bounds[1].x; + cmModel->maxs.y = gfxModel->bounds[1].y; + cmModel->maxs.z = gfxModel->bounds[1].z; + cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; + } + + addXModelsToCollision(projInfo, clipMap); + + clipMap->info.numMaterials = 1; + clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; + clipMap->info.materials[0].name = _strdup(BSPLinkingConstants::MISSING_IMAGE_NAME); + clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + + // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility + // might do weird stuff on walls, but from testing doesnt seem to do much + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + + // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, + // and then convert them back when it is being parsed into the clipmap. Requires some hacky + // logic, check populateBSPTree_r and addAABBTreeFromLeaf + + createPartitions(projInfo, clipMap); + + vec3_t* firstVert = &clipMap->verts[0]; + vec3_t clipMins; + vec3_t clipMaxs; + clipMins.x = firstVert->x; + clipMins.y = firstVert->y; + clipMins.z = firstVert->z; + clipMaxs.x = firstVert->x; + clipMaxs.y = firstVert->y; + clipMaxs.z = firstVert->z; + clipMins = BSPUtil::convertFromBO2Coords(clipMins); + clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); + for (unsigned int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + + _ASSERT(!tree->isLeaf); + + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVert->x; + mins.y = firstVert->y; + mins.z = firstVert->z; + maxs.x = firstVert->x; + maxs.y = firstVert->y; + maxs.z = firstVert->z; + mins = BSPUtil::convertFromBO2Coords(mins); + maxs = BSPUtil::convertFromBO2Coords(maxs); + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObjectToTree(std::move(currObject)); + } + + populateBSPTree(clipMap, tree); + + m_context.AddAsset(clipMap->name, clipMap); + } + + void createComWorld(BSPData* projInfo) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = new ComWorld; + comWorld->name = _strdup(projInfo->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; + + // default light is always empty + ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; + memset(defaultLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + sunLight->diffuseColor.r = 0.75f; + sunLight->diffuseColor.g = 0.75f; + sunLight->diffuseColor.b = 0.75f; + sunLight->diffuseColor.a = 1.0f; + sunLight->dir.x = 0.0f; + sunLight->dir.y = 0.0f; + sunLight->dir.z = 0.0f; + + m_context.AddAsset(comWorld->name, comWorld); + } + + void parseMapEntsJSON(json& entArrayJs, std::string& entityString) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + if (i == 0) + { + std::string className = currEntity["classname"]; + if (className.compare("worldspawn") != 0) + { + printf("ERROR: first entity in the map entity string must be the worldspawn class!"); + hasLinkFailed = true; + return; + } + } + + entityString.append("{\n"); + + for (auto& element : currEntity.items()) + { + std::string key = element.key(); + std::string value = element.value(); + entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); + } + + entityString.append("}\n"); + } + } + + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + std::string origin = currEntity["origin"]; + std::string angles = currEntity["angles"]; + + for (int k = 0; k < nameCount; k++) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", origin)); + entityString.append(std::format("\"angles\" \"{}\"\n", angles)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); + entityString.append("}\n"); + } + } + } + + void parseBombJSON(json& bombJs, std::string& entityString) + { + // add the bomb model + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + entityString.append("{\n"); + entityString.append("\"classname\" \"script_model\"\n"); + entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); + entityString.append("\"targetname\" \"sd_bomb\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append("\"spawnflags\" \"4\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append("}\n"); + } + if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) + { + hasLinkFailed = true; + printf("ERROR: unable to find s&d bomb xmodel\n"); + return; + } + + // add the bomb pickup trigger + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); + entModelBounds bounds; + bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig + bounds.mins.y = bomboriginV3.y - 32.0f; + bounds.mins.z = bomboriginV3.z - 8.0f; + bounds.maxs.x = bomboriginV3.x + 32.0f; + bounds.maxs.y = bomboriginV3.y + 32.0f; + bounds.maxs.z = bomboriginV3.z + 28.0f; + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_multiple\"\n"); + entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add A site bomb + { + std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; + std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; + vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); + vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteAPoint1V3.x; + bounds.mins.y = siteAPoint1V3.y; + bounds.mins.z = siteAPoint1V3.z; + bounds.maxs.x = siteAPoint1V3.x; + bounds.maxs.y = siteAPoint1V3.y; + bounds.maxs.z = siteAPoint1V3.z; + BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_a\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add B site bomb + { + std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; + std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; + vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); + vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteBPoint1V3.x; + bounds.mins.y = siteBPoint1V3.y; + bounds.mins.z = siteBPoint1V3.z; + bounds.maxs.x = siteBPoint1V3.x; + bounds.maxs.y = siteBPoint1V3.y; + bounds.maxs.z = siteBPoint1V3.z; + BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_b\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + } + + void createMapEnts(BSPData* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + std::string entityString; + + const auto entFile = m_search_path.Open("BSP/entities.json"); + json entJs; + if (!entFile.IsOpen()) + { + printf("WARN: can't find entity json BSP/entities.json, using default entities\n"); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + } + else + { + entJs = json::parse(*entFile.m_stream); + } + parseMapEntsJSON(entJs["entities"], entityString); + + const auto spawnFile = m_search_path.Open("BSP/spawns.json"); + json spawnJs; + if (!spawnFile.IsOpen()) + { + printf("WARN: cant find BSP/spawns.json, setting spawns to 0 0 0\n"); + spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + + int defenderNameCount = std::extent::value; + int attackerNameCount = std::extent::value; + int ffaNameCount = std::extent::value; + parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); + parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); + parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + + //const auto objectiveFile = m_search_path.Open("objectives.json"); + //if (!spawnFile.IsOpen()) + //{ + // printf("WARN: no objectives given\n"); + //} + //else + //{ + // json objectiveJs = json::parse(*objectiveFile.m_stream); + // parseBombJSON(objectiveJs, entityString); + //} + + mapEnts->entityString = _strdup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + m_context.AddAsset(mapEnts->name, mapEnts); + } + + void createGameWorldMp(BSPData* projInfo) + { + GameWorldMp* gameWorldMp = new GameWorldMp; + + gameWorldMp->name = _strdup(projInfo->bspName.c_str()); + + gameWorldMp->path.nodeCount = 0; + gameWorldMp->path.originalNodeCount = 0; + gameWorldMp->path.visBytes = 0; + gameWorldMp->path.smoothBytes = 0; + gameWorldMp->path.nodeTreeCount = 0; + + int nodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = new pathnode_t[nodeCount]; + gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = NULL; + gameWorldMp->path.smoothCache = NULL; + gameWorldMp->path.nodeTree = NULL; + + m_context.AddAsset(gameWorldMp->name, gameWorldMp); + } + + void createSkinnedVerts(BSPData* projInfo) + { + SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; + skinnedVerts->name = "skinnedverts"; + skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); + // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // But setting it to the world vertex count seems to work + + m_context.AddAsset("skinnedverts", skinnedVerts); + } + + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return NULL; + + FootstepTableDef* footstepTable = new FootstepTableDef; + footstepTable->name = _strdup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + void checkAndAddDefaultRequiredAssets(BSPData* projectInfo) + { + auto templateFile = m_search_path.Open("materials/material_template.json"); + if (!templateFile.IsOpen()) + { + printf("ERROR: failed to open materials/material_template.json\n"); + hasLinkFailed = true; + return; + } + materialTemplateJson = json::parse(*templateFile.m_stream); + + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) + { + hasLinkFailed = true; + return; + } + + addEmptyFootstepTableAsset("default_1st_person"); + addEmptyFootstepTableAsset("default_3rd_person"); + addEmptyFootstepTableAsset("default_1st_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_loud"); + addEmptyFootstepTableAsset("default_ai"); + + if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) + { + hasLinkFailed = true; + return; + } + } + }; + +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp similarity index 55% rename from src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp rename to src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 58e4bd55c..708602f19 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,21 +1,15 @@ -#include "Game/T6/Maps/CustomMaps.h" - -#include "LoaderCustomMapT6.h" +#include "LoaderBSP_T6.h" #include "BSPCreator.h" #include "CustomMapLinker.h" -#include "Game/T6/T6.h" - -#include - -using namespace T6; - namespace { - class CustomMapLoader final : public AssetCreator + using namespace BSP; + + class BSPLoader final : public AssetCreator { public: - CustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) : m_memory(memory), m_search_path(searchPath), m_zone(zone) @@ -25,21 +19,21 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { // custom maps must have a map_gfx file - auto mapGfxFile = m_search_path.Open("custom_map/map_gfx.fbx"); + auto mapGfxFile = m_search_path.Open("BSP/map_gfx.fbx"); if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); - CustomMapBSP* mapBSP = BSPCreator::createCustomMapBSP(m_zone.m_name, m_search_path); - if (mapBSP == NULL) + BSPData* BSP = BSP::createBSPData(m_zone.m_name, m_search_path); + if (BSP == nullptr) return AssetCreationResult::Failure(); - CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); - bool result = linker->linkCustomMap(mapBSP); + CustomMapLinker linker(m_memory, m_search_path, m_zone, context); + bool result = linker.linkCustomMap(BSP); if (result) { - auto gfxWorldAsset = context.LoadDependency(mapBSP->bspName); - _ASSERT(gfxWorldAsset != NULL); + auto gfxWorldAsset = context.LoadDependency(BSP->bspName); + _ASSERT(gfxWorldAsset != nullptr); return AssetCreationResult::Success(gfxWorldAsset); } else @@ -53,10 +47,10 @@ namespace }; } // namespace -namespace custom_map +namespace BSP { std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) { - return std::make_unique(memory, searchPath, zone); + return std::make_unique(memory, searchPath, zone); } -} // namespace custom_map +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h similarity index 86% rename from src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h rename to src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h index 5cf0e007f..4cacf2f28 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h @@ -7,7 +7,7 @@ #include -namespace custom_map +namespace BSP { std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); -} // namespace custom_map +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp rename to src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.h similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h rename to src/ObjLoading/Game/T6/BSP/fbx/ufbx.h diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp deleted file mode 100644 index 56396ab51..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "BSPCalculation.h" - - -BSPObject::BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) -{ - min.x = xMin; - min.y = yMin; - min.z = zMin; - max.x = xMax; - max.y = yMax; - max.z = zMax; - partitionIndex = objPartitionIndex; -} - -void BSPLeaf::addObject(std::shared_ptr object) -{ - objectList.emplace_back(std::move(object)); -} - -BSPObject* BSPLeaf::getObject(int index) -{ - return objectList.at(index).get(); -} - -int BSPLeaf::getObjectCount() -{ - return objectList.size(); -} - -BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance) -{ - front = std::move(frontTree); - back = std::move(backTree); - axis = nodeAxis; - distance = nodeDistance; -} - -PlaneSide BSPNode::objectIsInfront(BSPObject* object) -{ - float minCoord, maxCoord; - - // Select the relevant coordinate based on the plane's axis - if (axis == AXIS_X) - { - minCoord = object->min.x; - maxCoord = object->max.x; - } - else if (axis == AXIS_Y) - { - minCoord = object->min.y; - maxCoord = object->max.y; - } - else // axis == AXIS_Z - { - minCoord = object->min.z; - maxCoord = object->max.z; - } - - // Compare with the plane's distance - if (maxCoord < distance) - { - return SIDE_BACK; // Object is entirely on the negative side - } - else if (minCoord > distance) - { - return SIDE_FRONT; // Object is entirely on the positive side - } - else - { - return SIDE_INTERSECTS; - } -} - -BSPTree::BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel) -{ - min.x = xMin; - min.y = yMin; - min.z = zMin; - max.x = xMax; - max.y = yMax; - max.z = zMax; - level = treeLevel; - splitTree(); -} - -// For simplicity, only split across the X and Z axis. -// It is unlikely that there are many layers to a map, and is instead mostly flat -void BSPTree::splitTree() -{ - std::unique_ptr front; - std::unique_ptr back; - float halfLength; - - if (max.x - min.x > MAX_NODE_SIZE) - { - // split along the x axis - halfLength = (min.x + max.x) * 0.5f; - front = std::make_unique(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1); - back = std::make_unique(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1); - - isLeaf = false; - node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); - leaf = nullptr; - } - else if (max.z - min.z > MAX_NODE_SIZE) - { - // split along the z axis - halfLength = (min.z + max.z) * 0.5f; - front = std::make_unique(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1); - back = std::make_unique(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1); - - isLeaf = false; - node = std::make_unique(std::move(front), std::move(back), AXIS_Z, halfLength); - leaf = nullptr; - } - else - { - isLeaf = true; - node = nullptr; - leaf = std::make_unique(); - } -} - -void BSPTree::addObjectToTree(std::shared_ptr object) -{ - if (isLeaf) - { - leaf->addObject(std::move(object)); - } - else - { - PlaneSide side = node->objectIsInfront(object.get()); - if (side == SIDE_FRONT) - { - node->front->addObjectToTree(std::move(object)); - } - else if (side == SIDE_BACK) - { - node->back->addObjectToTree(std::move(object)); - } - else // intersects - { - node->front->addObjectToTree(object); - node->back->addObjectToTree(object); - } - } -} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h deleted file mode 100644 index 8f15fee73..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include - -#include "Game/T6/T6.h" -using namespace T6; - -constexpr int MAX_NODE_SIZE = 512; // maximum size a BSP node can be before it becomes a leaf - -enum PlaneAxis -{ - AXIS_X, - AXIS_Y, - AXIS_Z -}; - -enum PlaneSide -{ - SIDE_FRONT, - SIDE_BACK, - SIDE_INTERSECTS -}; - -class BSPObject -{ -public: - vec3_t min; - vec3_t max; - int partitionIndex; // index of the partition the object is contained in - - BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); -}; - - -class BSPLeaf -{ -public: - std::vector> objectList; - - void addObject(std::shared_ptr object); - BSPObject* getObject(int index); - int getObjectCount(); -}; - -class BSPTree; - -class BSPNode -{ -public: - std::unique_ptr front; - std::unique_ptr back; - - PlaneAxis axis; // axis that the split plane is on - float distance; // distance from the origin (0, 0, 0) to the plane - - BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); - PlaneSide objectIsInfront(BSPObject* object); -}; - -class BSPTree -{ -public: - bool isLeaf; - std::unique_ptr leaf; - std::unique_ptr node; - - int level; // level in the BSP tree - vec3_t min; - vec3_t max; - - BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); - void splitTree(); - void addObjectToTree(std::shared_ptr object); -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h b/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h deleted file mode 100644 index 959ca376a..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -// BSPGameConstants: -// These values are hardcoded ingame and will break the map if they are changed -namespace BSPGameConstants -{ - constexpr int MAX_COLLISION_VERTS = UINT16_MAX; - - constexpr int STATIC_LIGHT_INDEX = 0; - constexpr int SUN_LIGHT_INDEX = 1; - - inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_allies", - "mp_ctf_spawn_allies_start", - "mp_sd_spawn_defender", - "mp_dom_spawn_allies_start", - "mp_dem_spawn_defender_start", - "mp_dem_spawn_defenderOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_allies_start", - "mp_tdm_spawn_team1_start", - "mp_tdm_spawn_team2_start", - "mp_tdm_spawn_team3_start" - }; - - inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_axis", - "mp_ctf_spawn_axis_start", - "mp_sd_spawn_attacker", - "mp_dom_spawn_axis_start", - "mp_dem_spawn_attacker_start", - "mp_dem_spawn_attackerOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_axis_start", - "mp_tdm_spawn_team4_start", - "mp_tdm_spawn_team5_start", - "mp_tdm_spawn_team6_start" - }; - - inline const char* FFA_SPAWN_POINT_NAMES[] = { - "mp_tdm_spawn", - "mp_dm_spawn", - "mp_dom_spawn" - }; -} - -// BSPLinkingConstants: -// These values are BSP linking constants that are required for the link to be successful -namespace BSPLinkingConstants -{ - constexpr const char* MISSING_IMAGE_NAME = "missing_image"; - constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; - - constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ - "attackers": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "defenders": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "FFA": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ] - })"; - - - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ - "entities": [ - { - "classname": "worldspawn" - }, - { - "angles": "0 0 0", - "classname": "info_player_start", - "origin": "0 0 0" - }, - { - "angles": "0 0 0", - "classname": "mp_global_intermission", - "origin": "0 0 0" - } - ] - })"; -} - -// BSPEditableConstants: -// These values are BSP constants that can be edited and won't break the linker/game if changed -namespace BSPEditableConstants -{ - // Default xmodel values - // Unused as there is no support for xmodels right now - constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; - constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; - constexpr int DEFAULT_SMODEL_LIGHT = 1; - constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; - - // Default surface values - constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; - constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; - constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; - constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); - - // material flags determine the features of a surface - // unsure which flag type changes what right now - // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast - // 1 results in: normal surface features, grenades work, seems normal - constexpr int MATERIAL_SURFACE_FLAGS = 1; - constexpr int MATERIAL_CONTENT_FLAGS = 1; - - // terrain/world flags: does not change the type of terrain or what features they have - // from testing, as long at it isn't 0 things will work correctly - constexpr int LEAF_TERRAIN_CONTENTS = 1; - constexpr int WORLD_TERRAIN_CONTENTS = 1; - - // lightgrid (global) lighting colour - // since lightgrids are not well understood, this colour is used for the R, G and B values right now - constexpr unsigned char LIGHTGRID_COLOUR = 128; -}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp deleted file mode 100644 index 64aad2b2c..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "BSPCreator.h" - -#include "fbx/ufbx.h" -#include "Game/T6/T6.h" -using namespace T6; - -bool addFBXMeshToWorld(ufbx_node* node, - std::vector& surfaceVec, - std::vector& vertexVec, - std::vector& indexVec, - bool& hasTangentSpace) -{ - if (node->attrib_type != UFBX_ELEMENT_MESH) - { - printf("ignoring non-mesh node \"%s\"\n", node->name.data); - return false; - } - ufbx_mesh* mesh = node->mesh; - - if (mesh->instances.count != 1) - printf("mesh %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); - - if (mesh->num_triangles == 0) - { - printf("ignoring mesh %s: triangle count is 0.\n", node->name.data); - return false; - } - - if (mesh->num_indices % 3 != 0) - { - printf("ignoring mesh %s: it is not triangulated.\n", node->name.data); - return false; - } - - for (size_t k = 0; k < mesh->num_indices; k++) - { - if (mesh->vertex_indices[k] > UINT16_MAX) - { - printf("ignoring mesh %s, it has more than %i indices.\n", node->name.data, UINT16_MAX); - return false; - } - } - - if (mesh->vertex_tangent.exists == false) - hasTangentSpace = false; - - // Fix the target_unit_meters opt not working - // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back - ufbx_transform origTransform = node->local_transform; - origTransform.translation.x /= 100.0f; - origTransform.translation.y /= 100.0f; - origTransform.translation.z /= 100.0f; - origTransform.scale.x /= 100.0f; - origTransform.scale.y /= 100.0f; - origTransform.scale.z /= 100.0f; - ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - - // FBX loading code modified from https://ufbx.github.io/elements/meshes/ - // Seems like I have to use this exact way of loading, otherwise some values become incorrect - for (size_t i = 0; i < mesh->material_parts.count; i++) - { - ufbx_mesh_part* meshPart = &mesh->material_parts.data[i]; - - if (meshPart->num_faces == 0) - continue; - - CustomMapSurface surface; - surface.triCount = meshPart->num_triangles; - surface.indexOfFirstVertex = vertexVec.size(); - surface.indexOfFirstIndex = indexVec.size(); - - CustomMapMaterialType meshMaterialType; - if (mesh->materials.count == 0) - { - meshMaterialType = MATERIAL_TYPE_EMPTY; - } - //else if (mesh->materials.data[i]->textures.count != 0) - //{ - // meshMaterialType = CM_MATERIAL_TEXTURE; - // surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - //} - //else - //{ - // meshMaterialType = CM_MATERIAL_COLOUR; - // surface.material.materialName = ""; - //} - else - { - meshMaterialType = MATERIAL_TYPE_TEXTURE; - surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - } - surface.material.materialType = meshMaterialType; - - size_t num_triangles = meshPart->num_triangles; - CustomMapVertex* vertices = (CustomMapVertex*)calloc(num_triangles * 3, sizeof(CustomMapVertex)); - size_t num_vertices = 0; - - // Reserve space for the maximum triangle indices. - size_t num_tri_indices = mesh->max_face_triangles * 3; - uint32_t* tri_indices = (uint32_t*)calloc(num_tri_indices, sizeof(uint32_t)); - - _ASSERT(meshPart->num_triangles == meshPart->num_faces); - for (size_t face_ix = 0; face_ix < meshPart->num_faces; face_ix++) - { - ufbx_face face = mesh->faces.data[meshPart->face_indices.data[face_ix]]; - - // Triangulate the face into `tri_indices[]`. - uint32_t num_tris = ufbx_triangulate_face(tri_indices, num_tri_indices, mesh, face); - - // Iterate over each triangle corner contiguously. - for (size_t q = 0; q < num_tris * 3; q++) - { - uint32_t index = tri_indices[q]; - - CustomMapVertex* vertex = &vertices[num_vertices++]; - - //ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, index); - //vertex->pos.x = static_cast(pos.x); - //vertex->pos.y = static_cast(pos.y); - //vertex->pos.z = static_cast(pos.z); - // Fix the target_unit_meters opt not working - ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex->pos.x = static_cast(transformedPos.x); - vertex->pos.y = static_cast(transformedPos.y); - vertex->pos.z = static_cast(transformedPos.z); - - - switch (meshMaterialType) - { - case MATERIAL_TYPE_TEXTURE: - case MATERIAL_TYPE_EMPTY: - vertex->color.x = 1.0f; - vertex->color.y = 1.0f; - vertex->color.z = 1.0f; - vertex->color.w = 1.0f; - break; - case MATERIAL_TYPE_COLOUR: - { - float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); - ufbx_vec4 diffuse = mesh->materials.data[i]->fbx.diffuse_color.value_vec4; - vertex->color.x = static_cast(diffuse.x * factor); - vertex->color.y = static_cast(diffuse.y * factor); - vertex->color.z = static_cast(diffuse.z * factor); - vertex->color.w = static_cast(diffuse.w * factor); - break; - } - - default: - _ASSERT(false); - } - - - - // 1.0f - uv.y reason: - // https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange - ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); - vertex->texCoord.x = (float)(uv.x); - vertex->texCoord.y = (float)(1.0f - uv.y); - - ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); - vertex->normal.x = static_cast(normal.x); - vertex->normal.y = static_cast(normal.y); - vertex->normal.z = static_cast(normal.z); - - if (mesh->vertex_tangent.exists) - { - ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); - vertex->tangent.x = static_cast(tangent.x); - vertex->tangent.y = static_cast(tangent.y); - vertex->tangent.z = static_cast(tangent.z); - } - else - { - vertex->tangent.x = 0.0f; - vertex->tangent.y = 0.0f; - vertex->tangent.z = 0.0f; - } - } - } - - _ASSERT(num_vertices == num_triangles * 3); - - // Generate the index buffer. - ufbx_vertex_stream streams[1] = { - {vertices, num_vertices, sizeof(CustomMapVertex)}, - }; - size_t num_indices = num_triangles * 3; - uint32_t* indices = (uint32_t*)calloc(num_indices, sizeof(uint32_t)); - - // This call will deduplicate vertices, modifying the arrays passed in `streams[]`, - // indices are written in `indices[]` and the number of unique vertices is returned. - num_vertices = ufbx_generate_indices(streams, 1, indices, num_indices, NULL, NULL); - _ASSERT(num_vertices != 0); - - vertexVec.insert(vertexVec.end(), &vertices[0], &vertices[num_vertices]); - - size_t currIndexVecSize = indexVec.size(); - indexVec.resize(indexVec.size() + num_indices); - for (size_t m = 0; m < num_indices; m++) - { - indexVec[currIndexVecSize + m] = (uint16_t)indices[m]; - } - - surfaceVec.push_back(surface); - } - - return true; -} - -void loadWorldData(ufbx_scene* scene, CustomMapBSP* bsp, bool isGfxData) -{ - bool hasTangentSpace = true; - - for (size_t i = 0; i < scene->nodes.count; i++) - { - ufbx_node* node = scene->nodes.data[i]; - - if (node->attrib_type == UFBX_ELEMENT_MESH) - { - if (isGfxData) - addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); - else - addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); - } - else - { - //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); - } - } - - if (hasTangentSpace == false) - printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); -} - -CustomMapBSP* BSPCreator::createCustomMapBSP(std::string& mapName, ISearchPath& searchPath) -{ - ufbx_scene* gfxScene; - ufbx_scene* colScene; - - std::string gfxFbxPath = "custom_map/map_gfx.fbx"; - auto gfxFile = searchPath.Open(gfxFbxPath); - if (!gfxFile.IsOpen()) - { - printf("Failed to open map gfx fbx file: %s\n", gfxFbxPath.c_str()); - return NULL; - } - - char* gfxMapData = new char[static_cast(gfxFile.m_length)]; - gfxFile.m_stream->seekg(0); - gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); - - ufbx_error error; - ufbx_load_opts opts; // IDK why but opts don't seem to be working correctly, target_unit_meters isn't being used - memset(&opts, 0, sizeof(ufbx_load_opts)); - opts.target_axes = ufbx_axes_right_handed_y_up; - opts.generate_missing_normals = true; - opts.allow_missing_vertex_position = false; - opts.target_unit_meters = 100.0f; - //gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), &opts, &error); - gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), NULL, &error); - if (!gfxScene) - { - fprintf(stderr, "Failed to load map gfx fbx file: %s\n", error.description.data); - return NULL; - } - - std::string colFbxPath = "custom_map/map_col.fbx"; - auto colFile = searchPath.Open(colFbxPath); - if (!colFile.IsOpen()) - { - printf("Failed to open map collison fbx file: %s. map gfx will be used for collision instead.\n", colFbxPath.c_str()); - colScene = gfxScene; - } - else - { - char* colMapData = new char[static_cast(colFile.m_length)]; - colFile.m_stream->seekg(0); - colFile.m_stream->read(colMapData, colFile.m_length); - - colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), &opts, &error); - if (!colScene) - { - fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); - return NULL; - } - } - - CustomMapBSP* projInfo = new CustomMapBSP; - - projInfo->name = mapName; - projInfo->bspName = "maps/mp/" + mapName + ".d3dbsp"; - - loadWorldData(gfxScene, projInfo, true); - loadWorldData(colScene, projInfo, false); - - ufbx_free_scene(gfxScene); - if (gfxScene != colScene) - ufbx_free_scene(colScene); - - return projInfo; -} diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h deleted file mode 100644 index 2ba851711..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "Game/T6/Maps/CustomMaps.h" -#include "SearchPath/ISearchPath.h" - -class BSPCreator -{ -public: - static CustomMapBSP* createCustomMapBSP(std::string& mapName, ISearchPath& searchPath); -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h deleted file mode 100644 index c46af5f7b..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ /dev/null @@ -1,1983 +0,0 @@ -#pragma once - -#include "BSPCalculation.h" -#include "BSPConstants.h" -#include "BSPUtil.h" - -#include "Game/T6/Material/JsonMaterialLoaderT6.h" -#include "Utils/Pack.h" - -#include -using namespace nlohmann; - -class CustomMapLinker -{ -public: - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_zone(zone), - m_context(context) - { - hasLinkFailed = false; - } - - bool linkCustomMap(CustomMapBSP* projInfo) - { - _ASSERT(projInfo != NULL); - - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - - createComWorld(projInfo); - createMapEnts(projInfo); - createGameWorldMp(projInfo); - createSkinnedVerts(projInfo); - createGfxWorld(projInfo); // requires mapents asset - createClipMap(projInfo); // must go last (requires gfx and mapents asset) - - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - return true; - } - -private: - struct entModelBounds - { - vec3_t mins; - vec3_t maxs; - }; - - MemoryManager& m_memory; - ISearchPath& m_search_path; - Zone& m_zone; - AssetCreationContext& m_context; - - bool hasLinkFailed; - std::vector entityModelList; - - json materialTemplateJson; - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - bool overwriteDrawData(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - int vertexCount = projInfo->gfxWorld.vertices.size(); - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); - GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { - CustomMapVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); - //GfxVertex->xyz = WorldVertex->pos; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); - //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); - //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - - // unknown use variables - // binormalSign may be bitangent of the vertex - // lmapCoord may be the lightmap coordinate of the vertex - GfxVertex->binormalSign = 0.0f; - GfxVertex->lmapCoord.packed = 0; - } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; - - // we don't use vd1 but still needs to be initialised - // the data type varies and 0x20 is enough for all types - gfxWorld->draw.vertexDataSize1 = 0x20; - gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - - int indexCount = projInfo->gfxWorld.indices.size(); - _ASSERT(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; - gfxWorld->draw.indices = new uint16_t[indexCount]; - for (int i = 0; i < indexCount; i += 3) - { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; - } - - return true; - } - - struct s_sortedSurf - { - int surfaceIndex; - int vertexCount; - }; - - bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) - { - return surf0.vertexCount > surf1.vertexCount; - } - - Material* loadImageIntoMaterial(std::string& imageName) - { - std::string imagePath = std::format("images/{}.iwi", imageName); - auto imageFile = m_search_path.Open(imagePath); - if (!imageFile.IsOpen()) - { - printf("WARN: failed to find image %s.\n", imageName.c_str()); - return NULL; - } - - Material* material = new Material; - material->info.name = m_memory.Dup(imageName.c_str()); - - // parse the template file and replace the image name - materialTemplateJson["textures"][1]["image"] = imageName; - - AssetRegistration registration(imageName, material); - if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) - { - printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); - return NULL; - } - - m_context.AddAsset(std::move(registration)); - - return material; - } - - void overwriteMapSurfaces(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); - if (!overwriteResult) - return; - - unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; - gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxWorld.surfaces[i]; - - currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; - - currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; - - std::string surfMaterialName; - switch (objSurface->material.materialType) - { - case MATERIAL_TYPE_TEXTURE: - surfMaterialName = objSurface->material.materialName; - break; - - case MATERIAL_TYPE_COLOUR: - case MATERIAL_TYPE_EMPTY: - surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - break; - - default: - _ASSERT(false); - } - Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); - if (surfMaterial == NULL) - { - std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); - surfMaterial = loadImageIntoMaterial(missingImageName); - if (surfMaterial == NULL) - { - printf("Error: unable to find the missing image texture!\n"); - hasLinkFailed = true; - return; - } - } - currSurface->material = surfMaterial; - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) - { - uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); - } - - // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; - } - - // doesn't seem to matter what order the sorted surfs go in - gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = i; - } - - gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); - - // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; - memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); - - gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; - } - - void overwriteMapSModels(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - /* - Models are unsupported right now - Code is left in in case it is supported later on - */ - //unsigned int modelCount = projInfo->modelCount; - //gfxWorld->dpvs.smodelCount = modelCount; - //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - // - //for (unsigned int i = 0; i < modelCount; i++) - //{ - // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - // customMapModel* inModel = &projInfo->models[i]; - // - // auto xModelAsset = m_context.LoadDependency(inModel->name); - // if (xModelAsset == NULL) - // { - // printf("XModel %s not found!\n", inModel->name.c_str()); - // currModel->model = NULL; - // } - // else - // currModel->model = (XModel*)xModelAsset->Asset(); - // - // currModel->placement.origin.x = inModel->origin.x; - // currModel->placement.origin.y = inModel->origin.y; - // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - // currModel->placement.scale = inModel->scale; - // - // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - // - // // mins and maxs are calculated in world space not local space - // // TODO: this does not account for model rotation or scale - // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - // - // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - // currModel->flags = DEFAULT_SMODEL_FLAGS; - // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - // - // // unknown use / unused - // currModel->smid = i; - // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - // currModel->invScaleSq = 0.0f; - // currModel->lightingHandle = 0; - // currModel->colorsIndex = 0; - // currModel->visibility = 0; - // - // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // // TODO: calculate lighting and store it here - // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - //} - - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - - // all visdata is alligned by 128 - int allignedModelCount = BSPUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; - gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; - memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); - for (unsigned int i = 0; i < modelCount; i++) - { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) - gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; - } - - // always set to 0 - gfxWorld->dpvs.usageCount = 0; - } - - void cleanGfxWorld(GfxWorld* gfxWorld) - { - // checksum is generated by the game - gfxWorld->checksum = 0; - - // Remove Coronas - gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; - - // Remove exposure volumes - gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; - gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; - - // Remove hero lights - gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; - gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; - - // remove LUT data - gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; - gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; - - // remove occluders - gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; - - // remove Siege Skins - gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; - - // remove outdoor bounds - gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; - - // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; - - // remove shadow maps - gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; - gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; - - // remove stream info - gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; - gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; - - // remove sun data - memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); - gfxWorld->sun.hasValidData = false; - - // Remove Water - gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; - - // Remove Fog - gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; - gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; - gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; - gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; - - // materialMemory is unused - gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; - - // sunLight is overwritten by the game, just needs to be a valid pointer - gfxWorld->sunLight = new GfxLight; - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); - } - - void overwriteGfxLights(GfxWorld* gfxWorld) - { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - - gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; - memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); - } - - gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; - } - - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; - if (lightEntShadowVisSize != 0) - { - gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); - } - else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } - } - - // the lightgrid is used to light models in a dynamic way and is precomputed - void overwriteLightGrid(GfxWorld* gfxWorld) - { - // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. - // it works and that is all thats needed :) - - // mins and maxs define the range that the lightgrid will work in - // idk how these values are calculated, but the below values are larger - // than official map values - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 200; - gfxWorld->lightGrid.maxs[1] = 200; - gfxWorld->lightGrid.maxs[2] = 50; - - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - gfxWorld->lightGrid.offset = 0.0f; // default value - - // this will make the lookup into rawRowData always return the first row - int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; - gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; - memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - - gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); - GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); - row->colStart = 0; - row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game - row->zStart = 0; - row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks - row->firstEntry = 0; - for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 - { - row->lookupTable[i] = 0; - } - gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; - - // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow - gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data - GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; - for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) - { - entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - entryArray[i].visibility = 0; - } - gfxWorld->lightGrid.entries = entryArray; - - // colours are looked up by an entries colourindex - gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index - gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; - memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); - - // we use the colours array instead of coeffs array - gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; - } - - void updateGfxCells(GfxWorld* gfxWorld) - { - // Cells are basically data used to determine what can be seen and what cant be seen - // Right now custom maps have no optimisation so there is only 1 cell - int cellCount = 1; - - gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; - - int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); - gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); - - gfxWorld->cells = new GfxCell[cellCount]; - gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; - - // there is only 1 reflection probe - gfxWorld->cells[0].reflectionProbeCount = 1; - char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; - reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; - - // AABB trees are used to detect what should be rendered and what shouldn't - // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn - gfxWorld->cells[0].aabbTreeCount = 1; - gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; - gfxWorld->cells[0].aabbTree[0].childCount = 0; - gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; - gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) - { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; - } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node - // Nodes mnode_t.cellIndex indexes gfxWorld->cells - // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes - gfxWorld->nodeCount = 1; - gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; - gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 - - // planes are overwritten by the clipmap loading code ingame - gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.planes = NULL; - } - - void updateWorldBounds(GfxWorld* gfxWorld) - { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - - for (int i = 0; i < gfxWorld->surfaceCount; i++) - { - BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); - } - } - - void overwriteModels(GfxWorld* gfxWorld) - { - // these models are the collision for the entities defined in the mapents asset - // used for triggers and stuff - - gfxWorld->modelCount = entityModelList.size() + 1; - gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - - // first model is always the world model - gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - - for (size_t i = 0; i < entityModelList.size(); i++) - { - auto currEntModel = &gfxWorld->models[i + 1]; - entModelBounds currEntModelBounds = entityModelList[i]; - - currEntModel->startSurfIndex = 0; - currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces - currEntModel->bounds[0].x = currEntModelBounds.mins.x; - currEntModel->bounds[0].y = currEntModelBounds.mins.y; - currEntModel->bounds[0].z = currEntModelBounds.mins.z; - currEntModel->bounds[1].x = currEntModelBounds.maxs.x; - currEntModel->bounds[1].y = currEntModelBounds.maxs.y; - currEntModel->bounds[1].z = currEntModelBounds.maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - } - } - - void updateSunData(GfxWorld* gfxWorld) - { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = (float)0.001; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; - - gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; - gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; - gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; - gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; - gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; - gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; - gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; - gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; - gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; - gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; - gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; - gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; - gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; - gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; - gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; - gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; - } - - void updateReflectionProbeData(GfxWorld* gfxWorld) - { - gfxWorld->draw.reflectionProbeCount = 1; - - gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; - - // default values taken from mp_dig - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; - - gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - - std::string probeImageName = "*reflection_probe0"; - auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) - { - printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); - } - - void overwriteLightmapData(GfxWorld* gfxWorld) - { - gfxWorld->draw.lightmapCount = 1; - - gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - - std::string secondaryTexture = "*lightmap0_secondary"; - auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) - { - printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL - gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); - } - - void overwriteSkyBox(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - std::string skyBoxName = "skybox_" + projInfo->name; - gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); - - if (m_context.LoadDependency(skyBoxName) == NULL) - { - printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); - } - - // default skybox values from mp_dig - gfxWorld->skyDynIntensity.angle0 = 0.0f; - gfxWorld->skyDynIntensity.angle1 = 0.0f; - gfxWorld->skyDynIntensity.factor0 = 1.0f; - gfxWorld->skyDynIntensity.factor1 = 1.0f; - } - - void updateDynEntData(GfxWorld* gfxWorld) - { - int dynEntCount = 0; - gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. - // Making the value much larger than required fixes it, but idk what the root cause is - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; - gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; - gfxWorld->dpvsDyn.usageCount = 0; - - int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; - gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); - - int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; - gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); - gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); - - gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); - } - - void updateOutdoors(GfxWorld* gfxWorld) - { - float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); - float xScale = -(xRecip * gfxWorld->mins.x); - - float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); - float yScale = -(yRecip * gfxWorld->mins.y); - - float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); - float zScale = -(zRecip * gfxWorld->mins.z); - - memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); - - gfxWorld->outdoorLookupMatrix[0].x = xRecip; - gfxWorld->outdoorLookupMatrix[1].y = yRecip; - gfxWorld->outdoorLookupMatrix[2].z = zRecip; - gfxWorld->outdoorLookupMatrix[3].x = xScale; - gfxWorld->outdoorLookupMatrix[3].y = yScale; - gfxWorld->outdoorLookupMatrix[3].z = zScale; - gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - - std::string outdoorImageName = std::string("$outdoor"); - auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) - { - printf("ERROR! unable to find image $outdoor!\n"); - hasLinkFailed = true; - return; - } - gfxWorld->outdoorImage = outdoorImageAsset->Asset(); - } - - void createGfxWorld(CustomMapBSP* projInfo) - { - GfxWorld* gfxWorld = new GfxWorld; - gfxWorld->baseName = _strdup(projInfo->name.c_str()); - gfxWorld->name = _strdup(projInfo->bspName.c_str()); - - // Default values taken from mp_dig - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; - - cleanGfxWorld(gfxWorld); - - overwriteMapSurfaces(projInfo, gfxWorld); - - overwriteMapSModels(projInfo, gfxWorld); - - overwriteLightmapData(gfxWorld); - - overwriteSkyBox(projInfo, gfxWorld); - - updateReflectionProbeData(gfxWorld); - - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); - - updateOutdoors(gfxWorld); - - // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); - - overwriteLightGrid(gfxWorld); - - overwriteGfxLights(gfxWorld); - - overwriteModels(gfxWorld); - - updateSunData(gfxWorld); - - updateDynEntData(gfxWorld); - - m_context.AddAsset(gfxWorld->name, gfxWorld); - } - - void addXModelsToCollision(CustomMapBSP* projInfo, clipMap_t* clipMap) - { - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; - clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; - - for (unsigned int i = 0; i < clipMap->numStaticModels; i++) - { - GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; - GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; - cStaticModel_s* currModel = &clipMap->staticModelList[i]; - - memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); - currModel->xmodel = gfxModelDrawInst->model; - currModel->contents = gfxModelDrawInst->model->contents; - currModel->origin.x = gfxModelDrawInst->placement.origin.x; - currModel->origin.y = gfxModelDrawInst->placement.origin.y; - currModel->origin.z = gfxModelDrawInst->placement.origin.z; - - // TODO: this does not account for model rotation or scale - currModel->absmin.x = gfxModelInst->mins.x; - currModel->absmin.y = gfxModelInst->mins.y; - currModel->absmin.z = gfxModelInst->mins.z; - currModel->absmax.x = gfxModelInst->maxs.x; - currModel->absmax.y = gfxModelInst->maxs.y; - currModel->absmax.z = gfxModelInst->maxs.z; - - BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); - currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; - currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; - currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; - currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; - currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; - currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; - currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; - currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; - currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; - } - } - - void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) - { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - out_origin->x = temp.x * 0.5f; - out_origin->y = temp.y * 0.5f; - out_origin->z = temp.z * 0.5f; - - // Half-size is half the difference: (max - min) / 2 - temp.x = maxs->x - mins->x; - temp.y = maxs->y - mins->y; - temp.z = maxs->z - mins->z; - out_halfSize->x = temp.x * 0.5f; - out_halfSize->y = temp.y * 0.5f; - out_halfSize->z = temp.z * 0.5f; - } - - bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) - { - - unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); - - return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; - } - - void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) - { - if (node->isLeaf) - { - (*numLeafs)++; - // there won't be an AABB tree when objectList is empty - if (node->leaf->getObjectCount() > 0) - { - *numAABBTrees += node->leaf->getObjectCount() + 1; - - if (node->leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->leaf->getObjectCount(); - } - } - else - { - (*numPlanes)++; - (*numNodes)++; - traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - } - } - - vec3_t normalX = {1.0f, 0.0f, 0.0f}; - vec3_t normalY = {0.0f, 1.0f, 0.0f}; - vec3_t normalZ = {0.0f, 0.0f, 1.0f}; - - int currPlaneCount = 0; - int currNodeCount = 0; - int currLeafCount = 0; - int currAABBCount = 0; - - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) - { - _ASSERT(node->isLeaf); - - int objectCount = node->leaf->getObjectCount(); - int firstAABBIndex = currAABBCount; - currAABBCount += objectCount + 1; - - // calculate root AABB node mins and maxs - // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs - // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; - auto firstPartition = &clipMap->partitions[firstPartitionIndex]; - uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t aabbMins; - vec3_t aabbMaxs; - aabbMins.x = firstVert->x; - aabbMins.y = firstVert->y; - aabbMins.z = firstVert->z; - aabbMaxs.x = firstVert->x; - aabbMaxs.y = firstVert->y; - aabbMaxs.z = firstVert->z; - for (int i = 0; i < objectCount; i++) - { - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - auto currPartition = &clipMap->partitions[currPartitionIndex]; - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = clipMap->verts[vertIndex]; - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); - } - } - } - CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; - aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); - rootAABB->materialIndex = 0; - rootAABB->childCount = objectCount; - rootAABB->u.firstChildIndex = firstAABBIndex + 1; - - // populate child AABB nodes - for (int i = 0; i < objectCount; i++) - { - CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - - currAabbTree->materialIndex = 0; - currAabbTree->childCount = 0; - currAabbTree->u.partitionIndex = currPartitionIndex; - - // calculate partition origin and half size - CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; - uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; - vec3_t* firstVertex = &clipMap->verts[firstUind]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVertex->x; - mins.y = firstVertex->y; - mins.z = firstVertex->z; - maxs.x = firstVertex->x; - maxs.y = firstVertex->y; - maxs.z = firstVertex->z; - for (int i = 1; i < aabbPartition->nuinds; i++) - { - uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; - vec3_t* currVertex = &clipMap->verts[currUind]; - - BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); - } - - aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); - } - - return firstAABBIndex; - } - - // returns the index corresponding to the BSPTree* node parsed - int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) - { - new cplane_s; - new cNode_t; - new cLeaf_s; - new CollisionAabbTree; - - if (node->isLeaf) - { - int currLeafIndex = currLeafCount; - currLeafCount++; - cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; - - currLeaf->cluster = 0; - currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence - - // unused when leafBrushNode == 0 - currLeaf->mins.x = 0.0f; - currLeaf->mins.y = 0.0f; - currLeaf->mins.z = 0.0f; - currLeaf->maxs.x = 0.0f; - currLeaf->maxs.y = 0.0f; - currLeaf->maxs.z = 0.0f; - currLeaf->leafBrushNode = 0; - - if (node->leaf->getObjectCount() > 0) - { - currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); - currLeaf->collAabbCount = 1; - } - else - { - currLeaf->firstCollAabbIndex = 0; - currLeaf->collAabbCount = 0; - } - - return -1 - currLeafIndex; - } - else - { - cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; - currPlaneCount++; - - if (node->node->axis == AXIS_X) - { - // X is unchanged when going from OGL x -> BO2 x - currPlane->normal = normalX; - - // converting OGL -> BO2 X coords doesn't change the x coords at all, so - // the dist stays the same - currPlane->dist = (float)node->node->distance; - } - else - { - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // convert the z normal to the y normal, but don't negate it. Negative normals don't do - // what is expected when the game uses them - _ASSERT(node->u.node->axis == AXIS_Z); - currPlane->normal = normalY; - - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = (float)(-node->node->distance); - } - - bool foundType = false; - if (currPlane->normal.x == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 0; - } - else if (currPlane->normal.y == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 1; - } - else if (currPlane->normal.z == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 2; - } - else - _ASSERT(foundType); - - currPlane->signbits = 0; - if (currPlane->normal.x < 0.0f) - currPlane->signbits |= 1; - if (currPlane->normal.y < 0.0f) - currPlane->signbits |= 2; - if (currPlane->normal.z < 0.0f) - currPlane->signbits |= 4; - - currPlane->pad[0] = 0; - currPlane->pad[1] = 0; - - int currNodeIndex = currNodeCount; - currNodeCount++; - cNode_t* currNode = &clipMap->nodes[currNodeIndex]; - - currNode->plane = currPlane; - // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): - // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, - // the normal vector flips and objects behind the plane are now in front, and vise versa - // so the back node now represents the front, and the front node represents the back. - // Do the OGL -> Bo2 coord change on paper and it will make sense - if (currPlane->type == 1) - { - currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); - } - else - { - currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); - } - - return currNodeIndex; - } - } - - void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) - { - int numPlanes = 0; - int numNodes = 0; - int numLeafs = 0; - int numAABBTrees = 0; - int maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; - clipMap->numNodes = numNodes; - clipMap->nodes = new cNode_t[clipMap->numNodes]; - // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. - // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. - clipMap->aabbTreeCount = numAABBTrees; - clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; - memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); - currLeafCount = 1; - - populateBSPTree_r(clipMap, tree); - - _ASSERT(clipMap->info.planeCount == currPlaneCount); - _ASSERT(clipMap->numNodes == currNodeCount); - _ASSERT(clipMap->numLeafs == currLeafCount); - _ASSERT(clipMap->aabbTreeCount == currAABBCount); - } - - void createPartitions(CustomMapBSP* projInfo, clipMap_t* clipMap) - { - int collisionVertexCount = projInfo->colWorld.vertices.size(); - std::vector collisionVertVec; - for (int i = 0; i < collisionVertexCount; i++) - { - collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); - //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = new vec3_t[collisionVertexCount]; - memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); - - // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) - { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); - hasLinkFailed = true; - return; - } - - std::vector triIndexVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - CustomMapSurface* currSurface = &projInfo->colWorld.surfaces[i]; - int triCount = currSurface->triCount; - - for (int k = 0; k < triCount * 3; k += 3) - { - int firstIndex_Index = currSurface->indexOfFirstIndex; - int firstVertexIndex = currSurface->indexOfFirstVertex; - - // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire - // clipmap verts buffer, so this code updates the indexes to follow that. - int triIndex0 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = projInfo->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; - - // triangle index ordering is opposite to blenders, so its converted here - triIndexVec.push_back(triIndex2); - triIndexVec.push_back(triIndex1); - triIndexVec.push_back(triIndex0); - } - } - _ASSERT(triIndexVec.size() % 3 == 0); - clipMap->triCount = triIndexVec.size() / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[triIndexVec.size()]); - memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower - std::vector partitionVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - int triCount = projInfo->colWorld.surfaces[i].triCount; - int firstTriIndex = projInfo->colWorld.surfaces[i].indexOfFirstIndex / 3; - for (int k = 0; k < triCount; k++) - { - CollisionPartition newPartition; - newPartition.nuinds = 0; // initialised later - newPartition.fuind = 0; // initialised later - newPartition.triCount = 1; - newPartition.firstTri = firstTriIndex; - firstTriIndex += 1; - - partitionVec.push_back(newPartition); - } - } - clipMap->partitionCount = partitionVec.size(); - clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; - memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); - - int totalUindCount = 0; - std::vector uindVec; - for (int i = 0; i < clipMap->partitionCount; i++) - { - CollisionPartition* currPartition = &clipMap->partitions[i]; - std::vector uniqueVertVec; - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (size_t m = 0; m < uniqueVertVec.size(); m++) - { - if (uniqueVertVec[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - uniqueVertVec.push_back(vertIndex); - } - } - - currPartition->fuind = totalUindCount; - currPartition->nuinds = (int)uniqueVertVec.size(); - uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); - totalUindCount += currPartition->nuinds; - } - clipMap->info.nuinds = totalUindCount; - clipMap->info.uinds = new uint16_t[totalUindCount]; - memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - } - - void createClipMap(CustomMapBSP* projInfo) - { - clipMap_t* clipMap = new clipMap_t; - - clipMap->name = _strdup(projInfo->bspName.c_str()); - - std::string mapEntsName = projInfo->bspName.c_str(); - auto mapEntsAsset = m_context.LoadDependency(mapEntsName); - _ASSERT(mapEntsAsset != NULL); - clipMap->mapEnts = mapEntsAsset->Asset(); - - clipMap->pInfo = NULL; - - clipMap->box_model.mins.x = 0.0f; - clipMap->box_model.mins.y = 0.0f; - clipMap->box_model.mins.z = 0.0f; - clipMap->box_model.maxs.x = 0.0f; - clipMap->box_model.maxs.y = 0.0f; - clipMap->box_model.maxs.z = 0.0f; - clipMap->box_model.radius = 0.0f; - clipMap->box_model.info = NULL; - - // for some reason the maxs are negative, and mins are positive - // float box_mins = 3.4028235e38; - // float box_maxs = -3.4028235e38; - // hack: the floats above can't be converted to 32 bit floats, and the game requires them to be exact - // so we use the hex representation and set it using int pointers - unsigned int box_mins = 0x7F7FFFFF; - unsigned int box_maxs = 0xFF7FFFFF; - *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.z) = box_maxs; - - clipMap->box_model.leaf.brushContents = -1; - clipMap->box_model.leaf.terrainContents = 0; - clipMap->box_model.leaf.cluster = 0; - clipMap->box_model.leaf.collAabbCount = 0; - clipMap->box_model.leaf.firstCollAabbIndex = 0; - clipMap->box_model.leaf.leafBrushNode = 0; - - clipMap->box_brush = new cbrush_t; - clipMap->box_brush->axial_sflags[0][0] = -1; - clipMap->box_brush->axial_sflags[0][1] = -1; - clipMap->box_brush->axial_sflags[0][2] = -1; - clipMap->box_brush->axial_sflags[1][0] = -1; - clipMap->box_brush->axial_sflags[1][1] = -1; - clipMap->box_brush->axial_sflags[1][2] = -1; - clipMap->box_brush->axial_cflags[0][0] = -1; - clipMap->box_brush->axial_cflags[0][1] = -1; - clipMap->box_brush->axial_cflags[0][2] = -1; - clipMap->box_brush->axial_cflags[1][0] = -1; - clipMap->box_brush->axial_cflags[1][1] = -1; - clipMap->box_brush->axial_cflags[1][2] = -1; - clipMap->box_brush->contents = -1; - clipMap->box_brush->mins.x = 0.0f; - clipMap->box_brush->mins.y = 0.0f; - clipMap->box_brush->mins.z = 0.0f; - clipMap->box_brush->maxs.x = 0.0f; - clipMap->box_brush->maxs.y = 0.0f; - clipMap->box_brush->maxs.z = 0.0f; - clipMap->box_brush->numsides = 0; - clipMap->box_brush->numverts = 0; - clipMap->box_brush->sides = NULL; - clipMap->box_brush->verts = NULL; - - clipMap->numClusters = 1; - clipMap->vised = 0; - clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; - clipMap->visibility = new char[clipMap->clusterBytes]; - memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); - - clipMap->isInUse = true; - clipMap->checksum = 0; - - clipMap->num_constraints = 0; - clipMap->constraints = NULL; - clipMap->max_ropes = 32; - clipMap->ropes = new rope_t[clipMap->max_ropes]; - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); - - clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = NULL; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = NULL; - clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = NULL; - clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = NULL; - clipMap->info.numBrushes = NULL; - clipMap->info.brushes = NULL; - clipMap->info.brushBounds = NULL; - clipMap->info.brushContents = NULL; - - int dynEntCount = 0; - clipMap->originalDynEntCount = dynEntCount; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - clipMap->dynEntCount[1] = 0; - clipMap->dynEntCount[2] = 0; - clipMap->dynEntCount[3] = 0; - - // assume that there are 0 dyn ents from here on - clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; - clipMap->dynEntClientList[1] = NULL; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); - - clipMap->dynEntServerList[0] = NULL; - clipMap->dynEntServerList[1] = NULL; - - clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; - clipMap->dynEntCollList[1] = NULL; - clipMap->dynEntCollList[2] = NULL; - clipMap->dynEntCollList[3] = NULL; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); - - clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; - clipMap->dynEntPoseList[1] = NULL; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); - - clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; - clipMap->dynEntDefList[1] = NULL; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - - // cmodels is the collision for mapents - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - clipMap->numSubModels = gfxWorld->modelCount; - clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - for (unsigned int i = 0; i < clipMap->numSubModels; i++) - { - // bomb triggers use leafs, not world terrain so that might be an issue - - GfxBrushModel* gfxModel = &gfxWorld->models[i]; - cmodel_t* cmModel = &clipMap->cmodels[i]; - - cmModel->leaf.firstCollAabbIndex = 0; - cmModel->leaf.collAabbCount = 0; - cmModel->leaf.brushContents = 0; - cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; - cmModel->leaf.mins.x = 0.0f; - cmModel->leaf.mins.y = 0.0f; - cmModel->leaf.mins.z = 0.0f; - cmModel->leaf.maxs.x = 0.0f; - cmModel->leaf.maxs.y = 0.0f; - cmModel->leaf.maxs.z = 0.0f; - cmModel->leaf.leafBrushNode = 0; - cmModel->leaf.cluster = 0; - cmModel->info = NULL; - cmModel->mins.x = gfxModel->bounds[0].x; - cmModel->mins.y = gfxModel->bounds[0].y; - cmModel->mins.z = gfxModel->bounds[0].z; - cmModel->maxs.x = gfxModel->bounds[1].x; - cmModel->maxs.y = gfxModel->bounds[1].y; - cmModel->maxs.z = gfxModel->bounds[1].z; - cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; - } - - addXModelsToCollision(projInfo, clipMap); - - clipMap->info.numMaterials = 1; - clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(BSPLinkingConstants::MISSING_IMAGE_NAME); - clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; - - // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls, but from testing doesnt seem to do much - int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - - // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, - // and then convert them back when it is being parsed into the clipmap. Requires some hacky - // logic, check populateBSPTree_r and addAABBTreeFromLeaf - - createPartitions(projInfo, clipMap); - - vec3_t* firstVert = &clipMap->verts[0]; - vec3_t clipMins; - vec3_t clipMaxs; - clipMins.x = firstVert->x; - clipMins.y = firstVert->y; - clipMins.z = firstVert->z; - clipMaxs.x = firstVert->x; - clipMaxs.y = firstVert->y; - clipMaxs.z = firstVert->z; - clipMins = BSPUtil::convertFromBO2Coords(clipMins); - clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); - for (unsigned int i = 1; i < clipMap->vertCount; i++) - { - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); - } - - BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); - - _ASSERT(!tree->isLeaf); - - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVert->x; - mins.y = firstVert->y; - mins.z = firstVert->z; - maxs.x = firstVert->x; - maxs.y = firstVert->y; - maxs.z = firstVert->z; - mins = BSPUtil::convertFromBO2Coords(mins); - maxs = BSPUtil::convertFromBO2Coords(maxs); - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); - } - } - - std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - - tree->addObjectToTree(std::move(currObject)); - } - - populateBSPTree(clipMap, tree); - - m_context.AddAsset(clipMap->name, clipMap); - } - - void createComWorld(CustomMapBSP* projInfo) - { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset - ComWorld* comWorld = new ComWorld; - comWorld->name = _strdup(projInfo->bspName.c_str()); - comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; - comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; - - // default light is always empty - ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; - memset(defaultLight, 0, sizeof(ComPrimaryLight)); - - ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - memset(sunLight, 0, sizeof(ComPrimaryLight)); - sunLight->type = 1; - sunLight->diffuseColor.r = 0.75f; - sunLight->diffuseColor.g = 0.75f; - sunLight->diffuseColor.b = 0.75f; - sunLight->diffuseColor.a = 1.0f; - sunLight->dir.x = 0.0f; - sunLight->dir.y = 0.0f; - sunLight->dir.z = 0.0f; - - m_context.AddAsset(comWorld->name, comWorld); - } - - void parseMapEntsJSON(json& entArrayJs, std::string& entityString) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - if (i == 0) - { - std::string className = currEntity["classname"]; - if (className.compare("worldspawn") != 0) - { - printf("ERROR: first entity in the map entity string must be the worldspawn class!"); - hasLinkFailed = true; - return; - } - } - - entityString.append("{\n"); - - for (auto& element : currEntity.items()) - { - std::string key = element.key(); - std::string value = element.value(); - entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); - } - - entityString.append("}\n"); - } - } - - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - std::string origin = currEntity["origin"]; - std::string angles = currEntity["angles"]; - - for (int k = 0; k < nameCount; k++) - { - entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", origin)); - entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); - entityString.append("}\n"); - } - } - } - - void parseBombJSON(json& bombJs, std::string& entityString) - { - // add the bomb model - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - entityString.append("{\n"); - entityString.append("\"classname\" \"script_model\"\n"); - entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); - entityString.append("\"targetname\" \"sd_bomb\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append("\"spawnflags\" \"4\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append("}\n"); - } - if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) - { - hasLinkFailed = true; - printf("ERROR: unable to find s&d bomb xmodel\n"); - return; - } - - // add the bomb pickup trigger - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); - entModelBounds bounds; - bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig - bounds.mins.y = bomboriginV3.y - 32.0f; - bounds.mins.z = bomboriginV3.z - 8.0f; - bounds.maxs.x = bomboriginV3.x + 32.0f; - bounds.maxs.y = bomboriginV3.y + 32.0f; - bounds.maxs.z = bomboriginV3.z + 28.0f; - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_multiple\"\n"); - entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add A site bomb - { - std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; - std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; - vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); - vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteAPoint1V3.x; - bounds.mins.y = siteAPoint1V3.y; - bounds.mins.z = siteAPoint1V3.z; - bounds.maxs.x = siteAPoint1V3.x; - bounds.maxs.y = siteAPoint1V3.y; - bounds.maxs.z = siteAPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_a\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add B site bomb - { - std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; - std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; - vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); - vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteBPoint1V3.x; - bounds.mins.y = siteBPoint1V3.y; - bounds.mins.z = siteBPoint1V3.z; - bounds.maxs.x = siteBPoint1V3.x; - bounds.maxs.y = siteBPoint1V3.y; - bounds.maxs.z = siteBPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_b\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - } - - void createMapEnts(CustomMapBSP* projInfo) - { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - std::string entityString; - - const auto entFile = m_search_path.Open("custom_map/entities.json"); - json entJs; - if (!entFile.IsOpen()) - { - printf("WARN: can't find entity json custom_map/entities.json, using default entities\n"); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); - } - else - { - entJs = json::parse(*entFile.m_stream); - } - parseMapEntsJSON(entJs["entities"], entityString); - - const auto spawnFile = m_search_path.Open("custom_map/spawns.json"); - json spawnJs; - if (!spawnFile.IsOpen()) - { - printf("WARN: cant find custom_map/spawns.json, setting spawns to 0 0 0\n"); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); - } - - int defenderNameCount = std::extent::value; - int attackerNameCount = std::extent::value; - int ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); - - //const auto objectiveFile = m_search_path.Open("objectives.json"); - //if (!spawnFile.IsOpen()) - //{ - // printf("WARN: no objectives given\n"); - //} - //else - //{ - // json objectiveJs = json::parse(*objectiveFile.m_stream); - // parseBombJSON(objectiveJs, entityString); - //} - - mapEnts->entityString = _strdup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - m_context.AddAsset(mapEnts->name, mapEnts); - } - - void createGameWorldMp(CustomMapBSP* projInfo) - { - GameWorldMp* gameWorldMp = new GameWorldMp; - - gameWorldMp->name = _strdup(projInfo->bspName.c_str()); - - gameWorldMp->path.nodeCount = 0; - gameWorldMp->path.originalNodeCount = 0; - gameWorldMp->path.visBytes = 0; - gameWorldMp->path.smoothBytes = 0; - gameWorldMp->path.nodeTreeCount = 0; - - int nodeCount = gameWorldMp->path.nodeCount + 128; - gameWorldMp->path.nodes = new pathnode_t[nodeCount]; - gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - - gameWorldMp->path.pathVis = NULL; - gameWorldMp->path.smoothCache = NULL; - gameWorldMp->path.nodeTree = NULL; - - m_context.AddAsset(gameWorldMp->name, gameWorldMp); - } - - void createSkinnedVerts(CustomMapBSP* projInfo) - { - SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; - skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); - // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have - // But setting it to the world vertex count seems to work - - m_context.AddAsset("skinnedverts", skinnedVerts); - } - - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) - { - if (assetName.length() == 0) - return NULL; - - FootstepTableDef* footstepTable = new FootstepTableDef; - footstepTable->name = _strdup(assetName.c_str()); - memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); - - m_context.AddAsset(assetName, footstepTable); - - return footstepTable; - } - - void checkAndAddDefaultRequiredAssets(CustomMapBSP* projectInfo) - { - auto templateFile = m_search_path.Open("materials/material_template.json"); - if (!templateFile.IsOpen()) - { - printf("ERROR: failed to open materials/material_template.json\n"); - hasLinkFailed = true; - return; - } - materialTemplateJson = json::parse(*templateFile.m_stream); - - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) - { - hasLinkFailed = true; - return; - } - - addEmptyFootstepTableAsset("default_1st_person"); - addEmptyFootstepTableAsset("default_3rd_person"); - addEmptyFootstepTableAsset("default_1st_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_loud"); - addEmptyFootstepTableAsset("default_ai"); - - if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) - { - hasLinkFailed = true; - return; - } - } -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h deleted file mode 100644 index b18a97c68..000000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -// These options can be edited - -// material flags determine the features of the surface -// unsure which flag type changes what right now -// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast -// 1 results in: normal surface features, grenades work, seems normal -#define MATERIAL_SURFACE_FLAGS 1 -#define MATERIAL_CONTENT_FLAGS 1 - -// terrain/world flags: does not change the type of terrain or what features they have -// from testing, as long at it isn't 0 things will work correctly -#define LEAF_TERRAIN_CONTENTS 1 -#define WORLD_TERRAIN_CONTENTS 1 - -#define LIGHTGRID_COLOUR 128 // global lighting colour diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 6abdaaabf..cc391d98f 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -42,7 +42,7 @@ #include "Weapon/WeaponRawLoaderT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" -#include "CustomMap/LoaderCustomMapT6.h" +#include "BSP/LoaderBSP_T6.h" #include "TechniqueSet/LoaderTechniqueSetT6.h" #include @@ -442,7 +442,7 @@ namespace T6 collection.AddAssetCreator(z_barrier::CreateRawLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(z_barrier::CreateGdtLoaderT6(memory, searchPath, gdt, zone)); - collection.AddAssetCreator(custom_map::CreateLoaderT6(memory, searchPath, zone)); + collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone)); } } // namespace From a81944a7bef8191ec95792e6285cdc2f6913eb7d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 22 Oct 2025 16:42:12 +0800 Subject: [PATCH 023/123] WIP: Prepared project for updating CustomMapLinker.h --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 7 ++++--- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 5 +++++ src/ObjLoading/Game/T6/BSP/BSPUtil.h | 1 + src/ObjLoading/Game/T6/BSP/CustomMapLinker.h | 2 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 10 +++++----- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 322d940fa..c81730890 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1,4 +1,5 @@ #include "BSPCreator.h" +#include "BSPUtil.h" #include "fbx/ufbx.h" namespace @@ -192,7 +193,7 @@ namespace BSP { std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) { - std::string gfxFbxPath = "BSP/map_gfx.fbx"; + std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset("map_gfx.fbx"); auto gfxFile = searchPath.Open(gfxFbxPath); if (!gfxFile.IsOpen()) { @@ -221,7 +222,7 @@ namespace BSP } ufbx_scene* colScene; - std::string colFbxPath = "BSP/map_col.fbx"; + std::string colFbxPath = BSPUtil::getFileNameForBSPAsset("map_col.fbx"); auto colFile = searchPath.Open(colFbxPath); if (!colFile.IsOpen()) { @@ -252,7 +253,7 @@ namespace BSP } } - std::unique_ptr bsp; + std::unique_ptr bsp = std::make_unique(); bsp->name = mapName; bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index a6a1b8f43..118594bb3 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -5,6 +5,11 @@ #include "BSPUtil.h" +std::string BSPUtil::getFileNameForBSPAsset(std::string assetName) +{ + return std::format("BSP/{}", assetName); +} + // BO2 uses a different coordinate system, so this converts it back from OpenGLs default vec3_t BSPUtil::convertToBO2Coords(vec3_t OGL_coordinate) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index ddbcd58e1..7bad527f0 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -6,6 +6,7 @@ class BSPUtil { public: + static std::string getFileNameForBSPAsset(std::string assetName); static vec3_t convertToBO2Coords(vec3_t OGL_coordinate); static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate); static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs); diff --git a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h index 9b11e3480..33ea1dce5 100644 --- a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h @@ -1168,7 +1168,7 @@ namespace BSP // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. // convert the z normal to the y normal, but don't negate it. Negative normals don't do // what is expected when the game uses them - _ASSERT(node->u.node->axis == AXIS_Z); + _ASSERT(node->node->axis == AXIS_Z); currPlane->normal = normalY; // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 708602f19..b3a02399c 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -19,20 +19,20 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { // custom maps must have a map_gfx file - auto mapGfxFile = m_search_path.Open("BSP/map_gfx.fbx"); + auto mapGfxFile = m_search_path.Open(BSPUtil::getFileNameForBSPAsset("map_gfx.fbx")); if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); - BSPData* BSP = BSP::createBSPData(m_zone.m_name, m_search_path); - if (BSP == nullptr) + std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path); + if (bsp == nullptr) return AssetCreationResult::Failure(); CustomMapLinker linker(m_memory, m_search_path, m_zone, context); - bool result = linker.linkCustomMap(BSP); + bool result = linker.linkCustomMap(bsp.get()); if (result) { - auto gfxWorldAsset = context.LoadDependency(BSP->bspName); + auto gfxWorldAsset = context.LoadDependency(bsp->bspName); _ASSERT(gfxWorldAsset != nullptr); return AssetCreationResult::Success(gfxWorldAsset); } From 948ba5ba8b309b537cef9a93a9647fc62a36da71 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Fri, 24 Oct 2025 02:56:17 +0800 Subject: [PATCH 024/123] WIP: Converted custom map linker into different asset files --- src/ObjLoading/Game/T6/BSP/BSP.h | 1 + .../Game/T6/BSP/Linker/BSPLinker.cpp | 80 ++ src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h | 24 + .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 763 ++++++++++++++++++ .../Game/T6/BSP/Linker/ClipMapLinker.h | 33 + .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 40 + .../Game/T6/BSP/Linker/ComWorldLinker.h | 21 + .../Game/T6/BSP/Linker/GameWorldMpLinker.cpp | 38 + .../Game/T6/BSP/Linker/GameWorldMpLinker.h | 21 + .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 17 + .../Game/T6/BSP/Linker/GfxWorldLinker.h | 21 + .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 128 +++ .../Game/T6/BSP/Linker/MapEntsLinker.h | 21 + .../Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 26 + .../Game/T6/BSP/Linker/SkinnedVertsLinker.h | 21 + 15 files changed, 1255 insertions(+) create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index d2b1dfb19..0fb6934c5 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Utils/Logging/Log.h" diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp new file mode 100644 index 000000000..de6d6007b --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -0,0 +1,80 @@ +#include "BSPLinker.h" + +#include "ComWorldLinker.h" +#include "ClipMapLinker.h" +#include "GameWorldMpLinker.h" +#include "GfxWorldLinker.h" +#include "MapEntsLinker.h" +#include "SkinnedVertsLinker.h" + +namespace BSP +{ + FootstepTableDef* BSPLinker::addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return nullptr; + + FootstepTableDef* footstepTable = m_memory.Alloc(); + footstepTable->name = m_memory.Dup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + bool BSPLinker::addDefaultRequiredAssets(BSPData* bsp) + { + if (m_context.LoadDependency("maps/mp/" + bsp->name + ".gsc") == nullptr) + return false; + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_amb.gsc") == nullptr) + return false; + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_fx.gsc") == nullptr) + return false; + + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + ".csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr) + return false; + + addEmptyFootstepTableAsset("default_1st_person"); + addEmptyFootstepTableAsset("default_3rd_person"); + addEmptyFootstepTableAsset("default_1st_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_loud"); + addEmptyFootstepTableAsset("default_ai"); + + if (m_context.LoadDependency("animtrees/fxanim_props.atr") == nullptr) + return false; + } + + BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult BSPLinker::linkBSP(BSPData* bsp) + { + if (!addDefaultRequiredAssets(bsp)) + return AssetCreationResult::Failure(); + + ComWorldLinker comWorldLinker(m_memory, m_search_path, m_context); + ClipMapLinker clipMapLinker(m_memory, m_search_path, m_context); + GameWorldMpLinker gameWorldMpLinker(m_memory, m_search_path, m_context); + GfxWorldLinker gfxWorldLinker(m_memory, m_search_path, m_context); + MapEntsLinker mapEntsLinker(m_memory, m_search_path, m_context); + SkinnedVertsLinker skinnedVertsLinker(m_memory, m_search_path, m_context); + + comWorldLinker.linkComWorld(bsp); + mapEntsLinker.linkMapEnts(bsp); + gameWorldMpLinker.linkGameWorldMp(bsp); + skinnedVertsLinker.linkSkinnedVerts(bsp); + gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h new file mode 100644 index 000000000..be986f978 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class BSPLinker + { + public: + BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkBSP(BSPData* bsp); + + private: + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName); + bool addDefaultRequiredAssets(BSPData* bsp); + + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp new file mode 100644 index 000000000..8710f93cc --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -0,0 +1,763 @@ +#include "ClipMapLinker.h" +#include "../BSPUtil.h" + + +namespace +{ + +} + +namespace BSP +{ + ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + void ClipMapLinker::loadDynEnts(clipMap_t* clipMap) + { + int dynEntCount = 0; + clipMap->originalDynEntCount = dynEntCount; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + clipMap->dynEntCount[1] = 0; + clipMap->dynEntCount[2] = 0; + clipMap->dynEntCount[3] = 0; + + clipMap->dynEntClientList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntClientList[1] = nullptr; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = nullptr; + clipMap->dynEntServerList[1] = nullptr; + + clipMap->dynEntCollList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntCollList[1] = nullptr; + clipMap->dynEntCollList[2] = nullptr; + clipMap->dynEntCollList[3] = nullptr; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntPoseList[1] = nullptr; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntDefList[1] = nullptr; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + } + + void ClipMapLinker::loadVisibility(clipMap_t* clipMap) + { + // Only use one visbility cluster for the entire map + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = m_memory.Alloc(clipMap->clusterBytes); + // Official maps set visibility to all 0xFF + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + } + + void ClipMapLinker::loadBoxData(clipMap_t* clipMap) + { + // box_model and box_brush are what are used by game traces as "temporary" collision when + // no brush or model is specified to do the trace with. + // All values in this function are taken from official map BSPs + + // for some reason the maxs are negative, and mins are positive + // float box_mins = 3.4028235e38; + // float box_maxs = -3.4028235e38; + // hack: the floats above can't be safely converted to 32 bit floats, and the game requires them to be exact + // so we use the hex representation and set it using int pointers. + unsigned int box_mins = 0x7F7FFFFF; + unsigned int box_maxs = 0xFF7FFFFF; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.x)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.y)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.z)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.x)) = box_maxs; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.y)) = box_maxs; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.z)) = box_maxs; + + clipMap->box_model.leaf.brushContents = -1; + clipMap->box_model.leaf.terrainContents = 0; + clipMap->box_model.leaf.cluster = 0; + clipMap->box_model.leaf.collAabbCount = 0; + clipMap->box_model.leaf.firstCollAabbIndex = 0; + clipMap->box_model.leaf.leafBrushNode = 0; + clipMap->box_model.mins.x = 0.0f; + clipMap->box_model.mins.y = 0.0f; + clipMap->box_model.mins.z = 0.0f; + clipMap->box_model.maxs.x = 0.0f; + clipMap->box_model.maxs.y = 0.0f; + clipMap->box_model.maxs.z = 0.0f; + clipMap->box_model.radius = 0.0f; + clipMap->box_model.info = nullptr; + + clipMap->box_brush = m_memory.Alloc(); + clipMap->box_brush->axial_sflags[0][0] = -1; + clipMap->box_brush->axial_sflags[0][1] = -1; + clipMap->box_brush->axial_sflags[0][2] = -1; + clipMap->box_brush->axial_sflags[1][0] = -1; + clipMap->box_brush->axial_sflags[1][1] = -1; + clipMap->box_brush->axial_sflags[1][2] = -1; + clipMap->box_brush->axial_cflags[0][0] = -1; + clipMap->box_brush->axial_cflags[0][1] = -1; + clipMap->box_brush->axial_cflags[0][2] = -1; + clipMap->box_brush->axial_cflags[1][0] = -1; + clipMap->box_brush->axial_cflags[1][1] = -1; + clipMap->box_brush->axial_cflags[1][2] = -1; + clipMap->box_brush->contents = -1; + clipMap->box_brush->mins.x = 0.0f; + clipMap->box_brush->mins.y = 0.0f; + clipMap->box_brush->mins.z = 0.0f; + clipMap->box_brush->maxs.x = 0.0f; + clipMap->box_brush->maxs.y = 0.0f; + clipMap->box_brush->maxs.z = 0.0f; + clipMap->box_brush->numsides = 0; + clipMap->box_brush->numverts = 0; + clipMap->box_brush->sides = nullptr; + clipMap->box_brush->verts = nullptr; + } + + void ClipMapLinker::loadRopesAndConstraints(clipMap_t* clipMap) + { + clipMap->num_constraints = 0; // max 511 + clipMap->constraints = NULL; + + // The game allocates 32 empty ropes + clipMap->max_ropes = 32; // max 300 + clipMap->ropes = m_memory.Alloc (clipMap->max_ropes); + memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); + } + + void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) + { + // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) + auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); + assert(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + // Right now there is only one submodel, the world sub model + assert(gfxWorld->modelCount == 1); + + clipMap->numSubModels = 1; + clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + + GfxBrushModel* gfxModel = &gfxWorld->models[0]; + clipMap->cmodels[0].mins.x = gfxModel->bounds[0].x; + clipMap->cmodels[0].mins.y = gfxModel->bounds[0].y; + clipMap->cmodels[0].mins.z = gfxModel->bounds[0].z; + clipMap->cmodels[0].maxs.x = gfxModel->bounds[1].x; + clipMap->cmodels[0].maxs.y = gfxModel->bounds[1].y; + clipMap->cmodels[0].maxs.z = gfxModel->bounds[1].z; + clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + + // The world sub model has no leafs associated with it + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = 0; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + + clipMap->cmodels[0].info = NULL; // always set to 0 + } + + void ClipMapLinker::loadXModelCollision(clipMap_t* clipMap) + { + // Right now XModels aren't supported + clipMap->numStaticModels = 0; + clipMap->staticModelList = nullptr; + + // WIP code left in for future support + /* + auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); + assert(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; + clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; + + for (unsigned int i = 0; i < clipMap->numStaticModels; i++) + { + GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; + GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; + cStaticModel_s* currModel = &clipMap->staticModelList[i]; + + memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); + currModel->xmodel = gfxModelDrawInst->model; + currModel->contents = gfxModelDrawInst->model->contents; + currModel->origin.x = gfxModelDrawInst->placement.origin.x; + currModel->origin.y = gfxModelDrawInst->placement.origin.y; + currModel->origin.z = gfxModelDrawInst->placement.origin.z; + + // TODO: this does not account for model rotation or scale + currModel->absmin.x = gfxModelInst->mins.x; + currModel->absmin.y = gfxModelInst->mins.y; + currModel->absmin.z = gfxModelInst->mins.z; + currModel->absmax.x = gfxModelInst->maxs.x; + currModel->absmax.y = gfxModelInst->maxs.y; + currModel->absmax.z = gfxModelInst->maxs.z; + + BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); + currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; + currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; + currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; + currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; + currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; + currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; + currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; + currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; + currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; + } + */ + } + + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + out_origin->x = temp.x * 0.5f; + out_origin->y = temp.y * 0.5f; + out_origin->z = temp.z * 0.5f; + + // Half-size is half the difference: (max - min) / 2 + temp.x = maxs->x - mins->x; + temp.y = maxs->y - mins->y; + temp.z = maxs->z - mins->z; + out_halfSize->x = temp.x * 0.5f; + out_halfSize->y = temp.y * 0.5f; + out_halfSize->z = temp.z * 0.5f; + } + + void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->leaf->getObjectCount() > 0) + { + *numAABBTrees += node->leaf->getObjectCount() + 1; + + if (node->leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + } + } + + vec3_t normalX = { 1.0f, 0.0f, 0.0f }; + vec3_t normalY = { 0.0f, 1.0f, 0.0f }; + vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; + + int currPlaneCount = 0; + int currNodeCount = 0; + int currLeafCount = 0; + int currAABBCount = 0; + + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + { + assert(node->isLeaf); + + int objectCount = node->leaf->getObjectCount(); + int firstAABBIndex = currAABBCount; + currAABBCount += objectCount + 1; + + // calculate root AABB node mins and maxs + // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs + // so we have to recompute every min and max, not hard just tedious + int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; + auto firstPartition = &clipMap->partitions[firstPartitionIndex]; + uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t aabbMins; + vec3_t aabbMaxs; + aabbMins.x = firstVert->x; + aabbMins.y = firstVert->y; + aabbMins.z = firstVert->z; + aabbMaxs.x = firstVert->x; + aabbMaxs.y = firstVert->y; + aabbMaxs.z = firstVert->z; + for (int i = 0; i < objectCount; i++) + { + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + auto currPartition = &clipMap->partitions[currPartitionIndex]; + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = clipMap->verts[vertIndex]; + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; + aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); + rootAABB->materialIndex = 0; + rootAABB->childCount = objectCount; + rootAABB->u.firstChildIndex = firstAABBIndex + 1; + + // populate child AABB nodes + for (int i = 0; i < objectCount; i++) + { + CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + + currAabbTree->materialIndex = 0; + currAabbTree->childCount = 0; + currAabbTree->u.partitionIndex = currPartitionIndex; + + // calculate partition origin and half size + CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; + uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; + vec3_t* firstVertex = &clipMap->verts[firstUind]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVertex->x; + mins.y = firstVertex->y; + mins.z = firstVertex->z; + maxs.x = firstVertex->x; + maxs.y = firstVertex->y; + maxs.z = firstVertex->z; + for (int i = 1; i < aabbPartition->nuinds; i++) + { + uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; + vec3_t* currVertex = &clipMap->verts[currUind]; + + BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + } + + aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + } + + return firstAABBIndex; + } + + // returns the index corresponding to the BSPTree* node parsed + int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + { + if (node->isLeaf) + { + int currLeafIndex = currLeafCount; + currLeafCount++; + cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + + currLeaf->cluster = 0; + currLeaf->brushContents = 0; // no brushes used so contents is 0 + currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + + // unused when leafBrushNode == 0 + currLeaf->mins.x = 0.0f; + currLeaf->mins.y = 0.0f; + currLeaf->mins.z = 0.0f; + currLeaf->maxs.x = 0.0f; + currLeaf->maxs.y = 0.0f; + currLeaf->maxs.z = 0.0f; + currLeaf->leafBrushNode = 0; + + if (node->leaf->getObjectCount() > 0) + { + currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); + currLeaf->collAabbCount = 1; + } + else + { + currLeaf->firstCollAabbIndex = 0; + currLeaf->collAabbCount = 0; + } + + return -1 - currLeafIndex; + } + else + { + cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; + currPlaneCount++; + + if (node->node->axis == AXIS_X) + { + // X is unchanged when going from OGL x -> BO2 x + currPlane->normal = normalX; + + // converting OGL -> BO2 X coords doesn't change the x coords at all, so + // the dist stays the same + currPlane->dist = (float)node->node->distance; + } + else + { + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // convert the z normal to the y normal, but don't negate it. Negative normals don't do + // what is expected when the game uses them + assert(node->node->axis == AXIS_Z); + currPlane->normal = normalY; + + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // just negate it here as it is just the distance from the orgin along the axis + currPlane->dist = (float)(-node->node->distance); + } + + bool foundType = false; + if (currPlane->normal.x == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 0; + } + else if (currPlane->normal.y == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 1; + } + else if (currPlane->normal.z == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 2; + } + else + assert(foundType); + + currPlane->signbits = 0; + if (currPlane->normal.x < 0.0f) + currPlane->signbits |= 1; + if (currPlane->normal.y < 0.0f) + currPlane->signbits |= 2; + if (currPlane->normal.z < 0.0f) + currPlane->signbits |= 4; + + currPlane->pad[0] = 0; + currPlane->pad[1] = 0; + + int currNodeIndex = currNodeCount; + currNodeCount++; + cNode_t* currNode = &clipMap->nodes[currNodeIndex]; + + currNode->plane = currPlane; + // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): + // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, + // the normal vector flips and objects behind the plane are now in front, and vise versa + // so the back node now represents the front, and the front node represents the back. + // Do the OGL -> Bo2 coord change on paper and it will make sense + if (currPlane->type == 1) + { + currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); + } + + return currNodeIndex; + } + } + + void ClipMapLinker::populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + size_t numPlanes = 0; + size_t numNodes = 0; + size_t numLeafs = 0; + size_t numAABBTrees = 0; + size_t maxObjsPerLeaf = 0; + + traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); + + printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); + + clipMap->info.planeCount = numPlanes; + clipMap->info.planes = m_memory.Alloc(clipMap->info.planeCount); + clipMap->numNodes = numNodes; + clipMap->nodes = m_memory.Alloc(clipMap->numNodes); + // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. + // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. + clipMap->aabbTreeCount = numAABBTrees; + clipMap->aabbTrees = m_memory.Alloc(clipMap->aabbTreeCount); + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = m_memory.Alloc(clipMap->numLeafs); + memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); + currLeafCount = 1; + + populateBSPTree_r(clipMap, tree); + + assert(clipMap->info.planeCount == currPlaneCount); + assert(clipMap->numNodes == currNodeCount); + assert(clipMap->numLeafs == currLeafCount); + assert(clipMap->aabbTreeCount == currAABBCount); + } + + bool ClipMapLinker::createPartitions(clipMap_t* clipMap, BSPData* bsp) + { + int collisionVertexCount = bsp->colWorld.vertices.size(); + std::vector collisionVertVec; + for (int i = 0; i < collisionVertexCount; i++) + { + collisionVertVec.push_back(BSPUtil::convertToBO2Coords(bsp->colWorld.vertices[i].pos)); + //collisionVertVec.push_back(bsp->colWorld.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = m_memory.Alloc(collisionVertexCount); + memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); + return false; + } + + std::vector triIndexVec; + for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + { + BSPSurface* currSurface = &bsp->colWorld.surfaces[i]; + int triCount = currSurface->triCount; + + for (int k = 0; k < triCount * 3; k += 3) + { + int firstIndex_Index = currSurface->indexOfFirstIndex; + int firstVertexIndex = currSurface->indexOfFirstVertex; + + // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire + // clipmap verts buffer, so this code updates the indexes to follow that. + int triIndex0 = bsp->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = bsp->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = bsp->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + + // triangle index ordering is opposite to blenders, so its converted here + triIndexVec.push_back(triIndex2); + triIndexVec.push_back(triIndex1); + triIndexVec.push_back(triIndex0); + } + } + assert(triIndexVec.size() % 3 == 0); + + // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, + // and static_cast can't safely do the conversion + clipMap->triCount = triIndexVec.size() / 3; + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + std::vector partitionVec; + for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + { + int triCount = bsp->colWorld.surfaces[i].triCount; + int firstTriIndex = bsp->colWorld.surfaces[i].indexOfFirstIndex / 3; + for (int k = 0; k < triCount; k++) + { + CollisionPartition newPartition; + newPartition.nuinds = 0; // initialised later + newPartition.fuind = 0; // initialised later + newPartition.triCount = 1; + newPartition.firstTri = firstTriIndex; + firstTriIndex += 1; + + partitionVec.push_back(newPartition); + } + } + clipMap->partitionCount = partitionVec.size(); + clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); + memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); + + int totalUindCount = 0; + std::vector uindVec; + for (int i = 0; i < clipMap->partitionCount; i++) + { + CollisionPartition* currPartition = &clipMap->partitions[i]; + std::vector uniqueVertVec; + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (size_t m = 0; m < uniqueVertVec.size(); m++) + { + if (uniqueVertVec[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + uniqueVertVec.push_back(vertIndex); + } + } + + currPartition->fuind = totalUindCount; + currPartition->nuinds = (int)uniqueVertVec.size(); + uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); + totalUindCount += currPartition->nuinds; + } + clipMap->info.nuinds = totalUindCount; + clipMap->info.uinds = m_memory.Alloc(totalUindCount); + memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); + + return true; + } + + bool ClipMapLinker::loadBrushCollision(clipMap_t* clipMap, BSPData* bsp) + { + // No support for brushes, only tris right now + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = NULL; + clipMap->info.leafbrushNodesCount = 0; + clipMap->info.leafbrushNodes = NULL; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = NULL; + clipMap->info.numBrushVerts = 0; + clipMap->info.brushVerts = NULL; + clipMap->info.numBrushes = NULL; + clipMap->info.brushes = NULL; + clipMap->info.brushBounds = NULL; + clipMap->info.brushContents = NULL; + + // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, + // and then convert them back when it is being parsed into the clipmap. Requires some hacky + // logic, check populateBSPTree_r and addAABBTreeFromLeaf + + if (!createPartitions(clipMap, bsp)) + return false; + + vec3_t* firstVert = &clipMap->verts[0]; + vec3_t clipMins; + vec3_t clipMaxs; + clipMins.x = firstVert->x; + clipMins.y = firstVert->y; + clipMins.z = firstVert->z; + clipMaxs.x = firstVert->x; + clipMaxs.y = firstVert->y; + clipMaxs.z = firstVert->z; + clipMins = BSPUtil::convertFromBO2Coords(clipMins); + clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); + for (unsigned int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + + assert(!tree->isLeaf); + + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVert->x; + mins.y = firstVert->y; + mins.z = firstVert->z; + maxs.x = firstVert->x; + maxs.y = firstVert->y; + maxs.z = firstVert->z; + mins = BSPUtil::convertFromBO2Coords(mins); + maxs = BSPUtil::convertFromBO2Coords(maxs); + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObjectToTree(std::move(currObject)); + } + + populateBSPTree(clipMap, tree); + + return true; + } + + AssetCreationResult ClipMapLinker::linkClipMap(BSPData* bsp) + { + clipMap_t* clipMap = m_memory.Alloc(); + clipMap->name = m_memory.Dup(bsp->bspName.c_str()); + + clipMap->isInUse = true; + clipMap->checksum = 0; + clipMap->pInfo = nullptr; + + std::string mapEntsName = bsp->bspName; + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + assert(mapEntsAsset != nullptr); + clipMap->mapEnts = mapEntsAsset->Asset(); + + loadBoxData(clipMap); + + loadVisibility(clipMap); + + loadRopesAndConstraints(clipMap); + + loadSubModelCollision(clipMap, bsp); + + loadDynEnts(clipMap); + + loadXModelCollision(clipMap); + + // Clipmap materials define the properties of a material (bullet penetration, no collision, water, etc) + // Right now there is no way to define properties per material so only one material is used + clipMap->info.numMaterials = 1; + clipMap->info.materials = m_memory.Alloc(clipMap->info.numMaterials); + clipMap->info.materials[0].name = m_memory.Dup(BSPLinkingConstants::MISSING_IMAGE_NAME); + clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + + // set all edges to walkable + // might do weird stuff on walls, but from testing doesnt seem to do anything + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + + if (!loadBrushCollision(clipMap, bsp)) + return AssetCreationResult::Failure(); + + m_context.AddAsset(clipMap->name, clipMap); + + auto clipMapAsset = m_context.AddAsset(clipMap->name, clipMap); + return AssetCreationResult::Success(clipMapAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h new file mode 100644 index 000000000..9a08ebd7a --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "../BSPCalculation.h" + +namespace BSP +{ + class ClipMapLinker + { + public: + ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkClipMap(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + + void loadBoxData(clipMap_t* clipMap); + void loadVisibility(clipMap_t* clipMap); + void loadDynEnts(clipMap_t* clipMap); + void loadRopesAndConstraints(clipMap_t* clipMap); + void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); + void loadXModelCollision(clipMap_t* clipMap); + + bool loadBrushCollision(clipMap_t* clipMap, BSPData* bsp); + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree); + bool createPartitions(clipMap_t* clipMap, BSPData* bsp); + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp new file mode 100644 index 000000000..db15bd4ce --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -0,0 +1,40 @@ +#include "ComWorldLinker.h" + +namespace BSP +{ + ComWorldLinker::ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult ComWorldLinker::linkComWorld(BSPData* bsp) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = m_memory.Alloc(); + comWorld->name = m_memory.Dup(bsp->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = m_memory.Alloc(comWorld->primaryLightCount); + + // static light is always empty + ComPrimaryLight* staticLight = &comWorld->primaryLights[0]; + memset(staticLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + sunLight->diffuseColor.r = 0.75f; + sunLight->diffuseColor.g = 0.75f; + sunLight->diffuseColor.b = 0.75f; + sunLight->diffuseColor.a = 1.0f; + sunLight->dir.x = 0.0f; + sunLight->dir.y = 0.0f; + sunLight->dir.z = 0.0f; + + auto comWorldAsset = m_context.AddAsset(comWorld->name, comWorld); + return AssetCreationResult::Success(comWorldAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h new file mode 100644 index 000000000..21be6a161 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class ComWorldLinker + { + public: + ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkComWorld(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp new file mode 100644 index 000000000..1286837bd --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp @@ -0,0 +1,38 @@ +#include "GameWorldMpLinker.h" + +namespace BSP +{ + GameWorldMpLinker::GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult GameWorldMpLinker::linkGameWorldMp(BSPData* bsp) + { + GameWorldMp* gameWorldMp = m_memory.Alloc(); + + gameWorldMp->name = m_memory.Dup(bsp->bspName.c_str()); + + gameWorldMp->path.nodeCount = 0; + gameWorldMp->path.originalNodeCount = 0; + gameWorldMp->path.visBytes = 0; + gameWorldMp->path.smoothBytes = 0; + gameWorldMp->path.nodeTreeCount = 0; + + int nodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = m_memory.Alloc(nodeCount); + gameWorldMp->path.basenodes = m_memory.Alloc(nodeCount); + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = nullptr; + gameWorldMp->path.smoothCache = nullptr; + gameWorldMp->path.nodeTree = nullptr; + + auto gameWorldMpAsset = m_context.AddAsset(gameWorldMp->name, gameWorldMp); + return AssetCreationResult::Success(gameWorldMpAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h new file mode 100644 index 000000000..a1ca459ef --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class GameWorldMpLinker + { + public: + GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGameWorldMp(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp new file mode 100644 index 000000000..8d42b256d --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -0,0 +1,17 @@ +#include "GfxWorldLinker.h" + +namespace BSP +{ + GfxWorldLinker::GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult GfxWorldLinker::linkGfxWorld(BSPData* bsp) + { + return AssetCreationResult::Failure(); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h new file mode 100644 index 000000000..7d2193207 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class GfxWorldLinker + { + public: + GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGfxWorld(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp new file mode 100644 index 000000000..5ba3145b4 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -0,0 +1,128 @@ +#include "MapEntsLinker.h" +#include "../BSPUtil.h" + +#include +using namespace nlohmann; + +namespace +{ + bool parseMapEntsJSON(json& entArrayJs, std::string& entityString) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + if (i == 0) + { + std::string className = currEntity["classname"]; + if (className.compare("worldspawn") != 0) + { + con::error("ERROR: first entity in the map entity string must be the worldspawn class!"); + return false; + } + } + + entityString.append("{\n"); + + for (auto& element : currEntity.items()) + { + std::string key = element.key(); + std::string value = element.value(); + entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); + } + + entityString.append("}\n"); + } + + return true; + } + + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + std::string origin = currEntity["origin"]; + std::string angles = currEntity["angles"]; + + for (int k = 0; k < nameCount; k++) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", origin)); + entityString.append(std::format("\"angles\" \"{}\"\n", angles)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); + entityString.append("}\n"); + } + } + } +} + +namespace BSP +{ + MapEntsLinker::MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult MapEntsLinker::linkMapEnts(BSPData* bsp) + { + std::string entityString; + + json entJs; + std::string entityFilePath = BSPUtil::getFileNameForBSPAsset("entities.json"); + const auto entFile = m_search_path.Open(entityFilePath); + if (!entFile.IsOpen()) + { + con::warn("Can't find entity file {}, using default entities instead", entityFilePath); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + } + else + { + entJs = json::parse(*entFile.m_stream); + } + if (!parseMapEntsJSON(entJs["entities"], entityString)) + return AssetCreationResult::Failure(); + + json spawnJs; + std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset("spawns.json"); + const auto spawnFile = m_search_path.Open(spawnFilePath); + if (!spawnFile.IsOpen()) + { + con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); + spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + int defenderNameCount = std::extent::value; + int attackerNameCount = std::extent::value; + int ffaNameCount = std::extent::value; + parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); + parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); + parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + + MapEnts* mapEnts = m_memory.Alloc(); + mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); + + mapEnts->entityString = m_memory.Dup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = nullptr; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = nullptr; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = nullptr; + + auto mapEntsAsset = m_context.AddAsset(mapEnts->name, mapEnts); + return AssetCreationResult::Success(mapEntsAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h new file mode 100644 index 000000000..921af16da --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class MapEntsLinker + { + public: + MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkMapEnts(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp new file mode 100644 index 000000000..2647a14bb --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -0,0 +1,26 @@ +#include "SkinnedVertsLinker.h" + +namespace BSP +{ + SkinnedVertsLinker::SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) + { + std::string assetName = "skinnedverts"; + + // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // But setting it to the world vertex count seems to work + SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); + skinnedVerts->name = m_memory.Dup(assetName.c_str()); + skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.vertices.size()); + + auto skinnedVertsAsset = m_context.AddAsset(assetName, skinnedVerts); + return AssetCreationResult::Success(skinnedVertsAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h new file mode 100644 index 000000000..db7f03b78 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class SkinnedVertsLinker + { + public: + SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkSkinnedVerts(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file From 173565c7b316bd8cb47cb508acd4b96348b47626 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Fri, 24 Oct 2025 15:34:44 +0800 Subject: [PATCH 025/123] WIP: Finished updating BSP linker to use seperate files for each asset. --- src/ObjLoading/Game/T6/BSP/CustomMapLinker.h | 1987 ----------------- .../Game/T6/BSP/Linker/BSPLinker.cpp | 27 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 827 ++++++- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 14 + src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 17 +- 5 files changed, 867 insertions(+), 2005 deletions(-) delete mode 100644 src/ObjLoading/Game/T6/BSP/CustomMapLinker.h diff --git a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h deleted file mode 100644 index 33ea1dce5..000000000 --- a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h +++ /dev/null @@ -1,1987 +0,0 @@ -#pragma once - -#include "BSP.h" -#include "BSPCalculation.h" -#include "BSPUtil.h" - -#include "Game/T6/Material/JsonMaterialLoaderT6.h" -#include "Utils/Pack.h" - -#include -using namespace nlohmann; - -namespace BSP -{ - class CustomMapLinker - { - public: - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_zone(zone), - m_context(context) - { - hasLinkFailed = false; - } - - bool linkCustomMap(BSPData* projInfo) - { - _ASSERT(projInfo != NULL); - - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - - createComWorld(projInfo); - createMapEnts(projInfo); - createGameWorldMp(projInfo); - createSkinnedVerts(projInfo); - createGfxWorld(projInfo); // requires mapents asset - createClipMap(projInfo); // must go last (requires gfx and mapents asset) - - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - return true; - } - - private: - struct entModelBounds - { - vec3_t mins; - vec3_t maxs; - }; - - MemoryManager& m_memory; - ISearchPath& m_search_path; - Zone& m_zone; - AssetCreationContext& m_context; - - bool hasLinkFailed; - std::vector entityModelList; - - json materialTemplateJson; - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - bool overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) - { - int vertexCount = projInfo->gfxWorld.vertices.size(); - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); - GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { - BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); - //GfxVertex->xyz = WorldVertex->pos; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); - //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); - //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - - // unknown use variables - // binormalSign may be bitangent of the vertex - // lmapCoord may be the lightmap coordinate of the vertex - GfxVertex->binormalSign = 0.0f; - GfxVertex->lmapCoord.packed = 0; - } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; - - // we don't use vd1 but still needs to be initialised - // the data type varies and 0x20 is enough for all types - gfxWorld->draw.vertexDataSize1 = 0x20; - gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - - int indexCount = projInfo->gfxWorld.indices.size(); - _ASSERT(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; - gfxWorld->draw.indices = new uint16_t[indexCount]; - for (int i = 0; i < indexCount; i += 3) - { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; - } - - return true; - } - - struct s_sortedSurf - { - int surfaceIndex; - int vertexCount; - }; - - bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) - { - return surf0.vertexCount > surf1.vertexCount; - } - - Material* loadImageIntoMaterial(std::string& imageName) - { - std::string imagePath = std::format("images/{}.iwi", imageName); - auto imageFile = m_search_path.Open(imagePath); - if (!imageFile.IsOpen()) - { - printf("WARN: failed to find image %s.\n", imageName.c_str()); - return NULL; - } - - Material* material = new Material; - material->info.name = m_memory.Dup(imageName.c_str()); - - // parse the template file and replace the image name - materialTemplateJson["textures"][1]["image"] = imageName; - - AssetRegistration registration(imageName, material); - if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) - { - printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); - return NULL; - } - - m_context.AddAsset(std::move(registration)); - - return material; - } - - void overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) - { - bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); - if (!overwriteResult) - return; - - unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; - gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxWorld.surfaces[i]; - - currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; - - currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; - - std::string surfMaterialName; - switch (objSurface->material.materialType) - { - case MATERIAL_TYPE_TEXTURE: - surfMaterialName = objSurface->material.materialName; - break; - - case MATERIAL_TYPE_COLOUR: - case MATERIAL_TYPE_EMPTY: - surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - break; - - default: - _ASSERT(false); - } - Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); - if (surfMaterial == NULL) - { - std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); - surfMaterial = loadImageIntoMaterial(missingImageName); - if (surfMaterial == NULL) - { - printf("Error: unable to find the missing image texture!\n"); - hasLinkFailed = true; - return; - } - } - currSurface->material = surfMaterial; - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) - { - uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); - } - - // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; - } - - // doesn't seem to matter what order the sorted surfs go in - gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = i; - } - - gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); - - // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; - memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); - - gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; - } - - void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) - { - /* - Models are unsupported right now - Code is left in in case it is supported later on - */ - //unsigned int modelCount = projInfo->modelCount; - //gfxWorld->dpvs.smodelCount = modelCount; - //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - // - //for (unsigned int i = 0; i < modelCount; i++) - //{ - // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - // customMapModel* inModel = &projInfo->models[i]; - // - // auto xModelAsset = m_context.LoadDependency(inModel->name); - // if (xModelAsset == NULL) - // { - // printf("XModel %s not found!\n", inModel->name.c_str()); - // currModel->model = NULL; - // } - // else - // currModel->model = (XModel*)xModelAsset->Asset(); - // - // currModel->placement.origin.x = inModel->origin.x; - // currModel->placement.origin.y = inModel->origin.y; - // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - // currModel->placement.scale = inModel->scale; - // - // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - // - // // mins and maxs are calculated in world space not local space - // // TODO: this does not account for model rotation or scale - // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - // - // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - // currModel->flags = DEFAULT_SMODEL_FLAGS; - // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - // - // // unknown use / unused - // currModel->smid = i; - // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - // currModel->invScaleSq = 0.0f; - // currModel->lightingHandle = 0; - // currModel->colorsIndex = 0; - // currModel->visibility = 0; - // - // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // // TODO: calculate lighting and store it here - // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - //} - - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - - // all visdata is alligned by 128 - int allignedModelCount = BSPUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; - gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; - memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); - for (unsigned int i = 0; i < modelCount; i++) - { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) - gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; - } - - // always set to 0 - gfxWorld->dpvs.usageCount = 0; - } - - void cleanGfxWorld(GfxWorld* gfxWorld) - { - // checksum is generated by the game - gfxWorld->checksum = 0; - - // Remove Coronas - gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; - - // Remove exposure volumes - gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; - gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; - - // Remove hero lights - gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; - gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; - - // remove LUT data - gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; - gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; - - // remove occluders - gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; - - // remove Siege Skins - gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; - - // remove outdoor bounds - gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; - - // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; - - // remove shadow maps - gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; - gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; - - // remove stream info - gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; - gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; - - // remove sun data - memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); - gfxWorld->sun.hasValidData = false; - - // Remove Water - gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; - - // Remove Fog - gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; - gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; - gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; - gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; - - // materialMemory is unused - gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; - - // sunLight is overwritten by the game, just needs to be a valid pointer - gfxWorld->sunLight = new GfxLight; - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); - } - - void overwriteGfxLights(GfxWorld* gfxWorld) - { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - - gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; - memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); - } - - gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; - } - - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; - if (lightEntShadowVisSize != 0) - { - gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); - } - else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } - } - - // the lightgrid is used to light models in a dynamic way and is precomputed - void overwriteLightGrid(GfxWorld* gfxWorld) - { - // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. - // it works and that is all thats needed :) - - // mins and maxs define the range that the lightgrid will work in - // idk how these values are calculated, but the below values are larger - // than official map values - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 200; - gfxWorld->lightGrid.maxs[1] = 200; - gfxWorld->lightGrid.maxs[2] = 50; - - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - gfxWorld->lightGrid.offset = 0.0f; // default value - - // this will make the lookup into rawRowData always return the first row - int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; - gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; - memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - - gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); - GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); - row->colStart = 0; - row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game - row->zStart = 0; - row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks - row->firstEntry = 0; - for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 - { - row->lookupTable[i] = 0; - } - gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; - - // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow - gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data - GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; - for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) - { - entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - entryArray[i].visibility = 0; - } - gfxWorld->lightGrid.entries = entryArray; - - // colours are looked up by an entries colourindex - gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index - gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; - memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); - - // we use the colours array instead of coeffs array - gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; - } - - void updateGfxCells(GfxWorld* gfxWorld) - { - // Cells are basically data used to determine what can be seen and what cant be seen - // Right now custom maps have no optimisation so there is only 1 cell - int cellCount = 1; - - gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; - - int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); - gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); - - gfxWorld->cells = new GfxCell[cellCount]; - gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; - - // there is only 1 reflection probe - gfxWorld->cells[0].reflectionProbeCount = 1; - char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; - reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; - - // AABB trees are used to detect what should be rendered and what shouldn't - // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn - gfxWorld->cells[0].aabbTreeCount = 1; - gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; - gfxWorld->cells[0].aabbTree[0].childCount = 0; - gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; - gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) - { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; - } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node - // Nodes mnode_t.cellIndex indexes gfxWorld->cells - // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes - gfxWorld->nodeCount = 1; - gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; - gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 - - // planes are overwritten by the clipmap loading code ingame - gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.planes = NULL; - } - - void updateWorldBounds(GfxWorld* gfxWorld) - { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - - for (int i = 0; i < gfxWorld->surfaceCount; i++) - { - BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); - } - } - - void overwriteModels(GfxWorld* gfxWorld) - { - // these models are the collision for the entities defined in the mapents asset - // used for triggers and stuff - - gfxWorld->modelCount = entityModelList.size() + 1; - gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - - // first model is always the world model - gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - - for (size_t i = 0; i < entityModelList.size(); i++) - { - auto currEntModel = &gfxWorld->models[i + 1]; - entModelBounds currEntModelBounds = entityModelList[i]; - - currEntModel->startSurfIndex = 0; - currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces - currEntModel->bounds[0].x = currEntModelBounds.mins.x; - currEntModel->bounds[0].y = currEntModelBounds.mins.y; - currEntModel->bounds[0].z = currEntModelBounds.mins.z; - currEntModel->bounds[1].x = currEntModelBounds.maxs.x; - currEntModel->bounds[1].y = currEntModelBounds.maxs.y; - currEntModel->bounds[1].z = currEntModelBounds.maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - } - } - - void updateSunData(GfxWorld* gfxWorld) - { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = (float)0.001; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; - - gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; - gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; - gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; - gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; - gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; - gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; - gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; - gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; - gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; - gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; - gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; - gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; - gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; - gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; - gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; - gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; - } - - void updateReflectionProbeData(GfxWorld* gfxWorld) - { - gfxWorld->draw.reflectionProbeCount = 1; - - gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; - - // default values taken from mp_dig - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; - - gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - - std::string probeImageName = "*reflection_probe0"; - auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) - { - printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); - } - - void overwriteLightmapData(GfxWorld* gfxWorld) - { - gfxWorld->draw.lightmapCount = 1; - - gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - - std::string secondaryTexture = "*lightmap0_secondary"; - auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) - { - printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL - gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); - } - - void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) - { - std::string skyBoxName = "skybox_" + projInfo->name; - gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); - - if (m_context.LoadDependency(skyBoxName) == NULL) - { - printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); - } - - // default skybox values from mp_dig - gfxWorld->skyDynIntensity.angle0 = 0.0f; - gfxWorld->skyDynIntensity.angle1 = 0.0f; - gfxWorld->skyDynIntensity.factor0 = 1.0f; - gfxWorld->skyDynIntensity.factor1 = 1.0f; - } - - void updateDynEntData(GfxWorld* gfxWorld) - { - int dynEntCount = 0; - gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. - // Making the value much larger than required fixes it, but idk what the root cause is - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; - gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; - gfxWorld->dpvsDyn.usageCount = 0; - - int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; - gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); - - int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; - gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); - gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); - - gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); - } - - void updateOutdoors(GfxWorld* gfxWorld) - { - float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); - float xScale = -(xRecip * gfxWorld->mins.x); - - float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); - float yScale = -(yRecip * gfxWorld->mins.y); - - float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); - float zScale = -(zRecip * gfxWorld->mins.z); - - memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); - - gfxWorld->outdoorLookupMatrix[0].x = xRecip; - gfxWorld->outdoorLookupMatrix[1].y = yRecip; - gfxWorld->outdoorLookupMatrix[2].z = zRecip; - gfxWorld->outdoorLookupMatrix[3].x = xScale; - gfxWorld->outdoorLookupMatrix[3].y = yScale; - gfxWorld->outdoorLookupMatrix[3].z = zScale; - gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - - std::string outdoorImageName = std::string("$outdoor"); - auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) - { - printf("ERROR! unable to find image $outdoor!\n"); - hasLinkFailed = true; - return; - } - gfxWorld->outdoorImage = outdoorImageAsset->Asset(); - } - - void createGfxWorld(BSPData* projInfo) - { - GfxWorld* gfxWorld = new GfxWorld; - gfxWorld->baseName = _strdup(projInfo->name.c_str()); - gfxWorld->name = _strdup(projInfo->bspName.c_str()); - - // Default values taken from mp_dig - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; - - cleanGfxWorld(gfxWorld); - - overwriteMapSurfaces(projInfo, gfxWorld); - - overwriteMapSModels(projInfo, gfxWorld); - - overwriteLightmapData(gfxWorld); - - overwriteSkyBox(projInfo, gfxWorld); - - updateReflectionProbeData(gfxWorld); - - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); - - updateOutdoors(gfxWorld); - - // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); - - overwriteLightGrid(gfxWorld); - - overwriteGfxLights(gfxWorld); - - overwriteModels(gfxWorld); - - updateSunData(gfxWorld); - - updateDynEntData(gfxWorld); - - m_context.AddAsset(gfxWorld->name, gfxWorld); - } - - void addXModelsToCollision(BSPData* projInfo, clipMap_t* clipMap) - { - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; - clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; - - for (unsigned int i = 0; i < clipMap->numStaticModels; i++) - { - GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; - GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; - cStaticModel_s* currModel = &clipMap->staticModelList[i]; - - memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); - currModel->xmodel = gfxModelDrawInst->model; - currModel->contents = gfxModelDrawInst->model->contents; - currModel->origin.x = gfxModelDrawInst->placement.origin.x; - currModel->origin.y = gfxModelDrawInst->placement.origin.y; - currModel->origin.z = gfxModelDrawInst->placement.origin.z; - - // TODO: this does not account for model rotation or scale - currModel->absmin.x = gfxModelInst->mins.x; - currModel->absmin.y = gfxModelInst->mins.y; - currModel->absmin.z = gfxModelInst->mins.z; - currModel->absmax.x = gfxModelInst->maxs.x; - currModel->absmax.y = gfxModelInst->maxs.y; - currModel->absmax.z = gfxModelInst->maxs.z; - - BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); - currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; - currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; - currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; - currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; - currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; - currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; - currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; - currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; - currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; - } - } - - void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) - { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - out_origin->x = temp.x * 0.5f; - out_origin->y = temp.y * 0.5f; - out_origin->z = temp.z * 0.5f; - - // Half-size is half the difference: (max - min) / 2 - temp.x = maxs->x - mins->x; - temp.y = maxs->y - mins->y; - temp.z = maxs->z - mins->z; - out_halfSize->x = temp.x * 0.5f; - out_halfSize->y = temp.y * 0.5f; - out_halfSize->z = temp.z * 0.5f; - } - - bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) - { - - unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); - - return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; - } - - void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) - { - if (node->isLeaf) - { - (*numLeafs)++; - // there won't be an AABB tree when objectList is empty - if (node->leaf->getObjectCount() > 0) - { - *numAABBTrees += node->leaf->getObjectCount() + 1; - - if (node->leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->leaf->getObjectCount(); - } - } - else - { - (*numPlanes)++; - (*numNodes)++; - traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - } - } - - vec3_t normalX = { 1.0f, 0.0f, 0.0f }; - vec3_t normalY = { 0.0f, 1.0f, 0.0f }; - vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; - - int currPlaneCount = 0; - int currNodeCount = 0; - int currLeafCount = 0; - int currAABBCount = 0; - - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) - { - _ASSERT(node->isLeaf); - - int objectCount = node->leaf->getObjectCount(); - int firstAABBIndex = currAABBCount; - currAABBCount += objectCount + 1; - - // calculate root AABB node mins and maxs - // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs - // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; - auto firstPartition = &clipMap->partitions[firstPartitionIndex]; - uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t aabbMins; - vec3_t aabbMaxs; - aabbMins.x = firstVert->x; - aabbMins.y = firstVert->y; - aabbMins.z = firstVert->z; - aabbMaxs.x = firstVert->x; - aabbMaxs.y = firstVert->y; - aabbMaxs.z = firstVert->z; - for (int i = 0; i < objectCount; i++) - { - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - auto currPartition = &clipMap->partitions[currPartitionIndex]; - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = clipMap->verts[vertIndex]; - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); - } - } - } - CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; - aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); - rootAABB->materialIndex = 0; - rootAABB->childCount = objectCount; - rootAABB->u.firstChildIndex = firstAABBIndex + 1; - - // populate child AABB nodes - for (int i = 0; i < objectCount; i++) - { - CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - - currAabbTree->materialIndex = 0; - currAabbTree->childCount = 0; - currAabbTree->u.partitionIndex = currPartitionIndex; - - // calculate partition origin and half size - CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; - uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; - vec3_t* firstVertex = &clipMap->verts[firstUind]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVertex->x; - mins.y = firstVertex->y; - mins.z = firstVertex->z; - maxs.x = firstVertex->x; - maxs.y = firstVertex->y; - maxs.z = firstVertex->z; - for (int i = 1; i < aabbPartition->nuinds; i++) - { - uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; - vec3_t* currVertex = &clipMap->verts[currUind]; - - BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); - } - - aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); - } - - return firstAABBIndex; - } - - // returns the index corresponding to the BSPTree* node parsed - int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) - { - new cplane_s; - new cNode_t; - new cLeaf_s; - new CollisionAabbTree; - - if (node->isLeaf) - { - int currLeafIndex = currLeafCount; - currLeafCount++; - cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; - - currLeaf->cluster = 0; - currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence - - // unused when leafBrushNode == 0 - currLeaf->mins.x = 0.0f; - currLeaf->mins.y = 0.0f; - currLeaf->mins.z = 0.0f; - currLeaf->maxs.x = 0.0f; - currLeaf->maxs.y = 0.0f; - currLeaf->maxs.z = 0.0f; - currLeaf->leafBrushNode = 0; - - if (node->leaf->getObjectCount() > 0) - { - currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); - currLeaf->collAabbCount = 1; - } - else - { - currLeaf->firstCollAabbIndex = 0; - currLeaf->collAabbCount = 0; - } - - return -1 - currLeafIndex; - } - else - { - cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; - currPlaneCount++; - - if (node->node->axis == AXIS_X) - { - // X is unchanged when going from OGL x -> BO2 x - currPlane->normal = normalX; - - // converting OGL -> BO2 X coords doesn't change the x coords at all, so - // the dist stays the same - currPlane->dist = (float)node->node->distance; - } - else - { - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // convert the z normal to the y normal, but don't negate it. Negative normals don't do - // what is expected when the game uses them - _ASSERT(node->node->axis == AXIS_Z); - currPlane->normal = normalY; - - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = (float)(-node->node->distance); - } - - bool foundType = false; - if (currPlane->normal.x == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 0; - } - else if (currPlane->normal.y == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 1; - } - else if (currPlane->normal.z == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 2; - } - else - _ASSERT(foundType); - - currPlane->signbits = 0; - if (currPlane->normal.x < 0.0f) - currPlane->signbits |= 1; - if (currPlane->normal.y < 0.0f) - currPlane->signbits |= 2; - if (currPlane->normal.z < 0.0f) - currPlane->signbits |= 4; - - currPlane->pad[0] = 0; - currPlane->pad[1] = 0; - - int currNodeIndex = currNodeCount; - currNodeCount++; - cNode_t* currNode = &clipMap->nodes[currNodeIndex]; - - currNode->plane = currPlane; - // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): - // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, - // the normal vector flips and objects behind the plane are now in front, and vise versa - // so the back node now represents the front, and the front node represents the back. - // Do the OGL -> Bo2 coord change on paper and it will make sense - if (currPlane->type == 1) - { - currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); - } - else - { - currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); - } - - return currNodeIndex; - } - } - - void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) - { - size_t numPlanes = 0; - size_t numNodes = 0; - size_t numLeafs = 0; - size_t numAABBTrees = 0; - size_t maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; - clipMap->numNodes = numNodes; - clipMap->nodes = new cNode_t[clipMap->numNodes]; - // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. - // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. - clipMap->aabbTreeCount = numAABBTrees; - clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; - memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); - currLeafCount = 1; - - populateBSPTree_r(clipMap, tree); - - _ASSERT(clipMap->info.planeCount == currPlaneCount); - _ASSERT(clipMap->numNodes == currNodeCount); - _ASSERT(clipMap->numLeafs == currLeafCount); - _ASSERT(clipMap->aabbTreeCount == currAABBCount); - } - - void createPartitions(BSPData* projInfo, clipMap_t* clipMap) - { - int collisionVertexCount = projInfo->colWorld.vertices.size(); - std::vector collisionVertVec; - for (int i = 0; i < collisionVertexCount; i++) - { - collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); - //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = new vec3_t[collisionVertexCount]; - memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); - - // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) - { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); - hasLinkFailed = true; - return; - } - - std::vector triIndexVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - BSPSurface* currSurface = &projInfo->colWorld.surfaces[i]; - int triCount = currSurface->triCount; - - for (int k = 0; k < triCount * 3; k += 3) - { - int firstIndex_Index = currSurface->indexOfFirstIndex; - int firstVertexIndex = currSurface->indexOfFirstVertex; - - // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire - // clipmap verts buffer, so this code updates the indexes to follow that. - int triIndex0 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = projInfo->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; - - // triangle index ordering is opposite to blenders, so its converted here - triIndexVec.push_back(triIndex2); - triIndexVec.push_back(triIndex1); - triIndexVec.push_back(triIndex0); - } - } - _ASSERT(triIndexVec.size() % 3 == 0); - clipMap->triCount = triIndexVec.size() / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[triIndexVec.size()]); - memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower - std::vector partitionVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - int triCount = projInfo->colWorld.surfaces[i].triCount; - int firstTriIndex = projInfo->colWorld.surfaces[i].indexOfFirstIndex / 3; - for (int k = 0; k < triCount; k++) - { - CollisionPartition newPartition; - newPartition.nuinds = 0; // initialised later - newPartition.fuind = 0; // initialised later - newPartition.triCount = 1; - newPartition.firstTri = firstTriIndex; - firstTriIndex += 1; - - partitionVec.push_back(newPartition); - } - } - clipMap->partitionCount = partitionVec.size(); - clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; - memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); - - int totalUindCount = 0; - std::vector uindVec; - for (int i = 0; i < clipMap->partitionCount; i++) - { - CollisionPartition* currPartition = &clipMap->partitions[i]; - std::vector uniqueVertVec; - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (size_t m = 0; m < uniqueVertVec.size(); m++) - { - if (uniqueVertVec[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - uniqueVertVec.push_back(vertIndex); - } - } - - currPartition->fuind = totalUindCount; - currPartition->nuinds = (int)uniqueVertVec.size(); - uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); - totalUindCount += currPartition->nuinds; - } - clipMap->info.nuinds = totalUindCount; - clipMap->info.uinds = new uint16_t[totalUindCount]; - memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - } - - void createClipMap(BSPData* projInfo) - { - clipMap_t* clipMap = new clipMap_t; - - clipMap->name = _strdup(projInfo->bspName.c_str()); - - std::string mapEntsName = projInfo->bspName.c_str(); - auto mapEntsAsset = m_context.LoadDependency(mapEntsName); - _ASSERT(mapEntsAsset != NULL); - clipMap->mapEnts = mapEntsAsset->Asset(); - - clipMap->pInfo = NULL; - - clipMap->box_model.mins.x = 0.0f; - clipMap->box_model.mins.y = 0.0f; - clipMap->box_model.mins.z = 0.0f; - clipMap->box_model.maxs.x = 0.0f; - clipMap->box_model.maxs.y = 0.0f; - clipMap->box_model.maxs.z = 0.0f; - clipMap->box_model.radius = 0.0f; - clipMap->box_model.info = NULL; - - // for some reason the maxs are negative, and mins are positive - // float box_mins = 3.4028235e38; - // float box_maxs = -3.4028235e38; - // hack: the floats above can't be converted to 32 bit floats, and the game requires them to be exact - // so we use the hex representation and set it using int pointers - unsigned int box_mins = 0x7F7FFFFF; - unsigned int box_maxs = 0xFF7FFFFF; - *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.z) = box_maxs; - - clipMap->box_model.leaf.brushContents = -1; - clipMap->box_model.leaf.terrainContents = 0; - clipMap->box_model.leaf.cluster = 0; - clipMap->box_model.leaf.collAabbCount = 0; - clipMap->box_model.leaf.firstCollAabbIndex = 0; - clipMap->box_model.leaf.leafBrushNode = 0; - - clipMap->box_brush = new cbrush_t; - clipMap->box_brush->axial_sflags[0][0] = -1; - clipMap->box_brush->axial_sflags[0][1] = -1; - clipMap->box_brush->axial_sflags[0][2] = -1; - clipMap->box_brush->axial_sflags[1][0] = -1; - clipMap->box_brush->axial_sflags[1][1] = -1; - clipMap->box_brush->axial_sflags[1][2] = -1; - clipMap->box_brush->axial_cflags[0][0] = -1; - clipMap->box_brush->axial_cflags[0][1] = -1; - clipMap->box_brush->axial_cflags[0][2] = -1; - clipMap->box_brush->axial_cflags[1][0] = -1; - clipMap->box_brush->axial_cflags[1][1] = -1; - clipMap->box_brush->axial_cflags[1][2] = -1; - clipMap->box_brush->contents = -1; - clipMap->box_brush->mins.x = 0.0f; - clipMap->box_brush->mins.y = 0.0f; - clipMap->box_brush->mins.z = 0.0f; - clipMap->box_brush->maxs.x = 0.0f; - clipMap->box_brush->maxs.y = 0.0f; - clipMap->box_brush->maxs.z = 0.0f; - clipMap->box_brush->numsides = 0; - clipMap->box_brush->numverts = 0; - clipMap->box_brush->sides = NULL; - clipMap->box_brush->verts = NULL; - - clipMap->numClusters = 1; - clipMap->vised = 0; - clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; - clipMap->visibility = new char[clipMap->clusterBytes]; - memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); - - clipMap->isInUse = true; - clipMap->checksum = 0; - - clipMap->num_constraints = 0; - clipMap->constraints = NULL; - clipMap->max_ropes = 32; - clipMap->ropes = new rope_t[clipMap->max_ropes]; - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); - - clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = NULL; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = NULL; - clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = NULL; - clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = NULL; - clipMap->info.numBrushes = NULL; - clipMap->info.brushes = NULL; - clipMap->info.brushBounds = NULL; - clipMap->info.brushContents = NULL; - - int dynEntCount = 0; - clipMap->originalDynEntCount = dynEntCount; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - clipMap->dynEntCount[1] = 0; - clipMap->dynEntCount[2] = 0; - clipMap->dynEntCount[3] = 0; - - // assume that there are 0 dyn ents from here on - clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; - clipMap->dynEntClientList[1] = NULL; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); - - clipMap->dynEntServerList[0] = NULL; - clipMap->dynEntServerList[1] = NULL; - - clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; - clipMap->dynEntCollList[1] = NULL; - clipMap->dynEntCollList[2] = NULL; - clipMap->dynEntCollList[3] = NULL; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); - - clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; - clipMap->dynEntPoseList[1] = NULL; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); - - clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; - clipMap->dynEntDefList[1] = NULL; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - - // cmodels is the collision for mapents - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - clipMap->numSubModels = gfxWorld->modelCount; - clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - for (unsigned int i = 0; i < clipMap->numSubModels; i++) - { - // bomb triggers use leafs, not world terrain so that might be an issue - - GfxBrushModel* gfxModel = &gfxWorld->models[i]; - cmodel_t* cmModel = &clipMap->cmodels[i]; - - cmModel->leaf.firstCollAabbIndex = 0; - cmModel->leaf.collAabbCount = 0; - cmModel->leaf.brushContents = 0; - cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; - cmModel->leaf.mins.x = 0.0f; - cmModel->leaf.mins.y = 0.0f; - cmModel->leaf.mins.z = 0.0f; - cmModel->leaf.maxs.x = 0.0f; - cmModel->leaf.maxs.y = 0.0f; - cmModel->leaf.maxs.z = 0.0f; - cmModel->leaf.leafBrushNode = 0; - cmModel->leaf.cluster = 0; - cmModel->info = NULL; - cmModel->mins.x = gfxModel->bounds[0].x; - cmModel->mins.y = gfxModel->bounds[0].y; - cmModel->mins.z = gfxModel->bounds[0].z; - cmModel->maxs.x = gfxModel->bounds[1].x; - cmModel->maxs.y = gfxModel->bounds[1].y; - cmModel->maxs.z = gfxModel->bounds[1].z; - cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; - } - - addXModelsToCollision(projInfo, clipMap); - - clipMap->info.numMaterials = 1; - clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(BSPLinkingConstants::MISSING_IMAGE_NAME); - clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; - - // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls, but from testing doesnt seem to do much - int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - - // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, - // and then convert them back when it is being parsed into the clipmap. Requires some hacky - // logic, check populateBSPTree_r and addAABBTreeFromLeaf - - createPartitions(projInfo, clipMap); - - vec3_t* firstVert = &clipMap->verts[0]; - vec3_t clipMins; - vec3_t clipMaxs; - clipMins.x = firstVert->x; - clipMins.y = firstVert->y; - clipMins.z = firstVert->z; - clipMaxs.x = firstVert->x; - clipMaxs.y = firstVert->y; - clipMaxs.z = firstVert->z; - clipMins = BSPUtil::convertFromBO2Coords(clipMins); - clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); - for (unsigned int i = 1; i < clipMap->vertCount; i++) - { - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); - } - - BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); - - _ASSERT(!tree->isLeaf); - - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVert->x; - mins.y = firstVert->y; - mins.z = firstVert->z; - maxs.x = firstVert->x; - maxs.y = firstVert->y; - maxs.z = firstVert->z; - mins = BSPUtil::convertFromBO2Coords(mins); - maxs = BSPUtil::convertFromBO2Coords(maxs); - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); - } - } - - std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - - tree->addObjectToTree(std::move(currObject)); - } - - populateBSPTree(clipMap, tree); - - m_context.AddAsset(clipMap->name, clipMap); - } - - void createComWorld(BSPData* projInfo) - { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset - ComWorld* comWorld = new ComWorld; - comWorld->name = _strdup(projInfo->bspName.c_str()); - comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; - comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; - - // default light is always empty - ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; - memset(defaultLight, 0, sizeof(ComPrimaryLight)); - - ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - memset(sunLight, 0, sizeof(ComPrimaryLight)); - sunLight->type = 1; - sunLight->diffuseColor.r = 0.75f; - sunLight->diffuseColor.g = 0.75f; - sunLight->diffuseColor.b = 0.75f; - sunLight->diffuseColor.a = 1.0f; - sunLight->dir.x = 0.0f; - sunLight->dir.y = 0.0f; - sunLight->dir.z = 0.0f; - - m_context.AddAsset(comWorld->name, comWorld); - } - - void parseMapEntsJSON(json& entArrayJs, std::string& entityString) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - if (i == 0) - { - std::string className = currEntity["classname"]; - if (className.compare("worldspawn") != 0) - { - printf("ERROR: first entity in the map entity string must be the worldspawn class!"); - hasLinkFailed = true; - return; - } - } - - entityString.append("{\n"); - - for (auto& element : currEntity.items()) - { - std::string key = element.key(); - std::string value = element.value(); - entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); - } - - entityString.append("}\n"); - } - } - - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - std::string origin = currEntity["origin"]; - std::string angles = currEntity["angles"]; - - for (int k = 0; k < nameCount; k++) - { - entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", origin)); - entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); - entityString.append("}\n"); - } - } - } - - void parseBombJSON(json& bombJs, std::string& entityString) - { - // add the bomb model - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - entityString.append("{\n"); - entityString.append("\"classname\" \"script_model\"\n"); - entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); - entityString.append("\"targetname\" \"sd_bomb\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append("\"spawnflags\" \"4\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append("}\n"); - } - if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) - { - hasLinkFailed = true; - printf("ERROR: unable to find s&d bomb xmodel\n"); - return; - } - - // add the bomb pickup trigger - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); - entModelBounds bounds; - bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig - bounds.mins.y = bomboriginV3.y - 32.0f; - bounds.mins.z = bomboriginV3.z - 8.0f; - bounds.maxs.x = bomboriginV3.x + 32.0f; - bounds.maxs.y = bomboriginV3.y + 32.0f; - bounds.maxs.z = bomboriginV3.z + 28.0f; - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_multiple\"\n"); - entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add A site bomb - { - std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; - std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; - vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); - vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteAPoint1V3.x; - bounds.mins.y = siteAPoint1V3.y; - bounds.mins.z = siteAPoint1V3.z; - bounds.maxs.x = siteAPoint1V3.x; - bounds.maxs.y = siteAPoint1V3.y; - bounds.maxs.z = siteAPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_a\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add B site bomb - { - std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; - std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; - vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); - vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteBPoint1V3.x; - bounds.mins.y = siteBPoint1V3.y; - bounds.mins.z = siteBPoint1V3.z; - bounds.maxs.x = siteBPoint1V3.x; - bounds.maxs.y = siteBPoint1V3.y; - bounds.maxs.z = siteBPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_b\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - } - - void createMapEnts(BSPData* projInfo) - { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - std::string entityString; - - const auto entFile = m_search_path.Open("BSP/entities.json"); - json entJs; - if (!entFile.IsOpen()) - { - printf("WARN: can't find entity json BSP/entities.json, using default entities\n"); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); - } - else - { - entJs = json::parse(*entFile.m_stream); - } - parseMapEntsJSON(entJs["entities"], entityString); - - const auto spawnFile = m_search_path.Open("BSP/spawns.json"); - json spawnJs; - if (!spawnFile.IsOpen()) - { - printf("WARN: cant find BSP/spawns.json, setting spawns to 0 0 0\n"); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); - } - - int defenderNameCount = std::extent::value; - int attackerNameCount = std::extent::value; - int ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); - - //const auto objectiveFile = m_search_path.Open("objectives.json"); - //if (!spawnFile.IsOpen()) - //{ - // printf("WARN: no objectives given\n"); - //} - //else - //{ - // json objectiveJs = json::parse(*objectiveFile.m_stream); - // parseBombJSON(objectiveJs, entityString); - //} - - mapEnts->entityString = _strdup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - m_context.AddAsset(mapEnts->name, mapEnts); - } - - void createGameWorldMp(BSPData* projInfo) - { - GameWorldMp* gameWorldMp = new GameWorldMp; - - gameWorldMp->name = _strdup(projInfo->bspName.c_str()); - - gameWorldMp->path.nodeCount = 0; - gameWorldMp->path.originalNodeCount = 0; - gameWorldMp->path.visBytes = 0; - gameWorldMp->path.smoothBytes = 0; - gameWorldMp->path.nodeTreeCount = 0; - - int nodeCount = gameWorldMp->path.nodeCount + 128; - gameWorldMp->path.nodes = new pathnode_t[nodeCount]; - gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - - gameWorldMp->path.pathVis = NULL; - gameWorldMp->path.smoothCache = NULL; - gameWorldMp->path.nodeTree = NULL; - - m_context.AddAsset(gameWorldMp->name, gameWorldMp); - } - - void createSkinnedVerts(BSPData* projInfo) - { - SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; - skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); - // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have - // But setting it to the world vertex count seems to work - - m_context.AddAsset("skinnedverts", skinnedVerts); - } - - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) - { - if (assetName.length() == 0) - return NULL; - - FootstepTableDef* footstepTable = new FootstepTableDef; - footstepTable->name = _strdup(assetName.c_str()); - memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); - - m_context.AddAsset(assetName, footstepTable); - - return footstepTable; - } - - void checkAndAddDefaultRequiredAssets(BSPData* projectInfo) - { - auto templateFile = m_search_path.Open("materials/material_template.json"); - if (!templateFile.IsOpen()) - { - printf("ERROR: failed to open materials/material_template.json\n"); - hasLinkFailed = true; - return; - } - materialTemplateJson = json::parse(*templateFile.m_stream); - - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) - { - hasLinkFailed = true; - return; - } - - addEmptyFootstepTableAsset("default_1st_person"); - addEmptyFootstepTableAsset("default_3rd_person"); - addEmptyFootstepTableAsset("default_1st_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_loud"); - addEmptyFootstepTableAsset("default_ai"); - - if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) - { - hasLinkFailed = true; - return; - } - } - }; - -} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp index de6d6007b..ccd42bd76 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -48,6 +48,8 @@ namespace BSP if (m_context.LoadDependency("animtrees/fxanim_props.atr") == nullptr) return false; + + return true; } BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -69,12 +71,27 @@ namespace BSP MapEntsLinker mapEntsLinker(m_memory, m_search_path, m_context); SkinnedVertsLinker skinnedVertsLinker(m_memory, m_search_path, m_context); - comWorldLinker.linkComWorld(bsp); - mapEntsLinker.linkMapEnts(bsp); + if (comWorldLinker.linkComWorld(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + if (mapEntsLinker.linkMapEnts(bsp).HasFailed()) + return AssetCreationResult::Failure(); + gameWorldMpLinker.linkGameWorldMp(bsp); - skinnedVertsLinker.linkSkinnedVerts(bsp); - gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset - clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + if (gameWorldMpLinker.linkGameWorldMp(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + if (skinnedVertsLinker.linkSkinnedVerts(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + auto result = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + if (result.HasFailed()) + return AssetCreationResult::Failure(); + + if (clipMapLinker.linkClipMap(bsp).HasFailed()) // requires gfxworld and mapents asset + return AssetCreationResult::Failure(); + + return result; } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 8d42b256d..82fe31c5d 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -1,4 +1,6 @@ #include "GfxWorldLinker.h" +#include "../BSPUtil.h" +#include "Utils/Pack.h" namespace BSP { @@ -9,9 +11,832 @@ namespace BSP { } + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + void GfxWorldLinker::overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxWorld.vertices.size(); + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = m_memory.Alloc(vertexCount); + for (int i = 0; i < vertexCount; i++) + { + BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); + //GfxVertex->xyz = WorldVertex->pos; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); + //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); + //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); + + // unknown use variables + // binormalSign may be bitangent of the vertex + // lmapCoord may be the lightmap coordinate of the vertex + GfxVertex->binormalSign = 0.0f; + GfxVertex->lmapCoord.packed = 0; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = m_memory.Alloc(gfxWorld->draw.vertexDataSize1); + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxWorld.indices.size(); + assert(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = m_memory.Alloc(indexCount); + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; + } + } + + bool GfxWorldLinker::overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) + { + overwriteDrawData(projInfo, gfxWorld); + + unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = m_memory.Alloc(surfaceCount); + for (unsigned int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxWorld.surfaces[i]; + + currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; + + currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + std::string surfMaterialName; + switch (objSurface->material.materialType) + { + case MATERIAL_TYPE_TEXTURE: + surfMaterialName = objSurface->material.materialName; + break; + + case MATERIAL_TYPE_COLOUR: + case MATERIAL_TYPE_EMPTY: + surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + break; + + default: + assert(false); + } + + auto surfMaterialAsset = m_context.LoadDependency(surfMaterialName); + if (surfMaterialAsset == NULL) + { + std::string missingImageName = BSPLinkingConstants::MISSING_IMAGE_NAME; + surfMaterialAsset = m_context.LoadDependency(missingImageName); + if (surfMaterialAsset == NULL) + { + con::error("unable to find the missing image texture!"); + return false; + } + } + currSurface->material = surfMaterialAsset->Asset(); + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; + BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // doesn't seem to matter what order the sorted surfs go in + gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(surfaceCount); + for (unsigned int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = i; + } + + // surface materials are written to by the game + gfxWorld->dpvs.surfaceMaterials = m_memory.Alloc(surfaceCount); + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // set all surface types to lit opaque + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + + // visdata is written to by the game + // all visdata is alligned by 128 + unsigned int allignedSurfaceCount = BSPUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisDataCount = allignedSurfaceCount; + gfxWorld->dpvs.surfaceVisData[0] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisData[1] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisData[2] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisDataCameraSaved = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceCastsShadow = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceCastsSunShadow = m_memory.Alloc(allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[0], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, allignedSurfaceCount); + + return true; + } + + void GfxWorldLinker::overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) + { + /* + Models are unsupported right now + Code is left in in case it is supported later on + */ + //unsigned int modelCount = projInfo->modelCount; + //gfxWorld->dpvs.smodelCount = modelCount; + //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + // + //for (unsigned int i = 0; i < modelCount; i++) + //{ + // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + // customMapModel* inModel = &projInfo->models[i]; + // + // auto xModelAsset = m_context.LoadDependency(inModel->name); + // if (xModelAsset == NULL) + // { + // printf("XModel %s not found!\n", inModel->name.c_str()); + // currModel->model = NULL; + // } + // else + // currModel->model = (XModel*)xModelAsset->Asset(); + // + // currModel->placement.origin.x = inModel->origin.x; + // currModel->placement.origin.y = inModel->origin.y; + // currModel->placement.origin.z = inModel->origin.z; + // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.scale = inModel->scale; + // + // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // + // // mins and maxs are calculated in world space not local space + // // TODO: this does not account for model rotation or scale + // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + // + // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + // currModel->flags = DEFAULT_SMODEL_FLAGS; + // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + // + // // unknown use / unused + // currModel->smid = i; + // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + // currModel->invScaleSq = 0.0f; + // currModel->lightingHandle = 0; + // currModel->colorsIndex = 0; + // currModel->visibility = 0; + // + // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // // TODO: calculate lighting and store it here + // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + //} + + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + gfxWorld->dpvs.smodelInsts = m_memory.Alloc(modelCount); + gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc(modelCount); + + // visdata is written to by the game + // all visdata is alligned by 128 + int allignedModelCount = BSPUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + gfxWorld->dpvs.smodelVisData[0] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisData[1] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisData[2] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisDataCameraSaved = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelCastsShadow = m_memory.Alloc(allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + + // official maps set this to 0 + gfxWorld->dpvs.usageCount = 0; + } + + void GfxWorldLinker::cleanGfxWorld(GfxWorld* gfxWorld) + { + // checksum is generated by the game + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = m_memory.Alloc(); + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void GfxWorldLinker::overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + + gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + } + + gfxWorld->lightRegion = m_memory.Alloc(gfxWorld->primaryLightCount); + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc(lightEntShadowVisSize); + memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + // the lightgrid is used to light models in a dynamic way and is precomputed + void GfxWorldLinker::overwriteLightGrid(GfxWorld* gfxWorld) + { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value + + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = m_memory.Alloc(rowDataStartSize); + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); + + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow) + 0x10; + GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 + { + row->lookupTable[i] = 0; + } + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + + // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = m_memory.Alloc(gfxWorld->lightGrid.entryCount); + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); + memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + + // we use the colours array instead of coeffs array + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void GfxWorldLinker::updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = m_memory.Alloc(cellCasterBitsCount); + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = m_memory.Alloc(cellCount); + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); + reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = m_memory.Alloc(gfxWorld->cells[0].aabbTreeCount); + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = m_memory.Alloc(gfxWorld->dpvs.smodelCount); + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = m_memory.Alloc(sceneEntCellBitsCount); + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->dpvsPlanes.nodes = m_memory.Alloc(gfxWorld->nodeCount); + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + + // planes are overwritten by the clipmap loading code ingame + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void GfxWorldLinker::overwriteModels(GfxWorld* gfxWorld) + { + // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) + // Right now there is only one submodel, the world sub model + gfxWorld->modelCount = 1; + gfxWorld->models = m_memory.Alloc(gfxWorld->modelCount); + + // first model is always the world model + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + // Other models aren't implemented yet + // Code kept for future use + //for (size_t i = 0; i < entityModelList.size(); i++) + //{ + // auto currEntModel = &gfxWorld->models[i + 1]; + // entModelBounds currEntModelBounds = entityModelList[i]; + // + // currEntModel->startSurfIndex = 0; + // currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + // currEntModel->bounds[0].x = currEntModelBounds.mins.x; + // currEntModel->bounds[0].y = currEntModelBounds.mins.y; + // currEntModel->bounds[0].z = currEntModelBounds.mins.z; + // currEntModel->bounds[1].x = currEntModelBounds.maxs.x; + // currEntModel->bounds[1].y = currEntModelBounds.maxs.y; + // currEntModel->bounds[1].z = currEntModelBounds.maxs.z; + // memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + //} + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = (float)0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + bool GfxWorldLinker::updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + con::error("ERROR! unable to find reflection probe image {}!", probeImageName); + return false; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + + return true; + } + + bool GfxWorldLinker::overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); + gfxWorld->draw.lightmapSecondaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); + gfxWorld->draw.lightmaps = m_memory.Alloc(gfxWorld->draw.lightmapCount); + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + con::error("ERROR! unable to find lightmap image {}!", secondaryTexture); + return false; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + + return true; + } + + void GfxWorldLinker::overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) + { + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void GfxWorldLinker::updateDynEntData(GfxWorld* gfxWorld) + { + int dynEntCount = 0; + gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = m_memory.Alloc(dynEntCellBitsSize); + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[0][1] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[0][2] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = m_memory.Alloc(dynEntShadowVisCount); + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = m_memory.Alloc(gfxWorld->dpvsDyn.dynEntClientCount[0]); + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + bool GfxWorldLinker::updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + con::error("ERROR! unable to find outdoor image $outdoor!"); + return false; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + + return true; + } + AssetCreationResult GfxWorldLinker::linkGfxWorld(BSPData* bsp) { - return AssetCreationResult::Failure(); + GfxWorld* gfxWorld = m_memory.Alloc(); + gfxWorld->baseName = m_memory.Dup(bsp->name.c_str()); + gfxWorld->name = m_memory.Dup(bsp->bspName.c_str()); + + // Default values taken from official maps + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + if (!overwriteMapSurfaces(bsp, gfxWorld)) + return AssetCreationResult::Failure(); + + overwriteMapSModels(bsp, gfxWorld); + + if (!overwriteLightmapData(gfxWorld)) + return AssetCreationResult::Failure(); + + overwriteSkyBox(bsp, gfxWorld); + + if (!updateReflectionProbeData(gfxWorld)) + return AssetCreationResult::Failure(); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + if (!updateOutdoors(gfxWorld)) + return AssetCreationResult::Failure(); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + auto gfxWorldAsset = m_context.AddAsset(gfxWorld->name, gfxWorld); + return AssetCreationResult::Success(gfxWorldAsset); } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 7d2193207..594b51ffc 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -17,5 +17,19 @@ namespace BSP MemoryManager& m_memory; ISearchPath& m_search_path; AssetCreationContext& m_context; + + void overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld); + bool overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); + void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld); + void cleanGfxWorld(GfxWorld* gfxWorld); + void overwriteGfxLights(GfxWorld* gfxWorld); + void overwriteLightGrid(GfxWorld* gfxWorld); + void updateGfxCells(GfxWorld* gfxWorld); + void overwriteModels(GfxWorld* gfxWorld); + bool updateReflectionProbeData(GfxWorld* gfxWorld); + bool overwriteLightmapData(GfxWorld* gfxWorld); + void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); + void updateDynEntData(GfxWorld* gfxWorld); + bool updateOutdoors(GfxWorld* gfxWorld); }; } \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index b3a02399c..74bac61c0 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,6 +1,8 @@ #include "LoaderBSP_T6.h" #include "BSPCreator.h" -#include "CustomMapLinker.h" +#include "BSPUtil.h" +#include "Linker/BSPLinker.h" +//#include "CustomMapLinker.h" namespace { @@ -27,17 +29,8 @@ namespace if (bsp == nullptr) return AssetCreationResult::Failure(); - CustomMapLinker linker(m_memory, m_search_path, m_zone, context); - bool result = linker.linkCustomMap(bsp.get()); - - if (result) - { - auto gfxWorldAsset = context.LoadDependency(bsp->bspName); - _ASSERT(gfxWorldAsset != nullptr); - return AssetCreationResult::Success(gfxWorldAsset); - } - else - return AssetCreationResult::Failure(); + BSPLinker linker(m_memory, m_search_path, context); + return linker.linkBSP(bsp.get()); } private: From 9d4c32b6b4c73ac3770722e2ace87e0a07b4bace Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sun, 26 Oct 2025 18:20:04 +0800 Subject: [PATCH 026/123] Refactor to improve C++ and safe code use --- src/Common/Game/T6/T6_Assets.h | 17 +++ src/ObjLoading/Game/T6/BSP/BSP.h | 12 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 88 ++++++------ src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 84 +++++------ src/ObjLoading/Game/T6/BSP/BSPUtil.h | 4 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 37 +++-- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 27 ++-- .../Game/T6/BSP/Linker/GameWorldMpLinker.cpp | 10 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 131 ++++++++--------- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 136 ++++++++++-------- 10 files changed, 285 insertions(+), 261 deletions(-) diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 0a33bf519..d62c378c4 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -7096,6 +7096,23 @@ namespace T6 char* stringValue; }; + enum GfxLightType + { + GFX_LIGHT_TYPE_NONE = 0x0, + GFX_LIGHT_TYPE_DIR = 0x1, + GFX_LIGHT_TYPE_SPOT = 0x2, + GFX_LIGHT_TYPE_SPOT_SQUARE = 0x3, + GFX_LIGHT_TYPE_SPOT_ROUND = 0x4, + GFX_LIGHT_TYPE_OMNI = 0x5, + GFX_LIGHT_TYPE_COUNT = 0x6, + GFX_LIGHT_TYPE_DIR_SHADOWMAP = 0x6, + GFX_LIGHT_TYPE_SPOT_SHADOWMAP = 0x7, + GFX_LIGHT_TYPE_SPOT_SQUARE_SHADOWMAP = 0x8, + GFX_LIGHT_TYPE_SPOT_ROUND_SHADOWMAP = 0x9, + GFX_LIGHT_TYPE_OMNI_SHADOWMAP = 0xA, + GFX_LIGHT_TYPE_COUNT_WITH_SHADOWMAP_VERSIONS = 0xB, + }; + #ifndef __zonecodegenerator } // namespace T6 #endif diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 0fb6934c5..95e04897a 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -64,8 +64,12 @@ namespace BSP { constexpr int MAX_COLLISION_VERTS = UINT16_MAX; - constexpr int STATIC_LIGHT_INDEX = 0; - constexpr int SUN_LIGHT_INDEX = 1; + enum BSPDefaultLights + { + STATIC_LIGHT_INDEX = 0, + SUN_LIGHT_INDEX = 1, + BSP_DEFAULT_LIGHT_COUNT = 2 + }; inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { "mp_ctf_spawn_allies", @@ -182,5 +186,9 @@ namespace BSP // lightgrid (global) lighting colour // since lightgrids are not well understood, this colour is used for the R, G and B values right now constexpr unsigned char LIGHTGRID_COLOUR = 128; + + // Sunlight values + constexpr vec4_t SUNLIGHT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; + constexpr vec3_t SUNLIGHT_DIRECTION = { 0.0f, 0.0f, 0.0f }; }; } \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index c81730890..63a412247 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -59,87 +59,85 @@ namespace if (meshPart.num_faces == 0) continue; - surfaceVec.emplace_back(); - BSPSurface* surface = &surfaceVec[surfaceVec.size() - 1]; - + BSPSurface surface; size_t partTriangleNum = meshPart.num_triangles; - surface->triCount = static_cast(partTriangleNum); - surface->indexOfFirstVertex = vertexVec.size(); - surface->indexOfFirstIndex = indexVec.size(); + surface.triCount = static_cast(partTriangleNum); + surface.indexOfFirstVertex = static_cast(vertexVec.size()); + surface.indexOfFirstIndex = static_cast(indexVec.size()); if (mesh->materials.count == 0) { - surface->material.materialType = MATERIAL_TYPE_EMPTY; - surface->material.materialName = ""; + surface.material.materialType = MATERIAL_TYPE_EMPTY; + surface.material.materialName = ""; } else { - surface->material.materialType = MATERIAL_TYPE_TEXTURE; - surface->material.materialName = mesh->materials.data[meshPart.index]->name.data; + surface.material.materialType = MATERIAL_TYPE_TEXTURE; + surface.material.materialName = mesh->materials.data[meshPart.index]->name.data; } - std::vector vertices; - std::vector indices; - indices.resize(mesh->max_face_triangles * 3); + std::vector tempVertices; + std::vector tempIndices; + tempIndices.resize(mesh->max_face_triangles * 3); for (uint32_t faceIndex : meshPart.face_indices) { ufbx_face* face = &mesh->faces.data[faceIndex]; // Triangulate the face into the indices vector - uint32_t triangluatedTriCount = ufbx_triangulate_face(indices.data(), indices.size(), mesh, *face); + uint32_t triangluatedTriCount = ufbx_triangulate_face(tempIndices.data(), tempIndices.size(), mesh, *face); - // Iterate over each triangle corner contiguously. for (uint32_t idxOfIndex = 0; idxOfIndex < triangluatedTriCount * 3; idxOfIndex++) { - vertices.emplace_back(); - BSPVertex* vertex = &vertices[vertices.size() - 1]; - uint32_t index = indices[idxOfIndex]; + BSPVertex vertex; + uint32_t index = tempIndices[idxOfIndex]; ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex->pos.x = static_cast(transformedPos.x); - vertex->pos.y = static_cast(transformedPos.y); - vertex->pos.z = static_cast(transformedPos.z); + vertex.pos.x = static_cast(transformedPos.x); + vertex.pos.y = static_cast(transformedPos.y); + vertex.pos.z = static_cast(transformedPos.z); - if (surface->material.materialType == MATERIAL_TYPE_TEXTURE || surface->material.materialType == MATERIAL_TYPE_EMPTY) + if (surface.material.materialType == MATERIAL_TYPE_TEXTURE || surface.material.materialType == MATERIAL_TYPE_EMPTY) { - vertex->color.x = 1.0f; - vertex->color.y = 1.0f; - vertex->color.z = 1.0f; - vertex->color.w = 1.0f; + vertex.color.x = 1.0f; + vertex.color.y = 1.0f; + vertex.color.z = 1.0f; + vertex.color.w = 1.0f; } else // surface->material.materialType == MATERIAL_TYPE_COLOUR { float factor = static_cast(mesh->materials.data[meshPart.index]->fbx.diffuse_factor.value_real); ufbx_vec4 diffuse = mesh->materials.data[meshPart.index]->fbx.diffuse_color.value_vec4; - vertex->color.x = static_cast(diffuse.x * factor); - vertex->color.y = static_cast(diffuse.y * factor); - vertex->color.z = static_cast(diffuse.z * factor); - vertex->color.w = static_cast(diffuse.w * factor); + vertex.color.x = static_cast(diffuse.x * factor); + vertex.color.y = static_cast(diffuse.y * factor); + vertex.color.z = static_cast(diffuse.z * factor); + vertex.color.w = static_cast(diffuse.w * factor); } // 1.0f - uv.y reason: https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); - vertex->texCoord.x = static_cast(uv.x); - vertex->texCoord.y = static_cast(1.0f - uv.y); + vertex.texCoord.x = static_cast(uv.x); + vertex.texCoord.y = static_cast(1.0f - uv.y); ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); - vertex->normal.x = static_cast(normal.x); - vertex->normal.y = static_cast(normal.y); - vertex->normal.z = static_cast(normal.z); + vertex.normal.x = static_cast(normal.x); + vertex.normal.y = static_cast(normal.y); + vertex.normal.z = static_cast(normal.z); if (mesh->vertex_tangent.exists) { ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); - vertex->tangent.x = static_cast(tangent.x); - vertex->tangent.y = static_cast(tangent.y); - vertex->tangent.z = static_cast(tangent.z); + vertex.tangent.x = static_cast(tangent.x); + vertex.tangent.y = static_cast(tangent.y); + vertex.tangent.z = static_cast(tangent.z); } else { - vertex->tangent.x = 0.0f; - vertex->tangent.y = 0.0f; - vertex->tangent.z = 0.0f; + vertex.tangent.x = 0.0f; + vertex.tangent.y = 0.0f; + vertex.tangent.z = 0.0f; } + + tempVertices.emplace_back(vertex); } } @@ -147,7 +145,7 @@ namespace // ufbx_generate_indices will deduplicate vertices, modifying the arrays passed in streams, // indices are written to outIndices and the number of unique vertices is returned. ufbx_vertex_stream streams[1] = { - {vertices.data(), vertices.size(), sizeof(BSPVertex)}, + {tempVertices.data(), tempVertices.size(), sizeof(BSPVertex)}, }; std::vector outIndices; outIndices.resize(partTriangleNum * 3); @@ -155,14 +153,16 @@ namespace assert(numGeneratedVertices != 0); // trim non-unique vertexes and add to the world vertex vector - vertices.resize(numGeneratedVertices); - vertexVec.insert(vertexVec.end(), vertices.begin(), vertices.end()); + tempVertices.resize(numGeneratedVertices); + vertexVec.insert(vertexVec.end(), tempVertices.begin(), tempVertices.end()); // T6 uses unsigned shorts as their index type so we have to loop and convert them from an unsigned int for (size_t idx = 0; idx < outIndices.size(); idx++) { indexVec.emplace_back(static_cast(outIndices[idx])); } + + surfaceVec.emplace_back(surface); } } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 118594bb3..878f318db 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -10,79 +10,73 @@ std::string BSPUtil::getFileNameForBSPAsset(std::string assetName) return std::format("BSP/{}", assetName); } -// BO2 uses a different coordinate system, so this converts it back from OpenGLs default -vec3_t BSPUtil::convertToBO2Coords(vec3_t OGL_coordinate) +vec3_t BSPUtil::convertToBO2Coords(vec3_t coordinate) { vec3_t result; - result.x = OGL_coordinate.x; - result.y = -OGL_coordinate.z; - result.z = OGL_coordinate.y; + result.x = coordinate.x; + result.y = -coordinate.z; + result.z = coordinate.y; return result; } -// BO2 uses a weird coordinate system, so this converts it to OpenGLs default -vec3_t BSPUtil::convertFromBO2Coords(vec3_t bo2_coordinate) +vec3_t BSPUtil::convertFromBO2Coords(vec3_t coordinate) { vec3_t result; - result.x = bo2_coordinate.x; - result.y = bo2_coordinate.z; - result.z = -bo2_coordinate.y; + result.x = coordinate.x; + result.y = coordinate.z; + result.z = -coordinate.y; return result; } -void BSPUtil::calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs) +void BSPUtil::updateAABB(vec3_t* newAABBMins, vec3_t* newAABBMaxs, vec3_t* AABBMins, vec3_t* AABBMaxs) { - if (currmins->x > newmins->x) - currmins->x = newmins->x; + if (AABBMins->x > newAABBMins->x) + AABBMins->x = newAABBMins->x; - if (newmaxs->x > currmaxs->x) - currmaxs->x = newmaxs->x; + if (newAABBMaxs->x > AABBMaxs->x) + AABBMaxs->x = newAABBMaxs->x; - if (currmins->y > newmins->y) - currmins->y = newmins->y; + if (AABBMins->y > newAABBMins->y) + AABBMins->y = newAABBMins->y; - if (newmaxs->y > currmaxs->y) - currmaxs->y = newmaxs->y; + if (newAABBMaxs->y > AABBMaxs->y) + AABBMaxs->y = newAABBMaxs->y; - if (currmins->z > newmins->z) - currmins->z = newmins->z; + if (AABBMins->z > newAABBMins->z) + AABBMins->z = newAABBMins->z; - if (newmaxs->z > currmaxs->z) - currmaxs->z = newmaxs->z; + if (newAABBMaxs->z > AABBMaxs->z) + AABBMaxs->z = newAABBMaxs->z; } -void BSPUtil::calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs) +void BSPUtil::updateAABBWithpoint(vec3_t* point, vec3_t* AABBMins, vec3_t* AABBMaxs) { - if (currmins->x > point->x) - currmins->x = point->x; + if (AABBMins->x > point->x) + AABBMins->x = point->x; - if (point->x > currmaxs->x) - currmaxs->x = point->x; + if (point->x > AABBMaxs->x) + AABBMaxs->x = point->x; - if (currmins->y > point->y) - currmins->y = point->y; + if (AABBMins->y > point->y) + AABBMins->y = point->y; - if (point->y > currmaxs->y) - currmaxs->y = point->y; + if (point->y > AABBMaxs->y) + AABBMaxs->y = point->y; - if (currmins->z > point->z) - currmins->z = point->z; + if (AABBMins->z > point->z) + AABBMins->z = point->z; - if (point->z > currmaxs->z) - currmaxs->z = point->z; + if (point->z > AABBMaxs->z) + AABBMaxs->z = point->z; } vec3_t BSPUtil::calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - temp.x *= 0.5f; - temp.y *= 0.5f; - temp.z *= 0.5f; - return temp; + vec3_t result; + result.x = (mins->x + maxs->x) * 0.5f; + result.y = (mins->y + maxs->y) * 0.5f; + result.z = (mins->z + maxs->z) * 0.5f; + return result; } int BSPUtil::allignBy128(int size) diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 7bad527f0..30307bcf0 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -9,8 +9,8 @@ class BSPUtil static std::string getFileNameForBSPAsset(std::string assetName); static vec3_t convertToBO2Coords(vec3_t OGL_coordinate); static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate); - static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs); - static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs); + static void updateAABB(vec3_t* newAABBMins, vec3_t* newAABBMaxs, vec3_t* AABBMins, vec3_t* AABBMaxs); + static void updateAABBWithpoint(vec3_t* point, vec3_t* AABBMins, vec3_t* AABBMaxs); static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs); static int allignBy128(int size); static float distBetweenPoints(vec3_t p1, vec3_t p2); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 8710f93cc..e8617928a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -27,7 +27,6 @@ namespace BSP clipMap->dynEntClientList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); clipMap->dynEntClientList[1] = nullptr; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); clipMap->dynEntServerList[0] = nullptr; clipMap->dynEntServerList[1] = nullptr; @@ -36,15 +35,12 @@ namespace BSP clipMap->dynEntCollList[1] = nullptr; clipMap->dynEntCollList[2] = nullptr; clipMap->dynEntCollList[3] = nullptr; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); clipMap->dynEntPoseList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); clipMap->dynEntPoseList[1] = nullptr; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); clipMap->dynEntDefList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); clipMap->dynEntDefList[1] = nullptr; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); } void ClipMapLinker::loadVisibility(clipMap_t* clipMap) @@ -122,19 +118,18 @@ namespace BSP void ClipMapLinker::loadRopesAndConstraints(clipMap_t* clipMap) { clipMap->num_constraints = 0; // max 511 - clipMap->constraints = NULL; + clipMap->constraints = nullptr; // The game allocates 32 empty ropes clipMap->max_ropes = 32; // max 300 clipMap->ropes = m_memory.Alloc (clipMap->max_ropes); - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); } void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) { // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); - assert(gfxWorldAsset != NULL); + assert(gfxWorldAsset != nullptr); GfxWorld* gfxWorld = gfxWorldAsset->Asset(); // Right now there is only one submodel, the world sub model @@ -166,7 +161,7 @@ namespace BSP clipMap->cmodels[0].leaf.leafBrushNode = 0; clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = NULL; // always set to 0 + clipMap->cmodels[0].info = nullptr; // always set to 0 } void ClipMapLinker::loadXModelCollision(clipMap_t* clipMap) @@ -178,7 +173,7 @@ namespace BSP // WIP code left in for future support /* auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); - assert(gfxWorldAsset != NULL); + assert(gfxWorldAsset != nullptr); GfxWorld* gfxWorld = gfxWorldAsset->Asset(); clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; @@ -306,7 +301,7 @@ namespace BSP { uint16_t vertIndex = tri[l]; vec3_t vertCoord = clipMap->verts[vertIndex]; - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + BSPUtil::updateAABBWithpoint(&vertCoord, &aabbMins, &aabbMaxs); } } } @@ -343,7 +338,7 @@ namespace BSP uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; vec3_t* currVertex = &clipMap->verts[currUind]; - BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + BSPUtil::updateAABBWithpoint(currVertex, &mins, &maxs); } aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); @@ -631,17 +626,17 @@ namespace BSP { // No support for brushes, only tris right now clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = NULL; + clipMap->info.brushsides = nullptr; clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = NULL; + clipMap->info.leafbrushNodes = nullptr; clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = NULL; + clipMap->info.leafbrushes = nullptr; clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = NULL; - clipMap->info.numBrushes = NULL; - clipMap->info.brushes = NULL; - clipMap->info.brushBounds = NULL; - clipMap->info.brushContents = NULL; + clipMap->info.brushVerts = nullptr; + clipMap->info.numBrushes = 0; + clipMap->info.brushes = nullptr; + clipMap->info.brushBounds = nullptr; + clipMap->info.brushContents = nullptr; // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated // HACK: @@ -667,7 +662,7 @@ namespace BSP for (unsigned int i = 1; i < clipMap->vertCount; i++) { vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + BSPUtil::updateAABBWithpoint(&vertCoord, &clipMins, &clipMaxs); } BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); @@ -697,7 +692,7 @@ namespace BSP { uint16_t vertIndex = tri[l]; vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + BSPUtil::updateAABBWithpoint(&vertCoord, &mins, &maxs); } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index db15bd4ce..88e8b8a8a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -15,23 +15,22 @@ namespace BSP ComWorld* comWorld = m_memory.Alloc(); comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; + comWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; comWorld->primaryLights = m_memory.Alloc(comWorld->primaryLightCount); - // static light is always empty - ComPrimaryLight* staticLight = &comWorld->primaryLights[0]; - memset(staticLight, 0, sizeof(ComPrimaryLight)); - + // first (static) light is always empty + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - memset(sunLight, 0, sizeof(ComPrimaryLight)); - sunLight->type = 1; - sunLight->diffuseColor.r = 0.75f; - sunLight->diffuseColor.g = 0.75f; - sunLight->diffuseColor.b = 0.75f; - sunLight->diffuseColor.a = 1.0f; - sunLight->dir.x = 0.0f; - sunLight->dir.y = 0.0f; - sunLight->dir.z = 0.0f; + const vec4_t sunLightColor = BSPEditableConstants::SUNLIGHT_COLOR; + const vec3_t sunLightDirection = BSPEditableConstants::SUNLIGHT_DIRECTION; + sunLight->type = GFX_LIGHT_TYPE_DIR; + sunLight->diffuseColor.r = sunLightColor.r; + sunLight->diffuseColor.g = sunLightColor.g; + sunLight->diffuseColor.b = sunLightColor.b; + sunLight->diffuseColor.a = sunLightColor.a; + sunLight->dir.x = sunLightDirection.x; + sunLight->dir.y = sunLightDirection.y; + sunLight->dir.z = sunLightDirection.z; auto comWorldAsset = m_context.AddAsset(comWorld->name, comWorld); return AssetCreationResult::Success(comWorldAsset); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp index 1286837bd..460f71d37 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp @@ -21,12 +21,10 @@ namespace BSP gameWorldMp->path.smoothBytes = 0; gameWorldMp->path.nodeTreeCount = 0; - int nodeCount = gameWorldMp->path.nodeCount + 128; - gameWorldMp->path.nodes = m_memory.Alloc(nodeCount); - gameWorldMp->path.basenodes = m_memory.Alloc(nodeCount); - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - + // The game has 128 empty nodes allocated + int extraNodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = m_memory.Alloc(extraNodeCount); + gameWorldMp->path.basenodes = m_memory.Alloc(extraNodeCount); gameWorldMp->path.pathVis = nullptr; gameWorldMp->path.smoothCache = nullptr; gameWorldMp->path.nodeTree = nullptr; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 82fe31c5d..570ecb539 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -130,7 +130,7 @@ namespace BSP for (int k = 0; k < currSurface->tris.triCount * 3; k++) { uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + BSPUtil::updateAABBWithpoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); } // unused values @@ -191,68 +191,69 @@ namespace BSP /* Models are unsupported right now Code is left in in case it is supported later on + + unsigned int modelCount = projInfo->modelCount; + gfxWorld->dpvs.smodelCount = modelCount; + gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + + for (unsigned int i = 0; i < modelCount; i++) + { + auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + customMapModel* inModel = &projInfo->models[i]; + + auto xModelAsset = m_context.LoadDependency(inModel->name); + if (xModelAsset == NULL) + { + printf("XModel %s not found!\n", inModel->name.c_str()); + currModel->model = NULL; + } + else + currModel->model = (XModel*)xModelAsset->Asset(); + + currModel->placement.origin.x = inModel->origin.x; + currModel->placement.origin.y = inModel->origin.y; + currModel->placement.origin.z = inModel->origin.z; + currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); + currModel->placement.scale = inModel->scale; + + BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + + // mins and maxs are calculated in world space not local space + // TODO: this does not account for model rotation or scale + currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + + currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + currModel->flags = DEFAULT_SMODEL_FLAGS; + currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + + // unknown use / unused + currModel->smid = i; + memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + currModel->invScaleSq = 0.0f; + currModel->lightingHandle = 0; + currModel->colorsIndex = 0; + currModel->visibility = 0; + + // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // TODO: calculate lighting and store it here + currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + } */ - //unsigned int modelCount = projInfo->modelCount; - //gfxWorld->dpvs.smodelCount = modelCount; - //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - // - //for (unsigned int i = 0; i < modelCount; i++) - //{ - // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - // customMapModel* inModel = &projInfo->models[i]; - // - // auto xModelAsset = m_context.LoadDependency(inModel->name); - // if (xModelAsset == NULL) - // { - // printf("XModel %s not found!\n", inModel->name.c_str()); - // currModel->model = NULL; - // } - // else - // currModel->model = (XModel*)xModelAsset->Asset(); - // - // currModel->placement.origin.x = inModel->origin.x; - // currModel->placement.origin.y = inModel->origin.y; - // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - // currModel->placement.scale = inModel->scale; - // - // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - // - // // mins and maxs are calculated in world space not local space - // // TODO: this does not account for model rotation or scale - // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - // - // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - // currModel->flags = DEFAULT_SMODEL_FLAGS; - // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - // - // // unknown use / unused - // currModel->smid = i; - // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - // currModel->invScaleSq = 0.0f; - // currModel->lightingHandle = 0; - // currModel->colorsIndex = 0; - // currModel->visibility = 0; - // - // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // // TODO: calculate lighting and store it here - // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - //} unsigned int modelCount = 0; gfxWorld->dpvs.smodelCount = modelCount; @@ -546,7 +547,7 @@ namespace BSP for (int i = 0; i < gfxWorld->surfaceCount; i++) { - BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + BSPUtil::updateAABB(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); } } @@ -665,7 +666,7 @@ namespace BSP gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - std::string probeImageName = "*reflection_probe0"; + std::string probeImageName = "reflection_probe0"; auto probeImageAsset = m_context.LoadDependency(probeImageName); if (probeImageAsset == NULL) { @@ -689,7 +690,7 @@ namespace BSP memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - std::string secondaryTexture = "*lightmap0_secondary"; + std::string secondaryTexture = "lightmap0_secondary"; auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); if (secondaryTextureAsset == NULL) { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 5ba3145b4..6849a3ed0 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -8,14 +8,14 @@ namespace { bool parseMapEntsJSON(json& entArrayJs, std::string& entityString) { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) + for (size_t entIdx = 0; entIdx < entArrayJs.size(); entIdx++) { - auto currEntity = entArrayJs[i]; + auto& entity = entArrayJs[entIdx]; - if (i == 0) + if (entIdx == 0) { - std::string className = currEntity["classname"]; + std::string className; + entity.at("classname").get_to(className); if (className.compare("worldspawn") != 0) { con::error("ERROR: first entity in the map entity string must be the worldspawn class!"); @@ -25,7 +25,7 @@ namespace entityString.append("{\n"); - for (auto& element : currEntity.items()) + for (auto& element : entity.items()) { std::string key = element.key(); std::string value = element.value(); @@ -38,26 +38,31 @@ namespace return true; } - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], size_t nameCount) { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) + for (auto& element : entArrayJs.items()) { - auto currEntity = entArrayJs[i]; + std::string origin; + std::string angles; + auto& entity = element.value(); + entity.at("origin").get_to(origin); + entity.at("angles").get_to(angles); - std::string origin = currEntity["origin"]; - std::string angles = currEntity["angles"]; - - for (int k = 0; k < nameCount; k++) + for (size_t nameIdx = 0; nameIdx < nameCount; nameIdx++) { entityString.append("{\n"); entityString.append(std::format("\"origin\" \"{}\"\n", origin)); entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[nameIdx])); entityString.append("}\n"); } } } + + std::string loadMapEnts() + { + + } } namespace BSP @@ -71,58 +76,65 @@ namespace BSP AssetCreationResult MapEntsLinker::linkMapEnts(BSPData* bsp) { - std::string entityString; - - json entJs; - std::string entityFilePath = BSPUtil::getFileNameForBSPAsset("entities.json"); - const auto entFile = m_search_path.Open(entityFilePath); - if (!entFile.IsOpen()) + try { - con::warn("Can't find entity file {}, using default entities instead", entityFilePath); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + json entJs; + std::string entityFilePath = BSPUtil::getFileNameForBSPAsset("entities.json"); + const auto entFile = m_search_path.Open(entityFilePath); + if (!entFile.IsOpen()) + { + con::warn("Can't find entity file {}, using default entities instead", entityFilePath); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + } + else + { + entJs = json::parse(*entFile.m_stream); + } + std::string entityString; + if (!parseMapEntsJSON(entJs["entities"], entityString)) + return AssetCreationResult::Failure(); + + json spawnJs; + std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset("spawns.json"); + const auto spawnFile = m_search_path.Open(spawnFilePath); + if (!spawnFile.IsOpen()) + { + con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); + spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + size_t defenderNameCount = std::extent::value; + size_t attackerNameCount = std::extent::value; + size_t ffaNameCount = std::extent::value; + parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); + parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); + parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + + MapEnts* mapEnts = m_memory.Alloc(); + mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); + + mapEnts->entityString = m_memory.Dup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = nullptr; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = nullptr; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = nullptr; + + auto mapEntsAsset = m_context.AddAsset(mapEnts->name, mapEnts); + return AssetCreationResult::Success(mapEntsAsset); } - else + catch (const json::exception& e) { - entJs = json::parse(*entFile.m_stream); - } - if (!parseMapEntsJSON(entJs["entities"], entityString)) + con::error("JSON error when parsing map ents and spawns: {}", e.what()); return AssetCreationResult::Failure(); - - json spawnJs; - std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset("spawns.json"); - const auto spawnFile = m_search_path.Open(spawnFilePath); - if (!spawnFile.IsOpen()) - { - con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); } - int defenderNameCount = std::extent::value; - int attackerNameCount = std::extent::value; - int ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); - - MapEnts* mapEnts = m_memory.Alloc(); - mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); - - mapEnts->entityString = m_memory.Dup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = nullptr; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = nullptr; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = nullptr; - - auto mapEntsAsset = m_context.AddAsset(mapEnts->name, mapEnts); - return AssetCreationResult::Success(mapEntsAsset); } } From fc88b9af80367fb5fb2646fcfb6f131d6b574019 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 27 Oct 2025 19:07:21 +0800 Subject: [PATCH 027/123] Refactored clipmap generation, further refactored other files to better use c++ --- src/ObjLoading/Game/T6/BSP/BSP.h | 2 +- src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp | 2 +- src/ObjLoading/Game/T6/BSP/BSPCalculation.h | 2 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 6 +- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 81 ++- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 19 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 620 ++++++++---------- .../Game/T6/BSP/Linker/ClipMapLinker.h | 12 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 4 +- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 6 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 3 +- 11 files changed, 337 insertions(+), 420 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 95e04897a..f2811743a 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -62,7 +62,7 @@ namespace BSP // These values are hardcoded ingame and will break the map if they are changed namespace BSPGameConstants { - constexpr int MAX_COLLISION_VERTS = UINT16_MAX; + constexpr unsigned int MAX_COLLISION_VERTS = UINT16_MAX; enum BSPDefaultLights { diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp index 60ee89cd1..b8a3a22b7 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp @@ -20,7 +20,7 @@ namespace BSP objectList.emplace_back(std::move(object)); } - BSPObject* BSPLeaf::getObject(int index) + BSPObject* BSPLeaf::getObject(size_t index) { return objectList.at(index).get(); } diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h index 61b8de07a..9988f5193 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h @@ -35,7 +35,7 @@ namespace BSP std::vector> objectList; void addObject(std::shared_ptr object); - BSPObject* getObject(int index); + BSPObject* getObject(size_t index); size_t getObjectCount(); }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 63a412247..8aad76d0e 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -193,7 +193,8 @@ namespace BSP { std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) { - std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset("map_gfx.fbx"); + std::string gfxFbxFileName = "map_gfx.fbx"; + std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset(gfxFbxFileName); auto gfxFile = searchPath.Open(gfxFbxPath); if (!gfxFile.IsOpen()) { @@ -222,7 +223,8 @@ namespace BSP } ufbx_scene* colScene; - std::string colFbxPath = BSPUtil::getFileNameForBSPAsset("map_col.fbx"); + std::string colFbxFileName = "map_col.fbx"; + std::string colFbxPath = BSPUtil::getFileNameForBSPAsset(colFbxFileName); auto colFile = searchPath.Open(colFbxPath); if (!colFile.IsOpen()) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 878f318db..0a2820675 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -5,12 +5,12 @@ #include "BSPUtil.h" -std::string BSPUtil::getFileNameForBSPAsset(std::string assetName) +std::string BSPUtil::getFileNameForBSPAsset(std::string& assetName) { return std::format("BSP/{}", assetName); } -vec3_t BSPUtil::convertToBO2Coords(vec3_t coordinate) +vec3_t BSPUtil::convertToBO2Coords(vec3_t& coordinate) { vec3_t result; result.x = coordinate.x; @@ -19,7 +19,7 @@ vec3_t BSPUtil::convertToBO2Coords(vec3_t coordinate) return result; } -vec3_t BSPUtil::convertFromBO2Coords(vec3_t coordinate) +vec3_t BSPUtil::convertFromBO2Coords(vec3_t& coordinate) { vec3_t result; result.x = coordinate.x; @@ -28,54 +28,63 @@ vec3_t BSPUtil::convertFromBO2Coords(vec3_t coordinate) return result; } -void BSPUtil::updateAABB(vec3_t* newAABBMins, vec3_t* newAABBMaxs, vec3_t* AABBMins, vec3_t* AABBMaxs) +void BSPUtil::updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs) { - if (AABBMins->x > newAABBMins->x) - AABBMins->x = newAABBMins->x; + if (AABBMins.x > newAABBMins.x) + AABBMins.x = newAABBMins.x; - if (newAABBMaxs->x > AABBMaxs->x) - AABBMaxs->x = newAABBMaxs->x; + if (newAABBMaxs.x > AABBMaxs.x) + AABBMaxs.x = newAABBMaxs.x; - if (AABBMins->y > newAABBMins->y) - AABBMins->y = newAABBMins->y; + if (AABBMins.y > newAABBMins.y) + AABBMins.y = newAABBMins.y; - if (newAABBMaxs->y > AABBMaxs->y) - AABBMaxs->y = newAABBMaxs->y; + if (newAABBMaxs.y > AABBMaxs.y) + AABBMaxs.y = newAABBMaxs.y; - if (AABBMins->z > newAABBMins->z) - AABBMins->z = newAABBMins->z; + if (AABBMins.z > newAABBMins.z) + AABBMins.z = newAABBMins.z; - if (newAABBMaxs->z > AABBMaxs->z) - AABBMaxs->z = newAABBMaxs->z; + if (newAABBMaxs.z > AABBMaxs.z) + AABBMaxs.z = newAABBMaxs.z; } -void BSPUtil::updateAABBWithpoint(vec3_t* point, vec3_t* AABBMins, vec3_t* AABBMaxs) +void BSPUtil::updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs) { - if (AABBMins->x > point->x) - AABBMins->x = point->x; + if (AABBMins.x > point.x) + AABBMins.x = point.x; - if (point->x > AABBMaxs->x) - AABBMaxs->x = point->x; + if (point.x > AABBMaxs.x) + AABBMaxs.x = point.x; - if (AABBMins->y > point->y) - AABBMins->y = point->y; + if (AABBMins.y > point.y) + AABBMins.y = point.y; - if (point->y > AABBMaxs->y) - AABBMaxs->y = point->y; + if (point.y > AABBMaxs.y) + AABBMaxs.y = point.y; - if (AABBMins->z > point->z) - AABBMins->z = point->z; + if (AABBMins.z > point.z) + AABBMins.z = point.z; - if (point->z > AABBMaxs->z) - AABBMaxs->z = point->z; + if (point.z > AABBMaxs.z) + AABBMaxs.z = point.z; } -vec3_t BSPUtil::calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) +vec3_t BSPUtil::calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs) { vec3_t result; - result.x = (mins->x + maxs->x) * 0.5f; - result.y = (mins->y + maxs->y) * 0.5f; - result.z = (mins->z + maxs->z) * 0.5f; + result.x = (mins.x + maxs.x) * 0.5f; + result.y = (mins.y + maxs.y) * 0.5f; + result.z = (mins.z + maxs.z) * 0.5f; + return result; +} + +vec3_t BSPUtil::calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs) +{ + vec3_t result; + result.x = (maxs.x - mins.x) * 0.5f; + result.y = (maxs.y - mins.y) * 0.5f; + result.z = (maxs.z - mins.z) * 0.5f; return result; } @@ -84,7 +93,7 @@ int BSPUtil::allignBy128(int size) return ((size + 127) & 0xFFFFFF80); } -float BSPUtil::distBetweenPoints(vec3_t p1, vec3_t p2) +float BSPUtil::distBetweenPoints(vec3_t& p1, vec3_t& p2) { float x = p2.x - p1.x; float y = p2.y - p1.y; @@ -130,7 +139,7 @@ void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) out[2].z = in[2].z; } -vec3_t BSPUtil::convertStringToVec3(std::string str) +vec3_t BSPUtil::convertStringToVec3(std::string& str) { std::string v1Str = str; @@ -153,7 +162,7 @@ vec3_t BSPUtil::convertStringToVec3(std::string str) return result; } -std::string BSPUtil::convertVec3ToString(vec3_t vec) +std::string BSPUtil::convertVec3ToString(vec3_t& vec) { std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); return result; diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 30307bcf0..474d51c15 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -6,16 +6,17 @@ class BSPUtil { public: - static std::string getFileNameForBSPAsset(std::string assetName); - static vec3_t convertToBO2Coords(vec3_t OGL_coordinate); - static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate); - static void updateAABB(vec3_t* newAABBMins, vec3_t* newAABBMaxs, vec3_t* AABBMins, vec3_t* AABBMaxs); - static void updateAABBWithpoint(vec3_t* point, vec3_t* AABBMins, vec3_t* AABBMaxs); - static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs); + static std::string getFileNameForBSPAsset(std::string& assetName); + static vec3_t convertToBO2Coords(vec3_t& OGL_coordinate); + static vec3_t convertFromBO2Coords(vec3_t& bo2_coordinate); + static void updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs); + static void updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs); + static vec3_t calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs); + static vec3_t calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs); static int allignBy128(int size); - static float distBetweenPoints(vec3_t p1, vec3_t p2); + static float distBetweenPoints(vec3_t& p1, vec3_t& p2); static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); - static vec3_t convertStringToVec3(std::string str); - static std::string convertVec3ToString(vec3_t vec); + static vec3_t convertStringToVec3(std::string& str); + static std::string convertVec3ToString(vec3_t& vec); }; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index e8617928a..aee90d7ce 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -1,12 +1,6 @@ #include "ClipMapLinker.h" #include "../BSPUtil.h" - -namespace -{ - -} - namespace BSP { ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -122,7 +116,7 @@ namespace BSP // The game allocates 32 empty ropes clipMap->max_ropes = 32; // max 300 - clipMap->ropes = m_memory.Alloc (clipMap->max_ropes); + clipMap->ropes = m_memory.Alloc(clipMap->max_ropes); } void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) @@ -214,374 +208,334 @@ namespace BSP */ } - void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + int ClipMapLinker::addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - out_origin->x = temp.x * 0.5f; - out_origin->y = temp.y * 0.5f; - out_origin->z = temp.z * 0.5f; - - // Half-size is half the difference: (max - min) / 2 - temp.x = maxs->x - mins->x; - temp.y = maxs->y - mins->y; - temp.z = maxs->z - mins->z; - out_halfSize->x = temp.x * 0.5f; - out_halfSize->y = temp.y * 0.5f; - out_halfSize->z = temp.z * 0.5f; - } + assert(node->isLeaf); - void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) - { - if (node->isLeaf) + size_t objectCount = node->leaf->getObjectCount(); + size_t parentAABBIndex = AABBTreeVec.size(); + + assert(objectCount > 0); + + // add the parent AABB node + vec3_t parentMins; + vec3_t parentMaxs; + for (size_t objectIdx = 0; objectIdx < objectCount; objectIdx++) { - (*numLeafs)++; - // there won't be an AABB tree when objectList is empty - if (node->leaf->getObjectCount() > 0) + int partitionIndex = node->leaf->getObject(objectIdx)->partitionIndex; + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) { - *numAABBTrees += node->leaf->getObjectCount() + 1; + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = clipMap->verts[uind]; - if (node->leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->leaf->getObjectCount(); + // initalise the parent AABB with the first vertex + if (objectIdx == 0 && uindIdx == 0) + { + parentMins = vert; + parentMaxs = vert; + } + + BSPUtil::updateAABBWithPoint(vert, parentMins, parentMaxs); } } - else - { - (*numPlanes)++; - (*numNodes)++; - traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - } - } - - vec3_t normalX = { 1.0f, 0.0f, 0.0f }; - vec3_t normalY = { 0.0f, 1.0f, 0.0f }; - vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; - int currPlaneCount = 0; - int currNodeCount = 0; - int currLeafCount = 0; - int currAABBCount = 0; - - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) - { - assert(node->isLeaf); + CollisionAabbTree parentAABB; + parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); + parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); + parentAABB.materialIndex = 0; // always use the first material + parentAABB.childCount = static_cast(objectCount); + parentAABB.u.firstChildIndex = static_cast(parentAABBIndex + 1); + AABBTreeVec.emplace_back(parentAABB); - int objectCount = node->leaf->getObjectCount(); - int firstAABBIndex = currAABBCount; - currAABBCount += objectCount + 1; - - // calculate root AABB node mins and maxs - // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs - // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; - auto firstPartition = &clipMap->partitions[firstPartitionIndex]; - uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t aabbMins; - vec3_t aabbMaxs; - aabbMins.x = firstVert->x; - aabbMins.y = firstVert->y; - aabbMins.z = firstVert->z; - aabbMaxs.x = firstVert->x; - aabbMaxs.y = firstVert->y; - aabbMaxs.z = firstVert->z; - for (int i = 0; i < objectCount; i++) + // add child AABB nodes + for (size_t objectIdx = 0; objectIdx < objectCount; objectIdx++) { - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - auto currPartition = &clipMap->partitions[currPartitionIndex]; - - for (int k = 0; k < currPartition->triCount; k++) + int partitionIndex = node->leaf->getObject(objectIdx)->partitionIndex; + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + vec3_t childMins; + vec3_t childMaxs; + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = clipMap->verts[uind]; + + // initalise the child AABB with the first vertex + if (uindIdx == 0) { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = clipMap->verts[vertIndex]; - BSPUtil::updateAABBWithpoint(&vertCoord, &aabbMins, &aabbMaxs); + childMins = vert; + childMaxs = vert; } - } - } - CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; - aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); - rootAABB->materialIndex = 0; - rootAABB->childCount = objectCount; - rootAABB->u.firstChildIndex = firstAABBIndex + 1; - - // populate child AABB nodes - for (int i = 0; i < objectCount; i++) - { - CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - - currAabbTree->materialIndex = 0; - currAabbTree->childCount = 0; - currAabbTree->u.partitionIndex = currPartitionIndex; - - // calculate partition origin and half size - CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; - uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; - vec3_t* firstVertex = &clipMap->verts[firstUind]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVertex->x; - mins.y = firstVertex->y; - mins.z = firstVertex->z; - maxs.x = firstVertex->x; - maxs.y = firstVertex->y; - maxs.z = firstVertex->z; - for (int i = 1; i < aabbPartition->nuinds; i++) - { - uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; - vec3_t* currVertex = &clipMap->verts[currUind]; - BSPUtil::updateAABBWithpoint(currVertex, &mins, &maxs); + BSPUtil::updateAABBWithPoint(vert, childMins, childMaxs); } - aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + CollisionAabbTree childAABBTree; + childAABBTree.materialIndex = 0; // always use the first material + childAABBTree.childCount = 0; + childAABBTree.u.partitionIndex = partitionIndex; + childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); + childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); + AABBTreeVec.emplace_back(childAABBTree); } - return firstAABBIndex; + return static_cast(parentAABBIndex); } - // returns the index corresponding to the BSPTree* node parsed - int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + constexpr vec3_t normalX = { 1.0f, 0.0f, 0.0f }; + constexpr vec3_t normalY = { 0.0f, 1.0f, 0.0f }; + constexpr vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; + + // returns the index of the node/leaf parsed by the function + // Nodes are indexed by their index in the node array + // Leafs are indexed by (-1 - ) + // See https://developer.valvesoftware.com/wiki/BSP_(Source) + int16_t ClipMapLinker::loadBSPNode(clipMap_t* clipMap, BSPTree* tree) { - if (node->isLeaf) + if (tree->isLeaf) { - int currLeafIndex = currLeafCount; - currLeafCount++; - cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + cLeaf_s leaf; - currLeaf->cluster = 0; - currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + leaf.cluster = 0; // always use cluster 0 + leaf.brushContents = 0; // no brushes used so contents is 0 + leaf.terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // unused when leafBrushNode == 0 - currLeaf->mins.x = 0.0f; - currLeaf->mins.y = 0.0f; - currLeaf->mins.z = 0.0f; - currLeaf->maxs.x = 0.0f; - currLeaf->maxs.y = 0.0f; - currLeaf->maxs.z = 0.0f; - currLeaf->leafBrushNode = 0; - - if (node->leaf->getObjectCount() > 0) + leaf.mins.x = 0.0f; + leaf.mins.y = 0.0f; + leaf.mins.z = 0.0f; + leaf.maxs.x = 0.0f; + leaf.maxs.y = 0.0f; + leaf.maxs.z = 0.0f; + leaf.leafBrushNode = 0; + + if (tree->leaf->getObjectCount() > 0) { - currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); - currLeaf->collAabbCount = 1; + leaf.firstCollAabbIndex = addAABBTreeFromLeaf(tree, clipMap); + leaf.collAabbCount = 1; // 1 as it is the parent of all object AABBs } else { - currLeaf->firstCollAabbIndex = 0; - currLeaf->collAabbCount = 0; + leaf.firstCollAabbIndex = 0; + leaf.collAabbCount = 0; } - return -1 - currLeafIndex; + uint16_t leafIndex = static_cast(leafVec.size()); + leafVec.emplace_back(leaf); + + return -1 - leafIndex; } else { - cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; - currPlaneCount++; + cplane_s plane; - if (node->node->axis == AXIS_X) + if (tree->node->axis == AXIS_X) { - // X is unchanged when going from OGL x -> BO2 x - currPlane->normal = normalX; - - // converting OGL -> BO2 X coords doesn't change the x coords at all, so - // the dist stays the same - currPlane->dist = (float)node->node->distance; + plane.normal = normalX; + plane.dist = tree->node->distance; } else { - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // convert the z normal to the y normal, but don't negate it. Negative normals don't do - // what is expected when the game uses them - assert(node->node->axis == AXIS_Z); - currPlane->normal = normalY; - - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = (float)(-node->node->distance); + // converting Z coords to BO2 negates the z coords and sets it to the y coord. + assert(tree->node->axis == AXIS_Z); + plane.normal = normalY; + plane.dist = -tree->node->distance; } - bool foundType = false; - if (currPlane->normal.x == 1.0f) - { - assert(!foundType); - foundType = true; - currPlane->type = 0; - } - else if (currPlane->normal.y == 1.0f) - { - assert(!foundType); - foundType = true; - currPlane->type = 1; - } - else if (currPlane->normal.z == 1.0f) + if (plane.normal.x == 1.0f) + plane.type = 0; + else if (plane.normal.y == 1.0f) + plane.type = 1; + else { - assert(!foundType); - foundType = true; - currPlane->type = 2; + assert(plane.normal.z == 1.0f); + plane.type = 2; } - else - assert(foundType); - - currPlane->signbits = 0; - if (currPlane->normal.x < 0.0f) - currPlane->signbits |= 1; - if (currPlane->normal.y < 0.0f) - currPlane->signbits |= 2; - if (currPlane->normal.z < 0.0f) - currPlane->signbits |= 4; - - currPlane->pad[0] = 0; - currPlane->pad[1] = 0; - - int currNodeIndex = currNodeCount; - currNodeCount++; - cNode_t* currNode = &clipMap->nodes[currNodeIndex]; - - currNode->plane = currPlane; - // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): - // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, - // the normal vector flips and objects behind the plane are now in front, and vise versa - // so the back node now represents the front, and the front node represents the back. - // Do the OGL -> Bo2 coord change on paper and it will make sense - if (currPlane->type == 1) + + plane.signbits = 0; + if (plane.normal.x < 0.0f) + plane.signbits |= 1; + if (plane.normal.y < 0.0f) + plane.signbits |= 2; + if (plane.normal.z < 0.0f) + plane.signbits |= 4; + + plane.pad[0] = 0; + plane.pad[1] = 0; + + planeVec.emplace_back(plane); + + // The recursion of adding the children means the node needs to be added before the chilren are loaded + size_t nodeIndex = nodeVec.size(); + nodeVec.emplace_back(); + + cNode_t node; + node.plane = nullptr; // initalised after the BSP tree has been loaded + if (plane.type == 1) { - currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); + // Due to the plane normal going from Z to Y, objects behind the plane are now in front and objects in front are now behind + node.children[1] = loadBSPNode(clipMap, tree->node->front.get()); + node.children[0] = loadBSPNode(clipMap, tree->node->back.get()); } else { - currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); + node.children[0] = loadBSPNode(clipMap, tree->node->front.get()); + node.children[1] = loadBSPNode(clipMap, tree->node->back.get()); } - return currNodeIndex; + nodeVec.at(nodeIndex) = node; + + return static_cast(nodeIndex); } } - void ClipMapLinker::populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + void ClipMapLinker::loadBSPTree(clipMap_t* clipMap, BSPData* bsp) { - size_t numPlanes = 0; - size_t numNodes = 0; - size_t numLeafs = 0; - size_t numAABBTrees = 0; - size_t maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = m_memory.Alloc(clipMap->info.planeCount); - clipMap->numNodes = numNodes; - clipMap->nodes = m_memory.Alloc(clipMap->numNodes); - // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. - // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. - clipMap->aabbTreeCount = numAABBTrees; - clipMap->aabbTrees = m_memory.Alloc(clipMap->aabbTreeCount); - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = m_memory.Alloc(clipMap->numLeafs); - memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); - currLeafCount = 1; - - populateBSPTree_r(clipMap, tree); - - assert(clipMap->info.planeCount == currPlaneCount); - assert(clipMap->numNodes == currNodeCount); - assert(clipMap->numLeafs == currLeafCount); - assert(clipMap->aabbTreeCount == currAABBCount); - } + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate before it is added into the BSP tree. - bool ClipMapLinker::createPartitions(clipMap_t* clipMap, BSPData* bsp) - { - int collisionVertexCount = bsp->colWorld.vertices.size(); - std::vector collisionVertVec; - for (int i = 0; i < collisionVertexCount; i++) + vec3_t worldMins; + vec3_t worldMaxs; + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) { - collisionVertVec.push_back(BSPUtil::convertToBO2Coords(bsp->colWorld.vertices[i].pos)); - //collisionVertVec.push_back(bsp->colWorld.vertices[i].pos); + vec3_t vertex = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIdx]); + // initalise AABB with the first vertex + if (vertIdx == 0) + { + worldMins = vertex; + worldMaxs = vertex; + } + BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = m_memory.Alloc(collisionVertexCount); - memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); + std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); + assert(!tree->isLeaf); + for (int partitionIdx = 0; partitionIdx < clipMap->partitionCount; partitionIdx++) + { + vec3_t partitionMins; + vec3_t partitionMaxs; + CollisionPartition* partition = &clipMap->partitions[partitionIdx]; + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) + { + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = BSPUtil::convertFromBO2Coords(clipMap->verts[uind]); + // initalise the AABB with the first vertex + if (uindIdx == 0) + { + partitionMins = vert; + partitionMaxs = vert; + } + BSPUtil::updateAABBWithPoint(vert, partitionMins, partitionMaxs); + } + std::shared_ptr currObject = std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); + tree->addObjectToTree(std::move(currObject)); + } + + // load planes, nodes, leafs, and AABB trees + loadBSPNode(clipMap, tree.get()); + + clipMap->info.planeCount = static_cast(planeVec.size()); + clipMap->info.planes = m_memory.Alloc(planeVec.size()); + memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); + + clipMap->numNodes = static_cast(nodeVec.size()); + clipMap->nodes = m_memory.Alloc(nodeVec.size()); + memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); + + clipMap->numLeafs = static_cast(leafVec.size()); + clipMap->leafs = m_memory.Alloc(leafVec.size()); + memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); + + clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); + clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); + memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); + + // The plane of each node have the same index + for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) + clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; + } + + bool ClipMapLinker::loadPartitions(clipMap_t* clipMap, BSPData* bsp) + { // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) + // any vertex count over the uint16_t max means the vertices above the uint16_t max can't be indexed + if (static_cast(bsp->colWorld.vertices.size()) > BSPGameConstants::MAX_COLLISION_VERTS) { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", clipMap->vertCount, BSPGameConstants::MAX_COLLISION_VERTS); return false; } + clipMap->vertCount = static_cast(bsp->colWorld.vertices.size()); + clipMap->verts = m_memory.Alloc(clipMap->vertCount); + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) + clipMap->verts[vertIdx] = BSPUtil::convertToBO2Coords(bsp->colWorld.vertices[vertIdx].pos); + + // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's + // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. std::vector triIndexVec; - for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + for (BSPSurface& surface : bsp->colWorld.surfaces) { - BSPSurface* currSurface = &bsp->colWorld.surfaces[i]; - int triCount = currSurface->triCount; - - for (int k = 0; k < triCount * 3; k += 3) + int indexOfFirstIndex = surface.indexOfFirstIndex; + int indexOfFirstVertex = surface.indexOfFirstVertex; + for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx += 3) { - int firstIndex_Index = currSurface->indexOfFirstIndex; - int firstVertexIndex = currSurface->indexOfFirstVertex; - - // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire - // clipmap verts buffer, so this code updates the indexes to follow that. - int triIndex0 = bsp->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = bsp->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = bsp->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + int firstTriIndex = indexOfFirstIndex + indexIdx; + int triIndex0 = bsp->colWorld.indices[firstTriIndex + 0] + indexOfFirstVertex; + int triIndex1 = bsp->colWorld.indices[firstTriIndex + 1] + indexOfFirstVertex; + int triIndex2 = bsp->colWorld.indices[firstTriIndex + 2] + indexOfFirstVertex; // triangle index ordering is opposite to blenders, so its converted here - triIndexVec.push_back(triIndex2); - triIndexVec.push_back(triIndex1); - triIndexVec.push_back(triIndex0); + triIndexVec.emplace_back(triIndex2); + triIndexVec.emplace_back(triIndex1); + triIndexVec.emplace_back(triIndex0); } } - assert(triIndexVec.size() % 3 == 0); - - // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, - // and static_cast can't safely do the conversion - clipMap->triCount = triIndexVec.size() / 3; + // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion + clipMap->triCount = static_cast(triIndexVec.size() / 3); clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. std::vector partitionVec; - for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + std::vector uniqueIndicesVec; + for (BSPSurface& surface : bsp->colWorld.surfaces) { - int triCount = bsp->colWorld.surfaces[i].triCount; - int firstTriIndex = bsp->colWorld.surfaces[i].indexOfFirstIndex / 3; - for (int k = 0; k < triCount; k++) + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + int indexOfFirstTri = surface.indexOfFirstIndex / 3; + int indexOfFirstVertex = surface.indexOfFirstVertex; + for (int triIdx = 0; triIdx < surface.triCount; triIdx++) { - CollisionPartition newPartition; - newPartition.nuinds = 0; // initialised later - newPartition.fuind = 0; // initialised later - newPartition.triCount = 1; - newPartition.firstTri = firstTriIndex; - firstTriIndex += 1; - - partitionVec.push_back(newPartition); + CollisionPartition partition; + partition.triCount = 1; + partition.firstTri = indexOfFirstTri + triIdx; + + partition.nuinds = 3; + partition.fuind = static_cast(uniqueIndicesVec.size()); + + // All tri indices are unique since there is only one tri per partition + uint16_t* tri = clipMap->triIndices[partition.firstTri]; + uniqueIndicesVec.emplace_back(tri[0]); + uniqueIndicesVec.emplace_back(tri[1]); + uniqueIndicesVec.emplace_back(tri[2]); + + partitionVec.emplace_back(partition); } } - clipMap->partitionCount = partitionVec.size(); + clipMap->partitionCount = static_cast(partitionVec.size()); clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); - memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); + memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); + + clipMap->info.nuinds = static_cast(uniqueIndicesVec.size()); + clipMap->info.uinds = m_memory.Alloc(uniqueIndicesVec.size()); + memcpy(clipMap->info.uinds, uniqueIndicesVec.data(), sizeof(uint16_t) * uniqueIndicesVec.size()); + return true; + + /* + // Proper unique index creation code kept for future use int totalUindCount = 0; std::vector uindVec; for (int i = 0; i < clipMap->partitionCount; i++) @@ -606,7 +560,7 @@ namespace BSP } if (isVertexIndexUnique) - uniqueVertVec.push_back(vertIndex); + uniqueVertVec.emplace_back(vertIndex); } } @@ -618,11 +572,10 @@ namespace BSP clipMap->info.nuinds = totalUindCount; clipMap->info.uinds = m_memory.Alloc(totalUindCount); memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - - return true; + */ } - bool ClipMapLinker::loadBrushCollision(clipMap_t* clipMap, BSPData* bsp) + bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp) { // No support for brushes, only tris right now clipMap->info.numBrushSides = 0; @@ -638,70 +591,11 @@ namespace BSP clipMap->info.brushBounds = nullptr; clipMap->info.brushContents = nullptr; - // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, - // and then convert them back when it is being parsed into the clipmap. Requires some hacky - // logic, check populateBSPTree_r and addAABBTreeFromLeaf - - if (!createPartitions(clipMap, bsp)) + // load verts, tris, uinds and partitions + if (!loadPartitions(clipMap, bsp)) return false; - vec3_t* firstVert = &clipMap->verts[0]; - vec3_t clipMins; - vec3_t clipMaxs; - clipMins.x = firstVert->x; - clipMins.y = firstVert->y; - clipMins.z = firstVert->z; - clipMaxs.x = firstVert->x; - clipMaxs.y = firstVert->y; - clipMaxs.z = firstVert->z; - clipMins = BSPUtil::convertFromBO2Coords(clipMins); - clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); - for (unsigned int i = 1; i < clipMap->vertCount; i++) - { - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); - BSPUtil::updateAABBWithpoint(&vertCoord, &clipMins, &clipMaxs); - } - - BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); - - assert(!tree->isLeaf); - - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVert->x; - mins.y = firstVert->y; - mins.z = firstVert->z; - maxs.x = firstVert->x; - maxs.y = firstVert->y; - maxs.z = firstVert->z; - mins = BSPUtil::convertFromBO2Coords(mins); - maxs = BSPUtil::convertFromBO2Coords(maxs); - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - BSPUtil::updateAABBWithpoint(&vertCoord, &mins, &maxs); - } - } - - std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - - tree->addObjectToTree(std::move(currObject)); - } - - populateBSPTree(clipMap, tree); + loadBSPTree(clipMap, bsp); return true; } @@ -746,7 +640,7 @@ namespace BSP clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - if (!loadBrushCollision(clipMap, bsp)) + if (!loadWorldCollision(clipMap, bsp)) return AssetCreationResult::Failure(); m_context.AddAsset(clipMap->name, clipMap); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 9a08ebd7a..ec2a47666 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -26,8 +26,16 @@ namespace BSP void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); void loadXModelCollision(clipMap_t* clipMap); - bool loadBrushCollision(clipMap_t* clipMap, BSPData* bsp); + std::vector planeVec; + std::vector nodeVec; + std::vector leafVec; + std::vector AABBTreeVec; + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); + int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); + + void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); + bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); void populateBSPTree(clipMap_t* clipMap, BSPTree* tree); - bool createPartitions(clipMap_t* clipMap, BSPData* bsp); + bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); }; } \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 570ecb539..d16a8eee1 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -130,7 +130,7 @@ namespace BSP for (int k = 0; k < currSurface->tris.triCount * 3; k++) { uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::updateAABBWithpoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, currSurface->bounds[0], currSurface->bounds[1]); } // unused values @@ -547,7 +547,7 @@ namespace BSP for (int i = 0; i < gfxWorld->surfaceCount; i++) { - BSPUtil::updateAABB(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[i].bounds[0], gfxWorld->dpvs.surfaces[i].bounds[1], gfxWorld->mins, gfxWorld->maxs); } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 6849a3ed0..00459a565 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -79,7 +79,8 @@ namespace BSP try { json entJs; - std::string entityFilePath = BSPUtil::getFileNameForBSPAsset("entities.json"); + std::string entityFileName = "entities.json"; + std::string entityFilePath = BSPUtil::getFileNameForBSPAsset(entityFileName); const auto entFile = m_search_path.Open(entityFilePath); if (!entFile.IsOpen()) { @@ -95,7 +96,8 @@ namespace BSP return AssetCreationResult::Failure(); json spawnJs; - std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset("spawns.json"); + std::string spawnFileName = "spawns.json"; + std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset(spawnFileName); const auto spawnFile = m_search_path.Open(spawnFilePath); if (!spawnFile.IsOpen()) { diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 74bac61c0..630897a0c 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -21,7 +21,8 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { // custom maps must have a map_gfx file - auto mapGfxFile = m_search_path.Open(BSPUtil::getFileNameForBSPAsset("map_gfx.fbx")); + std::string mapGfxFileName = "map_gfx.fbx"; + auto mapGfxFile = m_search_path.Open(BSPUtil::getFileNameForBSPAsset(mapGfxFileName)); if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); From 9268342ca1828f3020038cd27de0a324663efaee Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 27 Oct 2025 20:00:32 +0800 Subject: [PATCH 028/123] Refactored GFX world loading --- src/ObjLoading/Game/T6/BSP/BSP.h | 2 +- .../Game/T6/BSP/Linker/ClipMapLinker.h | 4 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 447 ++++++++---------- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 26 +- 4 files changed, 206 insertions(+), 273 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index f2811743a..d146734c4 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -155,7 +155,7 @@ namespace BSP } // BSPEditableConstants: - // These values are BSP constants that can be edited and won't break the linker/game if changed + // These values are BSP constants that can be edited and may not break the linker/game if changed namespace BSPEditableConstants { // Default xmodel values diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index ec2a47666..44d8643bb 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -32,10 +32,8 @@ namespace BSP std::vector AABBTreeVec; int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); - void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); - bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); - void populateBSPTree(clipMap_t* clipMap, BSPTree* tree); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); + bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); }; } \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index d16a8eee1..223f57c4c 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -10,151 +10,123 @@ namespace BSP m_context(context) { } - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - void GfxWorldLinker::overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) + + void GfxWorldLinker::loadDrawData(BSPData* bsp, GfxWorld* gfxWorld) { - int vertexCount = projInfo->gfxWorld.vertices.size(); - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + size_t vertexCount = bsp->gfxWorld.vertices.size(); + gfxWorld->draw.vertexCount = static_cast(vertexCount); + gfxWorld->draw.vertexDataSize0 = static_cast(vertexCount * sizeof(GfxPackedWorldVertex)); GfxPackedWorldVertex* vertexBuffer = m_memory.Alloc(vertexCount); - for (int i = 0; i < vertexCount; i++) + for (size_t vertIdx = 0; vertIdx < vertexCount; vertIdx++) { - BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); - //GfxVertex->xyz = WorldVertex->pos; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); - //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); - //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - - // unknown use variables - // binormalSign may be bitangent of the vertex - // lmapCoord may be the lightmap coordinate of the vertex - GfxVertex->binormalSign = 0.0f; - GfxVertex->lmapCoord.packed = 0; + BSPVertex& bspVertex = bsp->gfxWorld.vertices.at(vertIdx); + GfxPackedWorldVertex* gfxVertex = &vertexBuffer[vertIdx]; + + gfxVertex->xyz = BSPUtil::convertToBO2Coords(bspVertex.pos); + gfxVertex->color.packed = pack32::Vec4PackGfxColor(bspVertex.color.v); + gfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(bspVertex.texCoord.v); + gfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.normal.v); + gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); + + // unimplemented variables + gfxVertex->binormalSign = 0.0f; + gfxVertex->lmapCoord.packed = 0; } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; + gfxWorld->draw.vd0.data = reinterpret_cast(vertexBuffer); - // we don't use vd1 but still needs to be initialised + // vd1 is unused but still needs to be initialised // the data type varies and 0x20 is enough for all types gfxWorld->draw.vertexDataSize1 = 0x20; gfxWorld->draw.vd1.data = m_memory.Alloc(gfxWorld->draw.vertexDataSize1); - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - int indexCount = projInfo->gfxWorld.indices.size(); + size_t indexCount = bsp->gfxWorld.indices.size(); assert(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indexCount = static_cast(indexCount); gfxWorld->draw.indices = m_memory.Alloc(indexCount); - for (int i = 0; i < indexCount; i += 3) + for (size_t indexIdx = 0; indexIdx < indexCount; indexIdx += 3) { // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; + gfxWorld->draw.indices[indexIdx + 2] = bsp->gfxWorld.indices.at(indexIdx + 0); + gfxWorld->draw.indices[indexIdx + 1] = bsp->gfxWorld.indices.at(indexIdx + 1); + gfxWorld->draw.indices[indexIdx + 0] = bsp->gfxWorld.indices.at(indexIdx + 2); } } - bool GfxWorldLinker::overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) + bool GfxWorldLinker::loadMapSurfaces(BSPData* bsp, GfxWorld* gfxWorld) { - overwriteDrawData(projInfo, gfxWorld); + loadDrawData(bsp, gfxWorld); - unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + size_t surfaceCount = bsp->gfxWorld.surfaces.size(); + gfxWorld->surfaceCount = static_cast(surfaceCount); + gfxWorld->dpvs.staticSurfaceCount = static_cast(surfaceCount); gfxWorld->dpvs.surfaces = m_memory.Alloc(surfaceCount); - for (unsigned int i = 0; i < surfaceCount; i++) + for (size_t surfIdx = 0; surfIdx < surfaceCount; surfIdx++) { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxWorld.surfaces[i]; + BSPSurface& bspSurface = bsp->gfxWorld.surfaces.at(surfIdx); + GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx]; - currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; + gfxSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + gfxSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + gfxSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; + gfxSurface->tris.triCount = static_cast(bspSurface.triCount); + gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex; - currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; + gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); + gfxSurface->tris.vertexDataOffset1 = 0; std::string surfMaterialName; - switch (objSurface->material.materialType) - { - case MATERIAL_TYPE_TEXTURE: - surfMaterialName = objSurface->material.materialName; - break; - - case MATERIAL_TYPE_COLOUR: - case MATERIAL_TYPE_EMPTY: + if (bspSurface.material.materialType == MATERIAL_TYPE_TEXTURE) + surfMaterialName = bspSurface.material.materialName; + else // MATERIAL_TYPE_COLOUR || MATERIAL_TYPE_EMPTY surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - break; - - default: - assert(false); - } auto surfMaterialAsset = m_context.LoadDependency(surfMaterialName); - if (surfMaterialAsset == NULL) + if (surfMaterialAsset == nullptr) { std::string missingImageName = BSPLinkingConstants::MISSING_IMAGE_NAME; surfMaterialAsset = m_context.LoadDependency(missingImageName); - if (surfMaterialAsset == NULL) + if (surfMaterialAsset == nullptr) { con::error("unable to find the missing image texture!"); return false; } } - currSurface->material = surfMaterialAsset->Asset(); - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) + gfxSurface->material = surfMaterialAsset->Asset(); + + GfxPackedWorldVertex* firstVert = reinterpret_cast(&gfxWorld->draw.vd0.data[gfxSurface->tris.vertexDataOffset0]); + gfxSurface->bounds[0].x = firstVert[0].xyz.x; + gfxSurface->bounds[0].y = firstVert[0].xyz.y; + gfxSurface->bounds[0].z = firstVert[0].xyz.z; + gfxSurface->bounds[1].x = firstVert[0].xyz.x; + gfxSurface->bounds[1].y = firstVert[0].xyz.y; + gfxSurface->bounds[1].z = firstVert[0].xyz.z; + for (size_t indexIdx = 0; indexIdx < static_cast(gfxSurface->tris.triCount * 3); indexIdx++) { - uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, currSurface->bounds[0], currSurface->bounds[1]); + uint16_t vertIndex = gfxWorld->draw.indices[gfxSurface->tris.baseIndex + indexIdx]; + BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, gfxSurface->bounds[0], gfxSurface->bounds[1]); } // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; + gfxSurface->tris.mins.x = 0.0f; + gfxSurface->tris.mins.y = 0.0f; + gfxSurface->tris.mins.z = 0.0f; + gfxSurface->tris.maxs.x = 0.0f; + gfxSurface->tris.maxs.y = 0.0f; + gfxSurface->tris.maxs.z = 0.0f; + gfxSurface->tris.himipRadiusInvSq = 0.0f; + gfxSurface->tris.vertexCount = 0; + gfxSurface->tris.firstVertex = 0; } // doesn't seem to matter what order the sorted surfs go in gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(surfaceCount); - for (unsigned int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = i; - } + for (size_t surfIdx = 0; surfIdx < surfaceCount; surfIdx++) + gfxWorld->dpvs.sortedSurfIndex[surfIdx] = static_cast(surfIdx); // surface materials are written to by the game gfxWorld->dpvs.surfaceMaterials = m_memory.Alloc(surfaceCount); - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); // set all surface types to lit opaque gfxWorld->dpvs.litSurfsBegin = 0; @@ -176,17 +148,11 @@ namespace BSP gfxWorld->dpvs.surfaceVisDataCameraSaved = m_memory.Alloc(allignedSurfaceCount); gfxWorld->dpvs.surfaceCastsShadow = m_memory.Alloc(allignedSurfaceCount); gfxWorld->dpvs.surfaceCastsSunShadow = m_memory.Alloc(allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[0], 0, allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, allignedSurfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, allignedSurfaceCount); return true; } - void GfxWorldLinker::overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) + void GfxWorldLinker::loadXModels(BSPData* bsp, GfxWorld* gfxWorld) { /* Models are unsupported right now @@ -204,10 +170,10 @@ namespace BSP customMapModel* inModel = &projInfo->models[i]; auto xModelAsset = m_context.LoadDependency(inModel->name); - if (xModelAsset == NULL) + if (xModelAsset == nullptr) { printf("XModel %s not found!\n", inModel->name.c_str()); - currModel->model = NULL; + currModel->model = nullptr; } else currModel->model = (XModel*)xModelAsset->Asset(); @@ -242,16 +208,16 @@ namespace BSP currModel->colorsIndex = 0; currModel->visibility = 0; - // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow // TODO: calculate lighting and store it here currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; } */ @@ -269,11 +235,6 @@ namespace BSP gfxWorld->dpvs.smodelVisData[2] = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelVisDataCameraSaved = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelCastsShadow = m_memory.Alloc(allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); for (unsigned int i = 0; i < modelCount; i++) { if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) @@ -293,55 +254,55 @@ namespace BSP // Remove Coronas gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; + gfxWorld->coronas = nullptr; // Remove exposure volumes gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumes = nullptr; gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; + gfxWorld->exposureVolumePlanes = nullptr; // Remove hero lights gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; + gfxWorld->heroLights = nullptr; gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; + gfxWorld->heroLightTree = nullptr; // remove LUT data gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumes = nullptr; gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; + gfxWorld->lutVolumePlanes = nullptr; // remove occluders gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; + gfxWorld->occluders = nullptr; // remove Siege Skins gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; + gfxWorld->siegeSkinInsts = nullptr; // remove outdoor bounds gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; + gfxWorld->outdoorBounds = nullptr; // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; + gfxWorld->ropeMaterial = nullptr; + gfxWorld->lutMaterial = nullptr; + gfxWorld->waterMaterial = nullptr; + gfxWorld->coronaMaterial = nullptr; // remove shadow maps gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumes = nullptr; gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; + gfxWorld->shadowMapVolumePlanes = nullptr; // remove stream info gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.aabbTrees = nullptr; gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; + gfxWorld->streamInfo.leafRefs = nullptr; // remove sun data memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); @@ -349,72 +310,64 @@ namespace BSP // Remove Water gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; + gfxWorld->waterBuffers[0].bufferSize = 0; + gfxWorld->waterBuffers[0].buffer = nullptr; + gfxWorld->waterBuffers[1].bufferSize = 0; + gfxWorld->waterBuffers[1].buffer = nullptr; // Remove Fog gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumes = nullptr; gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogModifierVolumePlanes = nullptr; gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumes = nullptr; gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; + gfxWorld->worldFogVolumePlanes = nullptr; // materialMemory is unused gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; + gfxWorld->materialMemory = nullptr; // sunLight is overwritten by the game, just needs to be a valid pointer gfxWorld->sunLight = m_memory.Alloc(); - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); } - void GfxWorldLinker::overwriteGfxLights(GfxWorld* gfxWorld) + void GfxWorldLinker::loadGfxLights(GfxWorld* gfxWorld) { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; + // there must be 2 or more lights, first is the static light and second is the sun light + gfxWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + gfxWorld->shadowGeom[lightIdx].smodelCount = 0; + gfxWorld->shadowGeom[lightIdx].surfaceCount = 0; + gfxWorld->shadowGeom[lightIdx].smodelIndex = nullptr; + gfxWorld->shadowGeom[lightIdx].sortedSurfIndex = nullptr; } gfxWorld->lightRegion = m_memory.Alloc(gfxWorld->primaryLightCount); - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; + gfxWorld->lightRegion[lightIdx].hullCount = 0; + gfxWorld->lightRegion[lightIdx].hulls = nullptr; } - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + unsigned int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; if (lightEntShadowVisSize != 0) - { gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc(lightEntShadowVisSize); - memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); - } else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } + gfxWorld->primaryLightEntityShadowVis = nullptr; } - // the lightgrid is used to light models in a dynamic way and is precomputed - void GfxWorldLinker::overwriteLightGrid(GfxWorld* gfxWorld) + void GfxWorldLinker::loadLightGrid(GfxWorld* gfxWorld) { - // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. - // it works and that is all thats needed :) + // there is almost no basis for the values in this code, they were chosen based on what looks correct when reverse engineering. - // mins and maxs define the range that the lightgrid will work in - // idk how these values are calculated, but the below values are larger + // mins and maxs define the range that the lightgrid will work in. + // unknown how these values are calculated, but the below values are larger // than official map values gfxWorld->lightGrid.mins[0] = 0; gfxWorld->lightGrid.mins[1] = 0; @@ -428,26 +381,24 @@ namespace BSP gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->lightGrid.offset = 0.0f; // default value - // this will make the lookup into rawRowData always return the first row + // setting all rowDataStart indexes to 0 will always index the first row in rawRowData int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; gfxWorld->lightGrid.rowDataStart = m_memory.Alloc(rowDataStartSize); - memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow) + 0x10; - GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); + // Adding 0x0F so the lookup table will be 0x10 bytes in size + gfxWorld->lightGrid.rawRowDataSize = static_cast(sizeof(GfxLightGridRow) + 0x0F); + GfxLightGridRow* row = static_cast(m_memory.AllocRaw(gfxWorld->lightGrid.rawRowDataSize)); row->colStart = 0; row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game row->zStart = 0; row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks row->firstEntry = 0; - for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 - { + for (int i = 0; i < 0x10; i++) // set the lookup table to all 0 row->lookupTable[i] = 0; - } - gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + gfxWorld->lightGrid.rawRowData = reinterpret_cast(row); // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table - gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid sample positions GfxLightGridEntry* entryArray = m_memory.Alloc(gfxWorld->lightGrid.entryCount); for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { @@ -457,33 +408,36 @@ namespace BSP } gfxWorld->lightGrid.entries = entryArray; - // colours are looked up by an entries colourindex + // colours are looked up with a lightgrid entries colorsIndex gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; + gfxWorld->lightGrid.coeffs = nullptr; gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; + gfxWorld->lightGrid.skyGridVolumes = nullptr; } - void GfxWorldLinker::updateGfxCells(GfxWorld* gfxWorld) + void GfxWorldLinker::loadGfxCells(GfxWorld* gfxWorld) { // Cells are basically data used to determine what can be seen and what cant be seen // Right now custom maps have no optimisation so there is only 1 cell int cellCount = 1; + gfxWorld->dpvsPlanes.cellCount = cellCount; gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); gfxWorld->cellCasterBits = m_memory.Alloc(cellCasterBitsCount); - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = m_memory.Alloc(sceneEntCellBitsCount); gfxWorld->cells = m_memory.Alloc(cellCount); gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].portals = nullptr; gfxWorld->cells[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].mins.y = gfxWorld->mins.y; gfxWorld->cells[0].mins.z = gfxWorld->mins.z; @@ -493,9 +447,8 @@ namespace BSP // there is only 1 reflection probe gfxWorld->cells[0].reflectionProbeCount = 1; - char* reflectionProbeIndexes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); - reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + gfxWorld->cells[0].reflectionProbes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); + gfxWorld->cells[0].reflectionProbes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; // AABB trees are used to detect what should be rendered and what shouldn't // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn @@ -504,12 +457,12 @@ namespace BSP gfxWorld->cells[0].aabbTree[0].childCount = 0; gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].surfaceCount = static_cast(gfxWorld->surfaceCount); + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = static_cast(gfxWorld->dpvs.smodelCount); gfxWorld->cells[0].aabbTree[0].smodelIndexes = m_memory.Alloc(gfxWorld->dpvs.smodelCount); - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + for (unsigned short smodelIdx = 0; smodelIdx < gfxWorld->dpvs.smodelCount; smodelIdx++) { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + gfxWorld->cells[0].aabbTree[0].smodelIndexes[smodelIdx] = smodelIdx; } gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; @@ -518,25 +471,20 @@ namespace BSP gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = m_memory.Alloc(sceneEntCellBitsCount); - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node + // nodes have the struct mnode_t, and there must be at least 1 node (similar to BSP nodes) // Nodes mnode_t.cellIndex indexes gfxWorld->cells // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + // Use only one node as there is no optimisation in custom maps gfxWorld->nodeCount = 1; gfxWorld->dpvsPlanes.nodes = m_memory.Alloc(gfxWorld->nodeCount); gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 // planes are overwritten by the clipmap loading code ingame gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.planes = NULL; + gfxWorld->dpvsPlanes.planes = nullptr; } - void updateWorldBounds(GfxWorld* gfxWorld) + void GfxWorldLinker::loadWorldBounds(GfxWorld* gfxWorld) { gfxWorld->mins.x = 0.0f; gfxWorld->mins.y = 0.0f; @@ -545,13 +493,13 @@ namespace BSP gfxWorld->maxs.y = 0.0f; gfxWorld->maxs.z = 0.0f; - for (int i = 0; i < gfxWorld->surfaceCount; i++) + for (int surfIdx = 0; surfIdx < gfxWorld->surfaceCount; surfIdx++) { - BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[i].bounds[0], gfxWorld->dpvs.surfaces[i].bounds[1], gfxWorld->mins, gfxWorld->maxs); + BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[surfIdx].bounds[0], gfxWorld->dpvs.surfaces[surfIdx].bounds[1], gfxWorld->mins, gfxWorld->maxs); } } - void GfxWorldLinker::overwriteModels(GfxWorld* gfxWorld) + void GfxWorldLinker::loadModels(GfxWorld* gfxWorld) { // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) // Right now there is only one submodel, the world sub model @@ -560,7 +508,7 @@ namespace BSP // first model is always the world model gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].surfaceCount = static_cast(gfxWorld->surfaceCount); gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; @@ -588,10 +536,10 @@ namespace BSP //} } - void updateSunData(GfxWorld* gfxWorld) + void GfxWorldLinker::loadSunData(GfxWorld* gfxWorld) { // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = (float)0.001; + gfxWorld->sunParse.fogTransitionTime = 0.001f; gfxWorld->sunParse.name[0] = 0x00; gfxWorld->sunParse.initWorldSun->control = 0; @@ -634,22 +582,18 @@ namespace BSP gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; } - bool GfxWorldLinker::updateReflectionProbeData(GfxWorld* gfxWorld) + bool GfxWorldLinker::loadReflectionProbeData(GfxWorld* gfxWorld) { gfxWorld->draw.reflectionProbeCount = 1; gfxWorld->draw.reflectionProbeTextures = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); // default values taken from mp_dig + gfxWorld->draw.reflectionProbes = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; @@ -664,11 +608,11 @@ namespace BSP gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr; std::string probeImageName = "reflection_probe0"; auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) + if (probeImageAsset == nullptr) { con::error("ERROR! unable to find reflection probe image {}!", probeImageName); return false; @@ -678,37 +622,33 @@ namespace BSP return true; } - bool GfxWorldLinker::overwriteLightmapData(GfxWorld* gfxWorld) + bool GfxWorldLinker::loadLightmapData(GfxWorld* gfxWorld) { gfxWorld->draw.lightmapCount = 1; gfxWorld->draw.lightmapPrimaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); gfxWorld->draw.lightmapSecondaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); - gfxWorld->draw.lightmaps = m_memory.Alloc(gfxWorld->draw.lightmapCount); - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); std::string secondaryTexture = "lightmap0_secondary"; auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) + if (secondaryTextureAsset == nullptr) { con::error("ERROR! unable to find lightmap image {}!", secondaryTexture); return false; } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps = m_memory.Alloc(gfxWorld->draw.lightmapCount); + gfxWorld->draw.lightmaps[0].primary = nullptr; // always nullptr gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); return true; } - void GfxWorldLinker::overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) + void GfxWorldLinker::loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) { std::string skyBoxName = "skybox_" + projInfo->name; gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); - if (m_context.LoadDependency(skyBoxName) == NULL) + if (m_context.LoadDependency(skyBoxName) == nullptr) { con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName); } @@ -720,45 +660,39 @@ namespace BSP gfxWorld->skyDynIntensity.factor1 = 1.0f; } - void GfxWorldLinker::updateDynEntData(GfxWorld* gfxWorld) + void GfxWorldLinker::loadDynEntData(GfxWorld* gfxWorld) { int dynEntCount = 0; gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. - // Making the value much larger than required fixes it, but idk what the root cause is + // Making the value much larger than required fixes it, but unsure what the root cause is gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; gfxWorld->dpvsDyn.usageCount = 0; int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; gfxWorld->dpvsDyn.dynEntCellBits[0] = m_memory.Alloc(dynEntCellBitsSize); - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + gfxWorld->dpvsDyn.dynEntCellBits[1] = nullptr; int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; gfxWorld->dpvsDyn.dynEntVisData[0][0] = m_memory.Alloc(dynEntVisData0Size); gfxWorld->dpvsDyn.dynEntVisData[0][1] = m_memory.Alloc(dynEntVisData0Size); gfxWorld->dpvsDyn.dynEntVisData[0][2] = m_memory.Alloc(dynEntVisData0Size); - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->dpvsDyn.dynEntVisData[1][0] = nullptr; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = nullptr; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = nullptr; + + unsigned int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); gfxWorld->primaryLightDynEntShadowVis[0] = m_memory.Alloc(dynEntShadowVisCount); - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + gfxWorld->primaryLightDynEntShadowVis[1] = nullptr; gfxWorld->sceneDynModel = m_memory.Alloc(gfxWorld->dpvsDyn.dynEntClientCount[0]); - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + gfxWorld->sceneDynBrush = nullptr; } - bool GfxWorldLinker::updateOutdoors(GfxWorld* gfxWorld) + bool GfxWorldLinker::loadOutdoors(GfxWorld* gfxWorld) { float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); float xScale = -(xRecip * gfxWorld->mins.x); @@ -781,7 +715,7 @@ namespace BSP std::string outdoorImageName = std::string("$outdoor"); auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) + if (outdoorImageAsset == nullptr) { con::error("ERROR! unable to find outdoor image $outdoor!"); return false; @@ -803,38 +737,37 @@ namespace BSP cleanGfxWorld(gfxWorld); - if (!overwriteMapSurfaces(bsp, gfxWorld)) + if (!loadMapSurfaces(bsp, gfxWorld)) return AssetCreationResult::Failure(); - overwriteMapSModels(bsp, gfxWorld); + loadXModels(bsp, gfxWorld); - if (!overwriteLightmapData(gfxWorld)) + if (!loadLightmapData(gfxWorld)) return AssetCreationResult::Failure(); - overwriteSkyBox(bsp, gfxWorld); + loadSkyBox(bsp, gfxWorld); - if (!updateReflectionProbeData(gfxWorld)) + if (!loadReflectionProbeData(gfxWorld)) return AssetCreationResult::Failure(); - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); + // world bounds are based on loaded surface mins/maxs + loadWorldBounds(gfxWorld); - if (!updateOutdoors(gfxWorld)) + if (!loadOutdoors(gfxWorld)) return AssetCreationResult::Failure(); // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); + loadGfxCells(gfxWorld); - overwriteLightGrid(gfxWorld); + loadLightGrid(gfxWorld); - overwriteGfxLights(gfxWorld); + loadGfxLights(gfxWorld); - overwriteModels(gfxWorld); + loadModels(gfxWorld); - updateSunData(gfxWorld); + loadSunData(gfxWorld); - updateDynEntData(gfxWorld); + loadDynEntData(gfxWorld); auto gfxWorldAsset = m_context.AddAsset(gfxWorld->name, gfxWorld); return AssetCreationResult::Success(gfxWorldAsset); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 594b51ffc..43963c34b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -18,18 +18,20 @@ namespace BSP ISearchPath& m_search_path; AssetCreationContext& m_context; - void overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld); - bool overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); - void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld); + void loadDrawData(BSPData* projInfo, GfxWorld* gfxWorld); + bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); + void loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld); - void overwriteGfxLights(GfxWorld* gfxWorld); - void overwriteLightGrid(GfxWorld* gfxWorld); - void updateGfxCells(GfxWorld* gfxWorld); - void overwriteModels(GfxWorld* gfxWorld); - bool updateReflectionProbeData(GfxWorld* gfxWorld); - bool overwriteLightmapData(GfxWorld* gfxWorld); - void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); - void updateDynEntData(GfxWorld* gfxWorld); - bool updateOutdoors(GfxWorld* gfxWorld); + void loadGfxLights(GfxWorld* gfxWorld); + void loadLightGrid(GfxWorld* gfxWorld); + void loadGfxCells(GfxWorld* gfxWorld); + void loadModels(GfxWorld* gfxWorld); + bool loadReflectionProbeData(GfxWorld* gfxWorld); + bool loadLightmapData(GfxWorld* gfxWorld); + void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); + void loadDynEntData(GfxWorld* gfxWorld); + bool loadOutdoors(GfxWorld* gfxWorld); + void loadSunData(GfxWorld* gfxWorld); + void loadWorldBounds(GfxWorld* gfxWorld); }; } \ No newline at end of file From 0e19f365ff51935cab447e117203b72007399764 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 27 Oct 2025 20:23:13 +0800 Subject: [PATCH 029/123] Refactored to make x64 builds succeed --- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 2 +- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 2 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 2 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 22 +++++++++---------- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 2 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 1 - 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 0a2820675..a5473e1e5 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -88,7 +88,7 @@ vec3_t BSPUtil::calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs) return result; } -int BSPUtil::allignBy128(int size) +size_t BSPUtil::allignBy128(size_t size) { return ((size + 127) & 0xFFFFFF80); } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 474d51c15..074cd4ab6 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -13,7 +13,7 @@ class BSPUtil static void updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs); static vec3_t calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs); static vec3_t calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs); - static int allignBy128(int size); + static size_t allignBy128(size_t size); static float distBetweenPoints(vec3_t& p1, vec3_t& p2); static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index aee90d7ce..8289c1a46 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -462,7 +462,7 @@ namespace BSP // any vertex count over the uint16_t max means the vertices above the uint16_t max can't be indexed if (static_cast(bsp->colWorld.vertices.size()) > BSPGameConstants::MAX_COLLISION_VERTS) { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", clipMap->vertCount, BSPGameConstants::MAX_COLLISION_VERTS); + con::error("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", clipMap->vertCount, BSPGameConstants::MAX_COLLISION_VERTS); return false; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 223f57c4c..813a8b567 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -130,18 +130,18 @@ namespace BSP // set all surface types to lit opaque gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount); // visdata is written to by the game // all visdata is alligned by 128 - unsigned int allignedSurfaceCount = BSPUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisDataCount = allignedSurfaceCount; + size_t allignedSurfaceCount = BSPUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisDataCount = static_cast(allignedSurfaceCount); gfxWorld->dpvs.surfaceVisData[0] = m_memory.Alloc(allignedSurfaceCount); gfxWorld->dpvs.surfaceVisData[1] = m_memory.Alloc(allignedSurfaceCount); gfxWorld->dpvs.surfaceVisData[2] = m_memory.Alloc(allignedSurfaceCount); @@ -228,8 +228,8 @@ namespace BSP // visdata is written to by the game // all visdata is alligned by 128 - int allignedModelCount = BSPUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + size_t allignedModelCount = BSPUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = static_cast(allignedModelCount); gfxWorld->dpvs.smodelVisData[0] = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelVisData[1] = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelVisData[2] = m_memory.Alloc(allignedModelCount); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 00459a565..c96f56789 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -119,7 +119,7 @@ namespace BSP mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); mapEnts->entityString = m_memory.Dup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character // don't need these mapEnts->trigger.count = 0; diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 630897a0c..70f36ac72 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -2,7 +2,6 @@ #include "BSPCreator.h" #include "BSPUtil.h" #include "Linker/BSPLinker.h" -//#include "CustomMapLinker.h" namespace { From 0fa473eadda55d90a3e8db98a9317bc60875a1be Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Mon, 27 Oct 2025 20:43:30 +0800 Subject: [PATCH 030/123] Formatted all BSP loading files using clang. --- src/ObjLoading/Game/T6/BSP/BSP.h | 82 ++--- src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp | 2 +- src/ObjLoading/Game/T6/BSP/BSPCalculation.h | 5 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 23 +- src/ObjLoading/Game/T6/BSP/BSPCreator.h | 2 +- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 330 +++++++++--------- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 36 +- .../Game/T6/BSP/Linker/BSPLinker.cpp | 24 +- src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h | 26 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 32 +- .../Game/T6/BSP/Linker/ClipMapLinker.h | 54 +-- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 9 +- .../Game/T6/BSP/Linker/ComWorldLinker.h | 22 +- .../Game/T6/BSP/Linker/GameWorldMpLinker.cpp | 9 +- .../Game/T6/BSP/Linker/GameWorldMpLinker.h | 22 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 39 +-- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 52 +-- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 16 +- .../Game/T6/BSP/Linker/MapEntsLinker.h | 22 +- .../Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 7 +- .../Game/T6/BSP/Linker/SkinnedVertsLinker.h | 22 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 4 +- 22 files changed, 411 insertions(+), 429 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index d146734c4..f64f738a3 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -1,13 +1,12 @@ #pragma once -#include -#include -#include -#include - +#include "Game/T6/T6.h" #include "Utils/Logging/Log.h" -#include "Game/T6/T6.h" +#include +#include +#include +#include using namespace T6; namespace BSP @@ -71,40 +70,32 @@ namespace BSP BSP_DEFAULT_LIGHT_COUNT = 2 }; - inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_allies", - "mp_ctf_spawn_allies_start", - "mp_sd_spawn_defender", - "mp_dom_spawn_allies_start", - "mp_dem_spawn_defender_start", - "mp_dem_spawn_defenderOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_allies_start", - "mp_tdm_spawn_team1_start", - "mp_tdm_spawn_team2_start", - "mp_tdm_spawn_team3_start" - }; - - inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_axis", - "mp_ctf_spawn_axis_start", - "mp_sd_spawn_attacker", - "mp_dom_spawn_axis_start", - "mp_dem_spawn_attacker_start", - "mp_dem_spawn_attackerOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_axis_start", - "mp_tdm_spawn_team4_start", - "mp_tdm_spawn_team5_start", - "mp_tdm_spawn_team6_start" - }; - - inline const char* FFA_SPAWN_POINT_NAMES[] = { - "mp_tdm_spawn", - "mp_dm_spawn", - "mp_dom_spawn" - }; - } + inline const char* DEFENDER_SPAWN_POINT_NAMES[] = {"mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start"}; + + inline const char* ATTACKER_SPAWN_POINT_NAMES[] = {"mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start"}; + + inline const char* FFA_SPAWN_POINT_NAMES[] = {"mp_tdm_spawn", "mp_dm_spawn", "mp_dom_spawn"}; + } // namespace BSPGameConstants // BSPLinkingConstants: // These values are BSP linking constants that are required for the link to be successful @@ -134,7 +125,6 @@ namespace BSP ] })"; - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ "entities": [ { @@ -152,7 +142,7 @@ namespace BSP } ] })"; - } + } // namespace BSPLinkingConstants // BSPEditableConstants: // These values are BSP constants that can be edited and may not break the linker/game if changed @@ -188,7 +178,7 @@ namespace BSP constexpr unsigned char LIGHTGRID_COLOUR = 128; // Sunlight values - constexpr vec4_t SUNLIGHT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; - constexpr vec3_t SUNLIGHT_DIRECTION = { 0.0f, 0.0f, 0.0f }; - }; -} \ No newline at end of file + constexpr vec4_t SUNLIGHT_COLOR = {0.75f, 0.75f, 0.75f, 1.0f}; + constexpr vec3_t SUNLIGHT_DIRECTION = {0.0f, 0.0f, 0.0f}; + }; // namespace BSPEditableConstants +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp index b8a3a22b7..61dccd929 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp @@ -148,4 +148,4 @@ namespace BSP } } } -} \ No newline at end of file +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h index 9988f5193..1d35cbe99 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h @@ -28,7 +28,6 @@ namespace BSP BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); }; - class BSPLeaf { public: @@ -47,7 +46,7 @@ namespace BSP std::unique_ptr front; std::unique_ptr back; - PlaneAxis axis; // axis that the split plane is on + PlaneAxis axis; // axis that the split plane is on float distance; // distance from the origin (0, 0, 0) to the plane BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); @@ -69,4 +68,4 @@ namespace BSP void splitTree(); void addObjectToTree(std::shared_ptr object); }; -} \ No newline at end of file +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 8aad76d0e..6c14330be 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -6,11 +6,8 @@ namespace { using namespace BSP; - void addFBXMeshToWorld(ufbx_node* node, - std::vector& surfaceVec, - std::vector& vertexVec, - std::vector& indexVec, - bool& hasTangentSpace) + void addFBXMeshToWorld( + ufbx_node* node, std::vector& surfaceVec, std::vector& vertexVec, std::vector& indexVec, bool& hasTangentSpace) { ufbx_mesh* mesh = node->mesh; @@ -43,7 +40,7 @@ namespace if (mesh->vertex_tangent.exists == false) hasTangentSpace = false; - // Fix the target_unit_meters ufbx opt not working + // Fix the target_unit_meters ufbx opt not working // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back ufbx_transform origTransform = node->local_transform; origTransform.translation.x /= 100.0f; @@ -53,12 +50,12 @@ namespace origTransform.scale.y /= 100.0f; origTransform.scale.z /= 100.0f; ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - + for (ufbx_mesh_part& meshPart : mesh->material_parts) { if (meshPart.num_faces == 0) continue; - + BSPSurface surface; size_t partTriangleNum = meshPart.num_triangles; surface.triCount = static_cast(partTriangleNum); @@ -187,7 +184,7 @@ namespace if (hasTangentSpace == false) con::warn("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX."); } -} +} // namespace namespace BSP { @@ -201,7 +198,7 @@ namespace BSP con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); return nullptr; } - + std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); if (gfxFile.m_stream->gcount() != gfxFile.m_length) @@ -211,7 +208,7 @@ namespace BSP } ufbx_error errorGfx; - ufbx_load_opts optsGfx {}; + ufbx_load_opts optsGfx{}; optsGfx.target_axes = ufbx_axes_right_handed_y_up; optsGfx.generate_missing_normals = true; optsGfx.allow_missing_vertex_position = false; @@ -243,7 +240,7 @@ namespace BSP } ufbx_error errorCol; - ufbx_load_opts optsCol {}; + ufbx_load_opts optsCol{}; optsCol.target_axes = ufbx_axes_right_handed_y_up; optsCol.generate_missing_normals = true; optsCol.allow_missing_vertex_position = false; @@ -269,4 +266,4 @@ namespace BSP return bsp; } -} // namespace BSP \ No newline at end of file +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.h b/src/ObjLoading/Game/T6/BSP/BSPCreator.h index 7a8395da0..8ab85e61c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.h +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.h @@ -6,4 +6,4 @@ namespace BSP { std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath); -}; \ No newline at end of file +}; // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index a5473e1e5..cd5ebe58b 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -1,169 +1,173 @@ #pragma once -#include -#include - #include "BSPUtil.h" -std::string BSPUtil::getFileNameForBSPAsset(std::string& assetName) -{ - return std::format("BSP/{}", assetName); -} - -vec3_t BSPUtil::convertToBO2Coords(vec3_t& coordinate) -{ - vec3_t result; - result.x = coordinate.x; - result.y = -coordinate.z; - result.z = coordinate.y; - return result; -} - -vec3_t BSPUtil::convertFromBO2Coords(vec3_t& coordinate) -{ - vec3_t result; - result.x = coordinate.x; - result.y = coordinate.z; - result.z = -coordinate.y; - return result; -} - -void BSPUtil::updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs) -{ - if (AABBMins.x > newAABBMins.x) - AABBMins.x = newAABBMins.x; - - if (newAABBMaxs.x > AABBMaxs.x) - AABBMaxs.x = newAABBMaxs.x; - - if (AABBMins.y > newAABBMins.y) - AABBMins.y = newAABBMins.y; - - if (newAABBMaxs.y > AABBMaxs.y) - AABBMaxs.y = newAABBMaxs.y; - - if (AABBMins.z > newAABBMins.z) - AABBMins.z = newAABBMins.z; - - if (newAABBMaxs.z > AABBMaxs.z) - AABBMaxs.z = newAABBMaxs.z; -} - -void BSPUtil::updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs) -{ - if (AABBMins.x > point.x) - AABBMins.x = point.x; - - if (point.x > AABBMaxs.x) - AABBMaxs.x = point.x; - - if (AABBMins.y > point.y) - AABBMins.y = point.y; - - if (point.y > AABBMaxs.y) - AABBMaxs.y = point.y; - - if (AABBMins.z > point.z) - AABBMins.z = point.z; - - if (point.z > AABBMaxs.z) - AABBMaxs.z = point.z; -} - -vec3_t BSPUtil::calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs) -{ - vec3_t result; - result.x = (mins.x + maxs.x) * 0.5f; - result.y = (mins.y + maxs.y) * 0.5f; - result.z = (mins.z + maxs.z) * 0.5f; - return result; -} - -vec3_t BSPUtil::calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs) -{ - vec3_t result; - result.x = (maxs.x - mins.x) * 0.5f; - result.y = (maxs.y - mins.y) * 0.5f; - result.z = (maxs.z - mins.z) * 0.5f; - return result; -} - -size_t BSPUtil::allignBy128(size_t size) -{ - return ((size + 127) & 0xFFFFFF80); -} +#include +#include -float BSPUtil::distBetweenPoints(vec3_t& p1, vec3_t& p2) -{ - float x = p2.x - p1.x; - float y = p2.y - p1.y; - float z = p2.z - p1.z; - return sqrtf((x * x) + (y * y) + (z * z)); -} - -// angles are in euler degrees -void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) -{ - float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f - - float cosX = cos(xRadians); - float sinX = sin(xRadians); - float cosY = cos(yRadians); - float sinY = sin(yRadians); - float cosZ = cos(zRadians); - float sinZ = sin(zRadians); - - axis[0].x = cosX * cosY; - axis[0].y = cosX * sinY; - axis[0].z = -sinX; - axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); - axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); - axis[1].z = sinZ * cosX; - axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); - axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); - axis[2].z = cosZ * cosX; -} - -void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) -{ - out[0].x = in[0].x; - out[0].y = in[1].x; - out[0].z = in[2].x; - out[1].x = in[0].y; - out[1].y = in[1].y; - out[1].z = in[2].y; - out[2].x = in[0].z; - out[2].y = in[1].z; - out[2].z = in[2].z; -} - -vec3_t BSPUtil::convertStringToVec3(std::string& str) -{ - std::string v1Str = str; - - int nextValIndex = 0; - while (v1Str[nextValIndex] != ' ') - nextValIndex++; - nextValIndex++; // skip past space - std::string v2Str = &v1Str[nextValIndex]; - - nextValIndex = 0; - while (v2Str[nextValIndex] != ' ') - nextValIndex++; - nextValIndex++; // skip past space - std::string v3Str = &v2Str[nextValIndex]; - - vec3_t result; - result.x = static_cast(atof(v1Str.c_str())); - result.y = static_cast(atof(v2Str.c_str())); - result.z = static_cast(atof(v3Str.c_str())); - return result; -} - -std::string BSPUtil::convertVec3ToString(vec3_t& vec) +namespace BSP { - std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); - return result; -} \ No newline at end of file + std::string BSPUtil::getFileNameForBSPAsset(std::string& assetName) + { + return std::format("BSP/{}", assetName); + } + + vec3_t BSPUtil::convertToBO2Coords(vec3_t& coordinate) + { + vec3_t result; + result.x = coordinate.x; + result.y = -coordinate.z; + result.z = coordinate.y; + return result; + } + + vec3_t BSPUtil::convertFromBO2Coords(vec3_t& coordinate) + { + vec3_t result; + result.x = coordinate.x; + result.y = coordinate.z; + result.z = -coordinate.y; + return result; + } + + void BSPUtil::updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs) + { + if (AABBMins.x > newAABBMins.x) + AABBMins.x = newAABBMins.x; + + if (newAABBMaxs.x > AABBMaxs.x) + AABBMaxs.x = newAABBMaxs.x; + + if (AABBMins.y > newAABBMins.y) + AABBMins.y = newAABBMins.y; + + if (newAABBMaxs.y > AABBMaxs.y) + AABBMaxs.y = newAABBMaxs.y; + + if (AABBMins.z > newAABBMins.z) + AABBMins.z = newAABBMins.z; + + if (newAABBMaxs.z > AABBMaxs.z) + AABBMaxs.z = newAABBMaxs.z; + } + + void BSPUtil::updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs) + { + if (AABBMins.x > point.x) + AABBMins.x = point.x; + + if (point.x > AABBMaxs.x) + AABBMaxs.x = point.x; + + if (AABBMins.y > point.y) + AABBMins.y = point.y; + + if (point.y > AABBMaxs.y) + AABBMaxs.y = point.y; + + if (AABBMins.z > point.z) + AABBMins.z = point.z; + + if (point.z > AABBMaxs.z) + AABBMaxs.z = point.z; + } + + vec3_t BSPUtil::calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs) + { + vec3_t result; + result.x = (mins.x + maxs.x) * 0.5f; + result.y = (mins.y + maxs.y) * 0.5f; + result.z = (mins.z + maxs.z) * 0.5f; + return result; + } + + vec3_t BSPUtil::calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs) + { + vec3_t result; + result.x = (maxs.x - mins.x) * 0.5f; + result.y = (maxs.y - mins.y) * 0.5f; + result.z = (maxs.z - mins.z) * 0.5f; + return result; + } + + size_t BSPUtil::allignBy128(size_t size) + { + return ((size + 127) & 0xFFFFFF80); + } + + float BSPUtil::distBetweenPoints(vec3_t& p1, vec3_t& p2) + { + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); + } + + // angles are in euler degrees + void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) + { + float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f + float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f + float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f + + float cosX = cos(xRadians); + float sinX = sin(xRadians); + float cosY = cos(yRadians); + float sinY = sin(yRadians); + float cosZ = cos(zRadians); + float sinZ = sin(zRadians); + + axis[0].x = cosX * cosY; + axis[0].y = cosX * sinY; + axis[0].z = -sinX; + axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); + axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); + axis[1].z = sinZ * cosX; + axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); + axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); + axis[2].z = cosZ * cosX; + } + + void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) + { + out[0].x = in[0].x; + out[0].y = in[1].x; + out[0].z = in[2].x; + out[1].x = in[0].y; + out[1].y = in[1].y; + out[1].z = in[2].y; + out[2].x = in[0].z; + out[2].y = in[1].z; + out[2].z = in[2].z; + } + + vec3_t BSPUtil::convertStringToVec3(std::string& str) + { + std::string v1Str = str; + + int nextValIndex = 0; + while (v1Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v2Str = &v1Str[nextValIndex]; + + nextValIndex = 0; + while (v2Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v3Str = &v2Str[nextValIndex]; + + vec3_t result; + result.x = static_cast(atof(v1Str.c_str())); + result.y = static_cast(atof(v2Str.c_str())); + result.z = static_cast(atof(v3Str.c_str())); + return result; + } + + std::string BSPUtil::convertVec3ToString(vec3_t& vec) + { + std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + return result; + } + +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 074cd4ab6..54bdd0a59 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -2,21 +2,23 @@ #include "BSP.h" - -class BSPUtil +namespace BSP { -public: - static std::string getFileNameForBSPAsset(std::string& assetName); - static vec3_t convertToBO2Coords(vec3_t& OGL_coordinate); - static vec3_t convertFromBO2Coords(vec3_t& bo2_coordinate); - static void updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs); - static void updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs); - static vec3_t calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs); - static vec3_t calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs); - static size_t allignBy128(size_t size); - static float distBetweenPoints(vec3_t& p1, vec3_t& p2); - static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); - static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); - static vec3_t convertStringToVec3(std::string& str); - static std::string convertVec3ToString(vec3_t& vec); -}; + class BSPUtil + { + public: + static std::string getFileNameForBSPAsset(std::string& assetName); + static vec3_t convertToBO2Coords(vec3_t& OGL_coordinate); + static vec3_t convertFromBO2Coords(vec3_t& bo2_coordinate); + static void updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs); + static void updateAABBWithPoint(vec3_t& point, vec3_t& AABBMins, vec3_t& AABBMaxs); + static vec3_t calcMiddleOfAABB(vec3_t& mins, vec3_t& maxs); + static vec3_t calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs); + static size_t allignBy128(size_t size); + static float distBetweenPoints(vec3_t& p1, vec3_t& p2); + static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); + static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); + static vec3_t convertStringToVec3(std::string& str); + static std::string convertVec3ToString(vec3_t& vec); + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp index ccd42bd76..0777491bf 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -1,7 +1,6 @@ #include "BSPLinker.h" - -#include "ComWorldLinker.h" #include "ClipMapLinker.h" +#include "ComWorldLinker.h" #include "GameWorldMpLinker.h" #include "GfxWorldLinker.h" #include "MapEntsLinker.h" @@ -52,15 +51,15 @@ namespace BSP return true; } - BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_context(context) - { - } + BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } - AssetCreationResult BSPLinker::linkBSP(BSPData* bsp) - { + AssetCreationResult BSPLinker::linkBSP(BSPData* bsp) + { if (!addDefaultRequiredAssets(bsp)) return AssetCreationResult::Failure(); @@ -92,6 +91,5 @@ namespace BSP return AssetCreationResult::Failure(); return result; - } -} - + } +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h index be986f978..f51abf4e5 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h @@ -7,18 +7,18 @@ namespace BSP { - class BSPLinker - { - public: - BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkBSP(BSPData* bsp); + class BSPLinker + { + public: + BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkBSP(BSPData* bsp); - private: - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName); - bool addDefaultRequiredAssets(BSPData* bsp); + private: + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName); + bool addDefaultRequiredAssets(BSPData* bsp); - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; - }; -} \ No newline at end of file + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 8289c1a46..5a7b99cb3 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -1,12 +1,12 @@ -#include "ClipMapLinker.h" #include "../BSPUtil.h" +#include "ClipMapLinker.h" namespace BSP { ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } @@ -18,7 +18,7 @@ namespace BSP clipMap->dynEntCount[1] = 0; clipMap->dynEntCount[2] = 0; clipMap->dynEntCount[3] = 0; - + clipMap->dynEntClientList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); clipMap->dynEntClientList[1] = nullptr; @@ -50,7 +50,7 @@ namespace BSP void ClipMapLinker::loadBoxData(clipMap_t* clipMap) { - // box_model and box_brush are what are used by game traces as "temporary" collision when + // box_model and box_brush are what are used by game traces as "temporary" collision when // no brush or model is specified to do the trace with. // All values in this function are taken from official map BSPs @@ -128,7 +128,7 @@ namespace BSP // Right now there is only one submodel, the world sub model assert(gfxWorld->modelCount == 1); - + clipMap->numSubModels = 1; clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); @@ -282,9 +282,9 @@ namespace BSP return static_cast(parentAABBIndex); } - constexpr vec3_t normalX = { 1.0f, 0.0f, 0.0f }; - constexpr vec3_t normalY = { 0.0f, 1.0f, 0.0f }; - constexpr vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; + constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; + constexpr vec3_t normalY = {0.0f, 1.0f, 0.0f}; + constexpr vec3_t normalZ = {0.0f, 0.0f, 1.0f}; // returns the index of the node/leaf parsed by the function // Nodes are indexed by their index in the node array @@ -296,7 +296,7 @@ namespace BSP { cLeaf_s leaf; - leaf.cluster = 0; // always use cluster 0 + leaf.cluster = 0; // always use cluster 0 leaf.brushContents = 0; // no brushes used so contents is 0 leaf.terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; @@ -428,7 +428,8 @@ namespace BSP } BSPUtil::updateAABBWithPoint(vert, partitionMins, partitionMaxs); } - std::shared_ptr currObject = std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); + std::shared_ptr currObject = + std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); tree->addObjectToTree(std::move(currObject)); } @@ -493,7 +494,7 @@ namespace BSP } // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion clipMap->triCount = static_cast(triIndexVec.size() / 3); - clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. @@ -501,7 +502,7 @@ namespace BSP std::vector uniqueIndicesVec; for (BSPSurface& surface : bsp->colWorld.surfaces) { - // partitions are made for each triangle, not one for each surface. + // partitions are made for each triangle, not one for each surface. // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower int indexOfFirstTri = surface.indexOfFirstIndex / 3; @@ -621,7 +622,7 @@ namespace BSP loadRopesAndConstraints(clipMap); loadSubModelCollision(clipMap, bsp); - + loadDynEnts(clipMap); loadXModelCollision(clipMap); @@ -648,5 +649,4 @@ namespace BSP auto clipMapAsset = m_context.AddAsset(clipMap->name, clipMap); return AssetCreationResult::Success(clipMapAsset); } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 44d8643bb..c97833a5b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -1,39 +1,39 @@ #pragma once #include "../BSP.h" +#include "../BSPCalculation.h" #include "Asset/IAssetCreator.h" #include "SearchPath/ISearchPath.h" #include "Utils/MemoryManager.h" -#include "../BSPCalculation.h" namespace BSP { - class ClipMapLinker - { - public: - ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkClipMap(BSPData* bsp); + class ClipMapLinker + { + public: + ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkClipMap(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; - void loadBoxData(clipMap_t* clipMap); - void loadVisibility(clipMap_t* clipMap); - void loadDynEnts(clipMap_t* clipMap); - void loadRopesAndConstraints(clipMap_t* clipMap); - void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); - void loadXModelCollision(clipMap_t* clipMap); + void loadBoxData(clipMap_t* clipMap); + void loadVisibility(clipMap_t* clipMap); + void loadDynEnts(clipMap_t* clipMap); + void loadRopesAndConstraints(clipMap_t* clipMap); + void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); + void loadXModelCollision(clipMap_t* clipMap); - std::vector planeVec; - std::vector nodeVec; - std::vector leafVec; - std::vector AABBTreeVec; - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); - int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); - void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); - bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); - bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); - }; -} \ No newline at end of file + std::vector planeVec; + std::vector nodeVec; + std::vector leafVec; + std::vector AABBTreeVec; + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); + int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); + void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); + bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); + bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 88e8b8a8a..dd794d369 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -4,8 +4,8 @@ namespace BSP { ComWorldLinker::ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } @@ -19,7 +19,7 @@ namespace BSP comWorld->primaryLights = m_memory.Alloc(comWorld->primaryLightCount); // first (static) light is always empty - + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; const vec4_t sunLightColor = BSPEditableConstants::SUNLIGHT_COLOR; const vec3_t sunLightDirection = BSPEditableConstants::SUNLIGHT_DIRECTION; @@ -35,5 +35,4 @@ namespace BSP auto comWorldAsset = m_context.AddAsset(comWorld->name, comWorld); return AssetCreationResult::Success(comWorldAsset); } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h index 21be6a161..ba5a39fb1 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h @@ -7,15 +7,15 @@ namespace BSP { - class ComWorldLinker - { - public: - ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkComWorld(BSPData* bsp); + class ComWorldLinker + { + public: + ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkComWorld(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; - }; -} \ No newline at end of file + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp index 460f71d37..56e574634 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp @@ -4,8 +4,8 @@ namespace BSP { GameWorldMpLinker::GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } @@ -28,9 +28,8 @@ namespace BSP gameWorldMp->path.pathVis = nullptr; gameWorldMp->path.smoothCache = nullptr; gameWorldMp->path.nodeTree = nullptr; - + auto gameWorldMpAsset = m_context.AddAsset(gameWorldMp->name, gameWorldMp); return AssetCreationResult::Success(gameWorldMpAsset); } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h index a1ca459ef..5f989105b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h @@ -7,15 +7,15 @@ namespace BSP { - class GameWorldMpLinker - { - public: - GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkGameWorldMp(BSPData* bsp); + class GameWorldMpLinker + { + public: + GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGameWorldMp(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; - }; -} \ No newline at end of file + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 813a8b567..bcfae3bb2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -1,16 +1,16 @@ -#include "GfxWorldLinker.h" #include "../BSPUtil.h" +#include "GfxWorldLinker.h" #include "Utils/Pack.h" namespace BSP { GfxWorldLinker::GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } - + void GfxWorldLinker::loadDrawData(BSPData* bsp, GfxWorld* gfxWorld) { size_t vertexCount = bsp->gfxWorld.vertices.size(); @@ -157,18 +157,18 @@ namespace BSP /* Models are unsupported right now Code is left in in case it is supported later on - + unsigned int modelCount = projInfo->modelCount; gfxWorld->dpvs.smodelCount = modelCount; gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - + for (unsigned int i = 0; i < modelCount; i++) { auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; customMapModel* inModel = &projInfo->models[i]; - + auto xModelAsset = m_context.LoadDependency(inModel->name); if (xModelAsset == nullptr) { @@ -177,15 +177,15 @@ namespace BSP } else currModel->model = (XModel*)xModelAsset->Asset(); - + currModel->placement.origin.x = inModel->origin.x; currModel->placement.origin.y = inModel->origin.y; currModel->placement.origin.z = inModel->origin.z; currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); currModel->placement.scale = inModel->scale; - + BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - + // mins and maxs are calculated in world space not local space // TODO: this does not account for model rotation or scale currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; @@ -194,12 +194,12 @@ namespace BSP currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - + currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; currModel->flags = DEFAULT_SMODEL_FLAGS; currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - + // unknown use / unused currModel->smid = i; memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); @@ -207,7 +207,7 @@ namespace BSP currModel->lightingHandle = 0; currModel->colorsIndex = 0; currModel->visibility = 0; - + // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow // TODO: calculate lighting and store it here currModel->lmapVertexInfo[0].numLmapVertexColors = 0; @@ -376,8 +376,8 @@ namespace BSP gfxWorld->lightGrid.maxs[1] = 200; gfxWorld->lightGrid.maxs[2] = 50; - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->lightGrid.offset = 0.0f; // default value @@ -409,7 +409,7 @@ namespace BSP gfxWorld->lightGrid.entries = entryArray; // colours are looked up with a lightgrid entries colorsIndex - gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colorCount = 0x1000; // 0x1000 as it should be enough to hold every index gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); @@ -519,7 +519,7 @@ namespace BSP // Other models aren't implemented yet // Code kept for future use - //for (size_t i = 0; i < entityModelList.size(); i++) + // for (size_t i = 0; i < entityModelList.size(); i++) //{ // auto currEntModel = &gfxWorld->models[i + 1]; // entModelBounds currEntModelBounds = entityModelList[i]; @@ -666,7 +666,7 @@ namespace BSP gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. // Making the value much larger than required fixes it, but unsure what the root cause is gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; @@ -772,5 +772,4 @@ namespace BSP auto gfxWorldAsset = m_context.AddAsset(gfxWorld->name, gfxWorld); return AssetCreationResult::Success(gfxWorldAsset); } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 43963c34b..f48de580d 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -7,31 +7,31 @@ namespace BSP { - class GfxWorldLinker - { - public: - GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkGfxWorld(BSPData* bsp); + class GfxWorldLinker + { + public: + GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGfxWorld(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; - void loadDrawData(BSPData* projInfo, GfxWorld* gfxWorld); - bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); - void loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); - void cleanGfxWorld(GfxWorld* gfxWorld); - void loadGfxLights(GfxWorld* gfxWorld); - void loadLightGrid(GfxWorld* gfxWorld); - void loadGfxCells(GfxWorld* gfxWorld); - void loadModels(GfxWorld* gfxWorld); - bool loadReflectionProbeData(GfxWorld* gfxWorld); - bool loadLightmapData(GfxWorld* gfxWorld); - void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); - void loadDynEntData(GfxWorld* gfxWorld); - bool loadOutdoors(GfxWorld* gfxWorld); - void loadSunData(GfxWorld* gfxWorld); - void loadWorldBounds(GfxWorld* gfxWorld); - }; -} \ No newline at end of file + void loadDrawData(BSPData* projInfo, GfxWorld* gfxWorld); + bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); + void loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); + void cleanGfxWorld(GfxWorld* gfxWorld); + void loadGfxLights(GfxWorld* gfxWorld); + void loadLightGrid(GfxWorld* gfxWorld); + void loadGfxCells(GfxWorld* gfxWorld); + void loadModels(GfxWorld* gfxWorld); + bool loadReflectionProbeData(GfxWorld* gfxWorld); + bool loadLightmapData(GfxWorld* gfxWorld); + void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); + void loadDynEntData(GfxWorld* gfxWorld); + bool loadOutdoors(GfxWorld* gfxWorld); + void loadSunData(GfxWorld* gfxWorld); + void loadWorldBounds(GfxWorld* gfxWorld); + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index c96f56789..5a75e614b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -1,5 +1,5 @@ -#include "MapEntsLinker.h" #include "../BSPUtil.h" +#include "MapEntsLinker.h" #include using namespace nlohmann; @@ -59,18 +59,15 @@ namespace } } - std::string loadMapEnts() - { - - } -} + std::string loadMapEnts() {} +} // namespace namespace BSP { MapEntsLinker::MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } @@ -138,5 +135,4 @@ namespace BSP return AssetCreationResult::Failure(); } } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h index 921af16da..468989c8c 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h @@ -7,15 +7,15 @@ namespace BSP { - class MapEntsLinker - { - public: - MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkMapEnts(BSPData* bsp); + class MapEntsLinker + { + public: + MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkMapEnts(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; - }; -} \ No newline at end of file + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp index 2647a14bb..9792d2447 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -4,8 +4,8 @@ namespace BSP { SkinnedVertsLinker::SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) : m_memory(memory), - m_search_path(searchPath), - m_context(context) + m_search_path(searchPath), + m_context(context) { } @@ -22,5 +22,4 @@ namespace BSP auto skinnedVertsAsset = m_context.AddAsset(assetName, skinnedVerts); return AssetCreationResult::Success(skinnedVertsAsset); } -} - +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h index db7f03b78..605a95acd 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h @@ -7,15 +7,15 @@ namespace BSP { - class SkinnedVertsLinker - { - public: - SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkSkinnedVerts(BSPData* bsp); + class SkinnedVertsLinker + { + public: + SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkSkinnedVerts(BSPData* bsp); - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - AssetCreationContext& m_context; - }; -} \ No newline at end of file + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 70f36ac72..118b265dc 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,7 +1,7 @@ -#include "LoaderBSP_T6.h" #include "BSPCreator.h" #include "BSPUtil.h" #include "Linker/BSPLinker.h" +#include "LoaderBSP_T6.h" namespace { @@ -28,7 +28,7 @@ namespace std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path); if (bsp == nullptr) return AssetCreationResult::Failure(); - + BSPLinker linker(m_memory, m_search_path, context); return linker.linkBSP(bsp.get()); } From b054cfc990c060d7843642a42ab69100cc5f6a33 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Fri, 31 Oct 2025 16:28:12 +0800 Subject: [PATCH 031/123] Updated missing image and colour only image asset names to use materials loaded by the game. --- src/ObjLoading/Game/T6/BSP/BSP.h | 4 ++-- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index f64f738a3..b4854134e 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -101,8 +101,8 @@ namespace BSP // These values are BSP linking constants that are required for the link to be successful namespace BSPLinkingConstants { - constexpr const char* MISSING_IMAGE_NAME = "missing_image"; - constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; + constexpr const char* MISSING_IMAGE_NAME = ",mc/lambert1"; + constexpr const char* COLOR_ONLY_IMAGE_NAME = ",white"; constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ "attackers": [ diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index bcfae3bb2..a8bd50b62 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -89,7 +89,7 @@ namespace BSP surfMaterialAsset = m_context.LoadDependency(missingImageName); if (surfMaterialAsset == nullptr) { - con::error("unable to find the missing image texture!"); + con::error("unable to load the missing image texture {}!", missingImageName); return false; } } From 208d305cc633bd45f4193ee4d01f4bc3143779b5 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Fri, 31 Oct 2025 17:03:20 +0800 Subject: [PATCH 032/123] CLANG formatted ObjLoaderT6 --- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index cc391d98f..1286eb1d5 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -1,6 +1,5 @@ -#include "ObjLoaderT6.h" - #include "Asset/GlobalAssetPoolsLoader.h" +#include "BSP/LoaderBSP_T6.h" #include "FontIcon/CsvLoaderFontIconT6.h" #include "FontIcon/JsonLoaderFontIconT6.h" #include "Game/T6/CommonT6.h" @@ -17,6 +16,7 @@ #include "Localize/LocalizeLoaderT6.h" #include "Material/LoaderMaterialT6.h" #include "ObjContainer/IPak/IPak.h" +#include "ObjLoaderT6.h" #include "ObjLoading.h" #include "PhysConstraints/GdtLoaderPhysConstraintsT6.h" #include "PhysConstraints/RawLoaderPhysConstraintsT6.h" @@ -28,6 +28,7 @@ #include "Slug/LoaderSlugT6.h" #include "Sound/LoaderSoundBankT6.h" #include "StringTable/LoaderStringTableT6.h" +#include "TechniqueSet/LoaderTechniqueSetT6.h" #include "Tracer/GdtLoaderTracerT6.h" #include "Tracer/RawLoaderTracerT6.h" #include "Utils/Logging/Log.h" @@ -42,8 +43,6 @@ #include "Weapon/WeaponRawLoaderT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" -#include "BSP/LoaderBSP_T6.h" -#include "TechniqueSet/LoaderTechniqueSetT6.h" #include #include From eef269f497808be338b6ae5b6ce313fb83f6335d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 16:16:15 +0800 Subject: [PATCH 033/123] Added map type metadata parsing to zone file --- .../SequenceZoneDefinitionMetaData.cpp | 22 +++++++++++++++---- .../Parsing/ZoneDefinitionParserState.cpp | 20 ++--------------- .../Parsing/ZoneDefinitionParserState.h | 2 +- .../Zone/Definition/ZoneDefinition.cpp | 3 ++- .../Zone/Definition/ZoneDefinition.h | 11 ++++++++-- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp index f60b2f207..b1f1588e1 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp @@ -1,5 +1,4 @@ #include "SequenceZoneDefinitionMetaData.h" - #include "Utils/Logging/Log.h" #include "Utils/StringUtils.h" #include "Zone/Definition/Parsing/Matcher/ZoneDefinitionMatcherFactory.h" @@ -10,7 +9,7 @@ namespace { - constexpr auto METADATA_CUSTOM_MAP = "custom_map"; + constexpr auto METADATA_MAP_TYPE = "map"; constexpr auto METADATA_GAME = "game"; constexpr auto METADATA_GDT = "gdt"; @@ -136,9 +135,24 @@ void SequenceZoneDefinitionMetaData::ProcessMatch(ZoneDefinitionParserState* sta { ProcessMetaDataGame(state, valueToken, value); } - else if (key == METADATA_CUSTOM_MAP) + else if (key == METADATA_MAP_TYPE) { - state->SetCustomMap(); + std::string valueLowerCase = value; + std::transform(valueLowerCase.begin(), + valueLowerCase.end(), + valueLowerCase.begin(), + [](unsigned char c) + { + return std::tolower(c); + }); + if (valueLowerCase.compare("sp") == 0) + state->SetMapType(ZoneDefinitionMapType::SP); + else if (valueLowerCase.compare("mp") == 0) + state->SetMapType(ZoneDefinitionMapType::MP); + else if (valueLowerCase.compare("zm") == 0) + state->SetMapType(ZoneDefinitionMapType::ZM); + else + throw ParsingException(valueToken.GetPos(), "map must be SP, MP or ZM"); } else if (key == METADATA_GDT) { diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp index a5ed3bf04..53aaa0a10 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp @@ -19,25 +19,9 @@ void ZoneDefinitionParserState::SetGame(const GameId game) m_asset_name_resolver = IAssetNameResolver::GetResolverForGame(game); } -void ZoneDefinitionParserState::SetCustomMap() +void ZoneDefinitionParserState::SetMapType(ZoneDefinitionMapType mapType) { - if (m_definition->is_custom_map == false) - { - m_definition->is_custom_map = true; - if (m_definition->m_game != GameId::T6) - { - printf("ERROR: Custom map linking is only supported on BO2 (T6).\n"); - return; - } - - printf("Processing zone as a custom map zone.\n"); - const auto gfxWorldAssetType = m_asset_name_resolver->GetAssetTypeByName("gfxworld"); - _ASSERT(gfxWorldAssetType); - - m_definition->m_assets.emplace_back(*gfxWorldAssetType, "gfxworld", false); - } - - + m_definition->m_map_type = mapType; } namespace diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h index 1be1bd843..74d9a0810 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h @@ -17,7 +17,7 @@ class ZoneDefinitionParserState void SetGame(GameId game); - void SetCustomMap(); + void SetMapType(ZoneDefinitionMapType mapType); void StartIPak(std::string ipakName); void StartIwd(std::string iwdName); diff --git a/src/ZoneCommon/Zone/Definition/ZoneDefinition.cpp b/src/ZoneCommon/Zone/Definition/ZoneDefinition.cpp index 41c401da9..53233f546 100644 --- a/src/ZoneCommon/Zone/Definition/ZoneDefinition.cpp +++ b/src/ZoneCommon/Zone/Definition/ZoneDefinition.cpp @@ -34,6 +34,7 @@ void ZoneDefinitionProperties::Include(const ZoneDefinitionProperties& otherProp } ZoneDefinition::ZoneDefinition() - : m_game(GameId::COUNT) + : m_game(GameId::COUNT), + m_map_type(ZoneDefinitionMapType::NONE) { } diff --git a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h index e87f52cb2..e774b1f26 100644 --- a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h +++ b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h @@ -15,6 +15,14 @@ enum class ZoneDefinitionObjContainerType : uint8_t IPAK }; +enum class ZoneDefinitionMapType : uint8_t +{ + NONE, + SP, + MP, + ZM +}; + class ZoneDefinitionObjContainer { public: @@ -69,6 +77,5 @@ class ZoneDefinition std::vector m_gdts; std::vector m_assets; std::vector m_obj_containers; - - bool is_custom_map = false; + ZoneDefinitionMapType m_map_type; }; From 3d2e890e34ecc198f94a4b23125028337ef5fe9b Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 16:19:06 +0800 Subject: [PATCH 034/123] Added the zone definition to the parameters of ConfigureCreatorCollection. ObjLoaderT6 has also been changed to conditionally add the BSP creator if the map type isn't NONE. --- src/Linker/ZoneCreation/ZoneCreator.cpp | 2 +- src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 3 ++- src/ObjLoading/Game/IW3/ObjLoaderIW3.h | 3 ++- src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp | 3 ++- src/ObjLoading/Game/IW4/ObjLoaderIW4.h | 3 ++- src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 3 ++- src/ObjLoading/Game/IW5/ObjLoaderIW5.h | 3 ++- src/ObjLoading/Game/T5/ObjLoaderT5.cpp | 3 ++- src/ObjLoading/Game/T5/ObjLoaderT5.h | 3 ++- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 13 ++++++++----- src/ObjLoading/Game/T6/ObjLoaderT6.h | 3 ++- src/ObjLoading/IObjLoader.h | 4 +++- 12 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Linker/ZoneCreation/ZoneCreator.cpp b/src/Linker/ZoneCreation/ZoneCreator.cpp index 5d32c9a4e..207ff8141 100644 --- a/src/Linker/ZoneCreation/ZoneCreator.cpp +++ b/src/Linker/ZoneCreation/ZoneCreator.cpp @@ -72,7 +72,7 @@ namespace zone_creator OutputPathFilesystem cacheDir(context.m_cache_dir); objCompiler->ConfigureCreatorCollection( creatorCollection, *zone, zoneDefinitionContext, *context.m_asset_search_path, lookup, creationContext, outDir, cacheDir); - objLoader->ConfigureCreatorCollection(creatorCollection, *zone, *context.m_asset_search_path, lookup); + objLoader->ConfigureCreatorCollection(creatorCollection, *zone, *context.m_asset_search_path, lookup, *context.m_definition); for (const auto& assetEntry : context.m_definition->m_assets) { diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index fb2c16275..1f346d8df 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -118,7 +118,8 @@ namespace } } // namespace -void ObjLoader::ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const +void ObjLoader::ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const { ConfigureDefaultCreators(collection, zone); ConfigureLoaders(collection, zone, searchPath); diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.h b/src/ObjLoading/Game/IW3/ObjLoaderIW3.h index a11c9d518..b2cde58c1 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.h +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.h @@ -11,6 +11,7 @@ namespace IW3 void LoadReferencedContainersForZone(ISearchPath& searchPath, Zone& zone) const override; void UnloadContainersOfZone(Zone& zone) const override; - void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const override; + void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const override; }; } // namespace IW3 diff --git a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp index ad5b83c4e..ab915e6ef 100644 --- a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp +++ b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp @@ -159,7 +159,8 @@ namespace } } // namespace -void ObjLoader::ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const +void ObjLoader::ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const { ConfigureDefaultCreators(collection, zone); ConfigureLoaders(collection, zone, searchPath, gdt); diff --git a/src/ObjLoading/Game/IW4/ObjLoaderIW4.h b/src/ObjLoading/Game/IW4/ObjLoaderIW4.h index 4d080914e..7f9b5cad3 100644 --- a/src/ObjLoading/Game/IW4/ObjLoaderIW4.h +++ b/src/ObjLoading/Game/IW4/ObjLoaderIW4.h @@ -12,6 +12,7 @@ namespace IW4 void LoadReferencedContainersForZone(ISearchPath& searchPath, Zone& zone) const override; void UnloadContainersOfZone(Zone& zone) const override; - void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const override; + void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const override; }; } // namespace IW4 diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index cb3d96cd8..c90ec6a3f 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -165,7 +165,8 @@ namespace } } // namespace -void ObjLoader::ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const +void ObjLoader::ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const { ConfigureDefaultCreators(collection, zone); ConfigureLoaders(collection, zone, searchPath, gdt); diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.h b/src/ObjLoading/Game/IW5/ObjLoaderIW5.h index 2b882ecb1..3337c4bc4 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.h +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.h @@ -11,6 +11,7 @@ namespace IW5 void LoadReferencedContainersForZone(ISearchPath& searchPath, Zone& zone) const override; void UnloadContainersOfZone(Zone& zone) const override; - void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const override; + void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const override; }; } // namespace IW5 diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index cc67cc5a5..a10683755 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -135,7 +135,8 @@ namespace } } // namespace -void ObjLoader::ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const +void ObjLoader::ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const { ConfigureDefaultCreators(collection, zone); ConfigureLoaders(collection, zone, searchPath); diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.h b/src/ObjLoading/Game/T5/ObjLoaderT5.h index 99fc99c83..3da8ab8d5 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.h +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.h @@ -11,6 +11,7 @@ namespace T5 void LoadReferencedContainersForZone(ISearchPath& searchPath, Zone& zone) const override; void UnloadContainersOfZone(Zone& zone) const override; - void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const override; + void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const override; }; } // namespace T5 diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 1286eb1d5..4a32d6b74 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -1,3 +1,5 @@ +#include "ObjLoaderT6.h" + #include "Asset/GlobalAssetPoolsLoader.h" #include "BSP/LoaderBSP_T6.h" #include "FontIcon/CsvLoaderFontIconT6.h" @@ -16,7 +18,6 @@ #include "Localize/LocalizeLoaderT6.h" #include "Material/LoaderMaterialT6.h" #include "ObjContainer/IPak/IPak.h" -#include "ObjLoaderT6.h" #include "ObjLoading.h" #include "PhysConstraints/GdtLoaderPhysConstraintsT6.h" #include "PhysConstraints/RawLoaderPhysConstraintsT6.h" @@ -381,7 +382,7 @@ namespace T6 collection.AddAssetCreator(std::make_unique>(zone)); } - void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) + void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) { auto& memory = zone.Memory(); @@ -441,14 +442,16 @@ namespace T6 collection.AddAssetCreator(z_barrier::CreateRawLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(z_barrier::CreateGdtLoaderT6(memory, searchPath, gdt, zone)); - collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone)); + if (definition.m_map_type != ZoneDefinitionMapType::NONE) + collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone)); } } // namespace - void ObjLoader::ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const + void ObjLoader::ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const { ConfigureDefaultCreators(collection, zone); - ConfigureLoaders(collection, zone, searchPath, gdt); + ConfigureLoaders(collection, zone, searchPath, gdt, definition); ConfigureGlobalAssetPoolsLoaders(collection, zone); } } // namespace T6 diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.h b/src/ObjLoading/Game/T6/ObjLoaderT6.h index 6e210b85b..f373ef2b5 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.h +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.h @@ -18,7 +18,8 @@ namespace T6 void LoadReferencedContainersForZone(ISearchPath& searchPath, Zone& zone) const override; void UnloadContainersOfZone(Zone& zone) const override; - void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const override; + void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const override; private: static bool VerifySoundBankChecksum(const SoundBank& soundBank, const SndRuntimeAssetBank& sndRuntimeAssetBank); diff --git a/src/ObjLoading/IObjLoader.h b/src/ObjLoading/IObjLoader.h index b534cac63..15aa82781 100644 --- a/src/ObjLoading/IObjLoader.h +++ b/src/ObjLoading/IObjLoader.h @@ -3,6 +3,7 @@ #include "Asset/AssetCreatorCollection.h" #include "Gdt/IGdtQueryable.h" #include "SearchPath/ISearchPath.h" +#include "Zone/Definition/ZoneDefinition.h" #include "Zone/Zone.h" class IObjLoader @@ -28,7 +29,8 @@ class IObjLoader */ virtual void UnloadContainersOfZone(Zone& zone) const = 0; - virtual void ConfigureCreatorCollection(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) const = 0; + virtual void ConfigureCreatorCollection( + AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt, ZoneDefinition& definition) const = 0; static const IObjLoader* GetObjLoaderForGame(GameId game); }; From 81e03312525c25c86b79eed4a82ddfa4fc03f989 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 17:06:08 +0800 Subject: [PATCH 035/123] Updated BSP linker to load assets on the FinalizeZone step. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 1 + .../Game/T6/BSP/Linker/BSPLinker.cpp | 45 ++++++++++++------- src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h | 2 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 14 +++--- .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 5 +-- .../Game/T6/BSP/Linker/ComWorldLinker.h | 2 +- .../Game/T6/BSP/Linker/GameWorldMpLinker.cpp | 5 +-- .../Game/T6/BSP/Linker/GameWorldMpLinker.h | 2 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 16 +++---- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 12 ++--- .../Game/T6/BSP/Linker/MapEntsLinker.h | 2 +- .../Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 11 ++--- .../Game/T6/BSP/Linker/SkinnedVertsLinker.h | 2 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 28 +++++++++--- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h | 2 +- 17 files changed, 87 insertions(+), 66 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 6c14330be..a8dab1af2 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1,4 +1,5 @@ #include "BSPCreator.h" + #include "BSPUtil.h" #include "fbx/ufbx.h" diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp index 0777491bf..7f29b6395 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -1,4 +1,5 @@ #include "BSPLinker.h" + #include "ClipMapLinker.h" #include "ComWorldLinker.h" #include "GameWorldMpLinker.h" @@ -58,10 +59,10 @@ namespace BSP { } - AssetCreationResult BSPLinker::linkBSP(BSPData* bsp) + bool BSPLinker::linkBSP(BSPData* bsp) { if (!addDefaultRequiredAssets(bsp)) - return AssetCreationResult::Failure(); + return false; ComWorldLinker comWorldLinker(m_memory, m_search_path, m_context); ClipMapLinker clipMapLinker(m_memory, m_search_path, m_context); @@ -70,26 +71,36 @@ namespace BSP MapEntsLinker mapEntsLinker(m_memory, m_search_path, m_context); SkinnedVertsLinker skinnedVertsLinker(m_memory, m_search_path, m_context); - if (comWorldLinker.linkComWorld(bsp).HasFailed()) - return AssetCreationResult::Failure(); + ComWorld* comWorld = comWorldLinker.linkComWorld(bsp); + if (comWorld == nullptr) + return false; + m_context.AddAsset(comWorld->name, comWorld); - if (mapEntsLinker.linkMapEnts(bsp).HasFailed()) - return AssetCreationResult::Failure(); + MapEnts* mapEnts = mapEntsLinker.linkMapEnts(bsp); + if (mapEnts == nullptr) + return false; + m_context.AddAsset(mapEnts->name, mapEnts); - gameWorldMpLinker.linkGameWorldMp(bsp); - if (gameWorldMpLinker.linkGameWorldMp(bsp).HasFailed()) - return AssetCreationResult::Failure(); + GameWorldMp* gameWorldMp = gameWorldMpLinker.linkGameWorldMp(bsp); + if (gameWorldMp == nullptr) + return false; + m_context.AddAsset(gameWorldMp->name, gameWorldMp); - if (skinnedVertsLinker.linkSkinnedVerts(bsp).HasFailed()) - return AssetCreationResult::Failure(); + SkinnedVertsDef* skinnedVerts = skinnedVertsLinker.linkSkinnedVerts(bsp); + if (skinnedVerts == nullptr) + return false; + m_context.AddAsset(skinnedVerts->name, skinnedVerts); - auto result = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset - if (result.HasFailed()) - return AssetCreationResult::Failure(); + GfxWorld* gfxWorld = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + if (gfxWorld == nullptr) + return false; + m_context.AddAsset(gfxWorld->name, gfxWorld); - if (clipMapLinker.linkClipMap(bsp).HasFailed()) // requires gfxworld and mapents asset - return AssetCreationResult::Failure(); + clipMap_t* clipMap = clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + if (clipMap == nullptr) + return false; + m_context.AddAsset(clipMap->name, clipMap); - return result; + return true; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h index f51abf4e5..e4d53a2a3 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkBSP(BSPData* bsp); + bool linkBSP(BSPData* bsp); private: FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 5a7b99cb3..b43b6e9a0 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -1,6 +1,7 @@ -#include "../BSPUtil.h" #include "ClipMapLinker.h" +#include "../BSPUtil.h" + namespace BSP { ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -494,7 +495,7 @@ namespace BSP } // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion clipMap->triCount = static_cast(triIndexVec.size() / 3); - clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. @@ -601,7 +602,7 @@ namespace BSP return true; } - AssetCreationResult ClipMapLinker::linkClipMap(BSPData* bsp) + clipMap_t* ClipMapLinker::linkClipMap(BSPData* bsp) { clipMap_t* clipMap = m_memory.Alloc(); clipMap->name = m_memory.Dup(bsp->bspName.c_str()); @@ -642,11 +643,8 @@ namespace BSP memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); if (!loadWorldCollision(clipMap, bsp)) - return AssetCreationResult::Failure(); - - m_context.AddAsset(clipMap->name, clipMap); + return nullptr; - auto clipMapAsset = m_context.AddAsset(clipMap->name, clipMap); - return AssetCreationResult::Success(clipMapAsset); + return clipMap; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index c97833a5b..ca5995577 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -12,7 +12,7 @@ namespace BSP { public: ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkClipMap(BSPData* bsp); + clipMap_t* linkClipMap(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index dd794d369..c4c7c7ff4 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -9,7 +9,7 @@ namespace BSP { } - AssetCreationResult ComWorldLinker::linkComWorld(BSPData* bsp) + ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = m_memory.Alloc(); @@ -32,7 +32,6 @@ namespace BSP sunLight->dir.y = sunLightDirection.y; sunLight->dir.z = sunLightDirection.z; - auto comWorldAsset = m_context.AddAsset(comWorld->name, comWorld); - return AssetCreationResult::Success(comWorldAsset); + return comWorld; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h index ba5a39fb1..2c688e38e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkComWorld(BSPData* bsp); + ComWorld* linkComWorld(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp index 56e574634..6852b1aaf 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp @@ -9,7 +9,7 @@ namespace BSP { } - AssetCreationResult GameWorldMpLinker::linkGameWorldMp(BSPData* bsp) + GameWorldMp* GameWorldMpLinker::linkGameWorldMp(BSPData* bsp) { GameWorldMp* gameWorldMp = m_memory.Alloc(); @@ -29,7 +29,6 @@ namespace BSP gameWorldMp->path.smoothCache = nullptr; gameWorldMp->path.nodeTree = nullptr; - auto gameWorldMpAsset = m_context.AddAsset(gameWorldMp->name, gameWorldMp); - return AssetCreationResult::Success(gameWorldMpAsset); + return gameWorldMp; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h index 5f989105b..69c8bef59 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkGameWorldMp(BSPData* bsp); + GameWorldMp* linkGameWorldMp(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index a8bd50b62..9f7386a3b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -1,5 +1,6 @@ -#include "../BSPUtil.h" #include "GfxWorldLinker.h" + +#include "../BSPUtil.h" #include "Utils/Pack.h" namespace BSP @@ -725,7 +726,7 @@ namespace BSP return true; } - AssetCreationResult GfxWorldLinker::linkGfxWorld(BSPData* bsp) + GfxWorld* GfxWorldLinker::linkGfxWorld(BSPData* bsp) { GfxWorld* gfxWorld = m_memory.Alloc(); gfxWorld->baseName = m_memory.Dup(bsp->name.c_str()); @@ -738,23 +739,23 @@ namespace BSP cleanGfxWorld(gfxWorld); if (!loadMapSurfaces(bsp, gfxWorld)) - return AssetCreationResult::Failure(); + return nullptr; loadXModels(bsp, gfxWorld); if (!loadLightmapData(gfxWorld)) - return AssetCreationResult::Failure(); + return nullptr; loadSkyBox(bsp, gfxWorld); if (!loadReflectionProbeData(gfxWorld)) - return AssetCreationResult::Failure(); + return nullptr; // world bounds are based on loaded surface mins/maxs loadWorldBounds(gfxWorld); if (!loadOutdoors(gfxWorld)) - return AssetCreationResult::Failure(); + return nullptr; // gfx cells depend on surface/smodel count loadGfxCells(gfxWorld); @@ -769,7 +770,6 @@ namespace BSP loadDynEntData(gfxWorld); - auto gfxWorldAsset = m_context.AddAsset(gfxWorld->name, gfxWorld); - return AssetCreationResult::Success(gfxWorldAsset); + return gfxWorld; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index f48de580d..230635905 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkGfxWorld(BSPData* bsp); + GfxWorld* linkGfxWorld(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 5a75e614b..09c43af73 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -1,6 +1,7 @@ -#include "../BSPUtil.h" #include "MapEntsLinker.h" +#include "../BSPUtil.h" + #include using namespace nlohmann; @@ -71,7 +72,7 @@ namespace BSP { } - AssetCreationResult MapEntsLinker::linkMapEnts(BSPData* bsp) + MapEnts* MapEntsLinker::linkMapEnts(BSPData* bsp) { try { @@ -90,7 +91,7 @@ namespace BSP } std::string entityString; if (!parseMapEntsJSON(entJs["entities"], entityString)) - return AssetCreationResult::Failure(); + return nullptr; json spawnJs; std::string spawnFileName = "spawns.json"; @@ -126,13 +127,12 @@ namespace BSP mapEnts->trigger.slabCount = 0; mapEnts->trigger.slabs = nullptr; - auto mapEntsAsset = m_context.AddAsset(mapEnts->name, mapEnts); - return AssetCreationResult::Success(mapEntsAsset); + return mapEnts; } catch (const json::exception& e) { con::error("JSON error when parsing map ents and spawns: {}", e.what()); - return AssetCreationResult::Failure(); + return nullptr; } } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h index 468989c8c..0c7681b31 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkMapEnts(BSPData* bsp); + MapEnts* linkMapEnts(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp index 9792d2447..33dba83e2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -9,17 +9,14 @@ namespace BSP { } - AssetCreationResult SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) + SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) { - std::string assetName = "skinnedverts"; - - // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // Pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have // But setting it to the world vertex count seems to work SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); - skinnedVerts->name = m_memory.Dup(assetName.c_str()); + skinnedVerts->name = m_memory.Dup("skinnedverts"); skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.vertices.size()); - auto skinnedVertsAsset = m_context.AddAsset(assetName, skinnedVerts); - return AssetCreationResult::Success(skinnedVertsAsset); + return skinnedVerts; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h index 605a95acd..d64b0c20f 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h @@ -11,7 +11,7 @@ namespace BSP { public: SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); - AssetCreationResult linkSkinnedVerts(BSPData* bsp); + SkinnedVertsDef* linkSkinnedVerts(BSPData* bsp); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 118b265dc..e73c3201f 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,13 +1,14 @@ +#include "LoaderBSP_T6.h" + #include "BSPCreator.h" #include "BSPUtil.h" #include "Linker/BSPLinker.h" -#include "LoaderBSP_T6.h" namespace { using namespace BSP; - class BSPLoader final : public AssetCreator + class BSPLoader final : public IAssetCreator { public: BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) @@ -17,20 +18,35 @@ namespace { } + std::optional GetHandlingAssetType() const override + { + // don't handle any asset types + return std::nullopt; + } + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + // BSP assets are added in the finalize zone step + return AssetCreationResult::NoAction(); + } + + void FinalizeZone(AssetCreationContext& context) override { // custom maps must have a map_gfx file std::string mapGfxFileName = "map_gfx.fbx"; auto mapGfxFile = m_search_path.Open(BSPUtil::getFileNameForBSPAsset(mapGfxFileName)); if (!mapGfxFile.IsOpen()) - return AssetCreationResult::NoAction(); + return; std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path); if (bsp == nullptr) - return AssetCreationResult::Failure(); + return; BSPLinker linker(m_memory, m_search_path, context); - return linker.linkBSP(bsp.get()); + if (!linker.linkBSP(bsp.get())) + con::error("BSP link has failed."); + + return; } private: @@ -42,7 +58,7 @@ namespace namespace BSP { - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) { return std::make_unique(memory, searchPath, zone); } diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h index 4cacf2f28..25ca4f8ae 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h @@ -9,5 +9,5 @@ namespace BSP { - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); + std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); } // namespace BSP From 56186d1d8081dafcdb70f7ba4a65acf63da79dda Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 17:25:14 +0800 Subject: [PATCH 036/123] The FinalizeZone step can now fail and the zone link will not ouput a fastfile if the FinalizeZone step fails. --- src/Linker/ZoneCreation/ZoneCreator.cpp | 3 ++- .../T6/KeyValuePairs/KeyValuePairsCompilerT6.cpp | 6 ++++-- src/ObjLoading/Asset/AssetCreatorCollection.cpp | 9 +++++++-- src/ObjLoading/Asset/AssetCreatorCollection.h | 2 +- src/ObjLoading/Asset/IAssetCreator.h | 6 +++++- .../Game/IW4/Menu/LoaderMenuListIW4.cpp | 4 +++- .../Game/IW5/Menu/LoaderMenuListIW5.cpp | 4 +++- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 15 +++++---------- 8 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Linker/ZoneCreation/ZoneCreator.cpp b/src/Linker/ZoneCreation/ZoneCreator.cpp index 207ff8141..1be5335e6 100644 --- a/src/Linker/ZoneCreation/ZoneCreator.cpp +++ b/src/Linker/ZoneCreation/ZoneCreator.cpp @@ -84,7 +84,8 @@ namespace zone_creator ++zoneDefinitionContext.m_asset_index_in_definition; } - creatorCollection.FinalizeZone(creationContext); + if (!creatorCollection.FinalizeZone(creationContext)) + return nullptr; return zone; } diff --git a/src/ObjCompiling/Game/T6/KeyValuePairs/KeyValuePairsCompilerT6.cpp b/src/ObjCompiling/Game/T6/KeyValuePairs/KeyValuePairsCompilerT6.cpp index 35724a687..3d52dab79 100644 --- a/src/ObjCompiling/Game/T6/KeyValuePairs/KeyValuePairsCompilerT6.cpp +++ b/src/ObjCompiling/Game/T6/KeyValuePairs/KeyValuePairsCompilerT6.cpp @@ -32,12 +32,12 @@ namespace return AssetCreationResult::NoAction(); } - void FinalizeZone(AssetCreationContext& context) override + bool FinalizeZone(AssetCreationContext& context) override { m_kvp_creator.Finalize(m_zone_definition); const auto commonKvps = m_kvp_creator.GetFinalKeyValuePairs(); if (commonKvps.empty()) - return; + return true; auto* gameKvps = m_memory.Alloc(); gameKvps->name = m_memory.Dup(m_zone.m_name.c_str()); @@ -61,6 +61,8 @@ namespace } context.AddAsset(AssetRegistration(m_zone.m_name, gameKvps)); + + return true; } private: diff --git a/src/ObjLoading/Asset/AssetCreatorCollection.cpp b/src/ObjLoading/Asset/AssetCreatorCollection.cpp index ee237293f..f6eb27547 100644 --- a/src/ObjLoading/Asset/AssetCreatorCollection.cpp +++ b/src/ObjLoading/Asset/AssetCreatorCollection.cpp @@ -79,10 +79,15 @@ AssetCreationResult AssetCreatorCollection::CreateDefaultAsset(const asset_type_ return AssetCreationResult::NoAction(); } -void AssetCreatorCollection::FinalizeZone(AssetCreationContext& context) const +bool AssetCreatorCollection::FinalizeZone(AssetCreationContext& context) const { for (const auto& creator : m_asset_creators) - creator->FinalizeZone(context); + { + if (!creator->FinalizeZone(context)) + return false; + } for (const auto& postProcessor : m_asset_post_processors) postProcessor->FinalizeZone(context); + + return true; } diff --git a/src/ObjLoading/Asset/AssetCreatorCollection.h b/src/ObjLoading/Asset/AssetCreatorCollection.h index 9b720b118..d11a237bf 100644 --- a/src/ObjLoading/Asset/AssetCreatorCollection.h +++ b/src/ObjLoading/Asset/AssetCreatorCollection.h @@ -27,7 +27,7 @@ class AssetCreatorCollection AssetCreationResult CreateAsset(asset_type_t assetType, const std::string& assetName, AssetCreationContext& context) const; AssetCreationResult CreateDefaultAsset(asset_type_t assetType, const std::string& assetName, AssetCreationContext& context) const; - void FinalizeZone(AssetCreationContext& context) const; + bool FinalizeZone(AssetCreationContext& context) const; private: std::vector> m_asset_creators_by_type; diff --git a/src/ObjLoading/Asset/IAssetCreator.h b/src/ObjLoading/Asset/IAssetCreator.h index 33153df60..f44329c0d 100644 --- a/src/ObjLoading/Asset/IAssetCreator.h +++ b/src/ObjLoading/Asset/IAssetCreator.h @@ -25,7 +25,11 @@ class IAssetCreator [[nodiscard]] virtual std::optional GetHandlingAssetType() const = 0; virtual AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) = 0; - virtual void FinalizeZone(AssetCreationContext& context) {}; + + virtual bool FinalizeZone(AssetCreationContext& context) + { + return true; + }; }; template class AssetCreator : public IAssetCreator diff --git a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp index 98ede01af..bd40d0d29 100644 --- a/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp +++ b/src/ObjLoading/Game/IW4/Menu/LoaderMenuListIW4.cpp @@ -73,9 +73,11 @@ namespace return AssetCreationResult::Success(context.AddAsset(std::move(registration))); } - void FinalizeZone(AssetCreationContext& context) override + bool FinalizeZone(AssetCreationContext& context) override { context.GetZoneAssetCreationState().FinalizeSupportingData(); + + return true; } private: diff --git a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp index b16d7fcf6..5f4297b6c 100644 --- a/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp +++ b/src/ObjLoading/Game/IW5/Menu/LoaderMenuListIW5.cpp @@ -73,9 +73,11 @@ namespace return AssetCreationResult::Success(context.AddAsset(std::move(registration))); } - void FinalizeZone(AssetCreationContext& context) override + bool FinalizeZone(AssetCreationContext& context) override { context.GetZoneAssetCreationState().FinalizeSupportingData(); + + return true; } private: diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index e73c3201f..a1399c8e3 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -30,23 +30,18 @@ namespace return AssetCreationResult::NoAction(); } - void FinalizeZone(AssetCreationContext& context) override + bool FinalizeZone(AssetCreationContext& context) override { - // custom maps must have a map_gfx file - std::string mapGfxFileName = "map_gfx.fbx"; - auto mapGfxFile = m_search_path.Open(BSPUtil::getFileNameForBSPAsset(mapGfxFileName)); - if (!mapGfxFile.IsOpen()) - return; - std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path); if (bsp == nullptr) - return; + return false; BSPLinker linker(m_memory, m_search_path, context); - if (!linker.linkBSP(bsp.get())) + bool result = linker.linkBSP(bsp.get()); + if (!result) con::error("BSP link has failed."); - return; + return result; } private: From 3948feaaf8aa59fffae1c77f65c93a21f0919039 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 17:27:32 +0800 Subject: [PATCH 037/123] Assets with a name starting with ',' will now be always loaded as a default dependency. --- src/ObjLoading/Asset/AssetCreationContext.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ObjLoading/Asset/AssetCreationContext.cpp b/src/ObjLoading/Asset/AssetCreationContext.cpp index 7624716c8..4fb6b778c 100644 --- a/src/ObjLoading/Asset/AssetCreationContext.cpp +++ b/src/ObjLoading/Asset/AssetCreationContext.cpp @@ -120,6 +120,9 @@ XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_ return LoadDefaultAssetDependency(assetType, std::format(",{}", assetName)); } + if (assetName.starts_with(',')) + return LoadDefaultAssetDependency(assetType, assetName); + if (m_ignored_asset_lookup->IsAssetIgnored(assetType, assetName)) return LoadDefaultAssetDependency(assetType, std::format(",{}", assetName)); From b7d629d26bf5755aeaa555b5ed97d853fa3e8c90 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 17:32:36 +0800 Subject: [PATCH 038/123] The linker should continue with building the fastfile if the asset list is empty and the map type isn't NONE. --- src/Linker/Linker.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp index 61050cf04..cb02202ee 100644 --- a/src/Linker/Linker.cpp +++ b/src/Linker/Linker.cpp @@ -1,5 +1,4 @@ #include "Linker.h" - #include "LinkerArgs.h" #include "LinkerPaths.h" #include "ObjContainer/SoundBank/SoundBankWriter.h" @@ -330,7 +329,7 @@ class LinkerImpl final : public Linker PathGameContext gameContext(paths, projectName, zoneDefinition->m_game); - if (!zoneDefinition->m_assets.empty()) + if (!zoneDefinition->m_assets.empty() || zoneDefinition->m_map_type != ZoneDefinitionMapType::NONE) { if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition)) return false; From 323a882b1d8e219b924c5e77e3c51c51f235bf9a Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 17:50:01 +0800 Subject: [PATCH 039/123] Refactored files to use CLANG formatting and linux compatabillty. --- .../T6/TechniqueSet/LoaderTechniqueSetT6.cpp | 23 ++++++++++--------- .../Game/T6/Techset/TechsetDumperT6.cpp | 16 ++++++------- .../SequenceZoneDefinitionMetaData.cpp | 1 + 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index c2eb7fa92..43cc6e0b5 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -1,5 +1,6 @@ -#include "Game/T6/T6.h" #include "LoaderTechniqueSetT6.h" + +#include "Game/T6/T6.h" #include "Shader/ShaderCommon.h" #include @@ -41,7 +42,7 @@ namespace if (techniqueJs.size() == 0) { - techniqueSet->techniques[i] = NULL; + techniqueSet->techniques[i] = nullptr; } else { @@ -52,8 +53,8 @@ namespace technique->name = _strdup(techName.c_str()); technique->flags = techniqueJs["flags"]; technique->passCount = techniqueJs["passCount"]; - _ASSERT(technique->passCount == 1); - + assert(technique->passCount == 1); + for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) { MaterialPass* currPass = &technique->passArray[passIndex]; @@ -69,7 +70,7 @@ namespace int argCount = currPass->perPrimArgCount + currPass->perObjArgCount + currPass->stableArgCount; if (argCount == 0) { - currPass->args = NULL; + currPass->args = nullptr; } else { @@ -101,7 +102,7 @@ namespace if (passJs["vertexDecl"].size() == 0) { - currPass->vertexDecl = NULL; + currPass->vertexDecl = nullptr; } else { @@ -113,13 +114,13 @@ namespace { currPass->vertexDecl->routing.data[i].source = (unsigned char)passJs["vertexDecl"]["routing"][i]["source"]; currPass->vertexDecl->routing.data[i].dest = (unsigned char)passJs["vertexDecl"]["routing"][i]["dest"]; - currPass->vertexDecl->routing.decl[i] = NULL; + currPass->vertexDecl->routing.decl[i] = nullptr; } } if (passJs["pixelShader"].size() == 0) { - currPass->pixelShader = NULL; + currPass->pixelShader = nullptr; } else { @@ -127,7 +128,7 @@ namespace std::string pixelName = passJs["pixelShader"]["name"]; currPass->pixelShader->name = _strdup(pixelName.c_str()); - currPass->pixelShader->prog.ps = NULL; + currPass->pixelShader->prog.ps = nullptr; const auto psFileName = shader::GetFileNameForPixelShaderAssetName(pixelName); const auto psFile = m_search_path.Open(psFileName); @@ -143,7 +144,7 @@ namespace if (passJs["vertexShader"].size() == 0) { - currPass->vertexShader = NULL; + currPass->vertexShader = nullptr; } else { @@ -151,7 +152,7 @@ namespace std::string vertexName = passJs["vertexShader"]["name"]; currPass->vertexShader->name = _strdup(vertexName.c_str()); - currPass->vertexShader->prog.vs = NULL; + currPass->vertexShader->prog.vs = nullptr; const auto vsFileName = shader::GetFileNameForVertexShaderAssetName(vertexName); const auto vsFile = m_search_path.Open(vsFileName); diff --git a/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp b/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp index f2cc2fc0a..4bb9da3ca 100644 --- a/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp +++ b/src/ObjWriting/Game/T6/Techset/TechsetDumperT6.cpp @@ -2,9 +2,9 @@ #include "Shader/ShaderCommon.h" +#include #include #include -#include using namespace nlohmann; using namespace T6; @@ -103,13 +103,13 @@ namespace techset { json techniqueJs = json::object(); - if (technique != NULL) + if (technique != nullptr) { techniqueJs["name"] = technique->name; techniqueJs["flags"] = technique->flags; techniqueJs["passCount"] = technique->passCount; - _ASSERT(technique->passCount == 1); + assert(technique->passCount == 1); techniqueJs["passArray"] = json::array(); for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) @@ -125,7 +125,7 @@ namespace techset passJs["materialType"] = currPass->materialType; json vertDeclJs = json::object(); - if (currPass->vertexDecl != NULL) + if (currPass->vertexDecl != nullptr) { vertDeclJs["streamCount"] = currPass->vertexDecl->streamCount; vertDeclJs["hasOptionalSource"] = currPass->vertexDecl->hasOptionalSource; @@ -135,13 +135,13 @@ namespace techset vertDeclJs["routing"][i]["source"] = currPass->vertexDecl->routing.data[i].source; vertDeclJs["routing"][i]["dest"] = currPass->vertexDecl->routing.data[i].dest; - _ASSERT(currPass->vertexDecl->routing.decl[i] == NULL); + assert(currPass->vertexDecl->routing.decl[i] == nullptr); } } passJs["vertexDecl"] = vertDeclJs; passJs["args"] = json::array(); - if (currPass->args != NULL) + if (currPass->args != nullptr) { for (int i = 0; i < currPass->perPrimArgCount + currPass->perObjArgCount + currPass->stableArgCount; i++) { @@ -169,7 +169,7 @@ namespace techset } json pixelJs = json::object(); - if (currPass->pixelShader != NULL) + if (currPass->pixelShader != nullptr) { pixelJs["name"] = currPass->pixelShader->name; if (shaderState->ShouldDumpPixelShader(currPass->pixelShader)) @@ -178,7 +178,7 @@ namespace techset passJs["pixelShader"] = pixelJs; json vertexJs = json::object(); - if (currPass->vertexShader != NULL) + if (currPass->vertexShader != nullptr) { vertexJs["name"] = currPass->vertexShader->name; if (shaderState->ShouldDumpVertexShader(currPass->vertexShader)) diff --git a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp index b1f1588e1..8b40c24fb 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp @@ -1,4 +1,5 @@ #include "SequenceZoneDefinitionMetaData.h" + #include "Utils/Logging/Log.h" #include "Utils/StringUtils.h" #include "Zone/Definition/Parsing/Matcher/ZoneDefinitionMatcherFactory.h" From 8bcefd953e2e6ca0959c073e636168d0d10349a3 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 18:02:39 +0800 Subject: [PATCH 040/123] Added ufbx to submodules. --- .gitmodules | 3 +++ thirdparty/ufbx | 1 + 2 files changed, 4 insertions(+) create mode 160000 thirdparty/ufbx diff --git a/.gitmodules b/.gitmodules index ffaaa9753..58bcab4af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "thirdparty/webview"] path = thirdparty/webview url = https://github.com/Laupetin/webview.git +[submodule "thirdparty/ufbx"] + path = thirdparty/ufbx + url = https://github.com/ufbx/ufbx diff --git a/thirdparty/ufbx b/thirdparty/ufbx new file mode 160000 index 000000000..13b5df8e8 --- /dev/null +++ b/thirdparty/ufbx @@ -0,0 +1 @@ +Subproject commit 13b5df8e89eff0ce898f7c37fa0716e3bc186573 From 2199544f690910474217b0f6e100bcc07d2eab31 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 18:08:58 +0800 Subject: [PATCH 041/123] Added premake files for ufbx --- premake5.lua | 2 ++ thirdparty/ufbx.lua | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 thirdparty/ufbx.lua diff --git a/premake5.lua b/premake5.lua index e1c1575a9..72c3258d7 100644 --- a/premake5.lua +++ b/premake5.lua @@ -103,6 +103,7 @@ include "thirdparty/minizip.lua" include "thirdparty/salsa20.lua" include "thirdparty/webview.lua" include "thirdparty/zlib.lua" +include "thirdparty/ufbx.lua" -- ThirdParty group: All projects that are external dependencies group "ThirdParty" @@ -117,6 +118,7 @@ group "ThirdParty" minizip:project() salsa20:project() zlib:project() + ufbx:project() if _OPTIONS["modman"] then webview:project() diff --git a/thirdparty/ufbx.lua b/thirdparty/ufbx.lua new file mode 100644 index 000000000..9f8407e47 --- /dev/null +++ b/thirdparty/ufbx.lua @@ -0,0 +1,23 @@ +ufbx = {} + +function ufbx:include(includes) + if includes:handle(self:name()) then + includedirs { + path.join(ThirdPartyFolder(), "ufbx") + } + end +end + +function ufbx:link() +end + +function ufbx:use() + +end + +function ufbx:name() + return "ufbx" +end + +function ufbx:project() +end From a8a8085340eb2ba7730778df4a99f230dd5091a2 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 18:30:08 +0800 Subject: [PATCH 042/123] Updated ufbx premake lua to properly add it to the project --- thirdparty/ufbx.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/thirdparty/ufbx.lua b/thirdparty/ufbx.lua index 9f8407e47..9b7138900 100644 --- a/thirdparty/ufbx.lua +++ b/thirdparty/ufbx.lua @@ -9,6 +9,7 @@ function ufbx:include(includes) end function ufbx:link() + links:add(self:name()) end function ufbx:use() @@ -20,4 +21,22 @@ function ufbx:name() end function ufbx:project() + local folder = ThirdPartyFolder() + local includes = Includes:create() + + project(self:name()) + targetdir(TargetDirectoryLib) + location "%{wks.location}/thirdparty/%{prj.name}" + kind "StaticLib" + language "C++" + + files { + path.join(folder, "ufbx/ufbx.h"), + path.join(folder, "ufbx/ufbx.c") + } + + self:include(includes) + + -- Disable warnings. They do not have any value to us since it is not our code. + warnings "off" end From fc0500e5b4e81425bb31bc5b31c2ed03916bf096 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 19:00:59 +0800 Subject: [PATCH 043/123] Added ufbx as an ObjLoading include --- src/ObjLoading.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ObjLoading.lua b/src/ObjLoading.lua index 9325ca884..2b6499cb4 100644 --- a/src/ObjLoading.lua +++ b/src/ObjLoading.lua @@ -21,6 +21,7 @@ function ObjLoading:link(links) links:linkto(minizip) links:linkto(zlib) links:linkto(libtomcrypt) + links:linkto(ufbx) end function ObjLoading:use() @@ -64,4 +65,5 @@ function ObjLoading:project() eigen:include(includes) json:include(includes) libtomcrypt:include(includes) + ufbx:include(includes) end From f7a36f938aded9781e5fbd7a9a0fc040bfa58e8e Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Sat, 1 Nov 2025 20:13:49 +0800 Subject: [PATCH 044/123] WIP: Updated premake scripts, removed non-submodule ufbx code, ported BSPCreator to use the submodule. OAT link still fails due to unknown premake issue. --- premake5.lua | 2 +- src/ObjLoading.lua | 2 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 3 +- src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp | 33093 -------------------- src/ObjLoading/Game/T6/BSP/fbx/ufbx.h | 6062 ---- thirdparty/ufbx.lua | 4 +- 6 files changed, 6 insertions(+), 39160 deletions(-) delete mode 100644 src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp delete mode 100644 src/ObjLoading/Game/T6/BSP/fbx/ufbx.h diff --git a/premake5.lua b/premake5.lua index 72c3258d7..c196a771b 100644 --- a/premake5.lua +++ b/premake5.lua @@ -118,7 +118,7 @@ group "ThirdParty" minizip:project() salsa20:project() zlib:project() - ufbx:project() + ufbx:project() if _OPTIONS["modman"] then webview:project() diff --git a/src/ObjLoading.lua b/src/ObjLoading.lua index 2b6499cb4..8db8f6c55 100644 --- a/src/ObjLoading.lua +++ b/src/ObjLoading.lua @@ -66,4 +66,4 @@ function ObjLoading:project() json:include(includes) libtomcrypt:include(includes) ufbx:include(includes) -end +end \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index a8dab1af2..0a6e43938 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1,7 +1,8 @@ #include "BSPCreator.h" #include "BSPUtil.h" -#include "fbx/ufbx.h" + +#include namespace { diff --git a/src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp deleted file mode 100644 index cd7273d88..000000000 --- a/src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp +++ /dev/null @@ -1,33093 +0,0 @@ -#ifndef UFBX_UFBX_C_INCLUDED -#define UFBX_UFBX_C_INCLUDED - -#if defined(UFBX_HEADER_PATH) - #include UFBX_HEADER_PATH -#else - #include "ufbx.h" -#endif - -// -- User configuration - -// User configuration: -// UFBX_REGRESSION Enable regression mode for development -// UFBX_UBSAN Explicitly enable undefined behavior sanitizer workarounds -// UFBX_NO_UNALIGNED_LOADS Do not use unaligned loads even when they are supported -// UFBX_USE_UNALIGNED_LOADS Forcibly use unaligned loads on unknown platforms -// UFBX_USE_SSE Explicitly enable SSE2 support (for x86) -// UFBX_HAS_FTELLO Allow ufbx to use `ftello()` to measure file size -// UFBX_WASM_32BIT Optimize WASM for 32-bit architectures -// UFBX_TRACE Log calls of `ufbxi_check()` for tracing execution -// UFBX_LITTLE_ENDIAN=0/1 Explicitly define little/big endian architecture -// UFBX_PATH_SEPARATOR='' Specify default platform path separator -// UFBX_NO_SSE Do not try to include SSE - -// Dependencies: -// UFBX_NO_MALLOC Disable default malloc/realloc/free -// UFBX_NO_STDIO Disable stdio FILE API -// UFBX_EXTERNAL_MALLOC Link to external ufbx_malloc() interface -// UFBX_EXTERNAL_STDIO Link to external ufbx_stdio_() interface -// UFBX_EXTERNAL_MATH Link to external interface -// UFBX_EXTERNAL_STRING Link to external interface - -// Freestanding: -// UFBX_MATH_PREFIX='ufbx_' Prefix for external functions used -// UFBX_STRING_PREFIX='ufbx_' Prefix for external functions used -// UFBX_NO_LIBC Do not include libc (implies UFBX_EXTERNAL_MATH/STRING/MALLOC/STDIO by default) -// UFBX_NO_LIBC_TYPES Do not include any libc headers, you must define all types in - -// Mostly internal for debugging: -// UFBX_STATIC_ANALYSIS Enable static analysis augmentation -// UFBX_DEBUG_BINARY_SEARCH Force using binary search for debugging -// UFBX_EXTENSIVE_THREADING Use threads for small inputs -// UFBX_POINTER_SIZE Allow specifying sizeof(void*) as a preprocessor constant -// UFBX_MAXIMUM_ALIGNMENT Maximum alignment used for allocation - -#if defined(UFBX_CONFIG_SOURCE) - #include UFBX_CONFIG_SOURCE -#endif - -// -- Configuration - -#define UFBXI_MAX_NON_ARRAY_VALUES 8 -#define UFBXI_MAX_NODE_DEPTH 32 -#define UFBXI_MAX_XML_DEPTH 32 -#define UFBXI_MAX_SKIP_SIZE 0x40000000 -#define UFBXI_MAP_MAX_SCAN 32 -#define UFBXI_KD_FAST_DEPTH 6 -#define UFBXI_HUGE_MAX_SCAN 16 -#define UFBXI_MIN_FILE_FORMAT_LOOKAHEAD 32 -#define UFBXI_FACE_GROUP_HASH_BITS 8 -#define UFBXI_MIN_THREADED_DEFLATE_BYTES 256 -#define UFBXI_MIN_THREADED_ASCII_VALUES 64 -#define UFBXI_GEOMETRY_CACHE_BUFFER_SIZE 512 - -#ifndef UFBXI_MAX_NURBS_ORDER -#define UFBXI_MAX_NURBS_ORDER 128 -#endif - -// By default enough to have squares be non-denormal -#ifndef UFBX_EPSILON -#define UFBX_EPSILON (sizeof(ufbx_real) == sizeof(float) ? \ - (ufbx_real)1.0842021795674597e-19f : (ufbx_real)1.4916681462400413e-154) -#endif - -// -- Feature exclusion - -#if !defined(UFBX_MINIMAL) - #if !defined(UFBX_NO_SUBDIVISION) - #define UFBXI_FEATURE_SUBDIVISION 1 - #endif - #if !defined(UFBX_NO_TESSELLATION) - #define UFBXI_FEATURE_TESSELLATION 1 - #endif - #if !defined(UFBX_NO_GEOMETRY_CACHE) - #define UFBXI_FEATURE_GEOMETRY_CACHE 1 - #endif - #if !defined(UFBX_NO_SCENE_EVALUATION) - #define UFBXI_FEATURE_SCENE_EVALUATION 1 - #endif - #if !defined(UFBX_NO_SKINNING_EVALUATION) - #define UFBXI_FEATURE_SKINNING_EVALUATION 1 - #endif - #if !defined(UFBX_NO_ANIMATION_BAKING) - #define UFBXI_FEATURE_ANIMATION_BAKING 1 - #endif - #if !defined(UFBX_NO_TRIANGULATION) - #define UFBXI_FEATURE_TRIANGULATION 1 - #endif - #if !defined(UFBX_NO_INDEX_GENERATION) - #define UFBXI_FEATURE_INDEX_GENERATION 1 - #endif - #if !defined(UFBX_NO_FORMAT_OBJ) - #define UFBXI_FEATURE_FORMAT_OBJ 1 - #endif -#endif - -#if defined(UFBX_DEV) - #if !defined(UFBX_NO_ERROR_STACK) - #define UFBXI_FEATURE_ERROR_STACK 1 - #endif -#endif - -#if !defined(UFBXI_FEATURE_SUBDIVISION) && defined(UFBX_ENABLE_SUBDIVISION) - #define UFBXI_FEATURE_SUBDIVISION 1 -#endif -#if !defined(UFBXI_FEATURE_TESSELLATION) && defined(UFBX_ENABLE_TESSELLATION) - #define UFBXI_FEATURE_TESSELLATION 1 -#endif -#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE) && defined(UFBX_ENABLE_GEOMETRY_CACHE) - #define UFBXI_FEATURE_GEOMETRY_CACHE 1 -#endif -#if !defined(UFBXI_FEATURE_SCENE_EVALUATION) && defined(UFBX_ENABLE_SCENE_EVALUATION) - #define UFBXI_FEATURE_SCENE_EVALUATION 1 -#endif -#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION) && defined(UFBX_ENABLE_SKINNING_EVALUATION) - #define UFBXI_FEATURE_SKINNING_EVALUATION 1 -#endif -#if !defined(UFBXI_FEATURE_ANIMATION_BAKING) && defined(UFBX_ENABLE_ANIMATION_BAKING) - #define UFBXI_FEATURE_ANIMATION_BAKING 1 -#endif -#if !defined(UFBXI_FEATURE_TRIANGULATION) && defined(UFBX_ENABLE_TRIANGULATION) - #define UFBXI_FEATURE_TRIANGULATION 1 -#endif -#if !defined(UFBXI_FEATURE_INDEX_GENERATION) && defined(UFBX_ENABLE_INDEX_GENERATION) - #define UFBXI_FEATURE_INDEX_GENERATION 1 -#endif -#if !defined(UFBXI_FEATURE_FORMAT_OBJ) && defined(UFBX_ENABLE_FORMAT_OBJ) - #define UFBXI_FEATURE_FORMAT_OBJ 1 -#endif -#if !defined(UFBXI_FEATURE_ERROR_STACK) && defined(UFBX_ENABLE_ERROR_STACK) - #define UFBXI_FEATURE_ERROR_STACK 1 -#endif - -#if !defined(UFBXI_FEATURE_SUBDIVISION) - #define UFBXI_FEATURE_SUBDIVISION 0 -#endif -#if !defined(UFBXI_FEATURE_TESSELLATION) - #define UFBXI_FEATURE_TESSELLATION 0 -#endif -#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE) - #define UFBXI_FEATURE_GEOMETRY_CACHE 0 -#endif -#if !defined(UFBXI_FEATURE_SCENE_EVALUATION) - #define UFBXI_FEATURE_SCENE_EVALUATION 0 -#endif -#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION) - #define UFBXI_FEATURE_SKINNING_EVALUATION 0 -#endif -#if !defined(UFBXI_FEATURE_ANIMATION_BAKING) - #define UFBXI_FEATURE_ANIMATION_BAKING 0 -#endif -#if !defined(UFBXI_FEATURE_TRIANGULATION) - #define UFBXI_FEATURE_TRIANGULATION 0 -#endif -#if !defined(UFBXI_FEATURE_INDEX_GENERATION) - #define UFBXI_FEATURE_INDEX_GENERATION 0 -#endif -#if !defined(UFBXI_FEATURE_FORMAT_OBJ) - #define UFBXI_FEATURE_FORMAT_OBJ 0 -#endif -#if !defined(UFBXI_FEATURE_ERROR_STACK) - #define UFBXI_FEATURE_ERROR_STACK 0 -#endif - -// Derived features - -#if UFBXI_FEATURE_GEOMETRY_CACHE - #define UFBXI_FEATURE_XML 1 -#else - #define UFBXI_FEATURE_XML 0 -#endif - -#if UFBXI_FEATURE_TRIANGULATION - #define UFBXI_FEATURE_KD 1 -#else - #define UFBXI_FEATURE_KD 0 -#endif - -#if !UFBXI_FEATURE_SUBDIVISION || !UFBXI_FEATURE_TESSELLATION || !UFBXI_FEATURE_GEOMETRY_CACHE || !UFBXI_FEATURE_SCENE_EVALUATION || !UFBXI_FEATURE_SKINNING_EVALUATION || !UFBXI_FEATURE_ANIMATION_BAKING || !UFBXI_FEATURE_TRIANGULATION || !UFBXI_FEATURE_INDEX_GENERATION || !UFBXI_FEATURE_XML || !UFBXI_FEATURE_KD || !UFBXI_FEATURE_FORMAT_OBJ - #define UFBXI_PARTIAL_FEATURES 1 -#endif - -// -- Headers - -// Legacy mapping -#if !defined(UFBX_EXTERNAL_MATH) && defined(UFBX_NO_MATH_H) - #define UFBX_EXTERNAL_MATH -#endif - -#if !defined(UFBX_NO_LIBC_TYPES) - #include -#endif - -#if !defined(UFBX_NO_LIBC) - #if !defined(UFBX_NO_FLOAT_H) - #include - #endif - #if !defined(UFBX_EXTERNAL_MATH) - #include - #endif - #if !defined(UFBX_EXTERNAL_STRING) - #include - #endif - #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) - #include - #endif - #if !defined(UFBX_NO_MALLOC) && !defined(UFBX_EXTERNAL_MALLOC) - #include - #endif -#else - #if !defined(UFBX_EXTERNAL_MATH) && !defined(UFBX_NO_EXTERNAL_MATH) - #define UFBX_EXTERNAL_MATH - #endif - #if !defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_NO_EXTERNAL_STRING) - #define UFBX_EXTERNAL_STRING - #endif - #if !defined(UFBX_EXTERNAL_MALLOC) && !defined(UFBX_NO_EXTERNAL_MALLOC) && !defined(UFBX_NO_MALLOC) - #define UFBX_EXTERNAL_MALLOC - #endif - #if !defined(UFBX_EXTERNAL_STDIO) && !defined(UFBX_NO_EXTERNAL_STDIO) && !defined(UFBX_NO_STDIO) - #define UFBX_EXTERNAL_STDIO - #endif -#endif - -#if defined(UFBX_EXTERNAL_STRING) && !defined(UFBX_STRING_PREFIX) - #define UFBX_STRING_PREFIX ufbx_ -#endif - -#if !defined(UFBX_EXTERNAL_MATH) - #if !defined(UFBX_MATH_PREFIX) - #define UFBX_MATH_PREFIX - #endif -#endif - -#define ufbxi_pre_cat2(a, b) a##b -#define ufbxi_pre_cat(a, b) ufbxi_pre_cat2(a, b) - -// -- External functions - -#ifndef ufbx_extern_abi - #if defined(UFBX_STATIC) - #define ufbx_extern_abi static - #else - #define ufbx_extern_abi - #endif -#endif - -#if defined(UFBX_MATH_PREFIX) - #define ufbxi_math_fn(name) ufbxi_pre_cat(UFBX_MATH_PREFIX, name) - #define ufbx_sqrt ufbxi_math_fn(sqrt) - #define ufbx_fabs ufbxi_math_fn(fabs) - #define ufbx_pow ufbxi_math_fn(pow) - #define ufbx_sin ufbxi_math_fn(sin) - #define ufbx_cos ufbxi_math_fn(cos) - #define ufbx_tan ufbxi_math_fn(tan) - #define ufbx_asin ufbxi_math_fn(asin) - #define ufbx_acos ufbxi_math_fn(acos) - #define ufbx_atan ufbxi_math_fn(atan) - #define ufbx_atan2 ufbxi_math_fn(atan2) - #define ufbx_copysign ufbxi_math_fn(copysign) - #define ufbx_fmin ufbxi_math_fn(fmin) - #define ufbx_fmax ufbxi_math_fn(fmax) - #define ufbx_nextafter ufbxi_math_fn(nextafter) - #define ufbx_rint ufbxi_math_fn(rint) - #define ufbx_floor ufbxi_math_fn(floor) - #define ufbx_ceil ufbxi_math_fn(ceil) - #define ufbx_isnan ufbxi_math_fn(isnan) -#endif - -#if !defined(UFBX_NO_EXTERNAL_DEFINES) - -#if defined(__cplusplus) -extern "C" { -#endif - -#if defined(UFBX_EXTERNAL_MATH) - ufbx_extern_abi double ufbx_sqrt(double x); - ufbx_extern_abi double ufbx_sin(double x); - ufbx_extern_abi double ufbx_cos(double x); - ufbx_extern_abi double ufbx_tan(double x); - ufbx_extern_abi double ufbx_asin(double x); - ufbx_extern_abi double ufbx_acos(double x); - ufbx_extern_abi double ufbx_atan(double x); - ufbx_extern_abi double ufbx_atan2(double y, double x); - ufbx_extern_abi double ufbx_pow(double x, double y); - ufbx_extern_abi double ufbx_fmin(double a, double b); - ufbx_extern_abi double ufbx_fmax(double a, double b); - ufbx_extern_abi double ufbx_fabs(double x); - ufbx_extern_abi double ufbx_copysign(double x, double y); - ufbx_extern_abi double ufbx_nextafter(double x, double y); - ufbx_extern_abi double ufbx_rint(double x); - ufbx_extern_abi double ufbx_floor(double x); - ufbx_extern_abi double ufbx_ceil(double x); - ufbx_extern_abi int ufbx_isnan(double x); -#endif - -#if defined(UFBX_EXTERNAL_STRING) - ufbx_extern_abi size_t ufbx_strlen(const char *str); - ufbx_extern_abi void *ufbx_memcpy(void *dst, const void *src, size_t count); - ufbx_extern_abi void *ufbx_memmove(void *dst, const void *src, size_t count); - ufbx_extern_abi void *ufbx_memset(void *dst, int ch, size_t count); - ufbx_extern_abi const void *ufbx_memchr(const void *ptr, int value, size_t count); - ufbx_extern_abi int ufbx_memcmp(const void *a, const void *b, size_t count); - ufbx_extern_abi int ufbx_strcmp(const char *a, const char *b); - ufbx_extern_abi int ufbx_strncmp(const char *a, const char *b, size_t count); -#endif - -#if defined(UFBX_EXTERNAL_MALLOC) - ufbx_extern_abi void *ufbx_malloc(size_t size); - ufbx_extern_abi void *ufbx_realloc(void *ptr, size_t old_size, size_t new_size); - ufbx_extern_abi void ufbx_free(void *ptr, size_t old_size); -#endif - -#if defined(UFBX_EXTERNAL_STDIO) - ufbx_extern_abi void *ufbx_stdio_open(const char *path, size_t path_len); - ufbx_extern_abi size_t ufbx_stdio_read(void *file, void *data, size_t size); - ufbx_extern_abi bool ufbx_stdio_skip(void *file, size_t size); - ufbx_extern_abi uint64_t ufbx_stdio_size(void *file); - ufbx_extern_abi void ufbx_stdio_close(void *file); -#endif - -#if defined(__cplusplus) -} -#endif - -#endif - -#if !defined(UFBX_INFINITY) - #if defined(INFINITY) - #define UFBX_INFINITY INFINITY - #else - #define UFBX_INFINITY (1e+300 * 1e+300) - #endif -#endif -#if !defined(UFBX_NAN) - #if defined(NAN) - #define UFBX_NAN NAN - #else - #define UFBX_NAN (UFBX_INFINITY * 0.0f) - #endif -#endif -#if !defined(UFBX_FLT_EPSILON) - #if defined(FLT_EPSILON) - #define UFBX_FLT_EPSILON FLT_EPSILON - #else - #define UFBX_FLT_EPSILON 1.192092896e-07f - #endif -#endif -#if !defined(UFBX_FLT_EVAL_METHOD) - #if defined(FLT_EVAL_METHOD) - #define UFBX_FLT_EVAL_METHOD FLT_EVAL_METHOD - #elif defined(__FLT_EVAL_METHOD__) - #define UFBX_FLT_EVAL_METHOD __FLT_EVAL_METHOD__ - #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) - #define UFBX_FLT_EVAL_METHOD 0 - #else - #define UFBX_FLT_EVAL_METHOD -1 - #endif -#endif - -#if defined(ufbx_malloc) || defined(ufbx_realloc) || defined(ufbx_free) - // User provided allocators - #if !defined(ufbx_malloc) || !defined(ufbx_realloc) || !defined(ufbx_free) - #error Inconsistent custom global allocator - #endif -#elif defined(UFBX_NO_MALLOC) - #define ufbx_malloc(size) ((void)(size), (void)NULL) - #define ufbx_realloc(ptr, old_size, new_size) ((void)(ptr), (void)(old_size), (void)(new_size), (void)NULL) - #define ufbx_free(ptr, old_size) ((void)(ptr), (void)(old_size)) -#elif defined(UFBX_EXTERNAL_MALLOC) - // Nop -#else - #define ufbx_malloc(size) malloc((size)) - #define ufbx_realloc(ptr, old_size, new_size) realloc((ptr), (new_size)) - #define ufbx_free(ptr, old_size) free((ptr)) -#endif - -#if !defined(ufbx_panic_handler) - static void ufbxi_panic_handler(const char *message) - { - (void)message; - #if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) - fprintf(stderr, "ufbx panic: %s\n", message); - #endif - ufbx_assert(false && "ufbx panic: See stderr for more information"); - } - #define ufbx_panic_handler ufbxi_panic_handler -#endif - -// -- Platform - -#if defined(_MSC_VER) - #define UFBXI_MSC_VER _MSC_VER -#else - #define UFBXI_MSC_VER 0 -#endif - -#if defined(__GNUC__) - #define UFBXI_GNUC __GNUC__ - #define UFBXI_GNUC_VERSION ufbx_pack_version(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else - #define UFBXI_GNUC 0 - #define UFBXI_GNUC_VERSION 0 -#endif - -#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #define ufbxi_noinline __declspec(noinline) - #define ufbxi_forceinline __forceinline - #define ufbxi_restrict __restrict - #if defined(_Check_return_) - #define ufbxi_nodiscard _Check_return_ - #else - #define ufbxi_nodiscard - #endif - #define ufbxi_unused - #define ufbxi_unlikely(cond) (cond) -#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) - #define ufbxi_noinline __attribute__((noinline)) - #define ufbxi_forceinline inline __attribute__((always_inline)) - #define ufbxi_restrict __restrict - #define ufbxi_nodiscard __attribute__((warn_unused_result)) - #define ufbxi_unused __attribute__((unused)) - #define ufbxi_unlikely(cond) __builtin_expect((cond), 0) -#else - #define ufbxi_noinline - #define ufbxi_forceinline - #define ufbxi_nodiscard - #define ufbxi_restrict - #define ufbxi_unused - #define ufbxi_unlikely(cond) (cond) -#endif - -#if !defined(UFBX_STANDARD_C) && defined(__clang__) - #define ufbxi_nounroll _Pragma("clang loop unroll(disable)") _Pragma("clang loop vectorize(disable)") -#elif !defined(UFBX_STANDARD_C) && UFBXI_GNUC >= 8 - #define ufbxi_nounroll _Pragma("GCC unroll 0") -#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #define ufbxi_nounroll __pragma(loop(no_vector)) -#else - #define ufbxi_nounroll -#endif - -#if defined(__GNUC__) && !defined(__clang__) - #define ufbxi_ignore(cond) (void)!(cond) -#else - #define ufbxi_ignore(cond) (void)(cond) -#endif - -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable: 4061) // enumerator 'ENUM' in switch of enum 'enum' is not explicitly handled by a case label - #pragma warning(disable: 4200) // nonstandard extension used: zero-sized array in struct/union - #pragma warning(disable: 4201) // nonstandard extension used: nameless struct/union - #pragma warning(disable: 4210) // nonstandard extension used: function given file scope - #pragma warning(disable: 4127) // conditional expression is constant - #pragma warning(disable: 4706) // assignment within conditional expression - #pragma warning(disable: 4789) // buffer 'type_and_name' of size 8 bytes will be overrun; 16 bytes will be written starting at offset 0 - #pragma warning(disable: 4820) // type': 'N' bytes padding added after data member 'member' - #if defined(UFBX_STANDARD_C) - #pragma warning(disable: 4996) // 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. - #endif - #if defined(UFBXI_PARTIAL_FEATURES) - #pragma warning(disable: 4100) // 'name': unreferenced formal parameter - #pragma warning(disable: 4505) // 'func': unreferenced function with internal linkage has been removed - #endif -#endif - -#if defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wmissing-field-initializers" - #pragma clang diagnostic ignored "-Wmissing-braces" - #pragma clang diagnostic ignored "-Wdouble-promotion" - #pragma clang diagnostic ignored "-Wpedantic" - #pragma clang diagnostic ignored "-Wcast-qual" - #pragma clang diagnostic ignored "-Wcast-align" - #pragma clang diagnostic ignored "-Wcovered-switch-default" - #pragma clang diagnostic ignored "-Wpadded" - #pragma clang diagnostic ignored "-Wswitch-enum" - #pragma clang diagnostic ignored "-Wfloat-equal" - #pragma clang diagnostic ignored "-Wformat-nonliteral" - #if __has_warning("-Watomic-implicit-seq-cst") - #pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" - #endif - #if defined(UFBX_STANDARD_C) - #pragma clang diagnostic ignored "-Wunused-function" - #endif - #if defined(UFBXI_PARTIAL_FEATURES) - #pragma clang diagnostic ignored "-Wunused-function" - #pragma clang diagnostic ignored "-Wunused-parameter" - #endif - #if defined(__cplusplus) - #pragma clang diagnostic ignored "-Wold-style-cast" - #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" - #else - #pragma clang diagnostic ignored "-Wdeclaration-after-statement" - #pragma clang diagnostic ignored "-Wbad-function-cast" - #endif -#endif - -#if defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - #pragma GCC diagnostic ignored "-Wmissing-braces" - #pragma GCC diagnostic ignored "-Wdouble-promotion" - #pragma GCC diagnostic ignored "-Wpedantic" - #pragma GCC diagnostic ignored "-Wcast-qual" - #pragma GCC diagnostic ignored "-Wcast-align" - #pragma GCC diagnostic ignored "-Wpadded" - #pragma GCC diagnostic ignored "-Wswitch-enum" - #pragma GCC diagnostic ignored "-Wfloat-equal" - #pragma GCC diagnostic ignored "-Wformat-nonliteral" - #pragma GCC diagnostic ignored "-Wlong-long" - #if defined(UFBX_STANDARD_C) - #pragma GCC diagnostic ignored "-Wunused-function" - #endif - #if defined(UFBXI_PARTIAL_FEATURES) - #pragma GCC diagnostic ignored "-Wunused-function" - #pragma GCC diagnostic ignored "-Wunused-parameter" - #endif - #if defined(__cplusplus) - #pragma GCC diagnostic ignored "-Wold-style-cast" - #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" - #else - #pragma GCC diagnostic ignored "-Wdeclaration-after-statement" - #pragma GCC diagnostic ignored "-Wbad-function-cast" - #if __GNUC__ >= 5 - #pragma GCC diagnostic ignored "-Wc90-c99-compat" - #pragma GCC diagnostic ignored "-Wc99-c11-compat" - #endif - #endif - // MSC isnan() definition triggers this error on MinGW GCC - #if defined(__MINGW32__) - #pragma GCC diagnostic ignored "-Wfloat-conversion" - #endif - // `-Warray-bounds` results in warnings if UBsan is enabled and pre-GCC-14 has no way of detecting it.. - #if UFBXI_GNUC_VERSION >= ufbx_pack_version(4, 3, 0) && UFBXI_GNUC_VERSION < ufbx_pack_version(14, 0, 0) - #pragma GCC diagnostic ignored "-Warray-bounds" - #endif -#endif - -#if !defined(ufbx_static_assert) - #if defined(__cplusplus) && __cplusplus >= 201103 - #define ufbx_static_assert(desc, cond) static_assert(cond, #desc ": " #cond) - #else - #define ufbx_static_assert(desc, cond) typedef char ufbxi_static_assert_##desc[(cond)?1:-1] - #endif -#endif - -#if defined(__has_feature) - #if __has_feature(undefined_behavior_sanitizer) && !defined(UFBX_UBSAN) - #define UFBX_UBSAN 1 - #endif -#endif - -#if defined(__SANITIZE_UNDEFINED__) && !defined(UFBX_UBSAN) - #define UFBX_UBSAN 1 -#endif - -// Don't use unaligned loads with UB-sanitizer -#if defined(UFBX_UBSAN) && !defined(UFBX_NO_UNALIGNED_LOADS) - #define UFBX_NO_UNALIGNED_LOADS -#endif - -#if defined(__clang_analyzer__) && !defined(UFBX_STATIC_ANALYSIS) - #define UFBX_STATIC_ANALYSIS 1 -#endif - -#if defined(UFBX_STATIC_ANALYSIS) - bool ufbxi_analysis_opaque; - #define ufbxi_maybe_null(ptr) (ufbxi_analysis_opaque ? (ptr) : NULL) - #define ufbxi_analysis_assert(cond) ufbx_assert(cond) -#else - #define ufbxi_maybe_null(ptr) (ptr) - #define ufbxi_analysis_assert(cond) (void)0 -#endif - -#if defined(UFBX_STATIC_ANALYSIS) || defined(UFBX_UBSAN) - #define ufbxi_maybe_uninit(cond, value, def) ((cond) ? (value) : (def)) -#else - #define ufbxi_maybe_uninit(cond, value, def) (value) -#endif - -#if !defined(ufbxi_trace) - #if defined(UFBX_TRACE) - #define ufbxi_trace(desc) (fprintf(stderr, "ufbx trace: %s:%d: %s\n", __FILE__, __LINE__, #desc), fflush(stderr), desc) - #else - #define ufbxi_trace(desc) (desc) - #endif -#endif - -#ifndef UFBX_PATH_SEPARATOR - #if defined(_WIN32) - #define UFBX_PATH_SEPARATOR '\\' - #else - #define UFBX_PATH_SEPARATOR '/' - #endif -#endif - -#if !defined(UFBX_STANDARD_C) && defined(_POSIX_C_SOURCE) - #if _POSIX_C_SOURCE >= 200112l - #ifndef UFBX_HAS_FTELLO - #define UFBX_HAS_FTELLO - #endif - #endif -#endif - -#if !defined(UFBX_STANDARD_C) && ((defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC)) || ((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__))) - #define UFBXI_ARCH_X64 1 -#else - #define UFBXI_ARCH_X64 0 -#endif - -#if defined(UFBX_USE_SSE) || (!defined(UFBX_STANDARD_C) && !defined(UFBX_NO_SSE) && UFBXI_ARCH_X64) - #define UFBXI_HAS_SSE 1 - #include - #include -#else - #define UFBXI_HAS_SSE 0 -#endif - -// -- Atomic counter - -#define UFBXI_THREAD_SAFE 1 - -#if defined(__cplusplus) - #define ufbxi_extern_c extern "C" -#else - #define ufbxi_extern_c -#endif - -#if !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) - typedef size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) __sync_fetch_and_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) __sync_fetch_and_sub((ptr), 1) - #define ufbxi_atomic_counter_load(ptr) __sync_fetch_and_add((ptr), 0) // TODO: Proper atomic load -#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #if defined(_M_X64) || defined(_M_ARM64) - ufbxi_extern_c __int64 _InterlockedIncrement64(__int64 volatile * lpAddend); - ufbxi_extern_c __int64 _InterlockedDecrement64(__int64 volatile * lpAddend); - ufbxi_extern_c __int64 _InterlockedExchangeAdd64(__int64 volatile * lpAddend, __int64 Value); - typedef volatile __int64 ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement64(ptr) - 1) - #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement64(ptr) + 1) - #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd64((ptr), 0)) - #else - ufbxi_extern_c long __cdecl _InterlockedIncrement(long volatile * lpAddend); - ufbxi_extern_c long __cdecl _InterlockedDecrement(long volatile * lpAddend); - ufbxi_extern_c long __cdecl _InterlockedExchangeAdd(long volatile * lpAddend, long Value); - typedef volatile long ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement(ptr) - 1) - #define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement(ptr) + 1) - #define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd((ptr), 0)) - #endif -#elif !defined(UFBX_STANDARD_C) && defined(__TINYC__) - #if defined(__x86_64__) || defined(_AMD64_) - static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { - __asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); - return value; - } - #elif defined(__i386__) || defined(_X86_) - static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) { - __asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); - return value; - } - #else - #error Unexpected TCC architecture - #endif - typedef volatile size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ufbxi_tcc_atomic_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) ufbxi_tcc_atomic_add((ptr), SIZE_MAX) - #define ufbxi_atomic_counter_load(ptr) ufbxi_tcc_atomic_add((ptr), 0) -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #include - #include - typedef struct { alignas(std::atomic_size_t) char data[sizeof(std::atomic_size_t)]; } ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (new (&(ptr)->data) std::atomic_size_t(0)) - #define ufbxi_atomic_counter_free(ptr) (((std::atomic_size_t*)(ptr)->data)->~atomic()) - #define ufbxi_atomic_counter_inc(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_add(1) - #define ufbxi_atomic_counter_dec(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_sub(1) - #define ufbxi_atomic_counter_load(ptr) ((std::atomic_size_t*)(ptr)->data)->load(std::memory_order_acquire) -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) - #include - typedef volatile atomic_size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) atomic_init(ptr, 0) - #define ufbxi_atomic_counter_free(ptr) (void)(ptr) - #define ufbxi_atomic_counter_inc(ptr) atomic_fetch_add((ptr), 1) - #define ufbxi_atomic_counter_dec(ptr) atomic_fetch_sub((ptr), 1) - #define ufbxi_atomic_counter_load(ptr) atomic_load_explicit((ptr), memory_order_acquire) -#else - typedef volatile size_t ufbxi_atomic_counter; - #define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0) - #define ufbxi_atomic_counter_inc(ptr) ((*(ptr))++) - #define ufbxi_atomic_counter_dec(ptr) ((*(ptr))--) - #define ufbxi_atomic_counter_load(ptr) (*(ptr)) - #undef UFBXI_THREAD_SAFE - #define UFBXI_THREAD_SAFE 0 -#endif - -// ^^ No references to before this point ^^ -// vv No more includes past this point vv - -#if defined(UFBX_STRING_PREFIX) - #define ufbxi_string_fn(name) ufbxi_pre_cat(UFBX_STRING_PREFIX, name) - #define strlen ufbxi_string_fn(strlen) - #define memcpy ufbxi_string_fn(memcpy) - #define memmove ufbxi_string_fn(memmove) - #define memset ufbxi_string_fn(memset) - #define memchr ufbxi_string_fn(memchr) - #define memcmp ufbxi_string_fn(memcmp) - #define strcmp ufbxi_string_fn(strcmp) - #define strncmp ufbxi_string_fn(strncmp) -#endif - -#if !defined(UFBX_LITTLE_ENDIAN) - #if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__)) - #define UFBX_LITTLE_ENDIAN 1 - #else - #define UFBX_LITTLE_ENDIAN 0 - #endif -#endif - -// Unaligned little-endian load functions -// On platforms that support unaligned access natively (x86, x64, ARM64) just use normal loads, -// with unaligned attributes, otherwise do manual byte-wise load. - -#define ufbxi_read_u8(ptr) (*(const uint8_t*)(ptr)) - -// Detect support for `__attribute__((aligned(1)))` -#if !defined(UFBX_STANDARD_C) && (defined(__clang__) && defined(__APPLE__)) - // Apple overrides Clang versioning, 5.0 here maps to 3.3 - #if __clang_major__ >= 5 - #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 - #endif -#elif !defined(UFBX_STANDARD_C) && defined(__clang__) - #if (__clang_major__ >= 4) || (__clang_major__ == 3 && __clang_minor__ >= 3) - #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 - #endif -#elif !defined(UFBX_STANDARD_C) && defined(__GNUC__) - #if __GNUC__ >= 5 - #define UFBXI_HAS_ATTRIBUTE_ALIGNED 1 - #endif -#endif - -#if defined(UFBXI_HAS_ATTRIBUTE_ALIGNED) - #define UFBXI_HAS_UNALIGNED 1 - #define UFBXI_HAS_ALIASING 1 - #define ufbxi_unaligned - typedef uint16_t __attribute__((aligned(1))) ufbxi_unaligned_u16; - typedef uint32_t __attribute__((aligned(1))) ufbxi_unaligned_u32; - typedef uint64_t __attribute__((aligned(1))) ufbxi_unaligned_u64; - typedef float __attribute__((aligned(1))) ufbxi_unaligned_f32; - typedef double __attribute__((aligned(1))) ufbxi_unaligned_f64; - typedef uint32_t __attribute__((may_alias)) ufbxi_aliasing_u32; -#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #define UFBXI_HAS_UNALIGNED 1 - #if defined(_M_IX86) - // MSVC seems to assume all pointers are unaligned for x86 - #define ufbxi_unaligned - #else - #define ufbxi_unaligned __unaligned - #endif - typedef uint16_t ufbxi_unaligned_u16; - typedef uint32_t ufbxi_unaligned_u32; - typedef uint64_t ufbxi_unaligned_u64; - typedef float ufbxi_unaligned_f32; - typedef double ufbxi_unaligned_f64; - // MSVC doesn't have aliasing types in theory, but it works in practice.. - #define UFBXI_HAS_ALIASING 1 - typedef uint32_t ufbxi_aliasing_u32; -#endif - -#if (defined(UFBXI_HAS_UNALIGNED) && UFBX_LITTLE_ENDIAN && !defined(UFBX_NO_UNALIGNED_LOADS)) || defined(UFBX_USE_UNALIGNED_LOADS) - #define ufbxi_read_u16(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u16*)(ptr)) - #define ufbxi_read_u32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u32*)(ptr)) - #define ufbxi_read_u64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u64*)(ptr)) - #define ufbxi_read_f32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f32*)(ptr)) - #define ufbxi_read_f64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f64*)(ptr)) -#else - static ufbxi_forceinline uint16_t ufbxi_read_u16(const void *ptr) { - const char *p = (const char*)ptr; - return (uint16_t)( - (unsigned)(uint8_t)p[0] << 0u | - (unsigned)(uint8_t)p[1] << 8u ); - } - static ufbxi_forceinline uint32_t ufbxi_read_u32(const void *ptr) { - const char *p = (const char*)ptr; - return (uint32_t)( - (unsigned)(uint8_t)p[0] << 0u | - (unsigned)(uint8_t)p[1] << 8u | - (unsigned)(uint8_t)p[2] << 16u | - (unsigned)(uint8_t)p[3] << 24u ); - } - static ufbxi_forceinline uint64_t ufbxi_read_u64(const void *ptr) { - const char *p = (const char*)ptr; - return (uint64_t)( - (uint64_t)(uint8_t)p[0] << 0u | - (uint64_t)(uint8_t)p[1] << 8u | - (uint64_t)(uint8_t)p[2] << 16u | - (uint64_t)(uint8_t)p[3] << 24u | - (uint64_t)(uint8_t)p[4] << 32u | - (uint64_t)(uint8_t)p[5] << 40u | - (uint64_t)(uint8_t)p[6] << 48u | - (uint64_t)(uint8_t)p[7] << 56u ); - } - static ufbxi_forceinline float ufbxi_read_f32(const void *ptr) { - uint32_t u = ufbxi_read_u32(ptr); - float f; - memcpy(&f, &u, 4); - return f; - } - static ufbxi_forceinline double ufbxi_read_f64(const void *ptr) { - uint64_t u = ufbxi_read_u64(ptr); - double f; - memcpy(&f, &u, 8); - return f; - } -#endif - -#define ufbxi_read_i8(ptr) (int8_t)(ufbxi_read_u8(ptr)) -#define ufbxi_read_i16(ptr) (int16_t)(ufbxi_read_u16(ptr)) -#define ufbxi_read_i32(ptr) (int32_t)(ufbxi_read_u32(ptr)) -#define ufbxi_read_i64(ptr) (int64_t)(ufbxi_read_u64(ptr)) - -ufbx_static_assert(sizeof_bool, sizeof(bool) == 1); -ufbx_static_assert(sizeof_char, sizeof(char) == 1); -ufbx_static_assert(sizeof_i8, sizeof(int8_t) == 1); -ufbx_static_assert(sizeof_i16, sizeof(int16_t) == 2); -ufbx_static_assert(sizeof_i32, sizeof(int32_t) == 4); -ufbx_static_assert(sizeof_i64, sizeof(int64_t) == 8); -ufbx_static_assert(sizeof_u8, sizeof(uint8_t) == 1); -ufbx_static_assert(sizeof_u16, sizeof(uint16_t) == 2); -ufbx_static_assert(sizeof_u32, sizeof(uint32_t) == 4); -ufbx_static_assert(sizeof_u64, sizeof(uint64_t) == 8); -ufbx_static_assert(sizeof_f32, sizeof(float) == 4); -ufbx_static_assert(sizeof_f64, sizeof(double) == 8); - -// -- Alignment - -#ifndef UFBX_MAXIMUM_ALIGNMENT -enum { UFBX_MAXIMUM_ALIGNMENT = sizeof(void*) > 8 ? sizeof(void*) : 8 }; -#endif - -#if !defined(UFBX_POINTER_SIZE) && !defined(UFBX_STANDARD_C) - #if (defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__)) && !defined(__CHERI__) - #define UFBX_POINTER_SIZE 8 - #elif defined(__wasm__) || defined(__EMSCRIPTEN__) - #define UFBX_POINTER_SIZE 4 - #endif -#endif -#if defined(UFBX_POINTER_SIZE) - ufbx_static_assert(pointer_size, UFBX_POINTER_SIZE == sizeof(void*)); -#else - #define UFBX_POINTER_SIZE 0 -#endif - -// -- Version - -#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 20, 0) -ufbx_abi_data_def const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION; - -ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u); - -// -- Fast copy - -#if UFBXI_HAS_SSE - #define ufbxi_copy_16_bytes(dst, src) _mm_storeu_si128((__m128i*)(dst), _mm_loadu_si128((const __m128i*)(src))) -#elif defined(UFBXI_HAS_UNALIGNED) - #define ufbxi_copy_16_bytes(dst, src) do { \ - ufbxi_unaligned ufbxi_unaligned_u64 *mi_dst = (ufbxi_unaligned ufbxi_unaligned_u64 *)(dst); \ - const ufbxi_unaligned ufbxi_unaligned_u64 *mi_src = (const ufbxi_unaligned ufbxi_unaligned_u64 *)src; \ - mi_dst[0] = mi_src[0]; \ - mi_dst[1] = mi_src[1]; \ - } while (0) -#else - #define ufbxi_copy_16_bytes(dst, src) memcpy((dst), (src), 16) -#endif - - -// -- Large fast integer - -#if !defined(UFBX_STANDARD_C) && (defined(__wasm__) || defined(__EMSCRIPTEN__)) && !defined(UFBX_WASM_32BIT) - typedef uint64_t ufbxi_fast_uint; -#else - typedef size_t ufbxi_fast_uint; -#endif - -// -- Wrapping right shift - -#if !defined(UFBX_UBSAN) && UFBXI_ARCH_X64 - #define ufbxi_wrap_shr64(a, b) ((a) >> (b)) -#else - #define ufbxi_wrap_shr64(a, b) ((a) >> ((b) & 63)) -#endif - -// -- Bit manipulation - -#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) - ufbxi_extern_c unsigned char _BitScanReverse(unsigned long * _Index, unsigned long _Mask); - ufbxi_extern_c unsigned char _BitScanReverse64(unsigned long * _Index, unsigned __int64 _Mask); - static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { - unsigned long index; - _BitScanReverse(&index, (unsigned long)v); - return 31 - (uint32_t)index; - } - static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { - unsigned long index; - #if defined(_M_X64) - _BitScanReverse64(&index, (unsigned __int64)v); - #else - uint32_t hi = (uint32_t)(v >> 32u); - uint32_t hi_nonzero = hi != 0 ? 1 : 0; - uint32_t part = hi_nonzero ? hi : (uint32_t)v; - _BitScanReverse(&index, (unsigned long)part); - index += hi_nonzero * 32u; - #endif - return 63 - (uint32_t)index; - } -#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) - #define ufbxi_lzcnt32(v) ((uint32_t)__builtin_clz((unsigned)(v))) - #define ufbxi_lzcnt64(v) ((uint32_t)__builtin_clzll((unsigned long long)(v))) -#else - // DeBrujin table lookup - static const uint8_t ufbxi_lzcnt32_table[] = { - 31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1, 23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0, - }; - static const uint8_t ufbxi_lzcnt64_table[] = { - 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, 9, 5, 28, 11, 13, 21, 42, - 19, 25, 31, 34, 40, 46, 52, 59, 1, 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, - 53, 18, 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0, - }; - static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt32(uint32_t v) { - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return ufbxi_lzcnt32_table[(v * 0x07c4acddu) >> 27]; - } - static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) { - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v |= v >> 32; - return ufbxi_lzcnt64_table[(v * UINT64_C(0x03f79d71b4cb0a89)) >> 58]; - } -#endif - -// -- Bit conversion - -#if defined(__cplusplus) - #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) memcpy(&(m_dst), &(m_src), sizeof(m_dst_type)) -#else - #define ufbxi_bit_cast(m_dst_type, m_dst, m_src_type, m_src) do { \ - union { m_dst_type mi_dst; m_src_type mi_src; } mi_union; \ - mi_union.mi_src = (m_src); (m_dst) = mi_union.mi_dst; } while (0) -#endif - -// -- Pointer alignment - -#if !defined(UFBX_STANDARD_C) && defined(__GNUC__) && defined(__has_builtin) - #if __has_builtin(__builtin_is_aligned) - #define ufbxi_is_aligned(m_ptr, m_align) __builtin_is_aligned((m_ptr), (m_align)) - #define ufbxi_is_aligned_mask(m_ptr, m_align) __builtin_is_aligned((m_ptr), (m_align) + 1) - #endif -#endif -#ifndef ufbxi_is_aligned - #define ufbxi_is_aligned(m_ptr, m_align) (((uintptr_t)(m_ptr) & ((m_align) - 1)) == 0) - #define ufbxi_is_aligned_mask(m_ptr, m_align) (((uintptr_t)(m_ptr) & (m_align)) == 0) -#endif - -// -- Debug - -#if defined(UFBX_DEBUG_BINARY_SEARCH) || defined(UFBX_REGRESSION) - #define ufbxi_clamp_linear_threshold(v) (2) -#else - #define ufbxi_clamp_linear_threshold(v) (v) -#endif - -#if defined(UFBX_REGRESSION) - #undef UFBXI_MAX_SKIP_SIZE - #define UFBXI_MAX_SKIP_SIZE 128 - - #undef UFBXI_MAP_MAX_SCAN - #define UFBXI_MAP_MAX_SCAN 2 - - #undef UFBXI_KD_FAST_DEPTH - #define UFBXI_KD_FAST_DEPTH 2 - - #undef UFBXI_FACE_GROUP_HASH_BITS - #define UFBXI_FACE_GROUP_HASH_BITS 2 -#endif - -#if defined(UFBX_REGRESSION) || defined(UFBX_EXTENSIVE_THREADING) - #undef UFBXI_MIN_THREADED_DEFLATE_BYTES - #define UFBXI_MIN_THREADED_DEFLATE_BYTES 2 - - #undef UFBXI_MIN_THREADED_ASCII_VALUES - #define UFBXI_MIN_THREADED_ASCII_VALUES 2 -#endif - -#if defined(UFBX_REGRESSION) - #define ufbxi_regression_assert(cond) ufbx_assert(cond) -#else - #define ufbxi_regression_assert(cond) (void)0 -#endif - -#if defined(UFBX_REGRESSION) || defined(UFBX_DEV) || defined(UFBX_UBSAN) - #define ufbxi_dev_assert(cond) ufbx_assert(cond) -#else - #define ufbxi_dev_assert(cond) (void)0 -#endif - -#define ufbxi_unreachable(reason) do { ufbx_assert(0 && reason); } while (0) - -#if defined(UFBX_REGRESSION) - #define UFBXI_IS_REGRESSION 1 -#else - #define UFBXI_IS_REGRESSION 0 -#endif - -#if defined(_MSC_VER) - #define ufbxi_thread_local __declspec(thread) -#elif defined(__GNUC__) || defined(__clang__) - #define ufbxi_thread_local __thread -#elif UFBXI_HAS_CPP11 - #define ufbxi_thread_local thread_local -#elif UFBX_STDC >= 201112L - #define ufbxi_thread_local _Thread_local -#endif - -#if defined(UFBXI_ANALYSIS_RECURSIVE) - #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth); - #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth); -#elif UFBXI_IS_REGRESSION && defined(ufbxi_thread_local) - #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) \ - { \ - m_ret m_name##_rec m_params; \ - static ufbxi_thread_local unsigned ufbxi_recursion_depth; \ - ufbx_assert(ufbxi_recursion_depth < m_max_depth); \ - ++ufbxi_recursion_depth; \ - m_ret ret = m_name##_rec m_args; \ - --ufbxi_recursion_depth; \ - return ret; \ - } \ - m_ret m_name##_rec m_params - #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) \ - { \ - void m_name##_rec m_params; \ - static ufbxi_thread_local unsigned ufbxi_recursion_depth; \ - ufbx_assert(ufbxi_recursion_depth < m_max_depth); \ - ++ufbxi_recursion_depth; \ - m_name##_rec m_args; \ - --ufbxi_recursion_depth; \ - } \ - void m_name##_rec m_params -#else - #define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) - #define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) -#endif - -// -- Utility - -#if defined(UFBX_UBSAN) - static void ufbxi_assert_zero(size_t offset) { ufbx_assert(offset == 0); } - #define ufbxi_add_ptr(ptr, offset) ((ptr) ? (ptr) + (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr))) - #define ufbxi_sub_ptr(ptr, offset) ((ptr) ? (ptr) - (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr))) -#else - #define ufbxi_add_ptr(ptr, offset) ((ptr) + (offset)) - #define ufbxi_sub_ptr(ptr, offset) ((ptr) - (offset)) -#endif - -#define ufbxi_arraycount(arr) (sizeof(arr) / sizeof(*(arr))) -#define ufbxi_for(m_type, m_name, m_begin, m_num) for (m_type *m_name = m_begin, *m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++) -#define ufbxi_for_ptr(m_type, m_name, m_begin, m_num) for (m_type **m_name = m_begin, **m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++) - -// WARNING: Evaluates `m_list` twice! -#define ufbxi_for_list(m_type, m_name, m_list) for (m_type *m_name = (m_list).data, *m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++) -#define ufbxi_for_ptr_list(m_type, m_name, m_list) for (m_type **m_name = (m_list).data, **m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++) - -#define ufbxi_string_literal(str) { str, sizeof(str) - 1 } - -static ufbxi_forceinline uint32_t ufbxi_min32(uint32_t a, uint32_t b) { return a < b ? a : b; } -static ufbxi_forceinline uint32_t ufbxi_max32(uint32_t a, uint32_t b) { return a < b ? b : a; } -static ufbxi_forceinline uint64_t ufbxi_min64(uint64_t a, uint64_t b) { return a < b ? a : b; } -static ufbxi_forceinline uint64_t ufbxi_max64(uint64_t a, uint64_t b) { return a < b ? b : a; } -static ufbxi_forceinline size_t ufbxi_min_sz(size_t a, size_t b) { return a < b ? a : b; } -static ufbxi_forceinline size_t ufbxi_max_sz(size_t a, size_t b) { return a < b ? b : a; } -static ufbxi_forceinline ufbx_real ufbxi_min_real(ufbx_real a, ufbx_real b) { return a < b ? a : b; } -static ufbxi_forceinline ufbx_real ufbxi_max_real(ufbx_real a, ufbx_real b) { return a < b ? b : a; } - -static ufbxi_forceinline int32_t ufbxi_f64_to_i32(double value) -{ - if (ufbx_fabs(value) <= (double)INT32_MAX) { - return (int32_t)value; - } else { - return value >= 0.0 ? INT32_MAX : INT32_MIN; - } -} - -static ufbxi_forceinline int64_t ufbxi_f64_to_i64(double value) -{ - if (ufbx_fabs(value) <= (double)INT64_MAX) { - return (int64_t)value; - } else { - return value >= 0.0 ? INT64_MAX : INT64_MIN; - } -} - -#if defined(UFBX_REGRESSION) - static size_t ufbxi_to_size(ptrdiff_t delta) { - ufbx_assert(delta >= 0); - return (size_t)delta; - } -#else - #define ufbxi_to_size(delta) ((size_t)(delta)) -#endif - -// Stable sort array `m_type m_data[m_size]` using the predicate `m_cmp_lambda(a, b)` -// `m_linear_size` is a hint for how large blocks handle initially do with insertion sort -// `m_tmp` must be a memory buffer with at least the same size and alignment as `m_data` -#define ufbxi_macro_stable_sort(m_type, m_linear_size, m_data, m_tmp, m_size, m_cmp_lambda) do { \ - typedef m_type mi_type; \ - mi_type *mi_src = (mi_type*)(m_tmp); \ - mi_type *mi_data = m_data, *mi_dst = mi_data; \ - size_t mi_block_size = ufbxi_clamp_linear_threshold(m_linear_size), mi_size = m_size; \ - /* Insertion sort in `m_linear_size` blocks */ \ - for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size) { \ - size_t mi_i_end = mi_base + mi_block_size; \ - if (mi_i_end > mi_size) mi_i_end = mi_size; \ - for (size_t mi_i = mi_base + 1; mi_i < mi_i_end; mi_i++) { \ - size_t mi_j = mi_i; \ - mi_src[0] = mi_dst[mi_i]; \ - for (; mi_j != mi_base; --mi_j) { \ - mi_type *a = &mi_src[0], *b = &mi_dst[mi_j - 1]; \ - if (!( m_cmp_lambda )) break; \ - mi_dst[mi_j] = mi_dst[mi_j - 1]; \ - } \ - mi_dst[mi_j] = mi_src[0]; \ - } \ - } \ - /* Merge sort ping-ponging between `m_data` and `m_tmp` */ \ - for (; mi_block_size < mi_size; mi_block_size *= 2) { \ - mi_type *mi_swap = mi_dst; mi_dst = mi_src; mi_src = mi_swap; \ - for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size * 2) { \ - size_t mi_i = mi_base, mi_i_end = mi_base + mi_block_size; \ - size_t mi_j = mi_i_end, mi_j_end = mi_j + mi_block_size; \ - size_t mi_k = mi_base; \ - if (mi_i_end > mi_size) mi_i_end = mi_size; \ - if (mi_j_end > mi_size) mi_j_end = mi_size; \ - while ((mi_i < mi_i_end) & (mi_j < mi_j_end)) { \ - mi_type *a = &mi_src[mi_j], *b = &mi_src[mi_i]; \ - if ( m_cmp_lambda ) { \ - mi_dst[mi_k] = *a; mi_j++; \ - } else { \ - mi_dst[mi_k] = *b; mi_i++; \ - } \ - mi_k++; \ - } \ - while (mi_i < mi_i_end) mi_dst[mi_k++] = mi_src[mi_i++]; \ - while (mi_j < mi_j_end) mi_dst[mi_k++] = mi_src[mi_j++]; \ - } \ - } \ - /* Copy the result to `m_data` if we ended up in `m_tmp` */ \ - if (mi_dst != mi_data) memcpy((void*)mi_data, mi_dst, sizeof(mi_type) * mi_size); \ - } while (0) - -#define ufbxi_macro_lower_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_cmp_lambda, m_eq_lambda) do { \ - typedef m_type mi_type; \ - const mi_type *mi_data = (m_data); \ - size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \ - ufbx_assert(mi_linear_size > 1); \ - /* Binary search until we get down to `m_linear_size` elements */ \ - while (mi_hi - mi_lo > mi_linear_size) { \ - size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \ - const mi_type *a = &mi_data[mi_mid]; \ - if ( m_cmp_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \ - } \ - /* Linearly scan until we find the edge */ \ - for (; mi_lo < mi_hi; mi_lo++) { \ - const mi_type *a = &mi_data[mi_lo]; \ - if ( m_eq_lambda ) { *(m_result_ptr) = mi_lo; break; } \ - } \ - } while (0) - -#define ufbxi_macro_upper_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_eq_lambda) do { \ - typedef m_type mi_type; \ - const mi_type *mi_data = (m_data); \ - size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \ - ufbx_assert(mi_linear_size > 1); \ - /* Linearly scan with galloping */ \ - for (size_t mi_step = 1; mi_step < 100 && mi_hi - mi_lo > mi_step; mi_step *= 2) { \ - const mi_type *a = &mi_data[mi_lo + mi_step]; \ - if (!( m_eq_lambda )) { mi_hi = mi_lo + mi_step; break; } \ - mi_lo += mi_step; \ - } \ - /* Binary search until we get down to `m_linear_size` elements */ \ - while (mi_hi - mi_lo > mi_linear_size) { \ - size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \ - const mi_type *a = &mi_data[mi_mid]; \ - if ( m_eq_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \ - } \ - /* Linearly scan until we find the edge */ \ - for (; mi_lo < mi_hi; mi_lo++) { \ - const mi_type *a = &mi_data[mi_lo]; \ - if (!( m_eq_lambda )) break; \ - } \ - *(m_result_ptr) = mi_lo; \ - } while (0) - -typedef bool ufbxi_less_fn(void *user, const void *a, const void *b); - -static ufbxi_noinline void ufbxi_stable_sort(size_t stride, size_t linear_size, void *in_data, void *in_tmp, size_t size, ufbxi_less_fn *less_fn, void *less_user) -{ - (void)linear_size; - - char *src = (char*)in_tmp; - char *data = (char*)in_data, *dst = (char*)data; - size_t block_size = ufbxi_clamp_linear_threshold(linear_size); - /* Insertion sort in `linear_size` blocks */ - for (size_t base = 0; base < size; base += block_size) { - size_t i_end = base + block_size; - if (i_end > size) i_end = size; - for (size_t i = base + 1; i < i_end; i++) { - - { - char *a = dst + i * stride, *b = dst + (i - 1) * stride; - if (!less_fn(less_user, a, b)) continue; - } - - size_t j = i - 1; - memcpy(src, dst + i * stride, stride); - memcpy(dst + i * stride, dst + j * stride, stride); - for (; j != base; --j) { - char *a = src, *b = dst + (j - 1) * stride; - if (!less_fn(less_user, a, b)) break; - memcpy(dst + j * stride, dst + (j - 1) * stride, stride); - } - memcpy(dst + j * stride, src, stride); - } - } - /* Merge sort ping-ponging between `data` and `tmp` */ - for (; block_size < size; block_size *= 2) { - char *swap = dst; dst = src; src = swap; - for (size_t base = 0; base < size; base += block_size * 2) { - size_t i = base, i_end = base + block_size; - size_t j = i_end, j_end = j + block_size; - size_t k = base; - if (i_end > size) i_end = size; - if (j_end > size) j_end = size; - while ((i < i_end) & (j < j_end)) { - char *a = src + j * stride, *b = src + i * stride; - if (less_fn(less_user, a, b)) { - memcpy(dst + k * stride, a, stride); - j++; - } else { - memcpy(dst + k * stride, b, stride); - i++; - } - k++; - } - - memcpy(dst + k * stride, src + i * stride, (i_end - i) * stride); - if (j < j_end) { - memcpy(dst + (k + (i_end - i)) * stride, src + j * stride, (j_end - j) * stride); - } - } - } - /* Copy the result to `data` if we ended up in `tmp` */ - if (dst != data) memcpy((void*)data, dst, size * stride); -} - -static ufbxi_forceinline void ufbxi_swap(void *a, void *b, size_t size) -{ -#if UFBXI_HAS_ALIASING && !defined(__CHERI__) // CHERI needs to copy pointer metadata tag bits.. - ufbxi_dev_assert(size % 4 == 0 && (uintptr_t)a % 4 == 0 && (uintptr_t)b % 4 == 0); - char *ca = (char*)a, *cb = (char*)b; - for (size_t i = 0; i < size; i += 4, ca += 4, cb += 4) { - ufbxi_aliasing_u32 *ua = (ufbxi_aliasing_u32*)ca, *ub = (ufbxi_aliasing_u32*)cb; - uint32_t va = *ua, vb = *ub; - *ua = vb; - *ub = va; - } -#else - union { - void *align_ptr; - uintptr_t align_uptr; - uint64_t align_u64; - char data[256]; - } tmp; - ufbxi_dev_assert(size <= sizeof(tmp)); - memcpy(tmp.data, a, size); - memcpy(a, b, size); - memcpy(b, tmp.data, size); -#endif -} - -static ufbxi_noinline void ufbxi_unstable_sort(void *in_data, size_t size, size_t stride, ufbxi_less_fn *less_fn, void *less_user) -{ - if (size <= 1) return; - - char *data = (char*)in_data; - size_t start = (size - 1) >> 1; - size_t end = size - 1; - for (;;) { - size_t root = start; - size_t child; - while ((child = root*2 + 1) <= end) { - size_t next = less_fn(less_user, data + child * stride, data + root * stride) ? root : child; - if (child + 1 <= end && less_fn(less_user, data + next * stride, data + (child + 1) * stride)) { - next = child + 1; - } - if (next == root) break; - ufbxi_swap(data + root * stride, data + next * stride, stride); - root = next; - } - - if (start > 0) { - start--; - } else if (end > 0) { - ufbxi_swap(data + end * stride, data, stride); - end--; - } else { - break; - } - } -} - -// -- Float parsing - -#define UFBXI_BIGINT_LIMB_BITS 32 -#define UFBXI_BIGINT_ACCUM_BITS (UFBXI_BIGINT_LIMB_BITS * 2) -#define UFBXI_BIGINT_LIMB_MAX (ufbxi_bigint_limb)(((ufbxi_bigint_accum)1 << UFBXI_BIGINT_LIMB_BITS) - 1) -typedef uint32_t ufbxi_bigint_limb; -typedef uint64_t ufbxi_bigint_accum; - -typedef struct { - ufbxi_bigint_limb *limbs; - uint32_t capacity; - uint32_t length; -} ufbxi_bigint; - -static ufbxi_bigint ufbxi_bigint_make(ufbxi_bigint_limb *limbs, size_t capacity) -{ - ufbxi_bigint bi = { limbs, (uint32_t)capacity }; - return bi; -} - -#define ufbxi_bigint_array(arr) ufbxi_bigint_make((arr), sizeof(arr) / sizeof(*(arr))) - -static const uint64_t ufbxi_pow5_tab[] = { - UINT64_C(0x1), UINT64_C(0x5), UINT64_C(0x19), UINT64_C(0x7d), UINT64_C(0x271), UINT64_C(0xc35), UINT64_C(0x3d09), UINT64_C(0x1312d), UINT64_C(0x5f5e1), - UINT64_C(0x1dcd65), UINT64_C(0x9502f9), UINT64_C(0x2e90edd), UINT64_C(0xe8d4a51), UINT64_C(0x48c27395), UINT64_C(0x16bcc41e9), UINT64_C(0x71afd498d), - UINT64_C(0x2386f26fc1), UINT64_C(0xb1a2bc2ec5), UINT64_C(0x3782dace9d9), UINT64_C(0x1158e460913d), UINT64_C(0x56bc75e2d631), UINT64_C(0x1b1ae4d6e2ef5), - UINT64_C(0x878678326eac9), UINT64_C(0x2a5a058fc295ed), UINT64_C(0xd3c21bcecceda1), UINT64_C(0x422ca8b0a00a425), UINT64_C(0x14adf4b7320334b9), UINT64_C(0x6765c793fa10079d), -}; - -static const double ufbxi_pow10_tab_f64[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, -}; - -static ufbxi_noinline void ufbxi_bigint_mad(ufbxi_bigint *bigint, ufbxi_bigint_accum multiplicand, ufbxi_bigint_accum addend) -{ - ufbxi_dev_assert((multiplicand | addend) >> (UFBXI_BIGINT_ACCUM_BITS - 1) == 0); - ufbxi_bigint b = *bigint; - ufbxi_bigint_limb m_lo = (ufbxi_bigint_limb)multiplicand; - ufbxi_bigint_limb m_hi = (ufbxi_bigint_limb)(multiplicand >> UFBXI_BIGINT_LIMB_BITS); - ufbxi_bigint_accum carry = addend; - for (uint32_t i = 0; i < b.length; i++) { - ufbxi_bigint_accum limb = (ufbxi_bigint_accum)b.limbs[i]; - ufbxi_bigint_accum lo = limb * m_lo + (carry & UFBXI_BIGINT_LIMB_MAX); - ufbxi_bigint_accum hi = limb * m_hi; - b.limbs[i] = (ufbxi_bigint_limb)lo; - carry = (carry >> 32u) + (lo >> 32u) + hi; - } - while (carry) { - b.limbs[b.length++] = (ufbxi_bigint_limb)carry; - ufbxi_dev_assert(b.length < b.capacity); - carry >>= 32u; - } - bigint->length = b.length; -} - -static ufbxi_noinline bool ufbxi_bigint_div(ufbxi_bigint *q, ufbxi_bigint *u, ufbxi_bigint *v) -{ - int32_t n = (int32_t)v->length; - int32_t m = (int32_t)u->length - n; - ufbxi_bigint_limb v_hi = v->limbs[v->length - 1]; - ufbxi_bigint_limb *un = u->limbs, *vn = v->limbs; - ufbxi_dev_assert(n >= 2 && m >= 1 && v_hi >> (UFBXI_BIGINT_LIMB_BITS - 1) != 0 && un[n+m - 1] >> (UFBXI_BIGINT_LIMB_BITS - 1) == 0); - un[n + m] = 0; - q->length = 0; - for (int32_t j = m - 1; j >= 0; j--) { - ufbxi_bigint_accum u_hi = ((ufbxi_bigint_accum)un[n+j] << UFBXI_BIGINT_LIMB_BITS) | un[n+j-1]; - ufbxi_bigint_accum t, qhat = u_hi / v_hi, rhat = u_hi % v_hi; - while (qhat >> UFBXI_BIGINT_LIMB_BITS != 0 || qhat*vn[n-2] > ((rhat<> UFBXI_BIGINT_LIMB_BITS != 0) break; - } - ufbxi_bigint_limb carry = 0; - for (int32_t i = 0; i < n; i++) { - ufbxi_bigint_accum p = qhat * vn[i]; - t = (ufbxi_bigint_accum)un[i+j] - carry - (ufbxi_bigint_limb)p; - un[i+j] = (ufbxi_bigint_limb)t; - carry = (ufbxi_bigint_limb)((p >> UFBXI_BIGINT_LIMB_BITS) - (t >> UFBXI_BIGINT_LIMB_BITS)); - } - t = (ufbxi_bigint_accum)un[j+n] - carry; - un[j+n] = (ufbxi_bigint_limb)t; - if (t >> UFBXI_BIGINT_LIMB_BITS != 0) { - qhat -= 1; - carry = 0; - for (int32_t i = 0; i < n; i++) { - t = (ufbxi_bigint_accum)un[i+j] + vn[i] + carry; - un[i+j] = (ufbxi_bigint_limb)t; - carry = (ufbxi_bigint_limb)(t >> UFBXI_BIGINT_LIMB_BITS); - } - un[j+n] += carry; - } - q->limbs[j] = (ufbxi_bigint_limb)qhat; - if (qhat && !q->length) { - ufbxi_dev_assert(j + 1 < (int32_t)q->capacity); - q->length = (uint32_t)(j + 1); - } - } - for (int32_t i = 0; i < n; i++) { - if (un[i]) return true; - } - return false; -} - -static void ufbxi_bigint_mul_pow5(ufbxi_bigint *b, uint32_t power) -{ - for (; power > 27; power -= 27) { - ufbxi_bigint_mad(b, ufbxi_pow5_tab[27], 0); - } - ufbxi_bigint_mad(b, ufbxi_pow5_tab[power], 0); -} - -static ufbxi_noinline void ufbxi_bigint_shift_left(ufbxi_bigint *bigint, uint32_t amount) -{ - uint32_t words = amount / UFBXI_BIGINT_LIMB_BITS, bits = amount % UFBXI_BIGINT_LIMB_BITS; - ufbxi_bigint b = *bigint; - ufbxi_dev_assert(b.length + words + 1 < b.capacity && b.capacity >= 4); - uint32_t bits_down = UFBXI_BIGINT_LIMB_BITS - bits - 1; - bigint->length += words + (b.limbs[b.length - 1] >> 1 >> bits_down != 0 ? 1 : 0); - b.limbs[b.length] = 0; - if (b.length <= 3 && words <= 3) { - ufbxi_bigint_limb l0 = b.limbs[0]; - ufbxi_bigint_limb l1 = ufbxi_maybe_uninit(b.length >= 1, b.limbs[1], ~0u); - ufbxi_bigint_limb l2 = ufbxi_maybe_uninit(b.length >= 2, b.limbs[2], ~0u); - b.limbs[0] = 0; - b.limbs[1] = 0; - b.limbs[2] = 0; - b.limbs[words + 0] = l0 << bits; - b.limbs[words + 1] = (l1 << bits) | (l0 >> 1 >> bits_down); - b.limbs[words + 2] = (l2 << bits) | (l1 >> 1 >> bits_down); - b.limbs[words + 3] = (l2 >> 1 >> bits_down); - } else { - for (uint32_t i = b.length + 1; i-- > 1; ) { - b.limbs[i + words] = (b.limbs[i] << bits) | (b.limbs[i - 1] >> 1 >> bits_down); - } - b.limbs[words] = b.limbs[0] << bits; - for (uint32_t i = 0; i < words; i++) { - b.limbs[i] = 0; - } - } -} - -static ufbxi_bigint_limb ufbxi_bigint_top_limb(const ufbxi_bigint b, uint32_t index) { - return index < b.length ? b.limbs[b.length - 1 - index] : 0; -} - -static ufbxi_noinline uint64_t ufbxi_bigint_extract_high(const ufbxi_bigint b, int32_t *p_exponent, bool *p_tail) -{ - ufbxi_dev_assert(b.length != 0); - uint64_t result = 0; - const uint32_t limb_count = 64 / UFBXI_BIGINT_LIMB_BITS; - for (uint32_t i = 0; i < limb_count; i++) { - result = (result << UFBXI_BIGINT_LIMB_BITS) | ufbxi_bigint_top_limb(b, i); - } - uint32_t shift = ufbxi_lzcnt64(result); - result <<= shift; - ufbxi_bigint_limb lo = ufbxi_bigint_top_limb(b, limb_count); - if (shift > 0) { - result |= lo >> (UFBXI_BIGINT_LIMB_BITS - shift); - } - *p_tail |= (ufbxi_bigint_limb)(lo << shift) != 0; - for (uint32_t i = limb_count + 1; i < b.length; i++) { - *p_tail |= ufbxi_bigint_top_limb(b, i) != 0; - } - *p_exponent += (int32_t)(b.length * UFBXI_BIGINT_LIMB_BITS - shift - 1); - return result; -} - -static uint64_t ufbxi_shift_right_round(uint64_t value, uint32_t shift, bool tail) -{ - if (shift == 0) return value; - if (shift > 64) return 0; - uint64_t result = value >> (shift - 1); - uint64_t tail_mask = (UINT64_C(1) << (shift - 1)) - 1; - - bool r_odd = (result & 0x2) != 0; - bool r_round = (result & 0x1) != 0; - bool r_tail = tail || (value & tail_mask) != 0; - uint64_t round_bit = (r_round && (r_odd || r_tail)) ? 1u : 0u; - - return (result >> 1u) + round_bit; -} - -typedef enum { - UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH = 0x1, - UFBXI_PARSE_DOUBLE_AS_BINARY32 = 0x2, -} ufbxi_parse_double_flag; - -static bool ufbxi_scan_ignorecase(const char *p, const char *end, const char *fmt) -{ - for (const char *f = fmt; *f; f++, p++) { - if (p >= end) return false; - if ((*p | 0x20) != *f) return false; - } - return true; -} - -static ufbxi_noinline bool ufbxi_parse_inf_nan(double *p_result, const char *str, size_t max_length, char **p_end) -{ - bool negative = false; - const char *p = str, *end = p + max_length; - if (p != end && (*p == '+' || *p == '-')) { - negative = *p++ == '-'; - } - - uint32_t top_bits = 0; - if (end - p >= 3 && (p[0] >= '0' && p[0] <= '9') && p[1] == '.' && p[2] == '#') { - // Legacy MSVC 1.#NAN - p += 3; - if (ufbxi_scan_ignorecase(p, end, "inf")) { - p += 3; - top_bits = 0x7ff0; - } else if (ufbxi_scan_ignorecase(p, end, "nan") || ufbxi_scan_ignorecase(p, end, "ind")) { - p += 3; - top_bits = 0x7ff8; - } else { - return false; - } - while (p != end && *p >= '0' && *p <= '9') { - p++; - } - } else { - // Standard - if (ufbxi_scan_ignorecase(p, end, "nan")) { - p += 3; - top_bits = 0x7ff8; - if (p != end && *p == '(') { - p++; - while (p != end && *p != ')') { - char c = *p; - if (!((c>='0'&&c<='9') || (c>='a'&&c<='z') || (c>='A'&&c<='Z'))) { - return false; - } - p++; - } - if (p == end) return false; - p++; - } - } else if (ufbxi_scan_ignorecase(p, end, "inf")) { - p += ufbxi_scan_ignorecase(p + 3, end, "inity") ? 8 : 3; - top_bits = 0x7ff0; - } - } - - *p_end = (char*)p; - top_bits |= negative ? 0x8000 : 0; - uint64_t bits = (uint64_t)top_bits << 48; - double result; - ufbxi_bit_cast(double, result, uint64_t, bits); - *p_result = result; - return true; -} - -static ufbxi_noinline double ufbxi_parse_double(const char *str, size_t max_length, char **p_end, uint32_t flags) -{ - const uint32_t max_limbs = 14; - - ufbxi_bigint_limb mantissa_limbs[42], divisor_limbs[42], quotient_limbs[42]; - ufbxi_bigint big_mantissa = ufbxi_bigint_array(mantissa_limbs); - ufbxi_bigint big_quotient = ufbxi_bigint_array(quotient_limbs); - int32_t dec_exponent = 0, has_dot = 0; - bool negative = false, tail = false, digits_valid = true; - uint64_t digits = 0; - uint32_t num_digits = 0; - - const char *p = str, *end = p + max_length; - if (p != end && (*p == '+' || *p == '-')) { - negative = *p++ == '-'; - } - while (p != end) { - char c = *p; - if (c >= '0' && c <= '9') { - if (big_mantissa.length < max_limbs) { - digits = digits * 10 + (uint64_t)(c - '0'); - num_digits++; - if (num_digits >= 18) { - ufbxi_dev_assert(num_digits < ufbxi_arraycount(ufbxi_pow5_tab)); - ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); - digits = 0; - num_digits = 0; - digits_valid = false; - } - dec_exponent -= has_dot; - } else { - dec_exponent += 1 - has_dot; - } - p++; - } else if (c == '.' && !has_dot) { - has_dot = true; - p++; - } else { - break; - } - } - if (p != end && (*p == 'e' || *p == 'E')) { - p++; - bool exp_negative = false; - if (p != end && (*p == '+' || *p == '-')) { - exp_negative = *p == '-'; - p++; - } - int32_t exp = 0; - while (p != end) { - char c = *p; - if (c >= '0' && c <= '9') { - p++; - exp = exp * 10 + (c - '0'); - if (exp >= 10000) break; - } else { - break; - } - } - dec_exponent += exp_negative ? -exp : exp; - } - - if (p != end) { - char c = *p; - if (c == '#' || c == 'i' || c == 'I' || c == 'n' || c == 'N') { - double result; - if (ufbxi_parse_inf_nan(&result, str, max_length, p_end)) { - return result; - } - } - } - - *p_end = (char*)p; - - // Both power of 10 and integer are exactly representable as doubles - // Powers of 10 are factored as 2*5, and 2^N can be always exactly represented. - if ((flags & UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH) != 0 && big_mantissa.length == 0 && dec_exponent >= -22 && dec_exponent <= 22 && (digits >> 53) == 0) { - double value; - if (dec_exponent < 0) { - value = (double)digits / ufbxi_pow10_tab_f64[-dec_exponent]; - } else { - value = (double)digits * ufbxi_pow10_tab_f64[dec_exponent]; - } - return negative ? -value : value; - } - - if (big_mantissa.length == 0) { - big_mantissa.limbs[0] = (ufbxi_bigint_limb)digits; - big_mantissa.limbs[1] = (ufbxi_bigint_limb)(digits >> 32u); - big_mantissa.length = (digits >> 32u) ? 2 : digits ? 1 : 0; - if (big_mantissa.length == 0) return negative ? -0.0 : 0.0; - } else { - ufbxi_dev_assert(num_digits < ufbxi_arraycount(ufbxi_pow5_tab)); - ufbxi_bigint_mad(&big_mantissa, ufbxi_pow5_tab[num_digits] << num_digits, digits); - } - - uint32_t enc_sign_shift = 63; - uint32_t enc_mantissa_bits = 53; - int32_t enc_max_exponent = 1023; - if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { - enc_sign_shift = 31; - enc_mantissa_bits = 24; - enc_max_exponent = 127; - } - - int32_t exponent = 0; - if (dec_exponent < 0) { - if (dec_exponent + (int32_t)big_mantissa.length * 10 <= -325) return negative ? -0.0 : 0.0; - - ufbxi_bigint big_divisor = ufbxi_bigint_array(divisor_limbs); - uint32_t pow5 = (uint32_t)-dec_exponent; - uint32_t initial_pow5 = pow5 <= 27 ? pow5 : 27; - uint64_t pow5_value = ufbxi_pow5_tab[initial_pow5]; - pow5 -= initial_pow5; - exponent += dec_exponent; - - if (pow5 == 0 && digits_valid && digits >> 63 == 0) { - uint32_t divisor_zeros = ufbxi_lzcnt64(pow5_value); - uint64_t mantissa_zeros = ufbxi_lzcnt64(digits) - 1; - uint64_t divisor_bits = pow5_value << divisor_zeros; - uint64_t mantissa_bits = digits << mantissa_zeros; - big_divisor.limbs[0] = (ufbxi_bigint_limb)divisor_bits; - big_divisor.limbs[1] = (ufbxi_bigint_limb)(divisor_bits >> 32u); - big_divisor.length = 2; - big_mantissa.limbs[0] = 0; - big_mantissa.limbs[1] = 0; - big_mantissa.limbs[2] = (ufbxi_bigint_limb)mantissa_bits; - big_mantissa.limbs[3] = (ufbxi_bigint_limb)(mantissa_bits >> 32u); - big_mantissa.length = 4; - exponent += (int32_t)divisor_zeros - (int32_t)mantissa_zeros - 64; - } else { - big_divisor.limbs[0] = (ufbxi_bigint_limb)pow5_value; - big_divisor.limbs[1] = (ufbxi_bigint_limb)(pow5_value >> 32u); - big_divisor.length = (pow5_value >> 32u) != 0 ? 2 : 1; - if (pow5 > 0) { - ufbxi_bigint_mul_pow5(&big_divisor, pow5); - } - - uint32_t divisor_zeros = ufbxi_lzcnt32(big_divisor.limbs[big_divisor.length - 1]); - if (big_divisor.length == 1) divisor_zeros += UFBXI_BIGINT_LIMB_BITS; - ufbxi_bigint_shift_left(&big_divisor, divisor_zeros); - uint32_t divisor_bits = big_divisor.length * UFBXI_BIGINT_LIMB_BITS; - - uint32_t mantissa_zeros = ufbxi_lzcnt32(big_mantissa.limbs[big_mantissa.length - 1]); - uint32_t mantissa_bits = big_mantissa.length * UFBXI_BIGINT_LIMB_BITS - mantissa_zeros; - uint32_t mantissa_min_bits = divisor_bits + enc_mantissa_bits + 2; - uint32_t mantissa_shift = mantissa_bits < mantissa_min_bits ? mantissa_min_bits - mantissa_bits : 0; - // Align mantissa to never have a high bit, this means we can skip the first digit during division. - mantissa_shift += ((mantissa_shift - mantissa_zeros) & (UFBXI_BIGINT_LIMB_BITS - 1)) == 0 ? 1 : 0; - if (mantissa_shift > 0) { - ufbxi_bigint_shift_left(&big_mantissa, mantissa_shift); - } - exponent += (int32_t)divisor_zeros - (int32_t)mantissa_shift; - } - - tail = ufbxi_bigint_div(&big_quotient, &big_mantissa, &big_divisor); - big_mantissa = big_quotient; - } else if (dec_exponent > 0) { - if (dec_exponent + (int32_t)(big_mantissa.length - 1) * 9 >= 310) return negative ? -UFBX_INFINITY : UFBX_INFINITY; - - exponent += dec_exponent; - ufbxi_bigint_mul_pow5(&big_mantissa, (uint32_t)dec_exponent); - } - - uint64_t mantissa = ufbxi_bigint_extract_high(big_mantissa, &exponent, &tail); - uint64_t sign_bit = (uint64_t)(negative ? 1u : 0u) << enc_sign_shift; - - uint32_t mantissa_shift = 64 - enc_mantissa_bits; - if (exponent > enc_max_exponent) { - return negative ? -UFBX_INFINITY : UFBX_INFINITY; - } else if (exponent <= -enc_max_exponent) { - mantissa_shift += (uint32_t)(-enc_max_exponent + 1 - exponent); - exponent = -enc_max_exponent + 1; - } - - mantissa = ufbxi_shift_right_round(mantissa, mantissa_shift, tail); - if (mantissa == 0) return negative ? -0.0 : 0.0; - - uint64_t bits = mantissa; - bits += (uint64_t)(exponent + enc_max_exponent - 1) << (enc_mantissa_bits - 1); - bits |= sign_bit; - - if (flags & UFBXI_PARSE_DOUBLE_AS_BINARY32) { - uint32_t bits_lo = (uint32_t)bits; - float result; - ufbxi_bit_cast(float, result, uint32_t, bits_lo); - return result; - } else { - double result; - ufbxi_bit_cast(double, result, uint64_t, bits); - return result; - } -} - -static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags(void) -{ - // We require evaluation in double precision, either for doubles (0) or always (1) - // and rounding to nearest, which we can check for with `1 + eps == 1 - eps`. - #if UFBX_FLT_EVAL_METHOD == 0 || UFBX_FLT_EVAL_METHOD == 1 - static volatile double ufbxi_volatile_eps = 2.2250738585072014e-308; - if (1.0 + ufbxi_volatile_eps == 1.0 - ufbxi_volatile_eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH; - #endif - - return 0; -} - -static ufbxi_forceinline int64_t ufbxi_parse_int64(const char *str, char **end) -{ - uint64_t abs_val = 0; - bool negative = *str == '-'; - bool positive = *str == '+'; - - size_t init_len = (negative | positive) ? 1 : 0; - size_t len = init_len; - for (; len < 30; len++) { - char c = str[len]; - if (!(c >= '0' && c <= '9')) break; - abs_val = 10 * abs_val + (uint64_t)(c - '0'); - } - if (len == 30 || len == init_len) { - *end = NULL; - return 0; - } - - // TODO: Wrap/clamp? - *end = (char*)str + len; - return negative ? (int64_t)(0 - abs_val) : (int64_t)abs_val; -} - -static ufbxi_noinline uint32_t ufbxi_parse_uint32_radix(const char *str, uint32_t radix) -{ - uint32_t value = 0; - for (const char *p = str; ; p++) { - char c = *p; - if (c >= '0' && c <= '9') { - value = value * radix + (uint32_t)(c - '0'); - } else if (radix == 16 && (c >= 'a' && c <= 'f')) { - value = value * radix + (uint32_t)(c + (10 - 'a')); - } else if (radix == 16 && (c >= 'A' && c <= 'F')) { - value = value * radix + (uint32_t)(c + (10 - 'A')); - } else { - break; - } - } - return value; -} - -// -- DEFLATE implementation - -#if !defined(ufbx_inflate) - -// Lookup data: [0:5] extra bits [5:8] flags [16:32] base value -// Generated by `misc/deflate_lut.py` -static const uint32_t ufbxi_deflate_length_lut[] = { - 0x00000020, 0x00030040, 0x00040040, 0x00050040, 0x00060040, 0x00070040, 0x00080040, 0x00090040, - 0x000a0040, 0x000b0041, 0x000d0041, 0x000f0041, 0x00110041, 0x00130042, 0x00170042, 0x001b0042, - 0x001f0042, 0x00230043, 0x002b0043, 0x00330043, 0x003b0043, 0x00430044, 0x00530044, 0x00630044, - 0x00730044, 0x00830045, 0x00a30045, 0x00c30045, 0x00e30045, 0x01020040, 0x00010020, 0x00010020, -}; -static const uint32_t ufbxi_deflate_dist_lut[] = { - 0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050001, 0x00070001, 0x00090002, 0x000d0002, - 0x00110003, 0x00190003, 0x00210004, 0x00310004, 0x00410005, 0x00610005, 0x00810006, 0x00c10006, - 0x01010007, 0x01810007, 0x02010008, 0x03010008, 0x04010009, 0x06010009, 0x0801000a, 0x0c01000a, - 0x1001000b, 0x1801000b, 0x2001000c, 0x3001000c, 0x4001000d, 0x6001000d, 0x00010020, 0x00010020, -}; - -static const uint8_t ufbxi_deflate_code_length_permutation[] = { - 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15, -}; - -#define UFBXI_INFLATE_FAST_MIN_IN 8 -#define UFBXI_INFLATE_FAST_MIN_OUT 2 - -#define UFBXI_HUFF_MAX_BITS 16 -#define UFBXI_HUFF_MAX_VALUE 288 -#define UFBXI_HUFF_FAST_BITS 10 -#define UFBXI_HUFF_FAST_SIZE (1 << UFBXI_HUFF_FAST_BITS) -#define UFBXI_HUFF_FAST_MASK (UFBXI_HUFF_FAST_SIZE - 1) -#define UFBXI_HUFF_MAX_LONG_BITS 5 -#define UFBXI_HUFF_MAX_LONG_SYMS 380 - -#define UFBXI_HUFF_CODELEN_FAST_BITS 8 -#define UFBXI_HUFF_CODELEN_FAST_MASK ((1<0] Base offset (halved) to `long_sym[]` -// [8:16] code_prefix // [fast_sym, fast=0, extra_mask=0] First 8-bits of the code (reverse of the lookup) -// -// (*1) Not allowing `end` if `fast` serves a dual purpose: It allows us to omit a check for the end symbol in the -// fast path and allows using the symbol as a 64-bit shift amount (x64/ARM64/WASM have native modulo 64 shifts). -// -// Valid bit patterns, all other patterns are forbidden (`sorted_to_sym[]` contains same patterns as `long_sym[]`): -// -// tree b e m f v -// -// lit_length.fast_sym[] N 0 0 1 L // Short N bit code (no extra allowed) for literal byte L -// lit_length.fast_sym[] N 0 1 1 I // Short N bit code (huff+extra bits) for length index I -// lit_length.fast_sym[] M 0 0 0 X // Long code at `lit_length.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` -// lit_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `lit_length.sorted_to_sym[]` to resolve (*1) -// lit_length.fast_sym[] N 1 0 0 0 // Short N bit code for end-of-block (256) symbol -// lit_length.fast_sym[] 0 1 0 0 1 // Invalid lit_length code -// -// lit_length.long_sym[] N 0 0 0 L // Long N bit code (no extra allowed) for literal byte L -// lit_length.long_sym[] N 0 1 0 I // Long N bit code (huff+extra bits) for length index I -// lit_length.long_sym[] N 1 0 0 0 // Long N bit code for end-of-block (256) symbol -// lit_length.long_sym[] 0 1 0 0 1 // Invalid lit_length code -// -// dist.fast_sym[] N 0 0 1 L // Short N bit code (huff+extra bits) for distance index I -// dist.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` -// dist.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `dist.sorted_to_sym[]` to resolve (*1) -// dist.fast_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code -// -// dist.long_sym[] N 0 0 0 I // Long N bit code (huff+extra bits) for distance index I -// dist.long_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code -// -// code_length.fast_sym[] N 0 0 1 B // Short N bit code (huff only, extra handled explicitly) for symbol bit count B -// code_length.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]` -// code_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `code_length.sorted_to_sym[]` to resolve (*1) -// -// code_length.long_sym[] N 0 0 0 B // Long N bit code (huff only, extra handled explicitly) for symbol bit count B -// -// (*1) Never necessary if `fast_bits >= 10` due to `long_sym[]` covering all possible codes, -// -typedef uint16_t ufbxi_huff_sym; - -#define ufbxi_huff_sym_total_bits(sym) ((uint32_t)(sym) & 0x1f) -#define ufbxi_huff_sym_long_mask(sym) ((uint32_t)(sym) & 0x1f) -#define ufbxi_huff_sym_long_offset(sym) ((uint32_t)(sym) >> 7u) -#define ufbxi_huff_sym_value(sym) ((uint32_t)(sym) >> 8u) - -enum { - UFBXI_HUFF_SYM_END = 0x20, - UFBXI_HUFF_SYM_MATCH = 0x40, - UFBXI_HUFF_SYM_FAST = 0x80, -}; - -#define UFBXI_HUFF_ERROR_SYM ((ufbxi_huff_sym)0x0120) // Error symbol, END (value 1) -#define UFBXI_HUFF_UNINITIALIZED_SYM ((ufbxi_huff_sym)0x0220) // Uninitialized symbol for regression, END (value 2) - -typedef struct { - ufbxi_huff_sym fast_sym[UFBXI_HUFF_FAST_SIZE]; // < Lookup from N bytes to symbol information - ufbxi_huff_sym long_sym[UFBXI_HUFF_MAX_LONG_SYMS]; // < Fast long symbol lookup - ufbxi_huff_sym sorted_to_sym[UFBXI_HUFF_MAX_VALUE]; // < Symbol information per sorted index - - uint32_t extra_shift_base[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < [0:6] shift [16:32] base value - uint16_t extra_mask[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < Mask for extra bits - - uint16_t past_max_code[UFBXI_HUFF_MAX_BITS]; // < One past maximum code value per bit length - int16_t code_to_sorted[UFBXI_HUFF_MAX_BITS]; // < Code to sorted symbol index per bit length - uint32_t num_symbols; - - uint32_t end_of_block_bits; -} ufbxi_huff_tree; - -typedef struct { - union { - struct { - ufbxi_huff_tree lit_length; - ufbxi_huff_tree dist; - }; - ufbxi_huff_tree trees[2]; - }; - uint32_t fast_bits; -} ufbxi_trees; - -typedef struct { - bool initialized; - ufbxi_trees static_trees; -} ufbxi_inflate_retain_imp; - -ufbx_static_assert(inflate_retain_size, sizeof(ufbxi_inflate_retain_imp) <= sizeof(ufbx_inflate_retain)); - -typedef struct { - ufbxi_bit_stream stream; - uint32_t fast_bits; - - char *out_begin; - char *out_ptr; - char *out_end; -} ufbxi_deflate_context; - -static ufbxi_forceinline uint32_t -ufbxi_bit_reverse(uint32_t mask, uint32_t num_bits) -{ - ufbxi_dev_assert(num_bits <= 16); - uint32_t x = mask; - x = (((x & 0xaaaa) >> 1) | ((x & 0x5555) << 1)); - x = (((x & 0xcccc) >> 2) | ((x & 0x3333) << 2)); - x = (((x & 0xf0f0) >> 4) | ((x & 0x0f0f) << 4)); - x = (((x & 0xff00) >> 8) | ((x & 0x00ff) << 8)); - return x >> (16 - num_bits); -} - -static ufbxi_noinline const char * -ufbxi_bit_chunk_refill(ufbxi_bit_stream *s, const char *ptr) -{ - // Copy any left-over data to the beginning of `buffer` - size_t left = ufbxi_to_size(s->chunk_real_end - ptr); - ufbxi_dev_assert(left < 64); - if (left > 0) memmove(s->buffer, ptr, left); - - s->num_read_before_chunk += ufbxi_to_size(ptr - s->chunk_begin); - - // Read more user data if the user supplied a `read_fn()`, otherwise - // we assume the initial data chunk is the whole input buffer. - if (s->read_fn && !s->cancelled) { - size_t to_read = ufbxi_min_sz(s->input_left, s->buffer_size - left); - if (to_read > 0) { - size_t num_read = s->read_fn(s->read_user, s->buffer + left, to_read); - // TODO: IO error, should unify with (currently broken) cancel logic - if (num_read > to_read) num_read = 0; - ufbxi_dev_assert(s->input_left >= num_read); - s->input_left -= num_read; - left += num_read; - } - } - - // Pad the rest with zeros - if (left < 64) { - memset(s->buffer + left, 0, 64 - left); - left = 64; - } - - s->chunk_begin = s->buffer; - s->chunk_ptr = s->buffer; - s->chunk_end = s->buffer + left - 8; - s->chunk_real_end = s->buffer + left; - return s->buffer; -} - -static ufbxi_noinline void ufbxi_bit_stream_init(ufbxi_bit_stream *s, const ufbx_inflate_input *input) -{ - size_t data_size = input->data_size; - if (data_size > input->total_size) { - data_size = input->total_size; - } - - s->read_fn = input->read_fn; - s->read_user = input->read_user; - s->progress_cb = input->progress_cb; - s->chunk_begin = (const char*)input->data; - s->chunk_ptr = (const char*)input->data; - s->chunk_end = ufbxi_add_ptr((const char*)input->data, ufbxi_max_sz(8, data_size) - 8); - s->chunk_real_end = ufbxi_add_ptr((const char*)input->data, data_size); - s->input_left = input->total_size - data_size; - - // Use the user buffer if it's large enough, otherwise `local_buffer` - if (input->buffer_size > sizeof(s->local_buffer)) { - s->buffer = (char*)input->buffer; - s->buffer_size = input->buffer_size; - } else { - s->buffer = s->local_buffer; - s->buffer_size = sizeof(s->local_buffer); - } - s->num_read_before_chunk = 0; - s->progress_bias = input->progress_size_before; - s->progress_total = input->total_size + input->progress_size_before + input->progress_size_after; - if (!s->progress_cb.fn || input->progress_interval_hint >= SIZE_MAX) { - s->progress_interval = SIZE_MAX; - } else if (input->progress_interval_hint > 0) { - s->progress_interval = (size_t)input->progress_interval_hint; - } else { - s->progress_interval = 0x4000; - } - s->cancelled = false; - - // Clear the initial bit buffer - s->bits = 0; - s->left = 0; - - // If the initial data buffer is not large enough to be read directly - // from refill the chunk once. - if (data_size < 64) { - ufbxi_bit_chunk_refill(s, s->chunk_begin); - } - - if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - s->chunk_ptr) > s->progress_interval + 8) { - s->chunk_yield = s->chunk_ptr + s->progress_interval; - } else { - s->chunk_yield = s->chunk_end; - } -} - -static ufbxi_noinline const char * -ufbxi_bit_yield(ufbxi_bit_stream *s, const char *ptr) -{ - if (ptr > s->chunk_end) { - ptr = ufbxi_bit_chunk_refill(s, ptr); - } - - if (s->progress_cb.fn) { - size_t num_read = s->num_read_before_chunk + ufbxi_to_size(ptr - s->chunk_begin); - - ufbx_progress progress = { s->progress_bias + num_read, s->progress_total }; - uint32_t result = (uint32_t)s->progress_cb.fn(s->progress_cb.user, &progress); - ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL); - if (result == UFBX_PROGRESS_CANCEL) { - s->cancelled = true; - ptr = s->local_buffer; - s->buffer = s->local_buffer; - s->buffer_size = sizeof(s->local_buffer); - s->chunk_begin = ptr; - s->chunk_ptr = ptr; - s->chunk_end = ptr + sizeof(s->local_buffer) - 8; - s->chunk_real_end = ptr + sizeof(s->local_buffer); - memset(s->local_buffer, 0, sizeof(s->local_buffer)); - } - } - - if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - ptr) > s->progress_interval + 8) { - s->chunk_yield = ptr + s->progress_interval; - } else { - s->chunk_yield = s->chunk_end; - } - - return ptr; -} - -static ufbxi_forceinline void -ufbxi_bit_refill(uint64_t *p_bits, size_t *p_left, const char **p_data, ufbxi_bit_stream *s) -{ - if (*p_data > s->chunk_yield) { - *p_data = ufbxi_bit_yield(s, *p_data); - if (s->cancelled) { - // Force an end-of-block symbol when cancelled so we don't need an - // extra branch in the chunk decoding loop. - *p_bits = s->cancel_bits; - } - } - - // See https://fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/ - // variant 4. This branchless refill guarantees [56,63] bits to be valid in `*p_bits`. - ufbxi_regression_assert(*p_left <= 64); \ - *p_bits |= ufbxi_read_u64(*p_data) << *p_left; - *p_data += (63 - *p_left) >> 3; - *p_left |= 56; -} - -// See `ufbxi_bit_refill()` -#define ufbxi_macro_bit_refill_fast(m_bits, m_left, m_data, m_refill_bits) do { \ - ufbxi_regression_assert(m_left <= 64); \ - m_bits |= m_refill_bits << m_left; \ - m_data += (63 - m_left) >> 3; \ - m_left |= 56; \ - } while (0) - -static ufbxi_noinline int -ufbxi_bit_copy_bytes(void *dst, ufbxi_bit_stream *s, size_t len) -{ - ufbx_assert(s->left % 8 == 0); - char *ptr = (char*)dst; - - // Copy the buffered bits first - while (len > 0 && s->left > 0) { - *ptr++ = (char)(uint8_t)s->bits; - len -= 1; - s->bits >>= 8; - s->left -= 8; - } - - // We need to clear the top bits as there may be data - // read ahead past `s->left` in some cases - s->bits = 0; - - // Copy the current chunk - size_t chunk_left = ufbxi_to_size(s->chunk_real_end - s->chunk_ptr); - if (chunk_left >= len) { - memcpy(ptr, s->chunk_ptr, len); - s->chunk_ptr += len; - return 1; - } else { - memcpy(ptr, s->chunk_ptr, chunk_left); - s->chunk_ptr += chunk_left; - ptr += chunk_left; - len -= chunk_left; - } - - // Read extra bytes from user - if (len > s->input_left) return 0; - size_t num_read = 0; - if (s->read_fn) { - num_read = s->read_fn(s->read_user, ptr, len); - s->input_left -= num_read; - } - return num_read == len; -} - -// 0: Success -// -1: Overfull -// -2: Underfull -static ufbxi_noinline ptrdiff_t -ufbxi_huff_build_imp(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits, uint32_t *bits_counts) -{ - uint32_t fast_mask = (1u << fast_bits) - 1; - - ufbx_assert(sym_count <= UFBXI_HUFF_MAX_VALUE); - tree->num_symbols = sym_count; - - uint32_t nonzero_sym_count = sym_count - bits_counts[0]; - - uint32_t total_syms[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit - uint32_t first_code[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit - - tree->code_to_sorted[0] = INT16_MAX; - tree->past_max_code[0] = 0; - total_syms[0] = 0; - - // Clear to uninitialized symbols - #if defined(UFBX_REGRESSION) - { - for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) { - tree->fast_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; - } - for (size_t i = 0; i < UFBXI_HUFF_MAX_VALUE; i++) { - tree->sorted_to_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; - } - for (size_t i = 0; i < UFBXI_HUFF_MAX_LONG_SYMS; i++) { - tree->long_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM; - } - } - #endif - - uint32_t last_valid_prefix = 0; - - // Resolve the maximum code per bit length and ensure that the tree is not - // overfull or underfull. - { - int num_codes_left = 1; - uint32_t code = 0; - uint32_t prev_count = 0; - uint32_t long_offset = 0; - for (uint32_t bits = 1; bits < UFBXI_HUFF_MAX_BITS; bits++) { - uint32_t count = bits_counts[bits]; - code = (code + prev_count) << 1; - first_code[bits] = code; - tree->past_max_code[bits] = (uint16_t)(code + count); - - uint32_t prev_syms = total_syms[bits - 1]; - total_syms[bits] = prev_syms + count; - - // Each bit level doubles the amount of codes and potentially removes some - num_codes_left = (num_codes_left << 1) - (int32_t)count; - if (num_codes_left < 0) { - return -1; - } - - if (count > 0 && bits > fast_bits && bits - fast_bits <= UFBXI_HUFF_MAX_LONG_BITS) { - uint32_t shift = bits - fast_bits; - uint32_t last_inclusive = num_codes_left == 0 ? (1u<> shift; - uint32_t last_prefix = (code + count + last_inclusive) >> shift; - uint32_t mask = (1u << shift) - 1u; - uint32_t half_step = 1u << (shift - 1u); - for (uint32_t prefix = first_prefix; prefix < last_prefix; prefix++) { - uint32_t rev_prefix = ufbxi_bit_reverse(prefix, fast_bits); - tree->fast_sym[rev_prefix] = (ufbxi_huff_sym)(mask | (long_offset << 8)); - long_offset += half_step; - } - - last_valid_prefix = last_prefix; - } - - if (count > 0) { - tree->code_to_sorted[bits] = (int16_t)((int)prev_syms - (int)code); - } else { - tree->code_to_sorted[bits] = INT16_MAX; - } - prev_count = count; - } - - // All codes should be used if there's more than one symbol, if there's only one symbol there should be - // only a single 1-bit code. - if (nonzero_sym_count > 1 && num_codes_left != 0) { - return -2; - } else if (nonzero_sym_count == 1 && total_syms[1] != 1) { - return -2; - } - - // We should always have enough space for long symbols as we support up to 5 (UFBXI_HUFF_MAX_LONG_BITS) - // bits and the largest tree has 286 symbols. For each bit we may waste at most 2^bits slots (conservative) - // and in the end we may waste 2^5 slots giving us `286+2+4+8+16+32+32 = 380` (UFBXI_HUFF_MAX_LONG_SYMS) - ufbx_assert(long_offset <= UFBXI_HUFF_MAX_LONG_SYMS); - } - - tree->end_of_block_bits = 0; - uint32_t num_extra = 0; - tree->extra_shift_base[0] = 0; - tree->extra_mask[0] = 0; - - // Fill `fast_sym[]` with error symbols if necessary, we don't need to do this if we have two or more symbols - // as the tree is guaranteed to be full, which means we will populate the whole `fast_sym[]` - if (nonzero_sym_count <= 1) { - for (uint32_t i = 0; i <= fast_mask; i++) { - tree->fast_sym[i] = UFBXI_HUFF_ERROR_SYM; - } - } - - // Generate per-length sorted-to-symbol and fast lookup tables - uint32_t bits_index[UFBXI_HUFF_MAX_BITS] = { 0 }; - for (uint32_t i = 0; i < sym_count; i++) { - uint32_t bits = sym_bits[i]; - if (bits == 0) continue; - - uint32_t sym = i << 8 | bits; - if (i >= sym_extra_offset) { - uint32_t extra = sym_extra[i - sym_extra_offset]; - sym += extra; - - // Store length/distance codes with extra values in a table. - // TODO: This is unnecessary for small values - if ((extra & 0xffff001f) != 0 && (extra & 0x20) == 0) { - uint32_t ix = ++num_extra; - tree->extra_shift_base[ix] = (extra & 0xffff0000) | bits; - tree->extra_mask[ix] = (uint16_t)((1u << (extra & 0x1f)) - 1); - sym = (sym & 0xff) | ix << 8; - } - - } - - uint32_t index = bits_index[bits]++; - uint32_t sorted = total_syms[bits - 1] + index; - tree->sorted_to_sym[sorted] = (ufbxi_huff_sym)sym; - - // Reverse the code and fill all fast lookups with the reversed prefix - uint32_t code = first_code[bits] + index; - uint32_t rev_code = ufbxi_bit_reverse(code, bits); - - if (bits <= fast_bits) { - uint32_t fast_sym = sym; - // The `end` and `fast` flags are mutually exclusive - if ((fast_sym & UFBXI_HUFF_SYM_END) == 0) { - fast_sym |= UFBXI_HUFF_SYM_FAST; - } - uint32_t hi_max = 1u << (fast_bits - bits); - for (uint32_t hi = 0; hi < hi_max; hi++) { - ufbxi_regression_assert(nonzero_sym_count <= 1 || tree->fast_sym[rev_code | hi << bits] == UFBXI_HUFF_UNINITIALIZED_SYM); - tree->fast_sym[rev_code | hi << bits] = (ufbxi_huff_sym)fast_sym; - } - } else if (bits <= fast_bits + UFBXI_HUFF_MAX_LONG_BITS && (code >> (bits - fast_bits)) < last_valid_prefix) { - uint32_t fast_sym = tree->fast_sym[rev_code & fast_mask]; - ufbxi_regression_assert(fast_sym != UFBXI_HUFF_UNINITIALIZED_SYM); - uint32_t long_bits = 0; - - uint32_t long_mask = fast_sym; - while (long_bits < UFBXI_HUFF_MAX_LONG_BITS && (long_mask & 1) != 0) { - long_mask >>= 1; - long_bits += 1; - } - ufbxi_dev_assert(long_bits >= 1); - - uint32_t long_base = fast_sym >> 7u; // aka (fast_sym >> 8) * 2 - uint32_t lo_bits = bits - fast_bits; - uint32_t hi_max = 1u << (long_bits - lo_bits); - uint32_t rev_suffix = rev_code >> fast_bits; - for (uint32_t hi = 0; hi < hi_max; hi++) { - ufbxi_regression_assert(tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] == UFBXI_HUFF_UNINITIALIZED_SYM); - tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] = (ufbxi_huff_sym)sym; - } - } else { - uint32_t fast_sym = (code >> (bits - fast_bits)) << 8; - ufbxi_regression_assert( - tree->fast_sym[rev_code & fast_mask] == UFBXI_HUFF_UNINITIALIZED_SYM || - tree->fast_sym[rev_code & fast_mask] == (ufbxi_huff_sym)fast_sym); - tree->fast_sym[rev_code & fast_mask] = (ufbxi_huff_sym)fast_sym; - } - - // Make sure the end-of-block symbol goes through the slow path - // Also store the end-of-block code so we can interrupt decoding - if (i == 256) { - tree->end_of_block_bits = rev_code; - } - } - - // Make sure all `fast_sym[]` are filled with an initialized value. - #if defined(UFBX_REGRESSION) - { - for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) { - if (i <= fast_mask) { - ufbx_assert(tree->fast_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM); - } else { - ufbx_assert(tree->fast_sym[i] == UFBXI_HUFF_UNINITIALIZED_SYM); - } - } - for (size_t i = 0; i < nonzero_sym_count; i++) { - ufbx_assert(tree->sorted_to_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM); - } - } - #endif - - return 0; -} - -// 0: Success -// -1: Overfull -// -2: Underfull -static ufbxi_noinline ptrdiff_t -ufbxi_huff_build(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits) -{ - // Count the number of codes per bit length - // `bits_counts[0]` contains the number of non-used symbols - uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit - memset(bits_counts, 0, sizeof(bits_counts)); - for (uint32_t i = 0; i < sym_count; i++) { - uint32_t bits = sym_bits[i]; - ufbx_assert(bits < UFBXI_HUFF_MAX_BITS); - bits_counts[bits]++; - } - - return ufbxi_huff_build_imp(tree, sym_bits, sym_count, sym_extra, sym_extra_offset, fast_bits, bits_counts); -} - -static ufbxi_forceinline ufbxi_huff_sym -ufbxi_huff_decode_bits(const ufbxi_huff_tree *tree, uint64_t bits, uint32_t fast_bits, uint32_t fast_mask) -{ - ufbxi_huff_sym sym = tree->fast_sym[bits & fast_mask]; - ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); - - if ((sym & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) != 0) { - return sym; - } - - uint32_t tail = (uint32_t)(bits >> fast_bits); - uint32_t long_mask = ufbxi_huff_sym_long_mask(sym); - if (long_mask) { - sym = tree->long_sym[ufbxi_huff_sym_long_offset(sym) + (tail & long_mask)]; - ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); - return sym; - } - - ufbxi_dev_assert(fast_bits <= 8); - - uint32_t code = ufbxi_huff_sym_value(sym); - uint32_t num_bits = fast_bits; - for (;;) { - code = code << 1 | (tail & 1); - tail >>= 1; - num_bits++; - - ufbxi_regression_assert(num_bits < UFBXI_HUFF_MAX_BITS); - if (code < tree->past_max_code[num_bits]) { - sym = tree->sorted_to_sym[(int32_t)code + (int32_t)tree->code_to_sorted[num_bits]]; - ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); - return sym; - } - } -} - -static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx_inflate_input *input) -{ - ptrdiff_t err = 0; - - // Override `fast_bits` if necessary, this must always be valid as it's checked in the beginning of `ufbx_inflate()`. - if (input && input->internal_fast_bits != 0) { - trees->fast_bits = (uint32_t)input->internal_fast_bits; - ufbx_assert(!(trees->fast_bits < 1 || trees->fast_bits == 9 || trees->fast_bits > 10)); - } else { - trees->fast_bits = UFBXI_HUFF_FAST_BITS; - } - - // 0-143: 8 bits, 144-255: 9 bits, 256-279: 7 bits, 280-287: 8 bits - uint8_t lit_length_bits[288]; // ufbxi_uninit - memset(lit_length_bits + 0, 8, 144 - 0); - memset(lit_length_bits + 144, 9, 256 - 144); - memset(lit_length_bits + 256, 7, 280 - 256); - memset(lit_length_bits + 280, 8, 288 - 280); - err |= ufbxi_huff_build(&trees->lit_length, lit_length_bits, sizeof(lit_length_bits), ufbxi_deflate_length_lut, 256, trees->fast_bits); - - // "Distance codes 0-31 are represented by (fixed-length) 5-bit codes" - uint8_t dist_bits[32]; // ufbxi_uninit - memset(dist_bits + 0, 5, 32 - 0); - err |= ufbxi_huff_build(&trees->dist, dist_bits, sizeof(dist_bits), ufbxi_deflate_dist_lut, 0, trees->fast_bits); - - // Building the static trees cannot fail as we use pre-defined code lengths. - ufbxi_ignore(err); - ufbx_assert(err == 0); -} - -// 0: Success -// -1: Huffman Overfull -// -2: Huffman Underfull -// -3: Code 16 repeat overflow -// -4: Code 17 repeat overflow -// -5: Code 18 repeat overflow -// -6: Bad length code -// -7: Cancelled -static ufbxi_noinline ptrdiff_t ufbxi_init_dynamic_huff_tree(ufbxi_deflate_context *dc, const ufbxi_huff_tree *huff_code_length, ufbxi_huff_tree *tree, - uint32_t num_symbols, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits) -{ - uint8_t code_lengths[UFBXI_HUFF_MAX_VALUE]; // ufbxi_uninit - ufbx_assert(num_symbols <= UFBXI_HUFF_MAX_VALUE); - - uint64_t bits = dc->stream.bits; - size_t left = dc->stream.left; - const char *data = dc->stream.chunk_ptr; - uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit - memset(bits_counts, 0, sizeof(bits_counts)); - - uint32_t symbol_index = 0; - uint8_t prev = 0; - while (symbol_index < num_symbols) { - ufbxi_bit_refill(&bits, &left, &data, &dc->stream); - if (dc->stream.cancelled) return -7; - - ufbxi_huff_sym sym = ufbxi_huff_decode_bits(huff_code_length, bits, UFBXI_HUFF_CODELEN_FAST_BITS, UFBXI_HUFF_CODELEN_FAST_MASK); - ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM); - - uint32_t inst = ufbxi_huff_sym_value(sym); - uint32_t sym_len = ufbxi_huff_sym_total_bits(sym); - - bits >>= sym_len; - left -= sym_len; - - if (inst <= 15) { - // "0 - 15: Represent code lengths of 0 - 15" - prev = (uint8_t)inst; - code_lengths[symbol_index++] = (uint8_t)inst; - bits_counts[(int32_t)inst]++; - } else if (inst == 16) { - // "16: Copy the previous code length 3 - 6 times. The next 2 bits indicate repeat length." - uint32_t num = 3 + ((uint32_t)bits & 0x3); - bits >>= 2; - left -= 2; - if (symbol_index + num > num_symbols) return -3; - memset(code_lengths + symbol_index, prev, num); - symbol_index += num; - bits_counts[(int32_t)prev] += num; - } else if (inst == 17) { - // "17: Repeat a code length of 0 for 3 - 10 times. (3 bits of length)" - uint32_t num = 3 + ((uint32_t)bits & 0x7); - bits >>= 3; - left -= 3; - if (symbol_index + num > num_symbols) return -4; - memset(code_lengths + symbol_index, 0, num); - symbol_index += num; - prev = 0; - bits_counts[0] += num; - } else if (inst == 18) { - // "18: Repeat a code length of 0 for 11 - 138 times (7 bits of length)" - uint32_t num = 11 + ((uint32_t)bits & 0x7f); - bits >>= 7; - left -= 7; - if (symbol_index + num > num_symbols) return -5; - memset(code_lengths + symbol_index, 0, num); - symbol_index += num; - prev = 0; - bits_counts[0] += num; - } else { - return -6; - } - } - - ptrdiff_t err = ufbxi_huff_build_imp(tree, code_lengths, num_symbols, sym_extra, sym_extra_offset, fast_bits, bits_counts); - if (err != 0) return err; - - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - - return 0; -} - -static ufbxi_noinline ptrdiff_t -ufbxi_init_dynamic_huff(ufbxi_deflate_context *dc, ufbxi_trees *trees) -{ - uint64_t bits = dc->stream.bits; - size_t left = dc->stream.left; - const char *data = dc->stream.chunk_ptr; - ufbxi_bit_refill(&bits, &left, &data, &dc->stream); - if (dc->stream.cancelled) return -28; - - trees->fast_bits = dc->fast_bits; - - // The header contains the number of Huffman codes in each of the three trees. - uint32_t num_lit_lengths = 257 + (uint32_t)(bits & 0x1f); - uint32_t num_dists = 1 + (uint32_t)(bits >> 5 & 0x1f); - uint32_t num_code_lengths = 4 + (uint32_t)(bits >> 10 & 0xf); - bits >>= 14; - left -= 14; - - // Code lengths for the "code length" Huffman tree are represented literally - // 3 bits in order of: 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 up to - // `num_code_lengths`, rest of the code lengths are 0 (unused) - uint8_t code_lengths[19]; // ufbxi_uninit - memset(code_lengths, 0, sizeof(code_lengths)); - for (size_t len_i = 0; len_i < num_code_lengths; len_i++) { - if (len_i == 14) { - ufbxi_bit_refill(&bits, &left, &data, &dc->stream); - if (dc->stream.cancelled) return -28; - } - code_lengths[ufbxi_deflate_code_length_permutation[len_i]] = (uint32_t)bits & 0x7; - bits >>= 3; - left -= 3; - } - - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - - ufbxi_huff_tree huff_code_length; // ufbxi_uninit - ptrdiff_t err; // ufbxi_uninit - - // Build the temporary "code length" Huffman tree used to encode the actual - // trees used to compress the data. Use that to build the literal/length and - // distance trees. - err = ufbxi_huff_build(&huff_code_length, code_lengths, ufbxi_arraycount(code_lengths), NULL, INT32_MAX, UFBXI_HUFF_CODELEN_FAST_BITS); - if (err) return -14 + 1 + err; - err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->lit_length, num_lit_lengths, ufbxi_deflate_length_lut, 256, dc->fast_bits); - if (err) return err == -7 ? -28 : -16 + 1 + err; - err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->dist, num_dists, ufbxi_deflate_dist_lut, 0, dc->fast_bits); - if (err) return err == -7 ? -28 : -22 + 1 + err; - - return 0; -} - -static ufbxi_noinline uint32_t ufbxi_adler32(const void *data, size_t size) -{ - ufbxi_fast_uint a = 1, b = 0; - const char *p = (const char*)data; - - // Adler-32 consists of two running sums modulo 65521. As an optimization - // we can accumulate N sums before applying the modulo, where N depends on - // the size of the type holding the sum. - const ufbxi_fast_uint num_before_wrap = sizeof(ufbxi_fast_uint) == 8 ? 380368439u : 5552u; - - ufbxi_fast_uint size_left = size; - while (size_left > 0) { - ufbxi_fast_uint num = size_left <= num_before_wrap ? size_left : num_before_wrap; - size_left -= num; - const char *end = p + num; - - // Align to 16 bytes - while (p != end && !ufbxi_is_aligned(p, 16)) { - a += (ufbxi_fast_uint)(uint8_t)p[0]; b += a; - p++; - } - -#if UFBXI_HAS_SSE - static const uint16_t factors[2][8] = { - { 16, 15, 14, 13, 12, 11, 10, 9, }, - { 8, 7, 6, 5, 4, 3, 2, 1, }, - }; - - const __m128i zero = _mm_setzero_si128(); - const __m128i factor_1 = _mm_set1_epi16(1); - const __m128i factor_16 = _mm_set1_epi16(16); - const __m128i factor_lo = _mm_loadu_si128((const __m128i*)factors[0]); - const __m128i factor_hi = _mm_loadu_si128((const __m128i*)factors[1]); - - for (;;) { - size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 5803) & ~(size_t)0xff; - if (chunk_size == 0) break; - const char *chunk_end = p + chunk_size; - - __m128i s1 = zero; - __m128i s2 = zero; - - while (p != chunk_end) { - __m128i s1_lo = zero, s1_hi = zero; - __m128i tmp_lo = zero, tmp_hi = zero; - - ufbxi_nounroll for (size_t i = 0; i < 256; i += 32) { - __m128i d0 = _mm_load_si128((const __m128i*)(p + i + 0)); - __m128i d1 = _mm_load_si128((const __m128i*)(p + i + 16)); - - tmp_lo = _mm_add_epi16(tmp_lo, s1_lo); - tmp_hi = _mm_add_epi16(tmp_hi, s1_hi); - s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d0, zero)); - s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d0, zero)); - - tmp_lo = _mm_add_epi16(tmp_lo, s1_lo); - tmp_hi = _mm_add_epi16(tmp_hi, s1_hi); - s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d1, zero)); - s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d1, zero)); - } - - s2 = _mm_add_epi32(s2, _mm_slli_epi32(s1, 8)); - s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_lo, factor_1)); - s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_hi, factor_1)); - - s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_lo, factor_16)); - s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_hi, factor_16)); - s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_lo, factor_lo)); - s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_hi, factor_hi)); - - p += 256; - } - - s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(2,3,0,1))); - s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(2,3,0,1))); - s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(1,0,3,2))); - s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(1,0,3,2))); - - b += chunk_size * a; - a += (uint32_t)_mm_cvtsi128_si32(s1); - b += (uint32_t)_mm_cvtsi128_si32(s2); - } -#elif UFBX_LITTLE_ENDIAN - for (;;) { - size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 256*8/4) & ~(size_t)0xf; - if (chunk_size == 0) break; - const char *chunk_end = p + chunk_size; - - uint64_t s1_lo = 0, s1_hi = 0; - uint64_t tmp, s2 = 0; - uint64_t mask8 = UINT64_C(0x00ff00ff00ff00ff); - uint64_t mask16 = UINT64_C(0x0000ffff0000ffff); - - while (p != chunk_end) { - uint64_t d0 = *(const uint64_t*)p; - uint64_t d1 = *(const uint64_t*)(p + 8); - - tmp = s1_lo + s1_hi; - s1_lo += d0 & mask8; - s1_hi += (d0 >> 8) & mask8; - - tmp += s1_lo + s1_hi; - s1_lo += d1 & mask8; - s1_hi += (d1 >> 8) & mask8; - - s2 += (tmp & mask16) + ((tmp >> 16) & mask16); - p += 16; - } - - uint64_t s1 = s1_lo + s1_hi; - s1 = (s1 & mask16) + ((s1 >> 16u) & mask16); - ufbxi_fast_uint s1_sum = (ufbxi_fast_uint)(s1 + (s1 >> 32u)); - - ufbxi_fast_uint s2_sum = (ufbxi_fast_uint)(s2 + (s2 >> 32u)) * 8; - s2_sum += ((ufbxi_fast_uint)(s1_lo >> 0) & 0xffff) * 8; - s2_sum += ((ufbxi_fast_uint)(s1_hi >> 0) & 0xffff) * 7; - s2_sum += ((ufbxi_fast_uint)(s1_lo >> 16) & 0xffff) * 6; - s2_sum += ((ufbxi_fast_uint)(s1_hi >> 16) & 0xffff) * 5; - s2_sum += ((ufbxi_fast_uint)(s1_lo >> 32) & 0xffff) * 4; - s2_sum += ((ufbxi_fast_uint)(s1_hi >> 32) & 0xffff) * 3; - s2_sum += ((ufbxi_fast_uint)(s1_lo >> 48) & 0xffff) * 2; - s2_sum += ((ufbxi_fast_uint)(s1_hi >> 48) & 0xffff) * 1; - - b += chunk_size * a; - a += s1_sum & 0xffffffffu; - b += s2_sum & 0xffffffffu; - } -#endif - - while (p != end) { - a += (size_t)(uint8_t)p[0]; b += a; - p++; - } - - a %= 65521u; - b %= 65521u; - } - - return (uint32_t)((b << 16) | (a & 0xffff)); -} - -static ufbxi_noinline int -ufbxi_inflate_block_slow(ufbxi_deflate_context *dc, ufbxi_trees *trees, size_t max_symbols) -{ - char *out_ptr = dc->out_ptr; - char *const out_begin = dc->out_begin; - char *const out_end = dc->out_end; - - uint32_t fast_bits = trees->fast_bits; - uint32_t fast_mask = (1u << fast_bits) - 1; - - uint64_t bits = dc->stream.bits; - size_t left = dc->stream.left; - const char *data = dc->stream.chunk_ptr; - - for (;;) { - if (max_symbols-- == 0) break; - - ufbxi_bit_refill(&bits, &left, &data, &dc->stream); - uint64_t sym_bits = bits; - - ufbxi_huff_sym sym0 = ufbxi_huff_decode_bits(&trees->lit_length, bits, fast_bits, fast_mask); - ufbxi_regression_assert(sym0 != UFBXI_HUFF_UNINITIALIZED_SYM); - - uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0); - - bits >>= sym0_bits; - left -= sym0_bits; - if (sym0 & UFBXI_HUFF_SYM_END) { - if (ufbxi_huff_sym_value(sym0) != 0) return -13; - - dc->out_ptr = out_ptr; - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - return 0; - } else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) { - if (out_ptr == out_end) return -10; - *out_ptr++ = (char)ufbxi_huff_sym_value(sym0); - continue; - } - - uint32_t sym0_value = ufbxi_huff_sym_value(sym0); - uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value]; - uint16_t len_mask = trees->lit_length.extra_mask[sym0_value]; - uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, len_shift_base) & len_mask); - - ufbxi_huff_sym sym1 = ufbxi_huff_decode_bits(&trees->dist, bits, fast_bits, fast_mask); - ufbxi_regression_assert(sym1 != UFBXI_HUFF_UNINITIALIZED_SYM); - if (sym1 & UFBXI_HUFF_SYM_END) return -11; - - uint32_t sym1_bits = ufbxi_huff_sym_total_bits(sym1); - - bits >>= sym1_bits; - left -= sym1_bits; - - uint32_t sym1_value = ufbxi_huff_sym_value(sym1); - uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value]; - uint16_t dist_mask = trees->dist.extra_mask[sym1_value]; - uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, dist_shift_base + sym0) & dist_mask); - - // Bounds checking - size_t out_space = ufbxi_to_size(out_end - out_ptr); - if ((ptrdiff_t)distance > out_ptr - out_begin || length > out_space) { - return -12; - } - - // Copy the match - const char *src = out_ptr - distance; - char *dst = out_ptr; - char *end = dst + length; - out_ptr += length; - - if (out_space >= length + 16) { - uint32_t min_dist = length < 16 ? length : 16; - if (distance >= min_dist) { - ufbxi_copy_16_bytes(dst, src); - while (length > 16) { - src += 16; - dst += 16; - length -= 16; - ufbxi_copy_16_bytes(dst, src); - } - } else { - while (dst != end) { - *dst++ = *src++; - } - } - } else { - while (dst != end) { - *dst++ = *src++; - } - } - } - - dc->out_ptr = out_ptr; - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - return 1; -} - -ufbx_static_assert(inflate_huff_fast_bits, UFBXI_HUFF_FAST_BITS <= 11); // `fast lit, fast len, slow dist` in 56 bits -ufbx_static_assert(inflate_huff_long_bits, UFBXI_HUFF_FAST_BITS + UFBXI_HUFF_MAX_LONG_BITS >= 15); // Largest code fits in a single long lookup - -// Optimized version of `ufbxi_inflate_block_slow()`. -// Has a lot of assumptions (see asserts) and does not call _any_ (even forceinlined) functions. -static ufbxi_noinline int -ufbxi_inflate_block_fast(ufbxi_deflate_context *dc, ufbxi_trees *trees) -{ - ufbxi_dev_assert(!dc->stream.cancelled); - ufbxi_dev_assert(trees->fast_bits == UFBXI_HUFF_FAST_BITS); - ufbxi_dev_assert(dc->stream.chunk_yield - dc->stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN); - ufbxi_dev_assert(dc->out_end - dc->out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT); - - char *out_ptr = dc->out_ptr; - char *const out_begin = dc->out_begin; - char *const out_end = dc->out_end - UFBXI_INFLATE_FAST_MIN_OUT; - - const ufbxi_huff_tree *tree_lit_length = &trees->lit_length; - const ufbxi_huff_tree *tree_dist = &trees->dist; - - uint64_t bits = dc->stream.bits; - size_t left = dc->stream.left; - const char *data = dc->stream.chunk_ptr; - const char *data_end = dc->stream.chunk_yield - UFBXI_INFLATE_FAST_MIN_IN; - - uint64_t sym01_bits; - ufbxi_huff_sym sym0, sym1; - uint64_t refill_bits = ufbxi_read_u64(data); - - #define ufbxi_fast_inflate_refill_and_decode() do { \ - ufbxi_macro_bit_refill_fast(bits, left, data, refill_bits); \ - sym01_bits = bits; \ - sym0 = tree_lit_length->fast_sym[sym01_bits & UFBXI_HUFF_FAST_MASK]; \ - sym1 = ((sym0 & UFBXI_HUFF_SYM_MATCH) ? tree_dist : tree_lit_length)->fast_sym[ufbxi_wrap_shr64(sym01_bits, sym0) & UFBXI_HUFF_FAST_MASK]; \ - refill_bits = ufbxi_read_u64(data); \ - } while (0) - - #define ufbxi_fast_inflate_should_continue() \ - (((data_end - data) | (out_end - out_ptr)) >= 0) - - ufbxi_fast_inflate_refill_and_decode(); - - for (;;) { - if ((sym0 & sym1) & UFBXI_HUFF_SYM_FAST) { - bits = ufbxi_wrap_shr64(sym01_bits, sym0 + sym1); - left -= (sym0 + sym1) & 0x3f; - - if (((sym0 | sym1) & UFBXI_HUFF_SYM_MATCH) == 0) { - // Literal, Literal - // -> Output the two literals and loop back to start. - - out_ptr[0] = (char)ufbxi_huff_sym_value(sym0); - out_ptr[1] = (char)ufbxi_huff_sym_value(sym1); - out_ptr += 2; - - ufbxi_fast_inflate_refill_and_decode(); - if (ufbxi_fast_inflate_should_continue()) continue; - break; - - } else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) { - // Literal, Match, (Distance) - // -> Output a single literal, decode the missing distance and fall through to match. - - out_ptr[0] = (char)ufbxi_huff_sym_value(sym0); - out_ptr += 1; - - sym01_bits = ufbxi_wrap_shr64(sym01_bits, sym0); - - // This must fit as literals never have extra bits and the match length is fast so: - // 10 (lit) + 10 (len code) + 5 (len extra) + 15 (dist code) + 13 (dist extra) = 53 <= 56 - sym0 = sym1; - sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK]; - - if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) { - // Slow sym1 - if (sym1 & UFBXI_HUFF_SYM_END) return -11; - uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS); - uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1); - sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)]; - if (sym1 & UFBXI_HUFF_SYM_END) return -11; - } - - bits = ufbxi_wrap_shr64(bits, sym1); - left -= sym1 & 0x3f; - } else { - // Match, Distance - // -> Fall through to match copy. - } - - } else { - if ((sym0 & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) == 0) { - // Slow sym0 - uint32_t tail = (uint32_t)(sym01_bits >> UFBXI_HUFF_FAST_BITS); - uint32_t long_mask = ufbxi_huff_sym_long_mask(sym0); - sym0 = tree_lit_length->long_sym[ufbxi_huff_sym_long_offset(sym0) + (tail & long_mask)]; - } - - uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0); - bits >>= sym0_bits; - left -= sym0_bits; - - if (sym0 & UFBXI_HUFF_SYM_END) { - if (ufbxi_huff_sym_value(sym0) != 0) return -13; - dc->out_ptr = out_ptr; - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - return 0; - } - - if (sym0 & UFBXI_HUFF_SYM_MATCH) { - sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK]; - - if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) { - // Slow sym1 - if (sym1 & UFBXI_HUFF_SYM_END) return -11; - uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS); - uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1); - sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)]; - if (sym1 & UFBXI_HUFF_SYM_END) return -11; - } - - bits = ufbxi_wrap_shr64(bits, sym1); - left -= sym1 & 0x3f; - } else { - *out_ptr++ = (char)ufbxi_huff_sym_value(sym0); - - ufbxi_fast_inflate_refill_and_decode(); - if (ufbxi_fast_inflate_should_continue()) continue; - break; - } - } - - uint32_t sym0_value = ufbxi_huff_sym_value(sym0); - uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value]; - uint16_t len_mask = trees->lit_length.extra_mask[sym0_value]; - uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, len_shift_base) & len_mask); - - uint32_t sym1_value = ufbxi_huff_sym_value(sym1); - uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value]; - uint16_t dist_mask = trees->dist.extra_mask[sym1_value]; - uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, dist_shift_base + sym0) & dist_mask); - - ufbxi_fast_inflate_refill_and_decode(); - - // Bounds checking: We don't actually handle the error here, just bail out to the slow implementation - ptrdiff_t dst_space = out_end - out_ptr - (ptrdiff_t)length + UFBXI_INFLATE_FAST_MIN_OUT; - ptrdiff_t src_space = out_ptr - out_begin - (ptrdiff_t)distance; - if ((dst_space | src_space) < 0) { - return -12; - } - - const char *src = out_ptr - distance; - char *dst = out_ptr; - char *end = dst + length; - out_ptr += length; - - // Copy the match - - uint32_t min_dist = length < 16 ? length : 16; - if (distance >= min_dist && dst_space >= 16) { - ufbxi_copy_16_bytes(dst, src); - while (length > 16) { - src += 16; - dst += 16; - length -= 16; - ufbxi_copy_16_bytes(dst, src); - } - } else { - while (dst != end) { - *dst++ = *src++; - } - } - - if (ufbxi_fast_inflate_should_continue()) continue; - break; - } - - dc->out_ptr = out_ptr; - dc->stream.bits = bits; - dc->stream.left = left; - dc->stream.chunk_ptr = data; - return 1; - - #undef ufbxi_fast_inflate_refill_and_decode - #undef ufbxi_fast_inflate_should_continue -} - -static void ufbxi_inflate_init_retain(ufbx_inflate_retain *retain) -{ - ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain; - if (!ret_imp->initialized) { - ufbxi_init_static_huff(&ret_imp->static_trees, NULL); - ret_imp->initialized = true; - } -} - -// TODO: Error codes should have a quick test if the destination buffer overflowed -// Returns actual number of decompressed bytes or negative error: -// -1: Bad compression method (ZLIB header) -// -2: Requires dictionary (ZLIB header) -// -3: Bad FCHECK (ZLIB header) -// -4: Bad NLEN (Uncompressed LEN != ~NLEN) -// -5: Uncompressed source overflow -// -6: Uncompressed destination overflow -// -7: Bad block type -// -8: Truncated checksum (deprecated, reported as -9) -// -9: Checksum mismatch -// -10: Literal destination overflow -// -11: Bad distance code or distance of (30..31) -// -12: Match out of bounds -// -13: Bad lit/length code -// -14: Codelen Huffman Overfull -// -15: Codelen Huffman Underfull -// -16 - -21: Litlen Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code -// -22 - -27: Distance Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code -// -28: Cancelled -// -29: Invalid ufbx_inflate_input.internal_fast_bits value -ufbxi_extern_c ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain) -{ - ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain; - - ptrdiff_t err; - ufbxi_deflate_context dc; - ufbxi_bit_stream_init(&dc.stream, input); - dc.out_begin = (char*)dst; - dc.out_ptr = (char*)dst; - dc.out_end = (char*)dst + dst_size; - if (input->internal_fast_bits != 0) { - dc.fast_bits = (uint32_t)input->internal_fast_bits; - if (dc.fast_bits < 1 || dc.fast_bits == 9 || dc.fast_bits > 10) return -29; - } else { - // TODO: Profile this - dc.fast_bits = input->total_size > 2048 ? 10 : 8; - } - - uint64_t bits = dc.stream.bits; - size_t left = dc.stream.left; - const char *data = dc.stream.chunk_ptr; - - ufbxi_bit_refill(&bits, &left, &data, &dc.stream); - if (dc.stream.cancelled) return -28; - - // Zlib header - if (!input->no_header) { - size_t cmf = (size_t)(bits & 0xff); - size_t flg = (size_t)(bits >> 8) & 0xff; - bits >>= 16; - left -= 16; - - if ((cmf & 0xf) != 0x8) return -1; - if ((flg & 0x20) != 0) return -2; - if ((cmf << 8 | flg) % 31u != 0) return -3; - } - - for (;;) { - ufbxi_bit_refill(&bits, &left, &data, &dc.stream); - if (dc.stream.cancelled) return -28; - - // Block header: [0:1] BFINAL [1:3] BTYPE - size_t header = (size_t)bits & 0x7; - bits >>= 3; - left -= 3; - - size_t type = header >> 1; - if (type == 0) { - - // Round up to the next byte - size_t align_bits = left & 0x7; - bits >>= align_bits; - left -= align_bits; - - size_t len = (size_t)(bits & 0xffff); - size_t nlen = (size_t)((bits >> 16) & 0xffff); - if ((len ^ nlen) != 0xffff) return -4; - if (dc.out_end - dc.out_ptr < (ptrdiff_t)len) return -6; - bits >>= 32; - left -= 32; - - dc.stream.bits = bits; - dc.stream.left = left; - dc.stream.chunk_ptr = data; - - // Copy `len` bytes of literal data - if (!ufbxi_bit_copy_bytes(dc.out_ptr, &dc.stream, len)) return -5; - - dc.out_ptr += len; - - } else if (type <= 2) { - - dc.stream.bits = bits; - dc.stream.left = left; - dc.stream.chunk_ptr = data; - - ufbxi_trees tree_data; // ufbxi_uninit - ufbxi_trees *trees; // ufbxi_uninit - if (type == 1) { - // Static Huffman: Initialize the trees once and cache them in `retain`. - if (!ret_imp->initialized) { - ufbxi_init_static_huff(&ret_imp->static_trees, input); - ret_imp->initialized = true; - } - trees = &ret_imp->static_trees; - } else { - // Dynamic Huffman - err = ufbxi_init_dynamic_huff(&dc, &tree_data); - if (err) return err; - trees = &tree_data; - } - - for (;;) { - bool fast_viable = trees->fast_bits == UFBXI_HUFF_FAST_BITS && dc.out_end - dc.out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT; - - // `ufbxi_inflate_block_fast()` needs a bit more upfront setup, see asserts on top of the function - if (fast_viable && dc.stream.chunk_yield - dc.stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN) { - err = ufbxi_inflate_block_fast(&dc, trees); - } else { - err = ufbxi_inflate_block_slow(&dc, trees, fast_viable ? 32 : SIZE_MAX); - } - - if (err < 0) return err; - - // `ufbxi_inflate_block()` returns normally on cancel so check it here - if (dc.stream.cancelled) return -28; - - if (err == 0) break; - } - - } else { - // 0b11 - reserved (error) - return -7; - } - - bits = dc.stream.bits; - left = dc.stream.left; - data = dc.stream.chunk_ptr; - - // BFINAL: End of stream - if (header & 1) break; - } - - // Check Adler-32 - { - // Round up to the next byte - size_t align_bits = left & 0x7; - bits >>= align_bits; - left -= align_bits; - ufbxi_bit_refill(&bits, &left, &data, &dc.stream); - if (dc.stream.cancelled) return -28; - - if (!input->no_checksum) { - uint32_t ref = (uint32_t)bits; - ref = (ref>>24) | ((ref>>8)&0xff00) | ((ref<<8)&0xff0000) | (ref<<24); - - uint32_t checksum = ufbxi_adler32(dc.out_begin, ufbxi_to_size(dc.out_ptr - dc.out_begin)); - if (ref != checksum) { - return -9; - } - } - } - - return dc.out_ptr - dc.out_begin; -} - -#endif // !defined(ufbx_inflate) - -// -- Printf - -typedef struct { - char *dst; - size_t length; - size_t pos; -} ufbxi_print_buffer; - -#define UFBXI_PRINT_UNSIGNED 0x1 -#define UFBXI_PRINT_STRING 0x2 -#define UFBXI_PRINT_SIZE_T 0x10 - -static void ufbxi_print_append(ufbxi_print_buffer *buf, size_t min_width, size_t max_width, const char *str) -{ - size_t width = 0; - for (width = 0; width < max_width; width++) { - if (!str[width]) break; - } - size_t pad = min_width > width ? min_width - width : 0; - for (size_t i = 0; i < pad; i++) { - if (buf->pos < buf->length) buf->dst[buf->pos++] = ' '; - } - for (size_t i = 0; i < width; i++) { - if (buf->pos < buf->length) buf->dst[buf->pos++] = str[i]; - } -} - -static char *ufbxi_print_format_int(char *buffer, uint64_t value) -{ - *--buffer = '\0'; - do { - uint32_t digit = (uint32_t)(value % 10); - value = value / 10; - *--buffer = (char)('0' + digit); - } while (value > 0); - return buffer; -} - -static void ufbxi_vprint(ufbxi_print_buffer *buf, const char *fmt, va_list args) -{ - char buffer[96]; // ufbxi_uninit - for (const char *p = fmt; *p;) { - if (*p == '%' && *++p != '%') { - size_t min_width = 0, max_width = SIZE_MAX; - if (*p == '*') { - p++; - min_width = (size_t)va_arg(args, int); - } - if (*p == '.') { - ufbxi_dev_assert(p[1] == '*'); - p += 2; - max_width = (size_t)va_arg(args, int); - } - uint32_t flags = 0; - switch (*p) { - case 'z': p++; flags |= UFBXI_PRINT_SIZE_T; break; - default: break; - } - switch (*p++) { - case 'u': flags |= UFBXI_PRINT_UNSIGNED; break; - case 's': flags |= UFBXI_PRINT_STRING; break; - default: break; - } - if (flags & UFBXI_PRINT_STRING) { - const char *str = va_arg(args, const char*); - ufbxi_print_append(buf, min_width, max_width, str); - } else if (flags & UFBXI_PRINT_UNSIGNED) { - uint64_t value = (flags & UFBXI_PRINT_SIZE_T) != 0 ? (uint64_t)va_arg(args, size_t) : (uint64_t)va_arg(args, uint32_t); - char *str = ufbxi_print_format_int(buffer + sizeof(buffer), value); - ufbxi_print_append(buf, min_width, max_width, str); - } else { - ufbxi_unreachable("Bad printf format"); - } - } else { - if (buf->pos < buf->length) buf->dst[buf->pos++] = *p; - p++; - } - } - if (buf->length && buf->dst) { - size_t end = buf->pos <= buf->length - 1 ? buf->pos : buf->length - 1; - buf->dst[end] = '\0'; - } -} - -// -- Errors - -static const char ufbxi_empty_char[1] = { '\0' }; - -static ufbxi_noinline int ufbxi_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list args) -{ - ufbxi_print_buffer buffer = { buf, buf_size }; - ufbxi_vprint(&buffer, fmt, args); - return (int)ufbxi_min_sz(buffer.pos, buf_size - 1); -} - -static ufbxi_noinline int ufbxi_snprintf(char *buf, size_t buf_size, const char *fmt, ...) -{ - va_list args; // ufbxi_uninit - va_start(args, fmt); - int result = ufbxi_vsnprintf(buf, buf_size, fmt, args); - va_end(args); - return result; -} - -static ufbxi_noinline void ufbxi_panicf_imp(ufbx_panic *panic, const char *fmt, ...) -{ - if (panic && panic->did_panic) return; - - va_list args; // ufbxi_uninit - - if (panic) { - va_start(args, fmt); - panic->did_panic = true; - panic->message_length = (size_t)ufbxi_vsnprintf(panic->message, sizeof(panic->message), fmt, args); - va_end(args); - } else { - va_start(args, fmt); - char message[UFBX_PANIC_MESSAGE_LENGTH]; - ufbxi_vsnprintf(message, sizeof(message), fmt, args); - va_end(args); - - ufbx_panic_handler(message); - } -} - -#define ufbxi_panicf(panic, cond, ...) \ - ((cond) ? false : (ufbxi_panicf_imp((panic), __VA_ARGS__), true)) - -// Prefix the error condition with $Description\0 for a human readable description -#define ufbxi_error_msg(cond, msg) "$" msg "\0" cond - -static ufbxi_noinline int ufbxi_fail_imp_err(ufbx_error *err, const char *cond, const char *func, uint32_t line) -{ - if (cond && cond[0] == '$') { - if (!err->description.data) { - err->description.data = cond + 1; - err->description.length = strlen(err->description.data); - } - -#if UFBXI_FEATURE_ERROR_STACK - // Skip the description part if adding to a stack - cond = cond + strlen(cond) + 1; -#endif - } - - // NOTE: This is the base function all fails boil down to, place a breakpoint here to - // break at the first error -#if UFBXI_FEATURE_ERROR_STACK - ufbx_assert(cond); - ufbx_assert(func); - if (err->stack_size < UFBX_ERROR_STACK_MAX_DEPTH) { - ufbx_error_frame *frame = &err->stack[err->stack_size++]; - frame->description.data = cond; - frame->description.length = strlen(cond); - frame->function.data = func; - frame->function.length = strlen(func); - frame->source_line = line; - } -#else - ufbxi_ignore(func); - ufbxi_ignore(line); -#endif - - return 0; -} - -ufbxi_nodiscard static ufbxi_noinline size_t ufbxi_utf8_valid_length(const char *str, size_t length) -{ - size_t index = 0; - while (index < length) { - uint8_t c = (uint8_t)str[index]; - size_t left = length - index; - - if ((c & 0x80) == 0) { - if (c != 0) { - index += 1; - continue; - } - } else if ((c & 0xe0) == 0xc0 && left >= 2) { - uint8_t t0 = (uint8_t)str[index + 1]; - uint32_t code = (uint32_t)c << 8 | (uint32_t)t0; - if ((code & 0xc0) == 0x80 && code >= 0xc280) { - index += 2; - continue; - } - } else if ((c & 0xf0) == 0xe0 && left >= 3) { - uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2]; - uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1; - if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) { - index += 3; - continue; - } - } else if ((c & 0xf8) == 0xf0 && left >= 4) { - uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3]; - uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2; - if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) { - index += 4; - continue; - } - } - - break; - } - - ufbx_assert(index <= length); - return index; -} - -static ufbxi_noinline void ufbxi_clean_string_utf8(char *str, size_t length) -{ - size_t pos = 0; - for (;;) { - pos += ufbxi_utf8_valid_length(str + pos, length); - if (pos == length) break; - str[pos++] = '?'; - } -} - -static ufbxi_noinline void ufbxi_set_err_info(ufbx_error *err, const char *data, size_t length) -{ - if (!err) return; - - if (length == SIZE_MAX) length = strlen(data); - size_t to_copy = ufbxi_min_sz(sizeof(err->info) - 1, length); - memcpy(err->info, data, to_copy); - err->info[to_copy] = '\0'; - err->info_length = to_copy; - ufbxi_clean_string_utf8(err->info, err->info_length); -} - -static ufbxi_noinline void ufbxi_fmt_err_info(ufbx_error *err, const char *fmt, ...) -{ - if (!err) return; - - va_list args; // ufbxi_uninit - va_start(args, fmt); - err->info_length = (size_t)ufbxi_vsnprintf(err->info, sizeof(err->info), fmt, args); - va_end(args); - ufbxi_clean_string_utf8(err->info, err->info_length); -} - -static ufbxi_noinline void ufbxi_clear_error(ufbx_error *err) -{ - if (!err) return; - - err->type = UFBX_ERROR_NONE; - err->description.data = ufbxi_empty_char; - err->description.length = 0; - err->stack_size = 0; - err->info[0] = '\0'; - err->info_length = 0; -} - -#if UFBXI_FEATURE_ERROR_STACK - #define ufbxi_function __FUNCTION__ - #define ufbxi_line __LINE__ - #define ufbxi_cond_str(cond) #cond -#else - #define ufbxi_function NULL - #define ufbxi_line 0 - #define ufbxi_cond_str(cond) "" -#endif - -#if UFBXI_FEATURE_ERROR_STACK - #define ufbxi_fail_err_no_msg(err, cond, func, line) ufbxi_fail_imp_err((err), (cond), (func), (line)) -#else - static ufbxi_noinline int ufbxi_fail_imp_err_no_stack(ufbx_error *err) { return ufbxi_fail_imp_err(err, NULL, NULL, 0); } - #define ufbxi_fail_err_no_msg(err, cond, func, line) ufbxi_fail_imp_err_no_stack((err)) -#endif - -#define ufbxi_check_err(err, cond) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_err_no_msg((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return 0; } } while (0) -#define ufbxi_check_return_err(err, cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_err_no_msg((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0) -#define ufbxi_fail_err(err, desc) return ufbxi_fail_err_no_msg(err, desc, ufbxi_function, ufbxi_line) - -#define ufbxi_check_err_msg(err, cond, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return 0; } } while (0) -#define ufbxi_check_return_err_msg(err, cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0) -#define ufbxi_fail_err_msg(err, desc, msg) return ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) -#define ufbxi_report_err_msg(err, desc, msg) (void)ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) - -static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *default_desc, ufbx_error *p_error) -{ - const char *desc = error->description.data; - if (!desc) desc = default_desc; - error->type = UFBX_ERROR_UNKNOWN; - if (!strcmp(desc, "Out of memory")) { - error->type = UFBX_ERROR_OUT_OF_MEMORY; - } else if (!strcmp(desc, "Memory limit exceeded")) { - error->type = UFBX_ERROR_MEMORY_LIMIT; - } else if (!strcmp(desc, "Allocation limit exceeded")) { - error->type = UFBX_ERROR_ALLOCATION_LIMIT; - } else if (!strcmp(desc, "Truncated file")) { - error->type = UFBX_ERROR_TRUNCATED_FILE; - } else if (!strcmp(desc, "IO error")) { - error->type = UFBX_ERROR_IO; - } else if (!strcmp(desc, "Cancelled")) { - error->type = UFBX_ERROR_CANCELLED; - } else if (!strcmp(desc, "Unrecognized file format")) { - error->type = UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT; - } else if (!strcmp(desc, "File not found")) { - error->type = UFBX_ERROR_FILE_NOT_FOUND; - } else if (!strcmp(desc, "Empty file")) { - error->type = UFBX_ERROR_EMPTY_FILE; - } else if (!strcmp(desc, "External file not found")) { - error->type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND; - } else if (!strcmp(desc, "Uninitialized options")) { - error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS; - } else if (!strcmp(desc, "Zero vertex size")) { - error->type = UFBX_ERROR_ZERO_VERTEX_SIZE; - } else if (!strcmp(desc, "Truncated vertex stream")) { - error->type = UFBX_ERROR_TRUNCATED_VERTEX_STREAM; - } else if (!strcmp(desc, "Invalid UTF-8")) { - error->type = UFBX_ERROR_INVALID_UTF8; - } else if (!strcmp(desc, "Feature disabled")) { - error->type = UFBX_ERROR_FEATURE_DISABLED; - } else if (!strcmp(desc, "Bad NURBS geometry")) { - error->type = UFBX_ERROR_BAD_NURBS; - } else if (!strcmp(desc, "Bad index")) { - error->type = UFBX_ERROR_BAD_INDEX; - } else if (!strcmp(desc, "Node depth limit exceeded")) { - error->type = UFBX_ERROR_NODE_DEPTH_LIMIT; - } else if (!strcmp(desc, "Threaded ASCII parse error")) { - error->type = UFBX_ERROR_THREADED_ASCII_PARSE; - } else if (!strcmp(desc, "Unsafe options")) { - error->type = UFBX_ERROR_UNSAFE_OPTIONS; - } else if (!strcmp(desc, "Duplicate override")) { - error->type = UFBX_ERROR_DUPLICATE_OVERRIDE; - } - error->description.data = desc; - error->description.length = strlen(desc); - if (p_error) { - memcpy(p_error, error, sizeof(ufbx_error)); - } -} - -// -- Allocator - -// Returned for zero size allocations, place in the constant data -// to catch writes to bad allocations. -#if defined(UFBX_REGRESSION) -static const char ufbxi_zero_size_buffer[4096] = { 0 }; -#else -static const char ufbxi_zero_size_buffer[64] = { 0 }; -#endif - -static ufbxi_forceinline size_t ufbxi_align_to_mask(size_t value, size_t align_mask) -{ - return value + (((size_t)0 - value) & align_mask); -} - -static ufbxi_forceinline size_t ufbxi_size_align_mask(size_t size) -{ - // Align to the all bits below the lowest set one in `size` up to the maximum alignment. - return ((size ^ (size - 1)) >> 1) & (UFBX_MAXIMUM_ALIGNMENT - 1); -} - -typedef struct { - ufbx_error *error; - size_t current_size; - size_t max_size; - size_t num_allocs; - size_t max_allocs; - size_t huge_size; - size_t chunk_max; - ufbx_allocator_opts ator; - const char *name; -} ufbxi_allocator; - -static ufbxi_forceinline bool ufbxi_does_overflow(size_t total, size_t a, size_t b) -{ - // If `a` and `b` have at most 4 bits per `size_t` byte, the product can't overflow. - if (((a | b) >> sizeof(size_t)*4) != 0) { - if (a != 0 && total / a != b) return true; - } - return false; -} - -static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, size_t n) -{ - // Always succeed with an empty non-NULL buffer for empty allocations - ufbx_assert(size > 0); - if (n == 0) return (void*)ufbxi_zero_size_buffer; - - size_t total = size * n; - ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL); - ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations - if (!(total < ator->max_size - ator->current_size)) { - ufbxi_report_err_msg(ator->error, "total <= ator->max_size - ator->current_size", "Memory limit exceeded"); - ufbxi_fmt_err_info(ator->error, "%s", ator->name); - return NULL; - } - if (!(ator->num_allocs < ator->max_allocs)) { - ufbxi_report_err_msg(ator->error, "ator->num_allocs < ator->max_allocs", "Allocation limit exceeded"); - ufbxi_fmt_err_info(ator->error, "%s", ator->name); - return NULL; - } - ator->num_allocs++; - - void *ptr; - if (ator->ator.allocator.alloc_fn) { - ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total); - } else if (ator->ator.allocator.realloc_fn) { - ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, NULL, 0, total); - } else { - ptr = ufbx_malloc(total); - } - - if (!ptr) { - ufbxi_report_err_msg(ator->error, "ptr", "Out of memory"); - ufbxi_fmt_err_info(ator->error, "%s", ator->name); - return NULL; - } - ufbx_assert(ufbxi_is_aligned_mask(ptr, ufbxi_size_align_mask(total))); - - ator->current_size += total; - - return ptr; -} - -static void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n); -static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t size, void *old_ptr, size_t old_n, size_t n) -{ - ufbx_assert(size > 0); - // realloc() with zero old/new size is equivalent to alloc()/free() - if (old_n == 0) return ufbxi_alloc_size(ator, size, n); - if (n == 0) { ufbxi_free_size(ator, size, old_ptr, old_n); return NULL; } - - size_t old_total = size * old_n; - size_t total = size * n; - - // The old values have been checked by a previous allocate call - ufbx_assert(!ufbxi_does_overflow(old_total, size, old_n)); - ufbx_assert(old_total <= ator->current_size); - - ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL); - ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations - ufbxi_check_return_err_msg(ator->error, total <= ator->max_size - ator->current_size, NULL, "Memory limit exceeded"); - ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); - ator->num_allocs++; - - void *ptr; - if (ator->ator.allocator.realloc_fn) { - ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, old_ptr, old_total, total); - } else if (ator->ator.allocator.alloc_fn) { - // Use user-provided alloc_fn() and free_fn() - ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total); - if (ptr) memcpy(ptr, old_ptr, old_total); - if (ator->ator.allocator.free_fn) { - ator->ator.allocator.free_fn(ator->ator.allocator.user, old_ptr, old_total); - } - } else { - ptr = ufbx_realloc(old_ptr, old_total, total); - } - - ufbxi_check_return_err_msg(ator->error, ptr, NULL, "Out of memory"); - ufbx_assert(ufbxi_is_aligned_mask(ptr, ufbxi_size_align_mask(total))); - - ator->current_size += total; - ator->current_size -= old_total; - - return ptr; -} - -static ufbxi_noinline void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n) -{ - ufbx_assert(size > 0); - if (n == 0) return; - ufbx_assert(ptr); - - size_t total = size * n; - - // The old values have been checked by a previous allocate call - ufbx_assert(!ufbxi_does_overflow(total, size, n)); - ufbx_assert(total <= ator->current_size); - - ator->current_size -= total; - - if (ator->ator.allocator.alloc_fn || ator->ator.allocator.realloc_fn) { - // Don't call default free() if there is an user-provided `alloc_fn()` - if (ator->ator.allocator.free_fn) { - ator->ator.allocator.free_fn(ator->ator.allocator.user, ptr, total); - } else if (ator->ator.allocator.realloc_fn) { - ator->ator.allocator.realloc_fn(ator->ator.allocator.user, ptr, total, 0); - } - } else { - ufbx_free(ptr, total); - } -} - -ufbxi_noinline ufbxi_nodiscard static bool ufbxi_grow_array_size(ufbxi_allocator *ator, size_t size, void *p_ptr, size_t *p_cap, size_t n) -{ - #if defined(UFBX_REGRESSION) - { - ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded"); - ator->num_allocs++; - } - #endif - - if (n <= *p_cap) return true; - void *ptr = *(void**)p_ptr; - size_t old_n = *p_cap; - if (old_n >= n) return true; - size_t new_n = ufbxi_max_sz(old_n * 2, n); - void *new_ptr = ufbxi_realloc_size(ator, size, ptr, old_n, new_n); - if (!new_ptr) return false; - *(void**)p_ptr = new_ptr; - *p_cap = new_n; - return true; -} - -static ufbxi_noinline void ufbxi_free_ator(ufbxi_allocator *ator) -{ - ufbx_assert(ator->current_size == 0); - - ufbx_free_allocator_fn *free_fn = ator->ator.allocator.free_allocator_fn; - if (free_fn) { - void *user = ator->ator.allocator.user; - free_fn(user); - } -} - -#define ufbxi_alloc(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_size((ator), sizeof(type), (n))) -#define ufbxi_alloc_zero(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_zero_size((ator), sizeof(type), (n))) -#define ufbxi_realloc(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_size((ator), sizeof(type), (old_ptr), (old_n), (n))) -#define ufbxi_realloc_zero(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_zero_size((ator), sizeof(type), (old_ptr), (old_n), (n))) -#define ufbxi_free(ator, type, ptr, n) ufbxi_free_size((ator), sizeof(type), (ptr), (n)) - -#define ufbxi_grow_array(ator, p_ptr, p_cap, n) ufbxi_grow_array_size((ator), sizeof(**(p_ptr)), (p_ptr), (p_cap), (n)) - -#define UFBXI_SCENE_IMP_MAGIC 0x58424655 -#define UFBXI_MESH_IMP_MAGIC 0x48534d55 -#define UFBXI_LINE_CURVE_IMP_MAGIC 0x55434c55 -#define UFBXI_CACHE_IMP_MAGIC 0x48434355 -#define UFBXI_ANIM_IMP_MAGIC 0x494e4155 -#define UFBXI_BAKED_ANIM_IMP_MAGIC 0x4b414255 -#define UFBXI_REFCOUNT_IMP_MAGIC 0x46455255 -#define UFBXI_BUF_CHUNK_IMP_MAGIC 0x46554255 - -// -- Memory buffer -// -// General purpose memory buffer that can be used either as a chunked linear memory -// allocator or a non-contiguous stack. You can convert the contents of `ufbxi_buf` -// to a contiguous range of memory by calling `ufbxi_make_array[_all]()` - -typedef struct ufbxi_buf_padding ufbxi_buf_padding; -typedef struct ufbxi_buf_chunk ufbxi_buf_chunk; - -struct ufbxi_buf_padding { - size_t original_pos; // < Original position before aligning - size_t prev_padding; // < Starting offset of the previous `ufbxi_buf_padding` -}; - -struct ufbxi_buf_chunk { - - // Linked list of nodes - ufbxi_buf_chunk *root; - ufbxi_buf_chunk *prev; - ufbxi_buf_chunk *next; - - union { - size_t magic; // < Magic for debugging - void *align_0; // < Align to 4x pointer size (16/32 bytes) - }; - - size_t size; // < Size of the chunk `data`, excluding this header - size_t pushed_pos; // < Size of valid data when pushed to the list - size_t next_size; // < Next geometrically growing chunk size to allocate - size_t padding_pos; // < One past the offset of the most recent `ufbxi_buf_padding` - - char data[]; // < Must be aligned to 8 bytes -}; - -ufbx_static_assert(buf_chunk_align, offsetof(ufbxi_buf_chunk, data) % 8 == 0); - -typedef struct { - ufbxi_allocator *ator; - - // Current chunks for normal and huge allocations. - // Ordered buffers (`!ufbxi_buf.unordered`) never use `chunks[1]` - ufbxi_buf_chunk *chunks[2]; - - // Inline state for non-huge chunks - size_t pos; // < Next offset to allocate from - size_t size; // < Size of the current chunk ie. `chunks[0]->size` (or 0 if `chunks[0] == NULL`) - - size_t num_items; // < Number of individual items pushed to the buffer - - size_t pushed_size; // < Cumulative size of pushed chunks, not tracked across pops - - bool unordered; // < Does not support popping from the buffer - bool clearable; // < Supports clearing the whole buffer even if `unordered` -} ufbxi_buf; - -typedef struct { - ufbxi_buf_chunk *chunk; - size_t pos; - size_t num_items; -} ufbxi_buf_state; - -static ufbxi_noinline void *ufbxi_push_size_new_block(ufbxi_buf *b, size_t size) -{ - bool huge = size >= b->ator->huge_size; - - // Use the second chunk "list" for huge unordered chunks. - // The state of these chunks is not tracked by `ufbxi_buf.pos/size`. - uint32_t list_ix = ((uint32_t)b->unordered & (uint32_t)huge); - - ufbxi_buf_chunk *chunk = b->chunks[list_ix]; - if (chunk) { - if (list_ix == 0) { - // Store the final position for the retired chunk and scan free - // chunks in case we find one the allocation fits in. - b->pushed_size += b->pos; - chunk->pushed_pos = b->pos; - ufbxi_buf_chunk *next = chunk->next; - while (next != NULL) { - ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - chunk = next; - ufbx_assert(b->unordered || chunk->pushed_pos == 0); - chunk->pushed_pos = 0; - if (size <= chunk->size) { - b->chunks[0] = chunk; - b->pos = (uint32_t)size; - b->size = chunk->size; - return chunk->data; - } - next = chunk->next; - } - } else if (b->clearable) { - // Keep track of the `UFBXI_HUGE_MAX_SCAN` largest chunks and - // retain them. Overflowing chunks are freed in `ufbxi_buf_clear()` - size_t align_mask = ufbxi_size_align_mask(size); - ufbxi_buf_chunk *next = chunk; - - ufbxi_buf_chunk *best_chunk = NULL; - size_t best_space = SIZE_MAX; - - // Clearable huge chunks are sorted by descending size. Check the first N - // chunks for reuse and find the place a new block should be inserted if - // no suitable space is found. Chunk ordering in the tail doesn't matter - // as those chunks are never reused. - // Unreachable chunks in the tail are freed in `ufbxi_buf_clear()`. - for (size_t i = 0; next && i < UFBXI_HUGE_MAX_SCAN; i++) { - ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - if (next->size < size) break; - chunk = next; - - // Try to reuse chunks using a best-fit strategy. - size_t pos = ufbxi_align_to_mask(chunk->pushed_pos, align_mask); - size_t space = chunk->size - pos; - if (size <= space) { - if (space < best_space) { - best_chunk = chunk; - best_space = space; - } - } - - next = chunk->next; - } - - // Early return if we found a slot. - if (best_chunk) { - size_t pos = ufbxi_align_to_mask(best_chunk->pushed_pos, align_mask); - best_chunk->pushed_pos = pos + size; - b->pushed_size += size; - return best_chunk->data + pos; - } - } - } - - // Allocate a new chunk, grow `next_size` geometrically but don't double - // the current or previous user sizes if they are larger. - size_t chunk_size, next_size; - - // If `size` is larger than `huge_size` don't grow `next_size` geometrically, - // but use a dedicated allocation. - if (huge) { - next_size = chunk ? chunk->next_size : 4096; - if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max; - chunk_size = size; - } else { - next_size = chunk ? chunk->next_size * 2 : 4096; - if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max; - chunk_size = next_size - sizeof(ufbxi_buf_chunk); - if (chunk_size < size) chunk_size = size; - } - - // Align chunk sizes to 16 bytes - chunk_size = ufbxi_align_to_mask(chunk_size, 0xf); - - ufbxi_buf_chunk *new_chunk = (ufbxi_buf_chunk*)ufbxi_alloc_size(b->ator, 1, sizeof(ufbxi_buf_chunk) + chunk_size); - if (!new_chunk) return NULL; - - new_chunk->prev = chunk; - new_chunk->size = chunk_size; - new_chunk->next_size = next_size; - new_chunk->magic = UFBXI_BUF_CHUNK_IMP_MAGIC; - new_chunk->padding_pos = 0; - new_chunk->pushed_pos = 0; - - // Link the chunk to the list and set it as the active one - if (chunk) { - ufbxi_buf_chunk *next = chunk->next; - if (next) next->prev = new_chunk; - new_chunk->next = next; - chunk->next = new_chunk; - new_chunk->root = chunk->root; - } else { - new_chunk->next = NULL; - new_chunk->root = new_chunk; - } - - if (list_ix == 0) { - b->chunks[0] = new_chunk; - b->pos = size; - b->size = chunk_size; - } else { - ufbxi_buf_chunk *root = b->chunks[1]; - b->pushed_size += size; - if (!root) { - b->chunks[1] = new_chunk; - } else if (root->size < chunk_size) { - // Swap root and self if necessary, we should have bailed out - // in the search loop in the first iteration so `new_chunk` should - // directly follow `root`. - // HACK: This ends up with `chunks[1]` entries having inconsistent - // `ufbxi_buf_chunk.root` pointers but other code only reads `chunks[1].root` - // TODO: Move roots out of the chunks? - ufbx_assert(root->next == new_chunk); - ufbx_assert(new_chunk->prev == root); - if (new_chunk->next) new_chunk->next->prev = root; - root->next = new_chunk->next; - new_chunk->next = root; - new_chunk->prev = NULL; - new_chunk->root = new_chunk; - b->chunks[1] = new_chunk; - } - new_chunk->pushed_pos = size; - } - - return new_chunk->data; -} - -static ufbxi_noinline void *ufbxi_push_size(ufbxi_buf *b, size_t size, size_t n) -{ - // Always succeed with an empty non-NULL buffer for empty allocations - ufbx_assert(size > 0); - if (n == 0) return (void*)ufbxi_zero_size_buffer; - - size_t total = size * n; - if (ufbxi_does_overflow(total, size, n)) return NULL; - - #if defined(UFBX_REGRESSION) - { - ufbxi_allocator *ator = b->ator; - ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); - ator->num_allocs++; - } - #endif - - b->num_items += n; - - // Align to the natural alignment based on the size - size_t align_mask = ufbxi_size_align_mask(size); - size_t pos = ufbxi_align_to_mask(b->pos, align_mask); - - if (!b->unordered && pos != b->pos) { - // Alignment mismatch in an unordered block. Align to 16 bytes to guarantee - // sufficient alignment for anything afterwards and mark the padding. - // If we overflow the current block we don't need to care as the block - // boundaries are not contiguous. - pos = ufbxi_align_to_mask(b->pos, 0xf); - if (total < SIZE_MAX - 16 && total + 16 <= b->size - pos) { - ufbxi_buf_chunk *chunk = b->chunks[0]; - ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + pos); - padding->original_pos = b->pos; - padding->prev_padding = chunk->padding_pos; - chunk->padding_pos = pos + 16 + 1; - b->pos = pos + 16 + total; - return (char*)padding + 16; - } else { - return ufbxi_push_size_new_block(b, total); - } - } else { - // Try to push to the current block. Allocate a new block - // if the aligned size doesn't fit. - if (total <= b->size - pos) { - b->pos = pos + total; - return b->chunks[0]->data + pos; - } else { - return ufbxi_push_size_new_block(b, total); - } - } -} - -static ufbxi_forceinline void *ufbxi_push_size_fast(ufbxi_buf *b, size_t size, size_t n) -{ - // Always succeed with an empty non-NULL buffer for empty allocations - ufbxi_regression_assert(size > 0); - ufbxi_regression_assert(n > 0); - - size_t total = size * n; - ufbxi_regression_assert(!ufbxi_does_overflow(total, size, n)); - - #if defined(UFBX_REGRESSION) - { - ufbxi_allocator *ator = b->ator; - ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); - ator->num_allocs++; - } - #endif - - b->num_items += n; - - // Homogeneous arrays should always be aligned - size_t pos = b->pos; - ufbxi_regression_assert((pos & ufbxi_size_align_mask(size)) == 0); - - // Try to push to the current block. Allocate a new block - // if the aligned size doesn't fit. - if (total <= b->size - pos) { - b->pos = pos + total; - return b->chunks[0]->data + pos; - } else { - return ufbxi_push_size_new_block(b, total); - } -} - -static ufbxi_noinline void *ufbxi_push_size_zero(ufbxi_buf *b, size_t size, size_t n) -{ - void *ptr = ufbxi_push_size(b, size, n); - if (ptr) memset(ptr, 0, size * n); - return ptr; -} - -ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_size_copy(ufbxi_buf *b, size_t size, size_t n, const void *data) -{ - // Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL` - ufbx_assert(size > 0); - if (n == 0) return (void*)ufbxi_zero_size_buffer; - - ufbx_assert(data); - void *ptr = ufbxi_push_size(b, size, n); - if (ptr) memcpy(ptr, data, size * n); - return ptr; -} - -ufbxi_nodiscard static ufbxi_forceinline void *ufbxi_push_size_copy_fast(ufbxi_buf *b, size_t size, size_t n, const void *data) -{ - // Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL` - ufbx_assert(size > 0); - if (n == 0) return (void*)ufbxi_zero_size_buffer; - - ufbx_assert(data); - void *ptr = ufbxi_push_size_fast(b, size, n); - if (ptr) memcpy(ptr, data, size * n); - return ptr; -} - -static ufbxi_noinline void ufbxi_buf_free_unused(ufbxi_buf *b) -{ - ufbx_assert(!b->unordered); - - ufbxi_buf_chunk *chunk = b->chunks[0]; - if (!chunk) return; - - ufbxi_buf_chunk *next = chunk->next; - while (next) { - ufbxi_buf_chunk *to_free = next; - next = next->next; - ufbx_assert(to_free->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - to_free->magic = 0; - ufbxi_free_size(b->ator, 1, to_free, sizeof(ufbxi_buf_chunk) + to_free->size); - } - chunk->next = NULL; - - while (b->pos == 0 && chunk) { - ufbxi_buf_chunk *prev = chunk->prev; - ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - chunk->magic = 0; - ufbxi_free_size(b->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size); - chunk = prev; - b->chunks[0] = prev; - if (prev) { - prev->next = NULL; - b->pos = prev->pushed_pos; - b->size = prev->size; - } else { - b->pos = 0; - b->size = 0; - } - } -} - -static ufbxi_noinline void ufbxi_pop_size(ufbxi_buf *b, size_t size, size_t n, void *dst, bool peek) -{ - ufbx_assert(!b->unordered); - ufbx_assert(size > 0); - ufbx_assert(b->num_items >= n); - if (!peek) b->num_items -= n; - - char *ptr = (char*)dst; - size_t bytes_left = size * n; - - // We've already pushed this, it better not overflow - ufbx_assert(!ufbxi_does_overflow(bytes_left, size, n)); - - if (ptr) { - ptr += bytes_left; - size_t pos = b->pos; - ufbxi_buf_chunk *chunk = b->chunks[0]; - for (;;) { - if (bytes_left <= pos) { - // Rest of the data is in this single chunk - pos -= bytes_left; - if (!peek) b->pos = pos; - ptr -= bytes_left; - if (bytes_left > 0) { - memcpy(ptr, chunk->data + pos, bytes_left); - } - break; - } else { - // Pop the whole chunk - ptr -= pos; - bytes_left -= pos; - memcpy(ptr, chunk->data, pos); - if (!peek) { - chunk->pushed_pos = 0; - chunk = chunk->prev; - b->chunks[0] = chunk; - b->size = chunk->size; - } else { - chunk = chunk->prev; - } - pos = chunk->pushed_pos; - } - } - } else { - size_t pos = b->pos; - ufbxi_buf_chunk *chunk = b->chunks[0]; - for (;;) { - if (bytes_left <= pos) { - // Rest of the data is in this single chunk - pos -= bytes_left; - if (!peek) b->pos = pos; - break; - } else { - // Pop the whole chunk - bytes_left -= pos; - if (!peek) { - chunk->pushed_pos = 0; - chunk = chunk->prev; - b->chunks[0] = chunk; - b->size = chunk->size; - } else { - chunk = chunk->prev; - } - pos = chunk->pushed_pos; - } - } - } - - if (!peek) { - // Check if we need to rewind past some alignment padding - ufbxi_buf_chunk *chunk = b->chunks[0]; - if (chunk) { - size_t pos = b->pos, padding_pos = chunk->padding_pos; - if (pos < padding_pos) { - ufbx_assert(pos + 1 == padding_pos); - ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + padding_pos - 1 - 16); - b->pos = padding->original_pos; - chunk->padding_pos = padding->prev_padding; - } - } - - // Immediately free popped items if all the allocations are huge - // as it means we want to have dedicated allocations for each push. - if (b->ator->huge_size <= 1) { - ufbxi_buf_free_unused(b); - } - } -} - -static ufbxi_noinline void *ufbxi_push_pop_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n) -{ - void *data = ufbxi_push_size(dst, size, n); - if (!data) return NULL; - ufbxi_pop_size(src, size, n, data, false); - return data; -} - -static ufbxi_noinline void *ufbxi_push_peek_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n) -{ - void *data = ufbxi_push_size(dst, size, n); - if (!data) return NULL; - ufbxi_pop_size(src, size, n, data, true); - return data; -} - -static ufbxi_noinline void ufbxi_buf_free(ufbxi_buf *buf) -{ - ufbxi_nounroll for (size_t i = 0; i < 2; i++) { - ufbxi_buf_chunk *chunk = buf->chunks[i]; - if (chunk) { - chunk = chunk->root; - while (chunk) { - ufbxi_buf_chunk *next = chunk->next; - ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - chunk->magic = 0; - ufbxi_free_size(buf->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size); - chunk = next; - } - } - buf->chunks[i] = NULL; - } - buf->pos = 0; - buf->size = 0; - buf->num_items = 0; -} - -static ufbxi_noinline void ufbxi_buf_clear(ufbxi_buf *buf) -{ - // Only unordered or clearable buffers can be cleared - ufbx_assert(!buf->unordered || buf->clearable); - - // Free the memory if using ASAN - if (buf->ator->huge_size <= 1) { - ufbxi_buf_free(buf); - return; - } - - // Reset the non-huge chunks as `chunk->next` is always free. - ufbxi_buf_chunk *chunk = buf->chunks[0]; - if (chunk) { - ufbxi_buf_chunk *root = chunk->root; - buf->chunks[0] = root; - buf->pos = 0; - buf->size = root->size; - } - buf->num_items = 0; - buf->pushed_size = 0; - - // Huge chunks are always sorted by descending size and - // `chunks[1]` points to the largest one. - ufbxi_buf_chunk *huge = buf->chunks[1]; - if (huge) { - // Reset the first N ones that are tracked. - for (size_t i = 0; huge && i < UFBXI_HUGE_MAX_SCAN; i++) { - huge->pushed_pos = 0; - huge = huge->next; - } - - // Got unreachable tail that should be freed: Unlink from the last - // tracked chunk and free the rest. - if (huge) { - huge->prev->next = NULL; - while (huge) { - ufbxi_buf_chunk *next = huge->next; - ufbx_assert(huge->magic == UFBXI_BUF_CHUNK_IMP_MAGIC); - huge->magic = 0; - ufbxi_free_size(buf->ator, 1, huge, sizeof(ufbxi_buf_chunk) + huge->size); - huge = next; - } - } - } -} - -#define ufbxi_push(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size((b), sizeof(type), (n))) -#define ufbxi_push_zero(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_zero((b), sizeof(type), (n))) -#define ufbxi_push_copy(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy((b), sizeof(type), (n), (data))) -#define ufbxi_push_copy_fast(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy_fast((b), sizeof(type), (n), (data))) -#define ufbxi_push_fast(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_fast((b), sizeof(type), (n))) -#define ufbxi_pop(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), false) -#define ufbxi_peek(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), true) -#define ufbxi_push_pop(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_pop_size((dst), (src), sizeof(type), (n))) -#define ufbxi_push_peek(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_peek_size((dst), (src), sizeof(type), (n))) - -// -- Hash map -// -// The actual element comparison is left to the user of `ufbxi_map`, see usage below. -// -// NOTES: -// ufbxi_map_insert() does not support duplicate values, use find first if duplicates are possible! -// Inserting duplicate elements fails with an assertion if `UFBX_REGRESSION` is enabled. - -typedef struct ufbxi_aa_node ufbxi_aa_node; - -typedef int ufbxi_cmp_fn(void *user, const void *a, const void *b); - -struct ufbxi_aa_node { - ufbxi_aa_node *left, *right; - uint32_t level; - uint32_t index; -}; - -typedef struct { - ufbxi_allocator *ator; - size_t data_size; - - void *items; - uint64_t *entries; - uint32_t mask; - - uint32_t capacity; - uint32_t size; - - ufbxi_cmp_fn *cmp_fn; - void *cmp_user; - - ufbxi_buf aa_buf; - ufbxi_aa_node *aa_root; - -} ufbxi_map; - -static ufbxi_noinline void ufbxi_map_init(ufbxi_map *map, ufbxi_allocator *ator, ufbxi_cmp_fn *cmp_fn, void *cmp_user) -{ - map->ator = ator; -#if defined(UFBX_REGRESSION) - // HACK: Maps contain pointers that are not stable between runs, in regression - // mode this causes instability in allocation patterns due to different AA trees - // being built, which is a problem in fuzz checks that need to have deterministic - // allocation counts. We can work around this using a local allocator that doesn't - // count the allocations. - { - ufbxi_allocator *regression_ator = (ufbxi_allocator*)ufbx_malloc(sizeof(ufbxi_allocator)); - ufbx_assert(regression_ator); - memset(regression_ator, 0, sizeof(ufbxi_allocator)); - regression_ator->name = "regression"; - regression_ator->error = ator->error; - regression_ator->huge_size = ator->huge_size; - regression_ator->max_size = SIZE_MAX; - regression_ator->max_allocs = SIZE_MAX; - regression_ator->chunk_max = 0x1000000; - map->aa_buf.ator = regression_ator; - } -#else - map->aa_buf.ator = ator; -#endif - map->cmp_fn = cmp_fn; - map->cmp_user = cmp_user; -} - -static ufbxi_noinline void ufbxi_map_free(ufbxi_map *map) -{ -#if defined(UFBX_REGRESSION) - ufbxi_allocator *regression_ator = map->aa_buf.ator; -#endif - - ufbxi_buf_free(&map->aa_buf); - ufbxi_free(map->ator, char, map->entries, map->data_size); - map->entries = NULL; - map->items = NULL; - map->aa_root = NULL; - map->mask = map->capacity = map->size = 0; - -#if defined(UFBX_REGRESSION) - if (regression_ator) { - ufbxi_free_ator(regression_ator); - ufbx_free(regression_ator, sizeof(ufbxi_allocator)); - } -#endif -} - -// Recursion limit: log2(2^64 / sizeof(ufbxi_aa_node)) -static ufbxi_noinline ufbxi_aa_node *ufbxi_aa_tree_insert(ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size) - ufbxi_recursive_function(ufbxi_aa_node *, ufbxi_aa_tree_insert, (map, node, value, index, item_size), 59, - (ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size)) -{ - if (!node) { - ufbxi_aa_node *new_node = ufbxi_push(&map->aa_buf, ufbxi_aa_node, 1); - if (!new_node) return NULL; - new_node->left = NULL; - new_node->right = NULL; - new_node->level = 1; - new_node->index = index; - return new_node; - } - - void *entry = (char*)map->items + node->index * item_size; - int cmp = map->cmp_fn(map->cmp_user, value, entry); - if (cmp < 0) { - node->left = ufbxi_aa_tree_insert(map, node->left, value, index, item_size); - } else if (cmp >= 0) { - node->right = ufbxi_aa_tree_insert(map, node->right, value, index, item_size); - } - - if (node->left && node->left->level == node->level) { - ufbxi_aa_node *left = node->left; - node->left = left->right; - left->right = node; - node = left; - } - - if (node->right && node->right->right && node->right->right->level == node->level) { - ufbxi_aa_node *right = node->right; - node->right = right->left; - right->left = node; - right->level += 1; - node = right; - } - - return node; -} - -static ufbxi_noinline void *ufbxi_aa_tree_find(ufbxi_map *map, const void *value, size_t item_size) -{ - ufbxi_aa_node *node = map->aa_root; - while (node) { - void *entry = (char*)map->items + node->index * item_size; - int cmp = map->cmp_fn(map->cmp_user, value, entry); - if (cmp < 0) { - node = node->left; - } else if (cmp > 0) { - node = node->right; - } else { - return entry; - } - } - return NULL; -} - -static ufbxi_noinline bool ufbxi_map_grow_size_imp(ufbxi_map *map, size_t item_size, size_t min_size) -{ - ufbx_assert(min_size > 0); - const double load_factor = 0.7; - - // Find the lowest power of two size that fits `min_size` within `load_factor` - size_t num_entries = map->mask + 1; - size_t new_size = (size_t)((double)num_entries * load_factor); - if (min_size < map->capacity + 1) min_size = map->capacity + 1; - while (new_size < min_size) { - num_entries *= 2; - new_size = (size_t)((double)num_entries * load_factor); - } - - // Check for overflow - ufbxi_check_return_err(map->ator->error, SIZE_MAX / num_entries > sizeof(uint64_t), false); - size_t alloc_size = num_entries * sizeof(uint64_t); - - // Allocate a combined entry/item memory block - ufbxi_check_return_err(map->ator->error, (SIZE_MAX - alloc_size) / new_size > item_size, false); - size_t data_size = alloc_size + new_size * item_size; - - char *data = ufbxi_alloc(map->ator, char, data_size); - ufbxi_check_return_err(map->ator->error, data, false); - - // Copy the previous user items over - uint64_t *old_entries = map->entries; - uint64_t *new_entries = (uint64_t*)data; - void *new_items = data + alloc_size; - if (map->size > 0) { - memcpy(new_items, map->items, item_size * map->size); - } - - // Re-hash the entries - uint32_t old_mask = map->mask; - uint32_t new_mask = (uint32_t)(num_entries) - 1; - memset(new_entries, 0, sizeof(uint64_t) * num_entries); - if (old_mask) { - for (uint32_t i = 0; i <= old_mask; i++) { - uint64_t entry, new_entry = old_entries[i]; - if (!new_entry) continue; - - // Reconstruct the hash of the old entry at `i` - uint32_t old_scan = (uint32_t)(new_entry & old_mask) - 1; - uint32_t hash = ((uint32_t)new_entry & ~old_mask) | ((i - old_scan) & old_mask); - uint32_t slot = hash & new_mask; - new_entry &= ~(uint64_t)new_mask; - - // Scan forward until we find an empty slot, potentially swapping - // `new_element` if it has a shorter scan distance (Robin Hood). - uint32_t scan = 1; - while ((entry = new_entries[slot]) != 0) { - uint32_t entry_scan = (uint32_t)(entry & new_mask); - if (entry_scan < scan) { - new_entries[slot] = new_entry + scan; - new_entry = (entry & ~(uint64_t)new_mask); - scan = entry_scan; - } - scan += 1; - slot = (slot + 1) & new_mask; - } - new_entries[slot] = new_entry + scan; - } - } - - // And finally free the previous allocation - ufbxi_free(map->ator, char, (char*)old_entries, map->data_size); - map->items = new_items; - map->data_size = data_size; - map->entries = new_entries; - map->mask = new_mask; - map->capacity = (uint32_t)new_size; - - return true; -} - -static ufbxi_forceinline bool ufbxi_map_grow_size(ufbxi_map *map, size_t size, size_t min_size) -{ - #if defined(UFBX_REGRESSION) - { - ufbxi_allocator *ator = map->ator; - ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded"); - ator->num_allocs++; - } - #endif - - if (map->size < map->capacity && map->capacity >= min_size) return true; - return ufbxi_map_grow_size_imp(map, size, min_size); -} - -static ufbxi_noinline void *ufbxi_map_find_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value) -{ - uint64_t *entries = map->entries; - uint32_t mask = map->mask, scan = 0; - - uint32_t ref = hash & ~mask; - if (!mask || scan == UINT32_MAX) return 0; - - // Scan entries until we find an exact match of the hash or until we hit - // an element that has lower scan distance than our search (Robin Hood). - // The encoding guarantees that zero slots also terminate with the same test. - for (;;) { - uint64_t entry = entries[(hash + scan) & mask]; - scan += 1; - if ((uint32_t)entry == ref + scan) { - uint32_t index = (uint32_t)(entry >> 32u); - void *data = (char*)map->items + size * index; - int cmp = map->cmp_fn(map->cmp_user, value, data); - if (cmp == 0) return data; - } else if ((entry & mask) < scan) { - if (map->aa_root) { - return ufbxi_aa_tree_find(map, value, size); - } else { - return NULL; - } - } - } -} - -static ufbxi_noinline void *ufbxi_map_insert_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value) -{ - if (!ufbxi_map_grow_size(map, size, 64)) return NULL; - - ufbxi_regression_assert(ufbxi_map_find_size(map, size, hash, value) == NULL); - - uint32_t index = map->size++; - - uint64_t *entries = map->entries; - uint32_t mask = map->mask; - - // Scan forward until we find an empty slot, potentially swapping - // `new_element` if it has a shorter scan distance (Robin Hood). - uint32_t slot = hash & mask; - uint64_t entry, new_entry = (uint64_t)index << 32u | (hash & ~mask); - uint32_t scan = 1; - while ((entry = entries[slot]) != 0) { - uint32_t entry_scan = (uint32_t)(entry & mask); - if (entry_scan < scan) { - entries[slot] = new_entry + scan; - new_entry = (entry & ~(uint64_t)mask); - scan = entry_scan; - } - scan += 1; - slot = (slot + 1) & mask; - - if (scan > UFBXI_MAP_MAX_SCAN) { - uint32_t new_index = (uint32_t)(new_entry >> 32u); - const void *new_value = new_index == index ? value : (const void*)((char*)map->items + size * new_index); - map->aa_root = ufbxi_aa_tree_insert(map, map->aa_root, new_value, new_index, size); - return (char*)map->items + size * index; - } - } - entries[slot] = new_entry + scan; - - return (char*)map->items + size * index; -} - -#define ufbxi_map_grow(map, type, min_size) ufbxi_map_grow_size((map), sizeof(type), (min_size)) -#define ufbxi_map_find(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_find_size((map), sizeof(type), (hash), (value))) -#define ufbxi_map_insert(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_insert_size((map), sizeof(type), (hash), (value))) - -static int ufbxi_map_cmp_uint64(void *user, const void *va, const void *vb) -{ - (void)user; - uint64_t a = *(const uint64_t*)va, b = *(const uint64_t*)vb; - if (a < b) return -1; - if (a > b) return +1; - return 0; -} - -static int ufbxi_map_cmp_const_char_ptr(void *user, const void *va, const void *vb) -{ - (void)user; - const char *a = *(const char **)va, *b = *(const char **)vb; - if (a < b) return -1; - if (a > b) return +1; - return 0; -} - -static int ufbxi_map_cmp_uintptr(void *user, const void *va, const void *vb) -{ - (void)user; - uintptr_t a = *(const uintptr_t*)va, b = *(const uintptr_t*)vb; - if (a < b) return -1; - if (a > b) return +1; - return 0; -} - -typedef struct { - uintptr_t ptr; - uint64_t id; -} ufbxi_ptr_id; - -static int ufbxi_map_cmp_ptr_id(void *user, const void *va, const void *vb) -{ - (void)user; - ufbxi_ptr_id a = *(const ufbxi_ptr_id*)va, b = *(const ufbxi_ptr_id*)vb; - if (a.id != b.id) return a.id < b.id ? -1 : +1; - if (a.ptr != b.ptr) return a.ptr < b.ptr ? -1 : +1; - return 0; -} - -// -- Hash functions - -static ufbxi_noinline uint32_t ufbxi_hash_string(const char *str, size_t length) -{ - uint32_t hash = (uint32_t)length; - uint32_t seed = UINT32_C(0x9e3779b9); - if (length >= 4) { - do { - uint32_t word = ufbxi_read_u32(str); - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - str += 4; - length -= 4; - } while (length >= 4); - - uint32_t word = ufbxi_read_u32(str + length - 4); - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - } else { - uint32_t word = 0; - if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0; - if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8; - if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16; - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - } - hash ^= hash >> 16; - hash *= UINT32_C(0x7feb352d); - hash ^= hash >> 15; - return hash; -} - -// NOTE: _Must_ match `ufbxi_hash_string()` -static ufbxi_noinline uint32_t ufbxi_hash_string_check_ascii(const char *str, size_t length, bool *p_non_ascii) -{ - uint32_t ascii_mask = 0; - uint32_t zero_mask = 0; - - ufbx_assert(length > 0); - - uint32_t hash = (uint32_t)length; - uint32_t seed = UINT32_C(0x9e3779b9); - if (length >= 4) { - do { - uint32_t word = ufbxi_read_u32(str); - ascii_mask |= word; - zero_mask |= UINT32_C(0x80808080) - word; - - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - str += 4; - length -= 4; - } while (length >= 4); - - uint32_t word = ufbxi_read_u32(str + length - 4); - ascii_mask |= word; - zero_mask |= UINT32_C(0x80808080) - word; - - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - } else { - uint32_t word = 0; - if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0; - if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8; - if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16; - - ascii_mask |= word; - zero_mask |= (UINT32_C(0x80808080) >> ((4u - length) * 8u)) - word; - - hash = ((hash << 5u | hash >> 27u) ^ word) * seed; - } - - // If any character has high bit set or is zero we're not ASCII - if (((ascii_mask | zero_mask) & 0x80808080u) != 0) { - *p_non_ascii = true; - } - - hash ^= hash >> 16; - hash *= UINT32_C(0x7feb352d); - hash ^= hash >> 15; - - return hash; -} - -static ufbxi_unused ufbxi_forceinline uint32_t ufbxi_hash32(uint32_t x) -{ - x ^= x >> 16; - x *= UINT32_C(0x7feb352d); - x ^= x >> 15; - x *= UINT32_C(0x846ca68b); - x ^= x >> 16; - return x; -} - -static ufbxi_unused ufbxi_forceinline uint32_t ufbxi_hash64(uint64_t x) -{ - x ^= x >> 32; - x *= UINT64_C(0xd6e8feb86659fd93); - x ^= x >> 32; - x *= UINT64_C(0xd6e8feb86659fd93); - x ^= x >> 32; - return (uint32_t)x; -} - -static ufbxi_forceinline uint32_t ufbxi_hash_uptr(uintptr_t ptr) -{ -#if UFBX_POINTER_SIZE == 8 - return ufbxi_hash64((uint64_t)ptr); -#elif UFBX_POINTER_SIZE == 4 - return ufbxi_hash32((uint32_t)ptr); -#else - if (sizeof(ptr) == 8) return ufbxi_hash64((uint64_t)ptr); - else if (sizeof(ptr) == 4) return ufbxi_hash32((uint32_t)ptr); - else return ufbxi_hash_string((const char*)&ptr, sizeof(uintptr_t)); -#endif -} - -static ufbxi_forceinline uint32_t ufbxi_hash_ptr_id(ufbxi_ptr_id id) -{ - // Trivial reduction is fine: Only `ptr` or `id` is defined. - return ufbxi_hash_uptr(id.ptr) ^ ufbxi_hash64(id.id); -} - -#define ufbxi_hash_ptr(ptr) ufbxi_hash_uptr((uintptr_t)(ptr)) - -// -- Warnings - -typedef struct { - ufbx_error *error; - ufbxi_buf *result; - ufbxi_buf tmp_stack; - uint32_t deferred_element_id_plus_one; - // Separate lists for specific and non-specific warnings - ufbx_warning *prev_warnings[UFBX_WARNING_TYPE_COUNT][2]; -} ufbxi_warnings; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_vwarnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, va_list args) -{ - if (!ws) return 1; - - // HACK(warning-element): Encode potential deferred element ID into `ufbx_warning.element_id`, - // `ws->element_id_index_plus_one` contains index to `uc->tmp_element_id`. - // Tag deferred indices with the high bit. - if (element_id == ~0u && ws->deferred_element_id_plus_one > 0) { - element_id = (ws->deferred_element_id_plus_one - 1) | 0x80000000u; - } - - uint32_t has_element_id = element_id != ~0u; - if (type >= UFBX_WARNING_TYPE_FIRST_DEDUPLICATED) { - ufbx_warning *prev = ws->prev_warnings[type][has_element_id]; - if (prev && prev->element_id == element_id) { - prev->count++; - return 1; - } - } - - char desc[256]; - size_t desc_len = (size_t)ufbxi_vsnprintf(desc, sizeof(desc), fmt, args); - - ufbxi_clean_string_utf8(desc, desc_len); - - char *desc_copy = ufbxi_push_copy(ws->result, char, desc_len + 1, desc); - ufbxi_check_err(ws->error, desc_copy); - - ufbx_warning *warning = ufbxi_push(&ws->tmp_stack, ufbx_warning, 1); - ufbxi_check_err(ws->error, warning); - - warning->type = type; - warning->description.data = desc_copy; - warning->description.length = desc_len; - warning->element_id = element_id; - warning->count = 1; - ws->prev_warnings[type][has_element_id] = warning; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_warnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, ...) -{ - // NOTE: `ws` may be `NULL` here, handled by `ufbxi_vwarnf()` - va_list args; // ufbxi_uninit - va_start(args, fmt); - int ok = ufbxi_vwarnf_imp(ws, type, element_id, fmt, args); - va_end(args); - return ok; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_pop_warnings(ufbxi_warnings *ws, ufbx_warning_list *warnings, bool *p_has_warning) -{ - warnings->count = ws->tmp_stack.num_items; - warnings->data = ufbxi_push_pop(ws->result, &ws->tmp_stack, ufbx_warning, warnings->count); - ufbxi_check_err(ws->error, warnings->data); - ufbxi_for_list(ufbx_warning, warning, *warnings) { - p_has_warning[warning->type] = true; - } - return 1; -} - -// -- String pool - -// All strings found in FBX files are interned for deduplication and fast -// comparison. Our fixed internal strings (`ufbxi_String`) are considered the -// canonical pointers for said strings so we can compare them by address. - -typedef struct { - ufbx_error *error; - ufbxi_buf buf; // < Buffer for the actual string data - ufbxi_map map; // < Map of `ufbxi_string` - size_t initial_size; // < Number of initial entries - char *temp_str; // < Temporary string buffer of `temp_cap` - size_t temp_cap; // < Capacity of the temporary buffer - ufbx_unicode_error_handling error_handling; - ufbxi_warnings *warnings; -} ufbxi_string_pool; - -typedef struct { - const char *raw_data; // < UTF-8 data follows at `raw_length+1` if `utf8_length > 0` - uint32_t raw_length; // < Length of the non-sanitized original string - uint32_t utf8_length; // < Length of sanitized UTF-8 string (or zero) -} ufbxi_sanitized_string; - -static ufbxi_forceinline bool ufbxi_str_equal(ufbx_string a, ufbx_string b) -{ - return a.length == b.length && !memcmp(a.data, b.data, a.length); -} - -static ufbxi_forceinline bool ufbxi_str_less(ufbx_string a, ufbx_string b) -{ - size_t len = ufbxi_min_sz(a.length, b.length); - int cmp = memcmp(a.data, b.data, len); - if (cmp != 0) return cmp < 0; - return a.length < b.length; -} - -static ufbxi_forceinline int ufbxi_str_cmp(ufbx_string a, ufbx_string b) -{ - size_t len = ufbxi_min_sz(a.length, b.length); - int cmp = memcmp(a.data, b.data, len); - if (cmp != 0) return cmp; - if (a.length != b.length) return a.length < b.length ? -1 : 1; - return 0; -} - -static ufbxi_forceinline ufbx_string ufbxi_str_c(const char *str) -{ - ufbx_string s = { str, strlen(str) }; - return s; -} - -static ufbxi_noinline uint32_t ufbxi_get_concat_key(const ufbx_string *parts, size_t num_parts) -{ - uint32_t key = 0, shift = 32; - ufbxi_for(const ufbx_string, part, parts, num_parts) { - size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data); - for (size_t i = 0; i < length; i++) { - shift -= 8; - key |= (uint32_t)(uint8_t)part->data[i] << shift; - if (shift == 0) return key; - } - } - return key; -} - -static ufbxi_noinline int ufbxi_concat_str_cmp(const ufbx_string *ref, const ufbx_string *parts, size_t num_parts) -{ - const char *ptr = ref->data, *end = ptr + ref->length; - ufbxi_for(const ufbx_string, part, parts, num_parts) { - size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data); - size_t to_cmp = ufbxi_min_sz(ufbxi_to_size(end - ptr), length); - int cmp = to_cmp > 0 ? memcmp(ptr, part->data, to_cmp) : 0; - if (cmp != 0) return cmp; - if (to_cmp != length) return -1; - ptr += length; - } - return ptr == end ? 0 : +1; -} - -static ufbxi_forceinline bool ufbxi_starts_with(ufbx_string str, ufbx_string prefix) -{ - return str.length >= prefix.length && !memcmp(str.data, prefix.data, prefix.length); -} - -static ufbxi_forceinline bool ufbxi_ends_with(ufbx_string str, ufbx_string suffix) -{ - return str.length >= suffix.length && !memcmp(str.data + str.length - suffix.length, suffix.data, suffix.length); -} - -static ufbxi_noinline bool ufbxi_remove_prefix_len(ufbx_string *str, const char *prefix, size_t prefix_len) -{ - ufbx_string prefix_str = { prefix, prefix_len }; - if (ufbxi_starts_with(*str, prefix_str)) { - str->data += prefix_len; - str->length -= prefix_len; - return true; - } - return false; -} - -static ufbxi_noinline bool ufbxi_remove_suffix_len(ufbx_string *str, const char *suffix, size_t suffix_len) -{ - ufbx_string suffix_str = { suffix, suffix_len }; - if (ufbxi_ends_with(*str, suffix_str)) { - str->length -= suffix_len; - return true; - } - return false; -} - -static ufbxi_forceinline bool ufbxi_remove_prefix_str(ufbx_string *str, ufbx_string prefix) -{ - return ufbxi_remove_prefix_len(str, prefix.data, prefix.length); -} - -static ufbxi_forceinline bool ufbxi_remove_suffix_c(ufbx_string *str, const char *suffix) -{ - return ufbxi_remove_suffix_len(str, suffix, strlen(suffix)); -} - -static int ufbxi_map_cmp_string(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_string *a = (const ufbx_string*)va, *b = (const ufbx_string*)vb; - return ufbxi_str_cmp(*a, *b); -} - -static ufbxi_forceinline ufbx_string ufbxi_safe_string(const char *data, size_t length) -{ - ufbx_string str = { length > 0 ? data : ufbxi_empty_char, length }; - return str; -} - -static void ufbxi_string_pool_temp_free(ufbxi_string_pool *pool) -{ - ufbxi_free(pool->map.ator, char, pool->temp_str, pool->temp_cap); - ufbxi_map_free(&pool->map); -} - -ufbxi_nodiscard static size_t ufbxi_add_replacement_char(ufbxi_string_pool *pool, char *dst, char c) -{ - switch (pool->error_handling) { - - case UFBX_UNICODE_ERROR_HANDLING_REPLACEMENT_CHARACTER: - dst[0] = (char)(uint8_t)0xefu; - dst[1] = (char)(uint8_t)0xbfu; - dst[2] = (char)(uint8_t)0xbdu; - return 3; - - case UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE: - dst[0] = '_'; - return 1; - - case UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK: - dst[0] = '?'; - return 1; - - case UFBX_UNICODE_ERROR_HANDLING_REMOVE: - return 0; - - case UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE: - dst[0] = c; - return 1; - - default: - return 0; - - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_sanitize_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, size_t valid_length, bool push_both) -{ - // Handle only invalid cases here - ufbx_assert(valid_length < length); - ufbxi_check_err_msg(pool->error, pool->error_handling != UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING, "Invalid UTF-8"); - ufbxi_check_err(pool->error, ufbxi_warnf_imp(pool->warnings, UFBX_WARNING_BAD_UNICODE, ~0u, "Bad UTF-8 string")); - - size_t index = valid_length; - size_t dst_len = index; - if (push_both) { - // Copy both the full raw string and the initial valid part - ufbxi_check_err(pool->error, length <= SIZE_MAX / 2 - 64); - ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length * 2 + 64)); - memcpy(pool->temp_str, str, length); - pool->temp_str[length] = '\0'; - memcpy(pool->temp_str + length + 1, str, index); - dst_len += length + 1; - } else { - - // Copy the initial valid part - ufbxi_check_err(pool->error, length <= SIZE_MAX - 64); - ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length + 64)); - memcpy(pool->temp_str, str, index); - } - - char *dst = pool->temp_str; - while (index < length) { - uint8_t c = (uint8_t)str[index]; - size_t left = length - index; - - // Not optimal but not the worst thing ever - if (pool->temp_cap - dst_len < 16) { - ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, dst_len + 16)); - dst = pool->temp_str; - } - - if ((c & 0x80) == 0) { - if (c != 0) { - dst[dst_len] = (char)c; - dst_len += 1; - index += 1; - continue; - } - } else if ((c & 0xe0) == 0xc0 && left >= 2) { - uint8_t t0 = (uint8_t)str[index + 1]; - uint32_t code = (uint32_t)c << 8 | (uint32_t)t0 << 0; - if ((code & 0xc0) == 0x80 && code >= 0xc280) { - dst[dst_len + 0] = (char)c; - dst[dst_len + 1] = (char)t0; - dst_len += 2; - index += 2; - continue; - } - } else if ((c & 0xf0) == 0xe0 && left >= 3) { - uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2]; - uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1; - if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) { - dst[dst_len + 0] = (char)c; - dst[dst_len + 1] = (char)t0; - dst[dst_len + 2] = (char)t1; - dst_len += 3; - index += 3; - continue; - } - } else if ((c & 0xf8) == 0xf0 && left >= 4) { - uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3]; - uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2; - if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) { - dst[dst_len + 0] = (char)c; - dst[dst_len + 1] = (char)t0; - dst[dst_len + 2] = (char)t1; - dst[dst_len + 3] = (char)t2; - dst_len += 4; - index += 4; - continue; - } - } - - dst_len += ufbxi_add_replacement_char(pool, dst + dst_len, (char)c); - index++; - } - - // Sanitized strings are packed to 32-bit integers, in practice this should be fine - // as strings are limited to 32-bit length in FBX itself. - // The only problem case is a massive string that is full of unicode errors, ie. - // >1GB binary blob, but these should never be sanitized. - ufbxi_check_err(pool->error, length <= UINT32_MAX); - sanitized->raw_data = pool->temp_str; - if (push_both) { - // Reserve `UINT32_MAX` for invalid UTF-8 without sanitization - size_t utf8_length = dst_len - (length + 1); - ufbxi_check_err(pool->error, utf8_length < UINT32_MAX); - sanitized->raw_length = (uint32_t)length; - sanitized->utf8_length = (uint32_t)utf8_length; - } else { - ufbxi_check_err(pool->error, dst_len <= UINT32_MAX); - sanitized->raw_length = (uint32_t)dst_len; - sanitized->utf8_length = 0; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_sanitized_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, uint32_t hash, bool raw) -{ - ufbxi_regression_assert(hash == ufbxi_hash_string(str, length)); - - ufbxi_check_err(pool->error, length <= UINT32_MAX); - ufbxi_check_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size)); - - const char *total_data = str; - size_t total_length = length; - - sanitized->raw_length = (uint32_t)length; - sanitized->utf8_length = 0; - - if (!raw) { - size_t valid_length = ufbxi_utf8_valid_length(str, length); - if (valid_length != length) { - ufbxi_check_err(pool->error, ufbxi_sanitize_string(pool, sanitized, str, length, valid_length, true)); - total_data = sanitized->raw_data; - total_length = sanitized->raw_length + sanitized->utf8_length + 1; - hash = ufbxi_hash_string(str, length); - } - } - - ufbx_string ref = { total_data, total_length }; - - ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref); - if (entry) { - sanitized->raw_data = entry->data; - } else { - entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref); - ufbxi_check_err(pool->error, entry); - entry->length = total_length; - char *dst = ufbxi_push(&pool->buf, char, total_length + 1); - ufbxi_check_err(pool->error, dst); - memcpy(dst, total_data, total_length); - dst[total_length] = '\0'; - entry->data = dst; - sanitized->raw_data = dst; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_push_string_imp(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool copy, bool raw) -{ - if (length == 0) return ufbxi_empty_char; - - ufbxi_check_return_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size), NULL); - - uint32_t hash; - if (raw) { - hash = ufbxi_hash_string(str, length); - } else { - bool non_ascii = false; - hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); - if (non_ascii) { - size_t valid_length = ufbxi_utf8_valid_length(str, length); - if (valid_length < length) { - ufbxi_sanitized_string sanitized; - ufbxi_check_return_err(pool->error, ufbxi_sanitize_string(pool, &sanitized, str, length, valid_length, false), NULL); - str = sanitized.raw_data; - length = sanitized.raw_length; - hash = ufbxi_hash_string(str, length); - *p_out_length = length; - } - } - } - - ufbx_string ref = { str, length }; - - ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref); - if (entry) return entry->data; - entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref); - ufbxi_check_return_err(pool->error, entry, NULL); - entry->length = length; - if (copy) { - char *dst = ufbxi_push(&pool->buf, char, length + 1); - ufbxi_check_return_err(pool->error, dst, NULL); - memcpy(dst, str, length); - dst[length] = '\0'; - entry->data = dst; - } else { - entry->data = str; - } - return entry->data; -} - -ufbxi_nodiscard static ufbxi_forceinline const char *ufbxi_push_string(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool raw) -{ - return ufbxi_push_string_imp(pool, str, length, p_out_length, true, raw); -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_push_string_place(ufbxi_string_pool *pool, const char **p_str, size_t *p_length, bool raw) -{ - const char *str = *p_str; - size_t length = *p_length; - ufbxi_check_err(pool->error, str || length == 0); - str = ufbxi_push_string(pool, str, length, p_length, raw); - ufbxi_check_err(pool->error, str); - *p_str = str; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_str(ufbxi_string_pool *pool, ufbx_string *p_str, bool raw) -{ - ufbxi_check_err(pool->error, p_str); - return ufbxi_push_string_place(pool, &p_str->data, &p_str->length, raw); -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_blob(ufbxi_string_pool *pool, ufbx_blob *p_blob, bool raw) -{ - if (p_blob->size == 0) { - p_blob->data = NULL; - return 1; - } - p_blob->data = ufbxi_push_string(pool, (const char*)p_blob->data, p_blob->size, &p_blob->size, raw); - ufbxi_check_err(pool->error, p_blob->data); - return 1; -} - -// -- String constants -// -// All strings in FBX files are pooled so by having canonical string constant -// addresses we can compare strings to these constants by comparing pointers. -// Keep the list alphabetically sorted! - -static const char ufbxi_AllSame[] = "AllSame"; -static const char ufbxi_Alphas[] = "Alphas"; -static const char ufbxi_AmbientColor[] = "AmbientColor"; -static const char ufbxi_AnimationCurveNode[] = "AnimationCurveNode"; -static const char ufbxi_AnimationCurve[] = "AnimationCurve"; -static const char ufbxi_AnimationLayer[] = "AnimationLayer"; -static const char ufbxi_AnimationStack[] = "AnimationStack"; -static const char ufbxi_ApertureFormat[] = "ApertureFormat"; -static const char ufbxi_ApertureMode[] = "ApertureMode"; -static const char ufbxi_AreaLightShape[] = "AreaLightShape"; -static const char ufbxi_AspectH[] = "AspectH"; -static const char ufbxi_AspectHeight[] = "AspectHeight"; -static const char ufbxi_AspectRatioMode[] = "AspectRatioMode"; -static const char ufbxi_AspectW[] = "AspectW"; -static const char ufbxi_AspectWidth[] = "AspectWidth"; -static const char ufbxi_Audio[] = "Audio"; -static const char ufbxi_AudioLayer[] = "AudioLayer"; -static const char ufbxi_BaseLayer[] = "BaseLayer"; -static const char ufbxi_BinaryData[] = "BinaryData"; -static const char ufbxi_BindPose[] = "BindPose"; -static const char ufbxi_BindingTable[] = "BindingTable"; -static const char ufbxi_Binormals[] = "Binormals"; -static const char ufbxi_BinormalsIndex[] = "BinormalsIndex"; -static const char ufbxi_BinormalsW[] = "BinormalsW"; -static const char ufbxi_BlendMode[] = "BlendMode"; -static const char ufbxi_BlendModes[] = "BlendModes"; -static const char ufbxi_BlendShapeChannel[] = "BlendShapeChannel"; -static const char ufbxi_BlendShape[] = "BlendShape"; -static const char ufbxi_BlendWeights[] = "BlendWeights"; -static const char ufbxi_BoundaryRule[] = "BoundaryRule"; -static const char ufbxi_Boundary[] = "Boundary"; -static const char ufbxi_ByEdge[] = "ByEdge"; -static const char ufbxi_ByPolygonVertex[] = "ByPolygonVertex"; -static const char ufbxi_ByPolygon[] = "ByPolygon"; -static const char ufbxi_ByVertex[] = "ByVertex"; -static const char ufbxi_ByVertice[] = "ByVertice"; -static const char ufbxi_Cache[] = "Cache"; -static const char ufbxi_CameraProjectionType[] = "CameraProjectionType"; -static const char ufbxi_CameraStereo[] = "CameraStereo"; -static const char ufbxi_CameraSwitcher[] = "CameraSwitcher"; -static const char ufbxi_Camera[] = "Camera"; -static const char ufbxi_CastLight[] = "CastLight"; -static const char ufbxi_CastShadows[] = "CastShadows"; -static const char ufbxi_Channel[] = "Channel"; -static const char ufbxi_Character[] = "Character"; -static const char ufbxi_Children[] = "Children"; -static const char ufbxi_Cluster[] = "Cluster"; -static const char ufbxi_CollectionExclusive[] = "CollectionExclusive"; -static const char ufbxi_Collection[] = "Collection"; -static const char ufbxi_ColorIndex[] = "ColorIndex"; -static const char ufbxi_Color[] = "Color"; -static const char ufbxi_Colors[] = "Colors"; -static const char ufbxi_Cone_angle[] = "Cone angle"; -static const char ufbxi_ConeAngle[] = "ConeAngle"; -static const char ufbxi_Connections[] = "Connections"; -static const char ufbxi_Constraint[] = "Constraint"; -static const char ufbxi_Content[] = "Content"; -static const char ufbxi_CoordAxisSign[] = "CoordAxisSign"; -static const char ufbxi_CoordAxis[] = "CoordAxis"; -static const char ufbxi_Count[] = "Count"; -static const char ufbxi_Creator[] = "Creator"; -static const char ufbxi_CurrentTextureBlendMode[] = "CurrentTextureBlendMode"; -static const char ufbxi_CurrentTimeMarker[] = "CurrentTimeMarker"; -static const char ufbxi_CustomFrameRate[] = "CustomFrameRate"; -static const char ufbxi_DecayType[] = "DecayType"; -static const char ufbxi_DefaultCamera[] = "DefaultCamera"; -static const char ufbxi_Default[] = "Default"; -static const char ufbxi_Definitions[] = "Definitions"; -static const char ufbxi_DeformPercent[] = "DeformPercent"; -static const char ufbxi_Deformer[] = "Deformer"; -static const char ufbxi_DiffuseColor[] = "DiffuseColor"; -static const char ufbxi_Dimension[] = "Dimension"; -static const char ufbxi_Dimensions[] = "Dimensions"; -static const char ufbxi_DisplayLayer[] = "DisplayLayer"; -static const char ufbxi_Document[] = "Document"; -static const char ufbxi_Documents[] = "Documents"; -static const char ufbxi_EdgeCrease[] = "EdgeCrease"; -static const char ufbxi_EdgeIndexArray[] = "EdgeIndexArray"; -static const char ufbxi_Edges[] = "Edges"; -static const char ufbxi_EmissiveColor[] = "EmissiveColor"; -static const char ufbxi_Entry[] = "Entry"; -static const char ufbxi_FBXHeaderExtension[] = "FBXHeaderExtension"; -static const char ufbxi_FBXHeaderVersion[] = "FBXHeaderVersion"; -static const char ufbxi_FBXVersion[] = "FBXVersion"; -static const char ufbxi_FKEffector[] = "FKEffector"; -static const char ufbxi_FarPlane[] = "FarPlane"; -static const char ufbxi_FbxPropertyEntry[] = "FbxPropertyEntry"; -static const char ufbxi_FbxSemanticEntry[] = "FbxSemanticEntry"; -static const char ufbxi_FieldOfViewX[] = "FieldOfViewX"; -static const char ufbxi_FieldOfViewY[] = "FieldOfViewY"; -static const char ufbxi_FieldOfView[] = "FieldOfView"; -static const char ufbxi_FileName[] = "FileName"; -static const char ufbxi_Filename[] = "Filename"; -static const char ufbxi_FilmHeight[] = "FilmHeight"; -static const char ufbxi_FilmSqueezeRatio[] = "FilmSqueezeRatio"; -static const char ufbxi_FilmWidth[] = "FilmWidth"; -static const char ufbxi_FlipNormals[] = "FlipNormals"; -static const char ufbxi_FocalLength[] = "FocalLength"; -static const char ufbxi_Form[] = "Form"; -static const char ufbxi_Freeze[] = "Freeze"; -static const char ufbxi_FrontAxisSign[] = "FrontAxisSign"; -static const char ufbxi_FrontAxis[] = "FrontAxis"; -static const char ufbxi_FullWeights[] = "FullWeights"; -static const char ufbxi_GateFit[] = "GateFit"; -static const char ufbxi_GeometricRotation[] = "GeometricRotation"; -static const char ufbxi_GeometricScaling[] = "GeometricScaling"; -static const char ufbxi_GeometricTranslation[] = "GeometricTranslation"; -static const char ufbxi_GeometryUVInfo[] = "GeometryUVInfo"; -static const char ufbxi_Geometry[] = "Geometry"; -static const char ufbxi_GlobalSettings[] = "GlobalSettings"; -static const char ufbxi_Hole[] = "Hole"; -static const char ufbxi_HotSpot[] = "HotSpot"; -static const char ufbxi_IKEffector[] = "IKEffector"; -static const char ufbxi_ImageData[] = "ImageData"; -static const char ufbxi_Implementation[] = "Implementation"; -static const char ufbxi_Indexes[] = "Indexes"; -static const char ufbxi_InheritType[] = "InheritType"; -static const char ufbxi_InnerAngle[] = "InnerAngle"; -static const char ufbxi_Intensity[] = "Intensity"; -static const char ufbxi_IsTheNodeInSet[] = "IsTheNodeInSet"; -static const char ufbxi_KeyAttrDataFloat[] = "KeyAttrDataFloat"; -static const char ufbxi_KeyAttrFlags[] = "KeyAttrFlags"; -static const char ufbxi_KeyAttrRefCount[] = "KeyAttrRefCount"; -static const char ufbxi_KeyCount[] = "KeyCount"; -static const char ufbxi_KeyTime[] = "KeyTime"; -static const char ufbxi_KeyValueFloat[] = "KeyValueFloat"; -static const char ufbxi_Key[] = "Key"; -static const char ufbxi_KnotVectorU[] = "KnotVectorU"; -static const char ufbxi_KnotVectorV[] = "KnotVectorV"; -static const char ufbxi_KnotVector[] = "KnotVector"; -static const char ufbxi_LayerElementBinormal[] = "LayerElementBinormal"; -static const char ufbxi_LayerElementColor[] = "LayerElementColor"; -static const char ufbxi_LayerElementEdgeCrease[] = "LayerElementEdgeCrease"; -static const char ufbxi_LayerElementHole[] = "LayerElementHole"; -static const char ufbxi_LayerElementMaterial[] = "LayerElementMaterial"; -static const char ufbxi_LayerElementNormal[] = "LayerElementNormal"; -static const char ufbxi_LayerElementPolygonGroup[] = "LayerElementPolygonGroup"; -static const char ufbxi_LayerElementSmoothing[] = "LayerElementSmoothing"; -static const char ufbxi_LayerElementTangent[] = "LayerElementTangent"; -static const char ufbxi_LayerElementUV[] = "LayerElementUV"; -static const char ufbxi_LayerElementVertexCrease[] = "LayerElementVertexCrease"; -static const char ufbxi_LayerElementVisibility[] = "LayerElementVisibility"; -static const char ufbxi_LayerElement[] = "LayerElement"; -static const char ufbxi_Layer[] = "Layer"; -static const char ufbxi_LayeredTexture[] = "LayeredTexture"; -static const char ufbxi_Lcl_Rotation[] = "Lcl Rotation"; -static const char ufbxi_Lcl_Scaling[] = "Lcl Scaling"; -static const char ufbxi_Lcl_Translation[] = "Lcl Translation"; -static const char ufbxi_LeftCamera[] = "LeftCamera"; -static const char ufbxi_LightType[] = "LightType"; -static const char ufbxi_Light[] = "Light"; -static const char ufbxi_LimbLength[] = "LimbLength"; -static const char ufbxi_LimbNode[] = "LimbNode"; -static const char ufbxi_Limb[] = "Limb"; -static const char ufbxi_Line[] = "Line"; -static const char ufbxi_Link[] = "Link"; -static const char ufbxi_LocalStart[] = "LocalStart"; -static const char ufbxi_LocalStop[] = "LocalStop"; -static const char ufbxi_LocalTime[] = "LocalTime"; -static const char ufbxi_LodGroup[] = "LodGroup"; -static const char ufbxi_MappingInformationType[] = "MappingInformationType"; -static const char ufbxi_Marker[] = "Marker"; -static const char ufbxi_MaterialAssignation[] = "MaterialAssignation"; -static const char ufbxi_Material[] = "Material"; -static const char ufbxi_Materials[] = "Materials"; -static const char ufbxi_Matrix[] = "Matrix"; -static const char ufbxi_Media[] = "Media"; -static const char ufbxi_Mesh[] = "Mesh"; -static const char ufbxi_Model[] = "Model"; -static const char ufbxi_Name[] = "Name"; -static const char ufbxi_NearPlane[] = "NearPlane"; -static const char ufbxi_NodeAttributeName[] = "NodeAttributeName"; -static const char ufbxi_NodeAttribute[] = "NodeAttribute"; -static const char ufbxi_Node[] = "Node"; -static const char ufbxi_Normals[] = "Normals"; -static const char ufbxi_NormalsIndex[] = "NormalsIndex"; -static const char ufbxi_NormalsW[] = "NormalsW"; -static const char ufbxi_Null[] = "Null"; -static const char ufbxi_NurbsCurve[] = "NurbsCurve"; -static const char ufbxi_NurbsSurfaceOrder[] = "NurbsSurfaceOrder"; -static const char ufbxi_NurbsSurface[] = "NurbsSurface"; -static const char ufbxi_Nurbs[] = "Nurbs"; -static const char ufbxi_OO[] = "OO\0"; -static const char ufbxi_OP[] = "OP\0"; -static const char ufbxi_ObjectMetaData[] = "ObjectMetaData"; -static const char ufbxi_ObjectType[] = "ObjectType"; -static const char ufbxi_Objects[] = "Objects"; -static const char ufbxi_Order[] = "Order"; -static const char ufbxi_OriginalUnitScaleFactor[] = "OriginalUnitScaleFactor"; -static const char ufbxi_OriginalUpAxis[] = "OriginalUpAxis"; -static const char ufbxi_OriginalUpAxisSign[] = "OriginalUpAxisSign"; -static const char ufbxi_OrthoZoom[] = "OrthoZoom"; -static const char ufbxi_OtherFlags[] = "OtherFlags"; -static const char ufbxi_OuterAngle[] = "OuterAngle"; -static const char ufbxi_PO[] = "PO\0"; -static const char ufbxi_PP[] = "PP\0"; -static const char ufbxi_PointsIndex[] = "PointsIndex"; -static const char ufbxi_Points[] = "Points"; -static const char ufbxi_PolygonGroup[] = "PolygonGroup"; -static const char ufbxi_PolygonIndexArray[] = "PolygonIndexArray"; -static const char ufbxi_PolygonVertexIndex[] = "PolygonVertexIndex"; -static const char ufbxi_PoseNode[] = "PoseNode"; -static const char ufbxi_Pose[] = "Pose"; -static const char ufbxi_Post_Extrapolation[] = "Post-Extrapolation"; -static const char ufbxi_PostRotation[] = "PostRotation"; -static const char ufbxi_Pre_Extrapolation[] = "Pre-Extrapolation"; -static const char ufbxi_PreRotation[] = "PreRotation"; -static const char ufbxi_PreviewDivisionLevels[] = "PreviewDivisionLevels"; -static const char ufbxi_Properties60[] = "Properties60"; -static const char ufbxi_Properties70[] = "Properties70"; -static const char ufbxi_PropertyTemplate[] = "PropertyTemplate"; -static const char ufbxi_R[] = "R\0\0"; -static const char ufbxi_ReferenceStart[] = "ReferenceStart"; -static const char ufbxi_ReferenceStop[] = "ReferenceStop"; -static const char ufbxi_ReferenceTime[] = "ReferenceTime"; -static const char ufbxi_RelativeFileName[] = "RelativeFileName"; -static const char ufbxi_RelativeFilename[] = "RelativeFilename"; -static const char ufbxi_RenderDivisionLevels[] = "RenderDivisionLevels"; -static const char ufbxi_Repetition[] = "Repetition"; -static const char ufbxi_RightCamera[] = "RightCamera"; -static const char ufbxi_RootNode[] = "RootNode"; -static const char ufbxi_Root[] = "Root"; -static const char ufbxi_RotationAccumulationMode[] = "RotationAccumulationMode"; -static const char ufbxi_RotationOffset[] = "RotationOffset"; -static const char ufbxi_RotationOrder[] = "RotationOrder"; -static const char ufbxi_RotationPivot[] = "RotationPivot"; -static const char ufbxi_Rotation[] = "Rotation"; -static const char ufbxi_S[] = "S\0\0"; -static const char ufbxi_ScaleAccumulationMode[] = "ScaleAccumulationMode"; -static const char ufbxi_ScalingOffset[] = "ScalingOffset"; -static const char ufbxi_ScalingPivot[] = "ScalingPivot"; -static const char ufbxi_Scaling[] = "Scaling"; -static const char ufbxi_SceneInfo[] = "SceneInfo"; -static const char ufbxi_SelectionNode[] = "SelectionNode"; -static const char ufbxi_SelectionSet[] = "SelectionSet"; -static const char ufbxi_ShadingModel[] = "ShadingModel"; -static const char ufbxi_Shape[] = "Shape"; -static const char ufbxi_Shininess[] = "Shininess"; -static const char ufbxi_Show[] = "Show"; -static const char ufbxi_Size[] = "Size"; -static const char ufbxi_Skin[] = "Skin"; -static const char ufbxi_SkinningType[] = "SkinningType"; -static const char ufbxi_Smoothing[] = "Smoothing"; -static const char ufbxi_Smoothness[] = "Smoothness"; -static const char ufbxi_SnapOnFrameMode[] = "SnapOnFrameMode"; -static const char ufbxi_SpecularColor[] = "SpecularColor"; -static const char ufbxi_Step[] = "Step"; -static const char ufbxi_SubDeformer[] = "SubDeformer"; -static const char ufbxi_T[] = "T\0\0"; -static const char ufbxi_TCDefinition[] = "TCDefinition"; -static const char ufbxi_Take[] = "Take"; -static const char ufbxi_Takes[] = "Takes"; -static const char ufbxi_Tangents[] = "Tangents"; -static const char ufbxi_TangentsIndex[] = "TangentsIndex"; -static const char ufbxi_TangentsW[] = "TangentsW"; -static const char ufbxi_Texture[] = "Texture"; -static const char ufbxi_Texture_alpha[] = "Texture alpha"; -static const char ufbxi_TextureId[] = "TextureId"; -static const char ufbxi_TextureRotationPivot[] = "TextureRotationPivot"; -static const char ufbxi_TextureScalingPivot[] = "TextureScalingPivot"; -static const char ufbxi_TextureUV[] = "TextureUV"; -static const char ufbxi_TextureUVVerticeIndex[] = "TextureUVVerticeIndex"; -static const char ufbxi_Thumbnail[] = "Thumbnail"; -static const char ufbxi_TimeMarker[] = "TimeMarker"; -static const char ufbxi_TimeMode[] = "TimeMode"; -static const char ufbxi_TimeProtocol[] = "TimeProtocol"; -static const char ufbxi_TimeSpanStart[] = "TimeSpanStart"; -static const char ufbxi_TimeSpanStop[] = "TimeSpanStop"; -static const char ufbxi_TransformLink[] = "TransformLink"; -static const char ufbxi_Transform[] = "Transform"; -static const char ufbxi_Translation[] = "Translation"; -static const char ufbxi_TrimNurbsSurface[] = "TrimNurbsSurface"; -static const char ufbxi_Type[] = "Type"; -static const char ufbxi_TypedIndex[] = "TypedIndex"; -static const char ufbxi_UVIndex[] = "UVIndex"; -static const char ufbxi_UVSet[] = "UVSet"; -static const char ufbxi_UVSwap[] = "UVSwap"; -static const char ufbxi_UV[] = "UV\0"; -static const char ufbxi_UnitScaleFactor[] = "UnitScaleFactor"; -static const char ufbxi_UpAxisSign[] = "UpAxisSign"; -static const char ufbxi_UpAxis[] = "UpAxis"; -static const char ufbxi_Version5[] = "Version5"; -static const char ufbxi_VertexCacheDeformer[] = "VertexCacheDeformer"; -static const char ufbxi_VertexCrease[] = "VertexCrease"; -static const char ufbxi_VertexCreaseIndex[] = "VertexCreaseIndex"; -static const char ufbxi_VertexIndexArray[] = "VertexIndexArray"; -static const char ufbxi_Vertices[] = "Vertices"; -static const char ufbxi_Video[] = "Video"; -static const char ufbxi_Visibility[] = "Visibility"; -static const char ufbxi_Weight[] = "Weight"; -static const char ufbxi_Weights[] = "Weights"; -static const char ufbxi_WrapModeU[] = "WrapModeU"; -static const char ufbxi_WrapModeV[] = "WrapModeV"; -static const char ufbxi_X[] = "X\0\0"; -static const char ufbxi_Y[] = "Y\0\0"; -static const char ufbxi_Z[] = "Z\0\0"; -static const char ufbxi_d_X[] = "d|X"; -static const char ufbxi_d_Y[] = "d|Y"; -static const char ufbxi_d_Z[] = "d|Z"; - -static const ufbx_string ufbxi_strings[] = { - { ufbxi_AllSame, 7 }, - { ufbxi_Alphas, 6 }, - { ufbxi_AmbientColor, 12 }, - { ufbxi_AnimationCurve, 14 }, - { ufbxi_AnimationCurveNode, 18 }, - { ufbxi_AnimationLayer, 14 }, - { ufbxi_AnimationStack, 14 }, - { ufbxi_ApertureFormat, 14 }, - { ufbxi_ApertureMode, 12 }, - { ufbxi_AreaLightShape, 14 }, - { ufbxi_AspectH, 7 }, - { ufbxi_AspectHeight, 12 }, - { ufbxi_AspectRatioMode, 15 }, - { ufbxi_AspectW, 7 }, - { ufbxi_AspectWidth, 11 }, - { ufbxi_Audio, 5 }, - { ufbxi_AudioLayer, 10 }, - { ufbxi_BaseLayer, 9 }, - { ufbxi_BinaryData, 10 }, - { ufbxi_BindPose, 8 }, - { ufbxi_BindingTable, 12 }, - { ufbxi_Binormals, 9 }, - { ufbxi_BinormalsIndex, 14 }, - { ufbxi_BinormalsW, 10 }, - { ufbxi_BlendMode, 9 }, - { ufbxi_BlendModes, 10 }, - { ufbxi_BlendShape, 10 }, - { ufbxi_BlendShapeChannel, 17 }, - { ufbxi_BlendWeights, 12 }, - { ufbxi_Boundary, 8 }, - { ufbxi_BoundaryRule, 12 }, - { ufbxi_ByEdge, 6 }, - { ufbxi_ByPolygon, 9 }, - { ufbxi_ByPolygonVertex, 15 }, - { ufbxi_ByVertex, 8 }, - { ufbxi_ByVertice, 9 }, - { ufbxi_Cache, 5 }, - { ufbxi_Camera, 6 }, - { ufbxi_CameraProjectionType, 20 }, - { ufbxi_CameraStereo, 12 }, - { ufbxi_CameraSwitcher, 14 }, - { ufbxi_CastLight, 9 }, - { ufbxi_CastShadows, 11 }, - { ufbxi_Channel, 7 }, - { ufbxi_Character, sizeof(ufbxi_Character) - 1 }, - { ufbxi_Children, 8 }, - { ufbxi_Cluster, 7 }, - { ufbxi_Collection, 10 }, - { ufbxi_CollectionExclusive, 19 }, - { ufbxi_Color, 5 }, - { ufbxi_ColorIndex, 10 }, - { ufbxi_Colors, 6 }, - { ufbxi_Cone_angle, 10 }, - { ufbxi_ConeAngle, 9 }, - { ufbxi_Connections, 11 }, - { ufbxi_Constraint, sizeof(ufbxi_Constraint) - 1 }, - { ufbxi_Content, 7 }, - { ufbxi_CoordAxis, 9 }, - { ufbxi_CoordAxisSign, 13 }, - { ufbxi_Count, 5 }, - { ufbxi_Creator, 7 }, - { ufbxi_CurrentTextureBlendMode, 23 }, - { ufbxi_CurrentTimeMarker, 17 }, - { ufbxi_CustomFrameRate, 15 }, - { ufbxi_DecayType, 9 }, - { ufbxi_Default, 7 }, - { ufbxi_DefaultCamera, 13 }, - { ufbxi_Definitions, 11 }, - { ufbxi_DeformPercent, 13 }, - { ufbxi_Deformer, 8 }, - { ufbxi_DiffuseColor, 12 }, - { ufbxi_Dimension, 9 }, - { ufbxi_Dimensions, 10 }, - { ufbxi_DisplayLayer, 12 }, - { ufbxi_Document, 8 }, - { ufbxi_Documents, 9 }, - { ufbxi_EdgeCrease, 10 }, - { ufbxi_EdgeIndexArray, 14 }, - { ufbxi_Edges, 5 }, - { ufbxi_EmissiveColor, 13 }, - { ufbxi_Entry, 5 }, - { ufbxi_FBXHeaderExtension, 18 }, - { ufbxi_FBXHeaderVersion, 16 }, - { ufbxi_FBXVersion, 10 }, - { ufbxi_FKEffector, 10 }, - { ufbxi_FarPlane, 8 }, - { ufbxi_FbxPropertyEntry, 16 }, - { ufbxi_FbxSemanticEntry, 16 }, - { ufbxi_FieldOfView, 11 }, - { ufbxi_FieldOfViewX, 12 }, - { ufbxi_FieldOfViewY, 12 }, - { ufbxi_FileName, 8 }, - { ufbxi_Filename, 8 }, - { ufbxi_FilmHeight, 10 }, - { ufbxi_FilmSqueezeRatio, 16 }, - { ufbxi_FilmWidth, 9 }, - { ufbxi_FlipNormals, 11 }, - { ufbxi_FocalLength, 11 }, - { ufbxi_Form, 4 }, - { ufbxi_Freeze, 6 }, - { ufbxi_FrontAxis, 9 }, - { ufbxi_FrontAxisSign, 13 }, - { ufbxi_FullWeights, 11 }, - { ufbxi_GateFit, 7 }, - { ufbxi_GeometricRotation, 17 }, - { ufbxi_GeometricScaling, 16 }, - { ufbxi_GeometricTranslation, 20 }, - { ufbxi_Geometry, 8 }, - { ufbxi_GeometryUVInfo, 14 }, - { ufbxi_GlobalSettings, 14 }, - { ufbxi_Hole, 4 }, - { ufbxi_HotSpot, 7 }, - { ufbxi_IKEffector, 10 }, - { ufbxi_ImageData, 9 }, - { ufbxi_Implementation, 14 }, - { ufbxi_Indexes, 7 }, - { ufbxi_InheritType, 11 }, - { ufbxi_InnerAngle, 10 }, - { ufbxi_Intensity, 9 }, - { ufbxi_IsTheNodeInSet, 14 }, - { ufbxi_Key, 3 }, - { ufbxi_KeyAttrDataFloat, 16 }, - { ufbxi_KeyAttrFlags, 12 }, - { ufbxi_KeyAttrRefCount, 15 }, - { ufbxi_KeyCount, 8 }, - { ufbxi_KeyTime, 7 }, - { ufbxi_KeyValueFloat, 13 }, - { ufbxi_KnotVector, 10 }, - { ufbxi_KnotVectorU, 11 }, - { ufbxi_KnotVectorV, 11 }, - { ufbxi_Layer, 5 }, - { ufbxi_LayerElement, 12 }, - { ufbxi_LayerElementBinormal, 20 }, - { ufbxi_LayerElementColor, 17 }, - { ufbxi_LayerElementEdgeCrease, 22 }, - { ufbxi_LayerElementHole, 16 }, - { ufbxi_LayerElementMaterial, 20 }, - { ufbxi_LayerElementNormal, 18 }, - { ufbxi_LayerElementPolygonGroup, 24 }, - { ufbxi_LayerElementSmoothing, 21 }, - { ufbxi_LayerElementTangent, 19 }, - { ufbxi_LayerElementUV, 14 }, - { ufbxi_LayerElementVertexCrease, 24 }, - { ufbxi_LayerElementVisibility, 22 }, - { ufbxi_LayeredTexture, 14 }, - { ufbxi_Lcl_Rotation, 12 }, - { ufbxi_Lcl_Scaling, 11 }, - { ufbxi_Lcl_Translation, 15 }, - { ufbxi_LeftCamera, 10 }, - { ufbxi_Light, 5 }, - { ufbxi_LightType, 9 }, - { ufbxi_Limb, 4 }, - { ufbxi_LimbLength, 10 }, - { ufbxi_LimbNode, 8 }, - { ufbxi_Line, 4 }, - { ufbxi_Link, 4 }, - { ufbxi_LocalStart, 10 }, - { ufbxi_LocalStop, 9 }, - { ufbxi_LocalTime, 9 }, - { ufbxi_LodGroup, 8 }, - { ufbxi_MappingInformationType, 22 }, - { ufbxi_Marker, 6 }, - { ufbxi_Material, 8 }, - { ufbxi_MaterialAssignation, 19 }, - { ufbxi_Materials, 9 }, - { ufbxi_Matrix, 6 }, - { ufbxi_Media, 5 }, - { ufbxi_Mesh, 4 }, - { ufbxi_Model, 5 }, - { ufbxi_Name, 4 }, - { ufbxi_NearPlane, 9 }, - { ufbxi_Node, 4 }, - { ufbxi_NodeAttribute, 13 }, - { ufbxi_NodeAttributeName, 17 }, - { ufbxi_Normals, 7 }, - { ufbxi_NormalsIndex, 12 }, - { ufbxi_NormalsW, 8 }, - { ufbxi_Null, 4 }, - { ufbxi_Nurbs, 5 }, - { ufbxi_NurbsCurve, 10 }, - { ufbxi_NurbsSurface, 12 }, - { ufbxi_NurbsSurfaceOrder, 17 }, - { ufbxi_OO, 2 }, - { ufbxi_OP, 2 }, - { ufbxi_ObjectMetaData, 14 }, - { ufbxi_ObjectType, 10 }, - { ufbxi_Objects, 7 }, - { ufbxi_Order, 5 }, - { ufbxi_OriginalUnitScaleFactor, 23 }, - { ufbxi_OriginalUpAxis, 14 }, - { ufbxi_OriginalUpAxisSign, 18 }, - { ufbxi_OrthoZoom, 9 }, - { ufbxi_OtherFlags, 10 }, - { ufbxi_OuterAngle, 10 }, - { ufbxi_PO, 2 }, - { ufbxi_PP, 2 }, - { ufbxi_Points, 6 }, - { ufbxi_PointsIndex, 11 }, - { ufbxi_PolygonGroup, 12 }, - { ufbxi_PolygonIndexArray, 17 }, - { ufbxi_PolygonVertexIndex, 18 }, - { ufbxi_Pose, 4 }, - { ufbxi_PoseNode, 8 }, - { ufbxi_Post_Extrapolation, 18 }, - { ufbxi_PostRotation, 12 }, - { ufbxi_Pre_Extrapolation, 17 }, - { ufbxi_PreRotation, 11 }, - { ufbxi_PreviewDivisionLevels, 21 }, - { ufbxi_Properties60, 12 }, - { ufbxi_Properties70, 12 }, - { ufbxi_PropertyTemplate, 16 }, - { ufbxi_R, 1 }, - { ufbxi_ReferenceStart, 14 }, - { ufbxi_ReferenceStop, 13 }, - { ufbxi_ReferenceTime, 13 }, - { ufbxi_RelativeFileName, 16 }, - { ufbxi_RelativeFilename, 16 }, - { ufbxi_RenderDivisionLevels, 20 }, - { ufbxi_Repetition, 10 }, - { ufbxi_RightCamera, 11 }, - { ufbxi_Root, 4 }, - { ufbxi_RootNode, 8 }, - { ufbxi_Rotation, 8 }, - { ufbxi_RotationAccumulationMode, 24 }, - { ufbxi_RotationOffset, 14 }, - { ufbxi_RotationOrder, 13 }, - { ufbxi_RotationPivot, 13 }, - { ufbxi_S, 1 }, - { ufbxi_ScaleAccumulationMode, 21 }, - { ufbxi_Scaling, 7 }, - { ufbxi_ScalingOffset, 13 }, - { ufbxi_ScalingPivot, 12 }, - { ufbxi_SceneInfo, 9 }, - { ufbxi_SelectionNode, 13 }, - { ufbxi_SelectionSet, 12 }, - { ufbxi_ShadingModel, 12 }, - { ufbxi_Shape, 5 }, - { ufbxi_Shininess, 9 }, - { ufbxi_Show, 4 }, - { ufbxi_Size, 4 }, - { ufbxi_Skin, 4 }, - { ufbxi_SkinningType, 12 }, - { ufbxi_Smoothing, 9 }, - { ufbxi_Smoothness, 10 }, - { ufbxi_SnapOnFrameMode, 15 }, - { ufbxi_SpecularColor, 13 }, - { ufbxi_Step, 4 }, - { ufbxi_SubDeformer, 11 }, - { ufbxi_T, 1 }, - { ufbxi_TCDefinition, 12 }, - { ufbxi_Take, 4 }, - { ufbxi_Takes, 5 }, - { ufbxi_Tangents, 8 }, - { ufbxi_TangentsIndex, 13 }, - { ufbxi_TangentsW, 9 }, - { ufbxi_Texture, 7 }, - { ufbxi_Texture_alpha, 13 }, - { ufbxi_TextureId, 9 }, - { ufbxi_TextureRotationPivot, 20 }, - { ufbxi_TextureScalingPivot, 19 }, - { ufbxi_TextureUV, 9 }, - { ufbxi_TextureUVVerticeIndex, 21 }, - { ufbxi_Thumbnail, 9 }, - { ufbxi_TimeMarker, 10 }, - { ufbxi_TimeMode, 8 }, - { ufbxi_TimeProtocol, 12 }, - { ufbxi_TimeSpanStart, 13 }, - { ufbxi_TimeSpanStop, 12 }, - { ufbxi_Transform, 9 }, - { ufbxi_TransformLink, 13 }, - { ufbxi_Translation, 11 }, - { ufbxi_TrimNurbsSurface, 16 }, - { ufbxi_Type, 4 }, - { ufbxi_TypedIndex, 10 }, - { ufbxi_UV, 2 }, - { ufbxi_UVIndex, 7 }, - { ufbxi_UVSet, 5 }, - { ufbxi_UVSwap, 6 }, - { ufbxi_UnitScaleFactor, 15 }, - { ufbxi_UpAxis, 6 }, - { ufbxi_UpAxisSign, 10 }, - { ufbxi_Version5, 8 }, - { ufbxi_VertexCacheDeformer, 19 }, - { ufbxi_VertexCrease, 12 }, - { ufbxi_VertexCreaseIndex, 17 }, - { ufbxi_VertexIndexArray, 16 }, - { ufbxi_Vertices, 8 }, - { ufbxi_Video, 5 }, - { ufbxi_Visibility, 10 }, - { ufbxi_Weight, 6 }, - { ufbxi_Weights, 7 }, - { ufbxi_WrapModeU, 9 }, - { ufbxi_WrapModeV, 9 }, - { ufbxi_X, 1 }, - { ufbxi_Y, 1 }, - { ufbxi_Z, 1 }, - { ufbxi_d_X, 3 }, - { ufbxi_d_Y, 3 }, - { ufbxi_d_Z, 3 }, -}; - -static const ufbx_vec3 ufbxi_one_vec3 = { 1.0f, 1.0f, 1.0f }; - -#define UFBXI_PI ((ufbx_real)3.14159265358979323846) -#define UFBXI_DPI (3.14159265358979323846) -#define UFBXI_DEG_TO_RAD ((ufbx_real)(UFBXI_PI / 180.0)) -#define UFBXI_RAD_TO_DEG ((ufbx_real)(180.0 / UFBXI_PI)) -#define UFBXI_DEG_TO_RAD_DOUBLE (UFBXI_DPI / 180.0) -#define UFBXI_RAD_TO_DEG_DOUBLE (180.0 / UFBXI_DPI) -#define UFBXI_MM_TO_INCH ((ufbx_real)0.0393700787) - -ufbx_inline ufbx_vec3 ufbxi_add3(ufbx_vec3 a, ufbx_vec3 b) { - ufbx_vec3 v = { a.x + b.x, a.y + b.y, a.z + b.z }; - return v; -} - -ufbx_inline ufbx_vec3 ufbxi_sub3(ufbx_vec3 a, ufbx_vec3 b) { - ufbx_vec3 v = { a.x - b.x, a.y - b.y, a.z - b.z }; - return v; -} - -ufbx_inline ufbx_vec3 ufbxi_mul3(ufbx_vec3 a, ufbx_real b) { - ufbx_vec3 v = { a.x * b, a.y * b, a.z * b }; - return v; -} - -ufbx_inline ufbx_vec3 ufbxi_lerp3(ufbx_vec3 a, ufbx_vec3 b, ufbx_real t) { - ufbx_real u = 1.0f - t; - ufbx_vec3 v = { a.x*u + b.x*t, a.y*u + b.y*t, a.z*u + b.z*t }; - return v; -} - -ufbx_inline ufbx_real ufbxi_dot3(ufbx_vec3 a, ufbx_vec3 b) { - return a.x*b.x + a.y*b.y + a.z*b.z; -} - -ufbx_inline ufbx_real ufbxi_length3(ufbx_vec3 v) -{ - return (ufbx_real)ufbx_sqrt(v.x*v.x + v.y*v.y + v.z*v.z); -} - -ufbx_inline ufbx_real ufbxi_min3(ufbx_vec3 v) -{ - return ufbxi_min_real(ufbxi_min_real(v.x, v.y), v.z); -} - -ufbx_inline ufbx_vec3 ufbxi_cross3(ufbx_vec3 a, ufbx_vec3 b) { - ufbx_vec3 v = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; - return v; -} - -ufbx_inline ufbx_vec3 ufbxi_normalize3(ufbx_vec3 a) { - ufbx_real len = (ufbx_real)ufbx_sqrt(ufbxi_dot3(a, a)); - if (len > UFBX_EPSILON) { - return ufbxi_mul3(a, (ufbx_real)1.0 / len); - } else { - ufbx_vec3 zero = { (ufbx_real)0 }; - return zero; - } -} - -ufbx_inline ufbx_vec3 ufbxi_neg3(ufbx_vec3 a) { - ufbx_vec3 v = { -a.x, -a.y, -a.z }; - return v; -} - -ufbx_inline ufbx_real ufbxi_distsq2(ufbx_vec2 a, ufbx_vec2 b) { - ufbx_real dx = a.x - b.x, dy = a.y - b.y; - return dx*dx + dy*dy; -} - -static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalize3(const ufbx_vec3 *a) { - return ufbxi_normalize3(*a); -} - -static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalized_cross3(const ufbx_vec3 *a, const ufbx_vec3 *b) { - return ufbxi_normalize3(ufbxi_cross3(*a, *b)); -} - -// -- Threading - -typedef struct ufbxi_task ufbxi_task; -typedef struct ufbxi_thread_pool ufbxi_thread_pool; - -typedef bool ufbxi_task_fn(ufbxi_task *task); - -struct ufbxi_task { - void *data; - const char *error; -}; - -typedef struct { - ufbxi_task task; - ufbxi_task_fn *fn; -} ufbxi_task_imp; - -typedef struct { - uint32_t max_index; - uint32_t wait_index; -} ufbxi_task_group; - -struct ufbxi_thread_pool { - ufbx_thread_opts opts; - ufbxi_allocator *ator; - ufbx_error *error; - void *user_ptr; - - bool enabled; - bool failed; - const char *error_desc; - - uint32_t start_index; - uint32_t execute_index; - uint32_t wait_index; - - ufbxi_task_group groups[UFBX_THREAD_GROUP_COUNT]; - uint32_t group; - - uint32_t num_tasks; - ufbxi_task_imp *tasks; -}; - -static void ufbxi_thread_pool_execute(ufbxi_thread_pool *pool, uint32_t index) -{ - ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks]; - if (imp->fn(&imp->task)) { - imp->task.error = NULL; - } else if (!imp->task.error) { - imp->task.error = ""; - } -} - -ufbxi_noinline static void ufbxi_thread_pool_update_finished(ufbxi_thread_pool *pool, uint32_t max_index) -{ - while (pool->wait_index < max_index) { - ufbxi_task_imp *task = &pool->tasks[pool->wait_index % pool->num_tasks]; - if (!pool->failed && task->task.error) { - pool->failed = true; - pool->error_desc = task->task.error; - } - pool->wait_index += 1; - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_imp(ufbxi_thread_pool *pool, uint32_t group, bool can_fail) -{ - uint32_t max_index = pool->groups[group].max_index; - - if (pool->groups[group].wait_index < max_index) { - pool->opts.pool.wait_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, max_index); - pool->groups[group].wait_index = max_index; - } - ufbxi_thread_pool_update_finished(pool, max_index); - - if (pool->failed && can_fail) { - ufbx_error *error = pool->error; - if (pool->error_desc) { - error->description.data = pool->error_desc; - error->description.length = strlen(pool->error_desc); - } - ufbxi_fail_err(error, "Task failed"); - } - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_group(ufbxi_thread_pool *pool) -{ - ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true)); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_all(ufbxi_thread_pool *pool) -{ - for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { - ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true)); - pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT; - } - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_init(ufbxi_thread_pool *pool, ufbx_error *error, ufbxi_allocator *ator, const ufbx_thread_opts *opts) -{ - if (!(opts->pool.run_fn && opts->pool.wait_fn)) return 1; - pool->enabled = true; - - uint32_t num_tasks = (uint32_t)ufbxi_min_sz(opts->num_tasks, INT32_MAX); - if (num_tasks == 0) { - num_tasks = 2048; - } - - pool->opts = *opts; - if (pool->opts.pool.init_fn) { - ufbx_thread_pool_info info; // ufbxi_uninit - info.max_concurrent_tasks = num_tasks; - ufbxi_check_err(error, pool->opts.pool.init_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, &info)); - } - pool->ator = ator; - pool->error = error; - - pool->num_tasks = num_tasks; - pool->tasks = ufbxi_alloc(ator, ufbxi_task_imp, num_tasks); - ufbxi_check_err(error, pool->tasks); - - return 1; -} - -ufbxi_noinline static void ufbxi_thread_pool_free(ufbxi_thread_pool *pool) -{ - if (!pool->enabled) return; - - // Wait for all pending tasks - for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { - pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT; - ufbxi_ignore(ufbxi_thread_pool_wait_imp(pool, pool->group, false)); - } - - if (pool->opts.pool.free_fn) { - pool->opts.pool.free_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool); - } - - ufbxi_free(pool->ator, ufbxi_task_imp, pool->tasks, pool->num_tasks); -} - -ufbxi_nodiscard ufbxi_noinline static uint32_t ufbxi_thread_pool_available_tasks(ufbxi_thread_pool *pool) -{ - return pool->num_tasks - (pool->start_index - pool->wait_index); -} - -ufbxi_noinline static void ufbxi_thread_pool_flush_group(ufbxi_thread_pool *pool) -{ - uint32_t group = pool->group; - uint32_t start_index = pool->execute_index; - uint32_t count = pool->start_index - start_index; - if (count > 0) { - if (pool->opts.pool.run_fn) { - pool->opts.pool.run_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, start_index, count); - } - pool->groups[group].max_index = start_index + count; - pool->execute_index = start_index + count; - } - pool->group = (group + 1) % UFBX_THREAD_GROUP_COUNT; -} - -ufbxi_nodiscard ufbxi_noinline static ufbxi_task *ufbxi_thread_pool_create_task(ufbxi_thread_pool *pool, ufbxi_task_fn *fn) -{ - uint32_t index = pool->start_index; - if (index - pool->wait_index >= pool->num_tasks) { - if (index - pool->wait_index >= pool->num_tasks) { - // No space left - return NULL; - } - } else if (index == INT32_MAX) { - // TODO: Expand to 64 bits if possible? - return NULL; - } - - ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks]; - if (index < pool->num_tasks) { - memset(imp, 0, sizeof(ufbxi_task_imp)); - } - - imp->fn = fn; - - return &imp->task; -} - -static void ufbxi_thread_pool_run_task(ufbxi_thread_pool *pool, ufbxi_task *task) -{ - (void)task; - uint32_t index = pool->start_index; - ufbx_assert(task == &pool->tasks[index % pool->num_tasks].task); - pool->start_index = index + 1; -} - -// -- Type definitions - -typedef struct ufbxi_node ufbxi_node; - -typedef enum { - UFBXI_VALUE_NONE, - UFBXI_VALUE_NUMBER, - UFBXI_VALUE_STRING, - UFBXI_VALUE_ARRAY, -} ufbxi_value_type; - -typedef union { - struct { double f; int64_t i; }; // < if `UFBXI_PROP_NUMBER` - ufbxi_sanitized_string s; // < if `UFBXI_PROP_STRING` -} ufbxi_value; - -typedef struct { - void *data; // < Pointer to `size` bool/int32_t/int64_t/float/double elements - size_t size; // < Number of elements - char type; // < FBX type code: b/i/l/f/d -} ufbxi_value_array; - -struct ufbxi_node { - const char *name; // < Name of the node (pooled, compare with == to ufbxi_* strings) - uint32_t num_children; // < Number of child nodes - uint8_t name_len; // < Length of `name` in bytes - - // If `value_type_mask == UFBXI_PROP_ARRAY` then the node is an array - // (`array` field is valid) otherwise the node has N values in `vals` - // where the type of each value is stored in 2 bits per value from LSB. - // ie. `vals[ix]` type is `(value_type_mask >> (ix*2)) & 0x3` - uint16_t value_type_mask; - - ufbxi_node *children; - union { - ufbxi_value_array *array; // if `prop_type_mask == UFBXI_PROP_ARRAY` - ufbxi_value *vals; // otherwise - }; -}; - -typedef struct ufbxi_refcount ufbxi_refcount; - -struct ufbxi_refcount { - ufbxi_refcount *parent; - void *align_0; - uint32_t self_magic; - uint32_t type_magic; - ufbxi_buf buf; - ufbxi_allocator ator; - uint64_t zero_pad_pre[8]; - ufbxi_atomic_counter refcount; - uint64_t zero_pad_post[8]; -}; - -static ufbxi_noinline void ufbxi_init_ref(ufbxi_refcount *refcount, uint32_t magic, ufbxi_refcount *parent); -static ufbxi_noinline void ufbxi_retain_ref(ufbxi_refcount *refcount); - -#define ufbxi_get_imp(type, ptr) ((type*)((char*)ptr - sizeof(ufbxi_refcount))) - -typedef struct { - ufbxi_refcount refcount; - ufbx_scene scene; - uint32_t magic; - - ufbxi_buf string_buf; -} ufbxi_scene_imp; - -ufbx_static_assert(scene_imp_offset, offsetof(ufbxi_scene_imp, scene) == sizeof(ufbxi_refcount)); - -typedef struct { - ufbxi_refcount refcount; - ufbx_mesh mesh; - uint32_t magic; -} ufbxi_mesh_imp; - -ufbx_static_assert(mesh_imp_offset, offsetof(ufbxi_mesh_imp, mesh) == sizeof(ufbxi_refcount)); - -typedef struct { - // Semantic string data and length eg. for a string token - // this string doesn't include the quotes. - char *str_data; - size_t str_len; - size_t str_cap; - - // Type of the token, either single character such as '{' or ':' - // or one of UFBXI_ASCII_* defines. - char type; - - // Sign for integer if negative. - bool negative; - - // Parsed semantic value - union { - double f64; - int64_t i64; - size_t name_len; - } value; -} ufbxi_ascii_token; - -typedef struct { - size_t max_token_length; - - const char *src; - const char *src_yield; - const char *src_end; - - bool read_first_comment; - bool found_version; - bool parse_as_f32; - bool src_is_retained; - - ufbxi_buf *retain_buf; - ufbxi_buf *src_buf; - - ufbxi_ascii_token prev_token; - ufbxi_ascii_token token; -} ufbxi_ascii; - -typedef struct { - const char *type; - ufbx_string sub_type; - ufbx_props props; -} ufbxi_template; - -typedef struct { - uint64_t fbx_id; - uint32_t element_id; - uint32_t user_id; -} ufbxi_fbx_id_entry; - -typedef struct { - ufbxi_ptr_id ptr_id; - uint64_t fbx_id; -} ufbxi_ptr_fbx_id_entry; - -typedef struct { - uint64_t node_fbx_id; - uint64_t attr_fbx_id; -} ufbxi_fbx_attr_entry; - -// Temporary connection before we resolve the element pointers -typedef struct { - uint64_t src, dst; - ufbx_string src_prop; - ufbx_string dst_prop; -} ufbxi_tmp_connection; - -typedef struct { - uint64_t fbx_id; - ufbx_string name; - ufbx_props props; - ufbx_dom_node *dom_node; -} ufbxi_element_info; - -typedef struct { - uint64_t bone_fbx_id; - ufbx_matrix bone_to_world; -} ufbxi_tmp_bone_pose; - -typedef struct { - ufbx_string prop_name; - uint32_t *face_texture; - size_t num_faces; - bool all_same; -} ufbxi_tmp_mesh_texture; - -typedef struct { - ufbxi_tmp_mesh_texture *texture_arr; - size_t texture_count; -} ufbxi_mesh_extra; - -typedef struct { - int32_t material_id; - int32_t texture_id; - ufbx_string prop_name; -} ufbxi_tmp_material_texture; - -typedef struct { - int32_t *blend_modes; - size_t num_blend_modes; - - ufbx_real *alphas; - size_t num_alphas; -} ufbxi_texture_extra; - -typedef enum { - UFBXI_OBJ_ATTRIB_POSITION, - UFBXI_OBJ_ATTRIB_UV, - UFBXI_OBJ_ATTRIB_NORMAL, - UFBXI_OBJ_ATTRIB_COLOR, -} ufbxi_obj_attrib; - -#define UFBXI_OBJ_NUM_ATTRIBS 3 -#define UFBXI_OBJ_NUM_ATTRIBS_EXT 4 - -typedef struct { - uint64_t min_ix, max_ix; -} ufbxi_obj_index_range; - -typedef struct { - size_t num_faces; - size_t num_indices; - ufbxi_obj_index_range vertex_range[UFBXI_OBJ_NUM_ATTRIBS]; - - ufbx_node *fbx_node; - ufbx_mesh *fbx_mesh; - - uint64_t fbx_node_id; - uint64_t fbx_mesh_id; - - uint32_t usemtl_base; - - uint32_t num_groups; -} ufbxi_obj_mesh; - -typedef struct { - const char *name; - uint32_t local_id; - uint32_t mesh_id; -} ufbxi_obj_group_entry; - -typedef struct { - uint64_t *indices; - size_t num_left; -} ufbxi_obj_fast_indices; - -// Temporary pointer to a `ufbx_anim_stack` by name used to patch start/stop -// time from "Takes" if necessary. -typedef struct { - const char *name; - ufbx_anim_stack *stack; -} ufbxi_tmp_anim_stack; - -typedef struct { - ufbx_string absolute_filename; - ufbx_blob content; -} ufbxi_file_content; - -typedef struct { - - // Current line and tokens. - // NOTE: `line` and `tokens` are not NULL-terminated nor UTF-8! - // `line` is guaranteed to be terminated by a `\n` - ufbx_string line; - ufbx_string *tokens; - size_t tokens_cap; - size_t num_tokens; - - ufbxi_obj_fast_indices fast_indices[UFBXI_OBJ_NUM_ATTRIBS]; - - size_t vertex_count[UFBXI_OBJ_NUM_ATTRIBS_EXT]; - ufbxi_buf tmp_vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT]; - ufbxi_buf tmp_indices[UFBXI_OBJ_NUM_ATTRIBS_EXT]; - ufbxi_buf tmp_color_valid; - ufbxi_buf tmp_faces; - ufbxi_buf tmp_face_smoothing; - ufbxi_buf tmp_face_group; - ufbxi_buf tmp_face_group_infos; - ufbxi_buf tmp_face_material; - ufbxi_buf tmp_meshes; - ufbxi_buf tmp_props; - - ufbxi_map group_map; - - size_t read_progress; - - ufbxi_obj_mesh *mesh; - - uint64_t usemtl_fbx_id; - uint32_t usemtl_index; - - uint32_t face_material; - - uint32_t face_group; - bool has_face_group; - - bool face_smoothing; - bool has_face_smoothing; - - bool has_vertex_color; - size_t mrgb_vertex_count; - - bool eof; - bool initialized; - - ufbx_blob mtllib_relative_path; - - ufbx_material **tmp_materials; - size_t tmp_materials_cap; - - ufbx_string object; - ufbx_string group; - bool material_dirty; - bool object_dirty; - bool group_dirty; - bool face_group_dirty; - -} ufbxi_obj_context; - -typedef struct { - - ufbx_error error; - uint32_t version; - ufbx_exporter exporter; - uint32_t exporter_version; - bool from_ascii; - bool local_big_endian; - bool file_big_endian; - bool sure_fbx; - bool retain_mesh_parts; - bool read_legacy_settings; - uint32_t double_parse_flags; - - ufbx_load_opts opts; - - // IO - uint64_t data_offset; - ufbx_read_fn *read_fn; - ufbx_skip_fn *skip_fn; - void *read_user; - - char *read_buffer; - size_t read_buffer_size; - - const char *data_begin; - const char *data; - size_t yield_size; - size_t data_size; - - // Allocators - ufbxi_allocator ator_result; - ufbxi_allocator ator_tmp; - - // Temporary maps - ufbxi_map prop_type_map; // < `ufbxi_prop_type_name` Property type to enum - ufbxi_map fbx_id_map; // < `ufbxi_fbx_id_entry` FBX ID to local ID - ufbxi_map ptr_fbx_id_map; // < `ufbxi_ptr_fbx_id_entry` Pointer/negative ID to FBX ID - ufbxi_map texture_file_map; // < `ufbxi_texture_file_entry` absolute raw filename to element ID - ufbxi_map anim_stack_map; // < `ufbxi_tmp_anim_stack` anim stacks by name before finalization - - // 6x00 specific maps - ufbxi_map fbx_attr_map; // < `ufbxi_fbx_attr_entry` Node ID to attrib ID - ufbxi_map node_prop_set; // < `const char*` Node property names - - // DOM nodes - ufbxi_map dom_node_map; // < `const char*` Node property names - - // Temporary array - char *tmp_arr; - size_t tmp_arr_size; - char *swap_arr; - size_t swap_arr_size; - - // Generated index buffers - size_t max_zero_indices; - size_t max_consecutive_indices; - - // Temporary buffers - ufbxi_buf tmp; - ufbxi_buf tmp_parse; - ufbxi_buf tmp_stack; - ufbxi_buf tmp_connections; - ufbxi_buf tmp_node_ids; - ufbxi_buf tmp_elements; - ufbxi_buf tmp_element_offsets; - ufbxi_buf tmp_element_fbx_ids; - ufbxi_buf tmp_element_ptrs; - ufbxi_buf tmp_typed_element_offsets[UFBX_ELEMENT_TYPE_COUNT]; - ufbxi_buf tmp_mesh_textures; - ufbxi_buf tmp_full_weights; - ufbxi_buf tmp_dom_nodes; - ufbxi_buf tmp_element_id; - ufbxi_buf tmp_ascii_spans; - ufbxi_buf tmp_thread_parse[UFBX_THREAD_GROUP_COUNT]; - size_t tmp_element_byte_offset; - - ufbxi_template *templates; - size_t num_templates; - - ufbx_dom_node *dom_parse_toplevel; - size_t dom_parse_num_children; - - uint32_t *p_element_id; - - // String pool - ufbxi_string_pool string_pool; - - // Result buffers, these are retained in `ufbx_scene` returned to user. - ufbxi_buf result; - - // Top-level state - ufbxi_node *top_nodes; - size_t top_nodes_len, top_nodes_cap; - bool parsed_to_end; - - // "Focused" top-level node and child index, if `top_child_index == SIZE_MAX` - // the children are parsed on demand. - ufbxi_node *top_node; - size_t top_child_index; - ufbxi_node top_child; - bool has_next_child; - - // Shared consecutive and all-zero index buffers - uint32_t *zero_indices; - uint32_t *consecutive_indices; - - // Call progress function periodically - ptrdiff_t progress_timer; - uint64_t progress_bytes_total; - uint64_t latest_progress_bytes; - size_t progress_interval; - - // Extra data on the side of elements - void **element_extra_arr; - size_t element_extra_cap; - - // Temporary per-element flags - uint8_t *tmp_element_flag; - - // IO (cold) - ufbx_close_fn *close_fn; - ufbx_size_fn *size_fn; - - ufbxi_ascii ascii; - - uint64_t synthetic_id_counter; - - bool has_geometry_transform_nodes; - bool has_scale_helper_nodes; - bool retain_vertex_w; - bool blender_full_weights; - - ufbx_mirror_axis mirror_axis; - - ufbxi_node root; - - ufbx_scene scene; - ufbxi_scene_imp *scene_imp; - - ufbx_inflate_retain *inflate_retain; - - // Per-mesh consecutive indices used by `ufbxi_flip_winding()`. - uint32_t *tmp_mesh_consecutive_indices; - - uint64_t root_id; - uint32_t num_elements; - - ufbxi_node legacy_node; - uint64_t legacy_implicit_anim_layer_id; - - ufbxi_file_content *file_content; - size_t num_file_content; - - int64_t ktime_sec; - double ktime_sec_double; - - bool eof; - ufbxi_obj_context obj; - - ufbx_matrix axis_matrix; - ufbx_real unit_scale; - - ufbxi_warnings warnings; - - bool deferred_failure; - bool deferred_load; - - const char *load_filename; - size_t load_filename_len; - - bool parse_threaded; - ufbxi_thread_pool thread_pool; - - uint8_t *base64_table; - -} ufbxi_context; - -static ufbxi_noinline int ufbxi_fail_imp(ufbxi_context *uc, const char *cond, const char *func, uint32_t line) -{ - return ufbxi_fail_imp_err(&uc->error, cond, func, line); -} - -#if UFBXI_FEATURE_ERROR_STACK - #define ufbxi_fail_no_msg(uc, cond, func, line) ufbxi_fail_imp((uc), (cond), (func), (line)) -#else - static ufbxi_noinline int ufbxi_fail_imp_no_stack(ufbxi_context *uc) { return ufbxi_fail_imp_err(&uc->error, NULL, NULL, 0); } - #define ufbxi_fail_no_msg(uc, cond, func, line) ufbxi_fail_imp_no_stack((uc)) -#endif - -#define ufbxi_check(cond) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_no_msg(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line) -#define ufbxi_check_return(cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_no_msg(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0) -#define ufbxi_fail(desc) return ufbxi_fail_no_msg(uc, desc, ufbxi_function, ufbxi_line) -#define ufbxi_fail_return(desc, ret) do { ufbxi_fail_no_msg(uc, desc, ufbxi_function, ufbxi_line); return ret; } while (0) - -#define ufbxi_check_msg(cond, msg) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line) -#define ufbxi_check_return_msg(cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0) -#define ufbxi_fail_msg(desc, msg) return ufbxi_fail_imp(uc, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line) - -#define ufbxi_warnf(type, ...) ufbxi_warnf_imp(&uc->warnings, type, ~0u, __VA_ARGS__) -#define ufbxi_warnf_tag(type, element_id, ...) ufbxi_warnf_imp(&uc->warnings, type, (element_id), __VA_ARGS__) - -// -- Progress - -static ufbxi_forceinline uint64_t ufbxi_get_read_offset(ufbxi_context *uc) -{ - return uc->data_offset + ufbxi_to_size(uc->data - uc->data_begin); -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_report_progress(ufbxi_context *uc) -{ - if (!uc->opts.progress_cb.fn) return 1; - - uint64_t read_offset = ufbxi_get_read_offset(uc); - uc->latest_progress_bytes = read_offset; - - ufbx_progress progress = { 0 }; - progress.bytes_read = read_offset; - progress.bytes_total = uc->progress_bytes_total; - if (progress.bytes_total < progress.bytes_read) { - progress.bytes_total = progress.bytes_read; - } - - uc->progress_timer = 1024; - uint32_t result = (uint32_t)uc->opts.progress_cb.fn(uc->opts.progress_cb.user, &progress); - ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL); - ufbxi_check_msg(result != UFBX_PROGRESS_CANCEL, "Cancelled"); - return 1; -} - -// TODO: Remove `ufbxi_unused` when it's not needed anymore -ufbxi_unused ufbxi_nodiscard static ufbxi_forceinline int ufbxi_progress(ufbxi_context *uc, size_t work_units) -{ - if (!uc->opts.progress_cb.fn) return 1; - ptrdiff_t left = uc->progress_timer - (ptrdiff_t)work_units; - uc->progress_timer = left; - if (left > 0) return 1; - return ufbxi_report_progress(uc); -} - -// -- IO - -static ufbxi_noinline const char *ufbxi_refill(ufbxi_context *uc, size_t size, bool require_size) -{ - ufbx_assert(uc->data_size < size); - ufbxi_check_return(!uc->eof, NULL); - if (require_size) { - ufbxi_check_return_msg(uc->read_fn || uc->data_size > 0, NULL, "Empty file"); - ufbxi_check_return_msg(uc->read_fn, NULL, "Truncated file"); - } else if (!uc->read_fn) { - uc->eof = true; - return uc->data; - } - - void *data_to_free = NULL; - size_t size_to_free = 0; - - // Grow the read buffer if necessary, data is copied over below with the - // usual path so the free is deferred (`size_to_free`, `data_to_free`) - if (size > uc->read_buffer_size) { - size_t new_size = ufbxi_max_sz(size, uc->opts.read_buffer_size); - new_size = ufbxi_max_sz(new_size, uc->read_buffer_size * 2); - size_to_free = uc->read_buffer_size; - data_to_free = uc->read_buffer; - char *new_buffer = ufbxi_alloc(&uc->ator_tmp, char, new_size); - ufbxi_check_return(new_buffer, NULL); - uc->read_buffer = new_buffer; - uc->read_buffer_size = new_size; - } - - // Copy the remains of the previous buffer to the beginning of the new one - size_t data_size = uc->data_size; - if (data_size > 0) { - ufbx_assert(uc->read_buffer != NULL && uc->data != NULL); - memmove(uc->read_buffer, uc->data, data_size); - } - - if (size_to_free) { - ufbxi_free(&uc->ator_tmp, char, data_to_free, size_to_free); - } - - // Fill the rest of the buffer with user data - size_t data_capacity = uc->read_buffer_size; - while (data_size < data_capacity) { - size_t to_read = data_capacity - data_size; - size_t read_result = uc->read_fn(uc->read_user, uc->read_buffer + data_size, to_read); - ufbxi_check_return_msg(read_result != SIZE_MAX, NULL, "IO error"); - ufbxi_check_return(read_result <= to_read, NULL); - data_size += read_result; - if (read_result == 0) { - uc->eof = true; - break; - } - } - - if (require_size) { - if (uc->data_offset == 0) { - ufbxi_check_return_msg(data_size > 0, NULL, "Empty file"); - } - ufbxi_check_return_msg(data_size >= size, NULL, "Truncated file"); - } - - uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin); - uc->data_begin = uc->data = uc->read_buffer; - uc->data_size = data_size; - - return uc->read_buffer; -} - -static ufbxi_forceinline void ufbxi_pause_progress(ufbxi_context *uc) -{ - uc->data_size += uc->yield_size; - uc->yield_size = 0; -} - -static ufbxi_noinline int ufbxi_resume_progress(ufbxi_context *uc) -{ - uc->yield_size = ufbxi_min_sz(uc->data_size, uc->progress_interval); - uc->data_size -= uc->yield_size; - - if (ufbxi_get_read_offset(uc) - uc->latest_progress_bytes >= uc->progress_interval) { - ufbxi_check(ufbxi_report_progress(uc)); - } - - return 1; -} - -static ufbxi_noinline const char *ufbxi_yield(ufbxi_context *uc, size_t size) -{ - const char *ret; - uc->data_size += uc->yield_size; - if (uc->data_size >= size) { - ret = uc->data; - } else { - ret = ufbxi_refill(uc, size, true); - } - uc->yield_size = ufbxi_min_sz(uc->data_size, ufbxi_max_sz(size, uc->progress_interval)); - uc->data_size -= uc->yield_size; - - ufbxi_check_return(ufbxi_report_progress(uc), NULL); - return ret; -} - -static ufbxi_forceinline const char *ufbxi_peek_bytes(ufbxi_context *uc, size_t size) -{ - if (uc->yield_size >= size) { - return uc->data; - } else { - return ufbxi_yield(uc, size); - } -} - -static ufbxi_forceinline const char *ufbxi_read_bytes(ufbxi_context *uc, size_t size) -{ - // Refill the current buffer if necessary - const char *ret; - if (uc->yield_size >= size) { - ret = uc->data; - } else { - ret = ufbxi_yield(uc, size); - if (!ret) return NULL; - } - - // Advance the read position inside the current buffer - uc->yield_size -= size; - uc->data = ret + size; - return ret; -} - -static ufbxi_forceinline void ufbxi_consume_bytes(ufbxi_context *uc, size_t size) -{ - // Bytes must have been checked first with `ufbxi_peek_bytes()` - ufbx_assert(size <= uc->yield_size); - uc->yield_size -= size; - uc->data += size; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_skip_bytes(ufbxi_context *uc, uint64_t size) -{ - if (uc->skip_fn) { - ufbxi_pause_progress(uc); - - if (size > uc->data_size) { - size -= uc->data_size; - uc->data += uc->data_size; - uc->data_size = 0; - - uc->data_offset += size; - while (size >= UFBXI_MAX_SKIP_SIZE) { - size -= UFBXI_MAX_SKIP_SIZE; - ufbxi_check_msg(uc->skip_fn(uc->read_user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file"); - - // Check that we can read at least one byte in case the file is broken - // and causes us to seek indefinitely forwards as `fseek()` does not - // report if we hit EOF... - char single_byte[1]; // ufbxi_uninit - size_t num_read = uc->read_fn(uc->read_user, single_byte, 1); - ufbxi_check_msg(num_read <= 1, "IO error"); - ufbxi_check_msg(num_read == 1, "Truncated file"); - } - - if (size > 0) { - ufbxi_check_msg(uc->skip_fn(uc->read_user, (size_t)size), "Truncated file"); - } - - } else { - uc->data += (size_t)size; - uc->data_size -= (size_t)size; - } - - ufbxi_check(ufbxi_resume_progress(uc)); - } else { - // Read and discard bytes in reasonable chunks - uint64_t skip_size = ufbxi_max64(uc->read_buffer_size, uc->opts.read_buffer_size); - while (size > 0) { - uint64_t to_skip = ufbxi_min64(size, skip_size); - ufbxi_check(ufbxi_read_bytes(uc, (size_t)to_skip)); - size -= to_skip; - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_to(ufbxi_context *uc, void *dst, size_t size) -{ - char *ptr = (char*)dst; - - ufbxi_pause_progress(uc); - - // Copy data from the current buffer first - size_t len = ufbxi_min_sz(uc->data_size, size); - memcpy(ptr, uc->data, len); - uc->data += len; - uc->data_size -= len; - ptr += len; - size -= len; - - // If there's data left to copy try to read from user IO - if (size > 0) { - uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin); - - uc->data_begin = uc->data = NULL; - uc->data_size = 0; - ufbxi_check(uc->read_fn); - - while (size > 0) { - size_t read_result = uc->read_fn(uc->read_user, ptr, size); - ufbxi_check_msg(read_result != SIZE_MAX, "IO error"); - ufbxi_check(read_result != 0); - - ptr += read_result; - size -= read_result; - uc->data_offset += read_result; - } - } - - ufbxi_check(ufbxi_resume_progress(uc)); - - return 1; -} - -static ufbxi_noinline void ufbxi_init_ator(ufbx_error *error, ufbxi_allocator *ator, const ufbx_allocator_opts *opts, const char *name) -{ - ufbx_allocator_opts zero_opts; - if (!opts) { - memset(&zero_opts, 0, sizeof(zero_opts)); - opts = &zero_opts; - } - - // `opts` is either passed in or `zero_opts`. - // cppcheck-suppress uninitvar - ator->ator = *opts; - ator->error = error; - ator->max_size = opts->memory_limit ? opts->memory_limit : SIZE_MAX; - ator->max_allocs = opts->allocation_limit ? opts->allocation_limit : SIZE_MAX; - ator->huge_size = opts->huge_threshold ? opts->huge_threshold : 0x100000; - ator->chunk_max = opts->max_chunk_size ? opts->max_chunk_size : 0x1000000; - ator->name = name; -} - -typedef struct { - ufbx_error error; - - ufbxi_allocator *parent_ator; - ufbxi_allocator ator; -} ufbxi_file_context; - -static ufbxi_noinline void ufbxi_begin_file_context(ufbxi_file_context *fc, ufbx_open_file_context ctx, const ufbx_allocator_opts *ator_opts) -{ - memset(fc, 0, sizeof(ufbxi_file_context)); - if (ctx) { - fc->parent_ator = (ufbxi_allocator*)ctx; - fc->ator = *fc->parent_ator; - fc->ator.error = &fc->error; - } else { - ufbxi_init_ator(&fc->error, &fc->ator, ator_opts, "file"); - } -} - -static ufbxi_noinline void ufbxi_end_file_context(ufbxi_file_context *fc, ufbx_error *error, bool ok) -{ - if (fc->parent_ator) { - fc->ator.error = fc->parent_ator->error; - *fc->parent_ator = fc->ator; - } else { - ufbxi_free_ator(&fc->ator); - } - if (error) { - if (!ok) { - ufbxi_fix_error_type(&fc->error, "Failed to open file", error); - } else { - ufbxi_clear_error(error); - } - } -} - -// -- File IO - -#if !defined(UFBX_NO_STDIO) && !defined(UFBX_EXTERNAL_STDIO) - -static ufbxi_noinline FILE *ufbxi_fopen(ufbxi_file_context *fc, const char *path, size_t path_len, bool null_terminated) -{ - FILE *file = NULL; -#if !defined(UFBX_STANDARD_C) && defined(_WIN32) - (void)null_terminated; - wchar_t wpath_buf[256], *wpath = NULL; // ufbxi_uninit - if (path_len < ufbxi_arraycount(wpath_buf) - 1) { - wpath = wpath_buf; - } else { - wpath = ufbxi_alloc(&fc->ator, wchar_t, path_len + 1); - if (!wpath) return NULL; - } - - // Convert UTF-8 to UTF-16 but allow stray surrogate pairs as the Windows - // file system encoding allows them as well.. - size_t wlen = 0; - for (size_t i = 0; i < path_len; ) { - uint32_t code = UINT32_MAX; - char c = path[i++]; - if ((c & 0x80) == 0) { - code = (uint32_t)c; - } else if ((c & 0xe0) == 0xc0) { - code = (uint32_t)(c & 0x1f); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - } else if ((c & 0xf0) == 0xe0) { - code = (uint32_t)(c & 0x0f); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - } else if ((c & 0xf8) == 0xf0) { - code = (uint32_t)(c & 0x07); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f); - } - if (code < 0x10000) { - wpath[wlen++] = (wchar_t)code; - } else { - code -= 0x10000; - wpath[wlen++] = (wchar_t)(0xd800 + (code >> 10)); - wpath[wlen++] = (wchar_t)(0xdc00 + (code & 0x3ff)); - } - } - wpath[wlen] = 0; - - #if UFBXI_MSC_VER >= 1400 - if (_wfopen_s(&file, wpath, L"rb") != 0) file = NULL; - #else - file = _wfopen(wpath, L"rb"); - #endif - if (wpath != wpath_buf) { - ufbxi_free(&fc->ator, wchar_t, wpath, path_len + 1); - } -#else - char copy_buf[256], *copy = NULL; // ufbxi_uninit - if (null_terminated) { - copy = (char*)path; - } else { - if (path_len < ufbxi_arraycount(copy_buf) - 1) { - copy = copy_buf; - } else { - copy = ufbxi_alloc(&fc->ator, char, path_len + 1); - if (!copy) return NULL; - } - memcpy(copy, path, path_len); - copy[path_len] = '\0'; - } - file = fopen(copy, "rb"); - if (!null_terminated && copy != copy_buf) { - ufbxi_free(&fc->ator, char, copy, path_len + 1); - } -#endif - if (!file) { - ufbxi_set_err_info(&fc->error, path, path_len); - ufbxi_report_err_msg(&fc->error, "file", "File not found"); - } - return file; -} - -static uint64_t ufbxi_ftell(FILE *file) -{ -#if !defined(UFBX_STANDARD_C) && defined(UFBX_HAS_FTELLO) - off_t result = ftello(file); - if (result >= 0) return (uint64_t)result; -#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - int64_t result = _ftelli64(file); - if (result >= 0) return (uint64_t)result; -#else - int64_t result = ftell(file); - if (result >= 0) return (uint64_t)result; -#endif - return UINT64_MAX; -} - -static size_t ufbxi_stdio_read(void *user, void *data, size_t max_size) -{ - FILE *file = (FILE*)user; - if (ferror(file)) return SIZE_MAX; - return fread(data, 1, max_size, file); -} - -static bool ufbxi_stdio_skip(void *user, size_t size) -{ - FILE *file = (FILE*)user; - ufbx_assert(size <= UFBXI_MAX_SKIP_SIZE); - if (fseek(file, (long)size, SEEK_CUR) != 0) return false; - if (ferror(file)) return false; - return true; -} - -static uint64_t ufbxi_stdio_size(void *user) -{ - FILE *file = (FILE*)user; - uint64_t result = 0; - uint64_t begin = ufbxi_ftell(file); - if (begin < UINT64_MAX) { - fpos_t pos; // ufbxi_uninit - if (fgetpos(file, &pos) == 0) { - if (fseek(file, 0, SEEK_END) == 0) { - uint64_t end = ufbxi_ftell(file); - if (end != UINT64_MAX && begin < end) { - result = end - begin; - } - // Both `rewind()` and `fsetpos()` to reset error and EOF - rewind(file); - fsetpos(file, &pos); - } - } - } - return result; -} - -static void ufbxi_stdio_close(void *user) -{ - FILE *file = (FILE*)user; - fclose(file); -} - -static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) -{ - stream->read_fn = &ufbxi_stdio_read; - stream->skip_fn = &ufbxi_stdio_skip; - stream->size_fn = &ufbxi_stdio_size; - stream->close_fn = close ? &ufbxi_stdio_close : NULL; - stream->user = file; -} - -static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) -{ - FILE *file = ufbxi_fopen(fc, path, path_len, null_terminated); - if (!file) return false; - ufbxi_stdio_init(stream, file, true); - return true; -} - -#elif defined(UFBX_EXTERNAL_STDIO) - -static ufbxi_noinline void ufbxi_stdio_init(ufbx_stream *stream, void *file, bool close) -{ - stream->read_fn = &ufbx_stdio_read; - stream->skip_fn = &ufbx_stdio_skip; - stream->size_fn = &ufbx_stdio_size; - stream->close_fn = close ? &ufbx_stdio_close : NULL; - stream->user = file; -} - -static ufbxi_noinline bool ufbxi_stdio_open(ufbxi_file_context *fc, ufbx_stream *stream, const char *path, size_t path_len, bool null_terminated) -{ - char copy_buf[256], *copy = NULL; // ufbxi_uninit - if (null_terminated) { - copy = (char*)path; - } else { - if (path_len < ufbxi_arraycount(copy_buf) - 1) { - copy = copy_buf; - } else { - copy = ufbxi_alloc(&fc->ator, char, path_len + 1); - if (!copy) return false; - } - memcpy(copy, path, path_len); - copy[path_len] = '\0'; - } - void *file = ufbx_stdio_open(copy, path_len); - if (!null_terminated && copy != copy_buf) { - ufbxi_free(&fc->ator, char, copy, path_len + 1); - } - if (!file) { - ufbxi_set_err_info(&fc->error, path, path_len); - ufbxi_report_err_msg(&fc->error, "file", "File not found"); - return false; - } - ufbxi_stdio_init(stream, file, true); - return true; -} - -#endif - -// -- Memory IO - -typedef struct { - const void *data; - size_t size; - size_t position; - ufbx_close_memory_cb close_cb; - - // Own allocation information - size_t self_size; - ufbxi_allocator *parent_ator; - ufbxi_allocator local_ator; - ufbx_error error; - char data_copy[]; -} ufbxi_memory_stream; - -static size_t ufbxi_memory_read(void *user, void *data, size_t max_size) -{ - ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; - size_t to_read = ufbxi_min_sz(stream->size - stream->position, max_size); - memcpy(data, (const char*)stream->data + stream->position, to_read); - stream->position += to_read; - return to_read; -} - -static bool ufbxi_memory_skip(void *user, size_t size) -{ - ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; - if (stream->size - stream->position < size) return false; - stream->position += size; - return true; -} - -static uint64_t ufbxi_memory_size(void *user) -{ - ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; - return stream->size; -} - -static void ufbxi_memory_close(void *user) -{ - ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user; - if (stream->close_cb.fn) { - stream->close_cb.fn(stream->close_cb.user, (void*)stream->data, stream->size); - } - - if (stream->parent_ator) { - ufbxi_free(stream->parent_ator, char, stream, stream->self_size); - } else { - ufbxi_allocator ator = stream->local_ator; - ufbxi_free(&ator, char, stream, stream->self_size); - ufbxi_free_ator(&ator); - } -} - -// -- XML - -#if UFBXI_FEATURE_XML - -typedef struct ufbxi_xml_tag ufbxi_xml_tag; -typedef struct ufbxi_xml_attrib ufbxi_xml_attrib; -typedef struct ufbxi_xml_document ufbxi_xml_document; - -struct ufbxi_xml_attrib { - ufbx_string name; - ufbx_string value; -}; - -struct ufbxi_xml_tag { - ufbx_string name; - ufbx_string text; - - ufbxi_xml_attrib *attribs; - size_t num_attribs; - - ufbxi_xml_tag *children; - size_t num_children; -}; - -struct ufbxi_xml_document { - ufbxi_xml_tag *root; - ufbxi_buf buf; -}; - -typedef struct { - ufbx_error error; - - ufbxi_allocator *ator; - - ufbxi_buf tmp_stack; - ufbxi_buf result; - - ufbxi_xml_document *doc; - - ufbx_read_fn *read_fn; - void *read_user; - - char *tok; - size_t tok_cap; - size_t tok_len; - - const char *pos, *pos_end; - char data[4096]; - - bool io_error; -} ufbxi_xml_context; - -enum { - UFBXI_XML_CTYPE_WHITESPACE = 0x1, - UFBXI_XML_CTYPE_SINGLE_QUOTE = 0x2, - UFBXI_XML_CTYPE_DOUBLE_QUOTE = 0x4, - UFBXI_XML_CTYPE_NAME_END = 0x8, - UFBXI_XML_CTYPE_TAG_START = 0x10, - UFBXI_XML_CTYPE_END_OF_FILE = 0x20, -}; - -// Generated by `misc/gen_xml_ctype.py` -static const uint8_t ufbxi_xml_ctype[256] = { - 32,0,0,0,0,0,0,0,0,9,9,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 9,0,12,0,0,0,0,10,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8, -}; - -static ufbxi_noinline void ufbxi_xml_refill(ufbxi_xml_context *xc) -{ - size_t num = xc->read_fn(xc->read_user, xc->data, sizeof(xc->data)); - if (num == SIZE_MAX || num < sizeof(xc->data)) xc->io_error = true; - if (num < sizeof(xc->data)) { - xc->data[num++] = '\0'; - } - xc->pos = xc->data; - xc->pos_end = xc->data + num; -} - -static ufbxi_forceinline void ufbxi_xml_advance(ufbxi_xml_context *xc) -{ - if (++xc->pos == xc->pos_end) ufbxi_xml_refill(xc); -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_push_token_char(ufbxi_xml_context *xc, char c) -{ - if (xc->tok_len == xc->tok_cap || UFBXI_IS_REGRESSION) { - ufbxi_check_err(&xc->error, ufbxi_grow_array(xc->ator, &xc->tok, &xc->tok_cap, xc->tok_len + 1)); - } - xc->tok[xc->tok_len++] = c; - return 1; -} - -static ufbxi_noinline int ufbxi_xml_accept(ufbxi_xml_context *xc, char ch) -{ - if (*xc->pos == ch) { - ufbxi_xml_advance(xc); - return 1; - } else { - return 0; - } -} - -static ufbxi_noinline void ufbxi_xml_skip_while(ufbxi_xml_context *xc, uint32_t ctypes) -{ - while (ufbxi_xml_ctype[(uint8_t)*xc->pos] & ctypes) { - ufbxi_xml_advance(xc); - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_skip_until_string(ufbxi_xml_context *xc, ufbx_string *dst, const char *suffix) -{ - xc->tok_len = 0; - size_t match_len = 0, ix = 0, suffix_len = strlen(suffix); - char buf[16] = { 0 }; - size_t wrap_mask = sizeof(buf) - 1; - ufbx_assert(suffix_len < sizeof(buf)); - for (;;) { - char c = *xc->pos; - ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file"); - ufbxi_xml_advance(xc); - if (ix >= suffix_len) { - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, buf[(ix - suffix_len) & wrap_mask])); - } - - buf[ix++ & wrap_mask] = c; - for (match_len = 0; match_len < suffix_len; match_len++) { - if (buf[(ix - suffix_len + match_len) & wrap_mask] != suffix[match_len]) { - break; - } - } - if (match_len == suffix_len) break; - } - - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); - if (dst) { - dst->length = xc->tok_len - 1; - dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); - ufbxi_check_err(&xc->error, dst->data); - } - - return 1; -} - -static ufbxi_noinline int ufbxi_xml_read_until(ufbxi_xml_context *xc, ufbx_string *dst, uint32_t ctypes) -{ - xc->tok_len = 0; - for (;;) { - char c = *xc->pos; - - if (c == '&') { - size_t entity_begin = xc->tok_len; - for (;;) { - ufbxi_xml_advance(xc); - c = *xc->pos; - ufbxi_check_err(&xc->error, c != '\0'); - if (c == ';') break; - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c)); - } - ufbxi_xml_advance(xc); - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); - - char *entity = xc->tok + entity_begin; - xc->tok_len = entity_begin; - - if (entity[0] == '#') { - unsigned long code = 0; - if (entity[1] == 'x') { - code = ufbxi_parse_uint32_radix(entity + 2, 16); - } else { - code = ufbxi_parse_uint32_radix(entity + 1, 10); - } - - char bytes[5] = { 0 }; - if (code < 0x80) { - bytes[0] = (char)code; - } else if (code < 0x800) { - bytes[0] = (char)(0xc0 | (code>>6)); - bytes[1] = (char)(0x80 | (code & 0x3f)); - } else if (code < 0x10000) { - bytes[0] = (char)(0xe0 | (code>>12)); - bytes[1] = (char)(0x80 | ((code>>6) & 0x3f)); - bytes[2] = (char)(0x80 | (code & 0x3f)); - } else { - bytes[0] = (char)(0xf0 | (code>>18)); - bytes[1] = (char)(0x80 | ((code>>12) & 0x3f)); - bytes[2] = (char)(0x80 | ((code>>6) & 0x3f)); - bytes[3] = (char)(0x80 | (code & 0x3f)); - } - for (char *b = bytes; *b; b++) { - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, *b)); - } - } else { - char ch = '\0'; - if (!strcmp(entity, "lt")) ch = '<'; - else if (!strcmp(entity, "quot")) ch = '"'; - else if (!strcmp(entity, "amp")) ch = '&'; - else if (!strcmp(entity, "apos")) ch = '\''; - else if (!strcmp(entity, "gt")) ch = '>'; - if (ch) { - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, ch)); - } - } - } else { - if ((ufbxi_xml_ctype[(uint8_t)c] & ctypes) != 0) break; - ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file"); - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c)); - ufbxi_xml_advance(xc); - } - } - - ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0')); - if (dst) { - dst->length = xc->tok_len - 1; - dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); - ufbxi_check_err(&xc->error, dst->data); - } - - return 1; -} - -// Recursion limited by check at the start -static ufbxi_noinline int ufbxi_xml_parse_tag(ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening) - ufbxi_recursive_function(int, ufbxi_xml_parse_tag, (xc, depth, p_closing, opening), UFBXI_MAX_XML_DEPTH + 1, - (ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening)) -{ - ufbxi_check_err(&xc->error, depth < UFBXI_MAX_XML_DEPTH); - - if (!ufbxi_xml_accept(xc, '<')) { - if (*xc->pos == '\0') { - *p_closing = true; - } else { - ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_TAG_START | UFBXI_XML_CTYPE_END_OF_FILE)); - bool has_text = false; - for (size_t i = 0; i < xc->tok_len; i++) { - if ((ufbxi_xml_ctype[(uint8_t)xc->tok[i]] & UFBXI_XML_CTYPE_WHITESPACE) == 0) { - has_text = true; - break; - } - } - - if (has_text) { - ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); - ufbxi_check_err(&xc->error, tag); - tag->name.data = ufbxi_empty_char; - - tag->text.length = xc->tok_len - 1; - tag->text.data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok); - ufbxi_check_err(&xc->error, tag->text.data); - } - } - return 1; - } - - if (ufbxi_xml_accept(xc, '/')) { - ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_NAME_END)); - ufbxi_check_err(&xc->error, opening && !strcmp(xc->tok, opening)); - ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); - if (!ufbxi_xml_accept(xc, '>')) return 0; - *p_closing = true; - return 1; - } else if (ufbxi_xml_accept(xc, '!')) { - if (ufbxi_xml_accept(xc, '[')) { - for (const char *ch = "CDATA["; *ch; ch++) { - if (!ufbxi_xml_accept(xc, *ch)) return 0; - } - - ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); - ufbxi_check_err(&xc->error, tag); - ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, &tag->text, "]]>")); - tag->name.data = ufbxi_empty_char; - - } else if (ufbxi_xml_accept(xc, '-')) { - if (!ufbxi_xml_accept(xc, '-')) return 0; - ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "-->")); - } else { - // TODO: !DOCTYPE - ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, ">")); - } - return 1; - } else if (ufbxi_xml_accept(xc, '?')) { - ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "?>")); - return 1; - } - - ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1); - ufbxi_check_err(&xc->error, tag); - ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &tag->name, UFBXI_XML_CTYPE_NAME_END)); - tag->text.data = ufbxi_empty_char; - - bool has_children = false; - - size_t num_attribs = 0; - for (;;) { - ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); - if (ufbxi_xml_accept(xc, '/')) { - if (!ufbxi_xml_accept(xc, '>')) return 0; - break; - } else if (ufbxi_xml_accept(xc, '>')) { - has_children = true; - break; - } else { - ufbxi_xml_attrib *attrib = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_attrib, 1); - ufbxi_check_err(&xc->error, attrib); - ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->name, UFBXI_XML_CTYPE_NAME_END)); - ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); - if (!ufbxi_xml_accept(xc, '=')) return 0; - ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE); - uint32_t quote_ctype = 0; - if (ufbxi_xml_accept(xc, '"')) { - quote_ctype = UFBXI_XML_CTYPE_DOUBLE_QUOTE; - } else if (ufbxi_xml_accept(xc, '\'')) { - quote_ctype = UFBXI_XML_CTYPE_SINGLE_QUOTE; - } else { - ufbxi_fail_err(&xc->error, "Bad attrib value"); - } - ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->value, quote_ctype)); - ufbxi_xml_advance(xc); - num_attribs++; - } - } - - tag->num_attribs = num_attribs; - tag->attribs = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_attrib, num_attribs); - ufbxi_check_err(&xc->error, tag->attribs); - - if (has_children) { - size_t children_begin = xc->tmp_stack.num_items; - for (;;) { - bool closing = false; - ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, depth + 1, &closing, tag->name.data)); - if (closing) break; - } - - tag->num_children = xc->tmp_stack.num_items - children_begin; - tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children); - ufbxi_check_err(&xc->error, tag->children); - } - - return 1; -} - -static ufbxi_noinline int ufbxi_xml_parse_root(ufbxi_xml_context *xc) -{ - ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->result, ufbxi_xml_tag, 1); - ufbxi_check_err(&xc->error, tag); - tag->name.data = ufbxi_empty_char; - tag->text.data = ufbxi_empty_char; - - for (;;) { - bool closing = false; - ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, 0, &closing, NULL)); - if (closing) break; - } - - tag->num_children = xc->tmp_stack.num_items; - tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children); - ufbxi_check_err(&xc->error, tag->children); - - xc->doc = ufbxi_push(&xc->result, ufbxi_xml_document, 1); - ufbxi_check_err(&xc->error, xc->doc); - - xc->doc->root = tag; - xc->doc->buf = xc->result; - - return 1; -} - -typedef struct { - ufbxi_allocator *ator; - ufbx_read_fn *read_fn; - void *read_user; - const char *prefix; - size_t prefix_length; -} ufbxi_xml_load_opts; - -static ufbxi_noinline ufbxi_xml_document *ufbxi_load_xml(ufbxi_xml_load_opts *opts, ufbx_error *error) -{ - ufbxi_xml_context xc = { UFBX_ERROR_NONE }; - xc.ator = opts->ator; - xc.read_fn = opts->read_fn; - xc.read_user = opts->read_user; - - xc.tmp_stack.ator = xc.ator; - xc.result.ator = xc.ator; - - xc.result.unordered = true; - - if (opts->prefix_length > 0) { - xc.pos = opts->prefix; - xc.pos_end = opts->prefix + opts->prefix_length; - } else { - ufbxi_xml_refill(&xc); - } - - int ok = ufbxi_xml_parse_root(&xc); - - ufbxi_buf_free(&xc.tmp_stack); - ufbxi_free(xc.ator, char, xc.tok, xc.tok_cap); - - if (ok) { - return xc.doc; - } else { - ufbxi_buf_free(&xc.result); - if (error) { - *error = xc.error; - } - - return NULL; - } -} - -static ufbxi_noinline void ufbxi_free_xml(ufbxi_xml_document *doc) -{ - ufbxi_buf buf = doc->buf; - ufbxi_buf_free(&buf); -} - -static ufbxi_noinline ufbxi_xml_tag *ufbxi_xml_find_child(ufbxi_xml_tag *tag, const char *name) -{ - ufbxi_for(ufbxi_xml_tag, child, tag->children, tag->num_children) { - if (!strcmp(child->name.data, name)) { - return child; - } - } - return NULL; -} - -static ufbxi_noinline ufbxi_xml_attrib *ufbxi_xml_find_attrib(ufbxi_xml_tag *tag, const char *name) -{ - ufbxi_for(ufbxi_xml_attrib, attrib, tag->attribs, tag->num_attribs) { - if (!strcmp(attrib->name.data, name)) { - return attrib; - } - } - return NULL; -} - -#endif - -// -- FBX value type information - -static char ufbxi_normalize_array_type(char type, char bool_type) { - switch (type) { - case 'r': return sizeof(ufbx_real) == sizeof(float) ? 'f' : 'd'; - case 'b': return bool_type; - default: return type; - } -} - -static ufbxi_noinline size_t ufbxi_array_type_size(char type) -{ - switch (type) { - case 'r': return sizeof(ufbx_real); - case 'b': return sizeof(bool); - case 'c': return sizeof(uint8_t); - case 'i': return sizeof(int32_t); - case 'l': return sizeof(int64_t); - case 'f': return sizeof(float); - case 'd': return sizeof(double); - case 's': return sizeof(ufbx_string); - case 'S': return sizeof(ufbx_string); - case 'C': return sizeof(ufbx_string); - default: return 1; - } -} - -// -- Node operations - -static ufbxi_noinline ufbxi_node *ufbxi_find_child(ufbxi_node *node, const char *name) -{ - ufbxi_for(ufbxi_node, c, node->children, node->num_children) { - if (c->name == name) return c; - } - return NULL; -} - -// Retrieve the type of a given value -ufbxi_forceinline static ufbxi_value_type ufbxi_get_val_type(ufbxi_node *node, size_t ix) -{ - return (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3); -} - -// Retrieve values from nodes with type codes: -// Any: '_' (ignore) -// NUMBER: 'I' int32_t 'L' int64_t 'F' float 'D' double 'R' ufbxi_real 'B' bool 'Z' size_t -// STRING: 'S' ufbx_string 'C' const char* (checked) 's' ufbx_string 'c' const char * (unchecked) 'b' ufbx_blob -ufbxi_nodiscard ufbxi_forceinline static int ufbxi_get_val_at(ufbxi_node *node, size_t ix, char fmt, void *v) -{ - ufbxi_dev_assert(ix < UFBXI_MAX_NON_ARRAY_VALUES); - ufbxi_value_type type = (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3); - switch (fmt) { - case '_': return 1; - case 'I': if (type == UFBXI_VALUE_NUMBER) { *(int32_t*)v = (int32_t)node->vals[ix].i; return 1; } else return 0; - case 'L': if (type == UFBXI_VALUE_NUMBER) { *(int64_t*)v = (int64_t)node->vals[ix].i; return 1; } else return 0; - case 'F': if (type == UFBXI_VALUE_NUMBER) { *(float*)v = (float)node->vals[ix].f; return 1; } else return 0; - case 'D': if (type == UFBXI_VALUE_NUMBER) { *(double*)v = (double)node->vals[ix].f; return 1; } else return 0; - case 'R': if (type == UFBXI_VALUE_NUMBER) { *(ufbx_real*)v = (ufbx_real)node->vals[ix].f; return 1; } else return 0; - case 'B': if (type == UFBXI_VALUE_NUMBER) { *(bool*)v = node->vals[ix].i != 0; return 1; } else return 0; - case 'Z': if (type == UFBXI_VALUE_NUMBER) { if (node->vals[ix].i < 0) return 0; *(size_t*)v = (size_t)node->vals[ix].i; return 1; } else return 0; - case 'S': if (type == UFBXI_VALUE_STRING) { - ufbxi_sanitized_string src = node->vals[ix].s; - ufbx_string *dst = (ufbx_string*)v; - if (src.utf8_length > 0) { - if (src.utf8_length == UINT32_MAX) return 0; - dst->data = src.raw_data + src.raw_length + 1; - dst->length = src.utf8_length; - } else { - dst->data = src.raw_data; - dst->length = src.raw_length; - } - return 1; - } else return 0; - case 's': if (type == UFBXI_VALUE_STRING) { - ufbxi_sanitized_string src = node->vals[ix].s; - ufbx_string *dst = (ufbx_string*)v; - dst->data = src.raw_data; - dst->length = src.raw_length; - return 1; - } else return 0; - case 'C': if (type == UFBXI_VALUE_STRING) { - ufbxi_sanitized_string src = node->vals[ix].s; - const char **dst = (const char **)v; - if (src.utf8_length > 0) { - if (src.utf8_length == UINT32_MAX) return 0; - *dst = src.raw_data + src.raw_length + 1; - } else { - *dst = src.raw_data; - } - return 1; - } else return 0; - case 'c': if (type == UFBXI_VALUE_STRING) { - ufbxi_sanitized_string src = node->vals[ix].s; - const char **dst = (const char **)v; - *dst = src.raw_data; - return 1; - } else return 0; - case 'b': if (type == UFBXI_VALUE_STRING) { - ufbxi_sanitized_string src = node->vals[ix].s; - ufbx_blob *dst = (ufbx_blob*)v; - dst->data = src.raw_data; - dst->size = src.raw_length; - return 1; - } else return 0; - default: - ufbxi_unreachable("Bad format char"); - return 0; - } -} - -ufbxi_nodiscard ufbxi_noinline static ufbxi_value_array *ufbxi_get_array(ufbxi_node *node, char fmt) -{ - if (node->value_type_mask != UFBXI_VALUE_ARRAY) return NULL; - ufbxi_value_array *array = node->array; - if (fmt != '?') { - fmt = ufbxi_normalize_array_type(fmt, 'b'); - if (array->type != fmt) return NULL; - } - return array; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val1(ufbxi_node *node, const char *fmt, void *v0) -{ - if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val2(ufbxi_node *node, const char *fmt, void *v0, void *v1) -{ - if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; - if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val3(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2) -{ - if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; - if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; - if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val4(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3) -{ - if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; - if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; - if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; - if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val5(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3, void *v4) -{ - if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0; - if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0; - if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0; - if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0; - if (!ufbxi_get_val_at(node, 4, fmt[4], v4)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val1(ufbxi_node *node, const char *name, const char *fmt, void *v0) -{ - ufbxi_node *child = ufbxi_find_child(node, name); - if (!child) return 0; - if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val2(ufbxi_node *node, const char *name, const char *fmt, void *v0, void *v1) -{ - ufbxi_node *child = ufbxi_find_child(node, name); - if (!child) return 0; - if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0; - if (!ufbxi_get_val_at(child, 1, fmt[1], v1)) return 0; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline ufbxi_value_array *ufbxi_find_array(ufbxi_node *node, const char *name, char fmt) -{ - ufbxi_node *child = ufbxi_find_child(node, name); - if (!child) return NULL; - return ufbxi_get_array(child, fmt); -} - -static ufbxi_node *ufbxi_find_child_strcmp(ufbxi_node *node, const char *name) -{ - char leading = name[0]; - ufbxi_for(ufbxi_node, c, node->children, node->num_children) { - if (c->name[0] != leading) continue; - if (!strcmp(c->name, name)) return c; - } - return NULL; -} - -// -- Element extra data allocation - -ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_element_extra_size(ufbxi_context *uc, uint32_t id, size_t size) -{ - if (uc->element_extra_cap <= id) { - size_t old_cap = uc->element_extra_cap; - ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->element_extra_arr, &uc->element_extra_cap, id + 1), NULL); - memset(uc->element_extra_arr + old_cap, 0, (uc->element_extra_cap - old_cap) * sizeof(void*)); - } - - if (uc->element_extra_arr[id]) return uc->element_extra_arr[id]; - - void *extra = ufbxi_push_size_zero(&uc->tmp, size, 1); - ufbxi_check_return(extra, NULL); - uc->element_extra_arr[id] = extra; - - return extra; -} - -static ufbxi_noinline void *ufbxi_get_element_extra(ufbxi_context *uc, uint32_t id) -{ - if (id < uc->element_extra_cap) { - return uc->element_extra_arr[id]; - } else { - return NULL; - } -} - -#define ufbxi_push_element_extra(uc, id, type) (type*)ufbxi_push_element_extra_size((uc), (id), sizeof(type)) - -// -- Parsing state machine -// -// When reading the file we maintain a coarse representation of the structure so -// that we can resolve array info (type, included in result, etc). Using this info -// we can often read/decompress the contents directly into the right memory area. - -typedef enum { - UFBXI_PARSE_ROOT, - UFBXI_PARSE_FBX_HEADER_EXTENSION, - UFBXI_PARSE_SCENE_INFO, - UFBXI_PARSE_THUMBNAIL, - UFBXI_PARSE_DEFINITIONS, - UFBXI_PARSE_OBJECTS, - UFBXI_PARSE_CONNECTIONS, - UFBXI_PARSE_RELATIONS, - UFBXI_PARSE_TAKES, - UFBXI_PARSE_FBX_VERSION, - UFBXI_PARSE_MODEL, - UFBXI_PARSE_GEOMETRY, - UFBXI_PARSE_NODE_ATTRIBUTE, - UFBXI_PARSE_LEGACY_MODEL, - UFBXI_PARSE_LEGACY_MEDIA, - UFBXI_PARSE_LEGACY_VIDEO, - UFBXI_PARSE_LEGACY_SWITCHER, - UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE, - UFBXI_PARSE_REFERENCES, - UFBXI_PARSE_REFERENCE, - UFBXI_PARSE_ANIMATION_CURVE, - UFBXI_PARSE_DEFORMER, - UFBXI_PARSE_ASSOCIATE_MODEL, - UFBXI_PARSE_LEGACY_LINK, - UFBXI_PARSE_POSE, - UFBXI_PARSE_POSE_NODE, - UFBXI_PARSE_TEXTURE, - UFBXI_PARSE_VIDEO, - UFBXI_PARSE_LAYERED_TEXTURE, - UFBXI_PARSE_SELECTION_NODE, - UFBXI_PARSE_COLLECTION, - UFBXI_PARSE_AUDIO, - UFBXI_PARSE_UNKNOWN_OBJECT, - UFBXI_PARSE_LAYER_ELEMENT_NORMAL, - UFBXI_PARSE_LAYER_ELEMENT_BINORMAL, - UFBXI_PARSE_LAYER_ELEMENT_TANGENT, - UFBXI_PARSE_LAYER_ELEMENT_UV, - UFBXI_PARSE_LAYER_ELEMENT_COLOR, - UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE, - UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE, - UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING, - UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY, - UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP, - UFBXI_PARSE_LAYER_ELEMENT_HOLE, - UFBXI_PARSE_LAYER_ELEMENT_MATERIAL, - UFBXI_PARSE_LAYER_ELEMENT_OTHER, - UFBXI_PARSE_GEOMETRY_UV_INFO, - UFBXI_PARSE_SHAPE, - UFBXI_PARSE_TAKE, - UFBXI_PARSE_TAKE_OBJECT, - UFBXI_PARSE_CHANNEL, - UFBXI_PARSE_UNKNOWN, -} ufbxi_parse_state; - -typedef enum { - UFBXI_ARRAY_FLAG_RESULT = 0x1, // < Allocate the array from the result buffer - UFBXI_ARRAY_FLAG_TMP_BUF = 0x2, // < Allocate the array from the long-term temporary buffer - UFBXI_ARRAY_FLAG_PAD_BEGIN = 0x4, // < Pad the begin of the array with 4 zero elements to guard from invalid -1 index accesses - UFBXI_ARRAY_FLAG_ACCURATE_F32 = 0x8, // < Must be parsed as bit-accurate 32-bit floats -} ufbxi_array_flags; - -typedef struct { - char type; // < FBX type code of the array: b,i,l,f,d (or 'r' meaning ufbx_real '-' ignore, 's'/'S' for strings, 'C' for content) - uint8_t flags; // < Combination of `ufbxi_array_flags` -} ufbxi_array_info; - -static ufbxi_noinline ufbxi_parse_state ufbxi_update_parse_state(ufbxi_parse_state parent, const char *name) -{ - switch (parent) { - - case UFBXI_PARSE_ROOT: - if (name == ufbxi_FBXHeaderExtension) return UFBXI_PARSE_FBX_HEADER_EXTENSION; - if (name == ufbxi_Definitions) return UFBXI_PARSE_DEFINITIONS; - if (name == ufbxi_Objects) return UFBXI_PARSE_OBJECTS; - if (name == ufbxi_Connections) return UFBXI_PARSE_CONNECTIONS; - if (name == ufbxi_Takes) return UFBXI_PARSE_TAKES; - if (name == ufbxi_Model) return UFBXI_PARSE_LEGACY_MODEL; - if (!strcmp(name, "References")) return UFBXI_PARSE_REFERENCES; - if (!strcmp(name, "Relations")) return UFBXI_PARSE_RELATIONS; - if (name == ufbxi_Media) return UFBXI_PARSE_LEGACY_MEDIA; - if (!strcmp(name, "Switcher")) return UFBXI_PARSE_LEGACY_SWITCHER; - if (!strcmp(name, "SceneGenericPersistence")) return UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE; - break; - - case UFBXI_PARSE_FBX_HEADER_EXTENSION: - if (name == ufbxi_FBXVersion) return UFBXI_PARSE_FBX_VERSION; - if (name == ufbxi_SceneInfo) return UFBXI_PARSE_SCENE_INFO; - break; - - case UFBXI_PARSE_SCENE_INFO: - if (name == ufbxi_Thumbnail) return UFBXI_PARSE_THUMBNAIL; - break; - - case UFBXI_PARSE_OBJECTS: - if (name == ufbxi_Model) return UFBXI_PARSE_MODEL; - if (name == ufbxi_Geometry) return UFBXI_PARSE_GEOMETRY; - if (name == ufbxi_NodeAttribute) return UFBXI_PARSE_NODE_ATTRIBUTE; - if (name == ufbxi_AnimationCurve) return UFBXI_PARSE_ANIMATION_CURVE; - if (name == ufbxi_Deformer) return UFBXI_PARSE_DEFORMER; - if (name == ufbxi_Pose) return UFBXI_PARSE_POSE; - if (name == ufbxi_Texture) return UFBXI_PARSE_TEXTURE; - if (name == ufbxi_Video) return UFBXI_PARSE_VIDEO; - if (name == ufbxi_LayeredTexture) return UFBXI_PARSE_LAYERED_TEXTURE; - if (name == ufbxi_SelectionNode) return UFBXI_PARSE_SELECTION_NODE; - if (name == ufbxi_Collection) return UFBXI_PARSE_COLLECTION; - if (name == ufbxi_Audio) return UFBXI_PARSE_AUDIO; - return UFBXI_PARSE_UNKNOWN_OBJECT; - - case UFBXI_PARSE_MODEL: - case UFBXI_PARSE_GEOMETRY: - if (name[0] == 'L') { - if (name == ufbxi_LayerElementNormal) return UFBXI_PARSE_LAYER_ELEMENT_NORMAL; - if (name == ufbxi_LayerElementBinormal) return UFBXI_PARSE_LAYER_ELEMENT_BINORMAL; - if (name == ufbxi_LayerElementTangent) return UFBXI_PARSE_LAYER_ELEMENT_TANGENT; - if (name == ufbxi_LayerElementUV) return UFBXI_PARSE_LAYER_ELEMENT_UV; - if (name == ufbxi_LayerElementColor) return UFBXI_PARSE_LAYER_ELEMENT_COLOR; - if (name == ufbxi_LayerElementVertexCrease) return UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE; - if (name == ufbxi_LayerElementEdgeCrease) return UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE; - if (name == ufbxi_LayerElementSmoothing) return UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING; - if (name == ufbxi_LayerElementVisibility) return UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY; - if (name == ufbxi_LayerElementPolygonGroup) return UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP; - if (name == ufbxi_LayerElementHole) return UFBXI_PARSE_LAYER_ELEMENT_HOLE; - if (name == ufbxi_LayerElementMaterial) return UFBXI_PARSE_LAYER_ELEMENT_MATERIAL; - if (!strncmp(name, "LayerElement", 12)) return UFBXI_PARSE_LAYER_ELEMENT_OTHER; - } - if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE; - break; - - case UFBXI_PARSE_DEFORMER: - if (!strcmp(name, "AssociateModel")) return UFBXI_PARSE_ASSOCIATE_MODEL; - break; - - case UFBXI_PARSE_LEGACY_MEDIA: - if (name == ufbxi_Video) return UFBXI_PARSE_LEGACY_VIDEO; - break; - - case UFBXI_PARSE_LEGACY_VIDEO: - return UFBXI_PARSE_VIDEO; - - case UFBXI_PARSE_LEGACY_MODEL: - if (name == ufbxi_GeometryUVInfo) return UFBXI_PARSE_GEOMETRY_UV_INFO; - if (name == ufbxi_Link) return UFBXI_PARSE_LEGACY_LINK; - if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; - if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE; - break; - - case UFBXI_PARSE_POSE: - if (name == ufbxi_PoseNode) return UFBXI_PARSE_POSE_NODE; - break; - - case UFBXI_PARSE_TAKES: - if (name == ufbxi_Take) return UFBXI_PARSE_TAKE; - break; - - case UFBXI_PARSE_TAKE: - return UFBXI_PARSE_TAKE_OBJECT; - - case UFBXI_PARSE_TAKE_OBJECT: - if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; - break; - - case UFBXI_PARSE_CHANNEL: - if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL; - break; - - case UFBXI_PARSE_REFERENCES: - return UFBXI_PARSE_REFERENCE; - - default: - break; - - } - - return UFBXI_PARSE_UNKNOWN; -} - -static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, ufbxi_array_info *info) -{ - info->flags = 0; - - // Retain all arrays if user wants the DOM representation - if (uc->opts.retain_dom) { - info->flags |= UFBXI_ARRAY_FLAG_RESULT; - } - - switch (parent) { - - case UFBXI_PARSE_THUMBNAIL: - if (name == ufbxi_ImageData) { - info->type = 'c'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_GEOMETRY: - case UFBXI_PARSE_MODEL: - if (name == ufbxi_Vertices) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_PolygonVertexIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Edges) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - return true; - } else if (name == ufbxi_Indexes) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Points) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_KnotVector) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_KnotVectorU) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_KnotVectorV) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_PointsIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Normals) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_LEGACY_MODEL: - if (name == ufbxi_Vertices) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_Normals) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_Materials) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_PolygonVertexIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Children) { - info->type = 's'; - return true; - } - break; - - case UFBXI_PARSE_ANIMATION_CURVE: - if (name == ufbxi_KeyTime) { - info->type = uc->opts.ignore_animation ? '-' : 'l'; - return true; - } else if (name == ufbxi_KeyValueFloat) { - info->type = uc->opts.ignore_animation ? '-' : 'r'; - return true; - } else if (name == ufbxi_KeyAttrFlags) { - info->type = uc->opts.ignore_animation ? '-' : 'i'; - return true; - } else if (name == ufbxi_KeyAttrDataFloat) { - // The float data in a keyframe attribute array is represented as integers - // in versions >= 7200 as some of the elements aren't actually floats (!) - info->type = uc->from_ascii && uc->version >= 7200 ? 'i' : 'f'; - if (uc->opts.ignore_animation) info->type = '-'; - if (uc->from_ascii && uc->version < 7200) { - info->flags |= UFBXI_ARRAY_FLAG_ACCURATE_F32; - } - return true; - } else if (name == ufbxi_KeyAttrRefCount) { - info->type = uc->opts.ignore_animation ? '-' : 'i'; - return true; - } - break; - - case UFBXI_PARSE_TEXTURE: - if (!strcmp(name, "ModelUVTranslation") || !strcmp(name, "ModelUVScaling") || !strcmp(name, "Cropping")) { - info->type = uc->opts.retain_dom ? 'r' : '-'; - return true; - } - break; - - case UFBXI_PARSE_VIDEO: - if (name == ufbxi_Content) { - info->type = uc->opts.ignore_embedded ? '-' : 'C'; - return true; - } - break; - - case UFBXI_PARSE_LAYERED_TEXTURE: - if (name == ufbxi_BlendModes) { - info->type = 'i'; - info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; - return true; - } else if (name == ufbxi_Alphas) { - info->type = 'r'; - info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; - return true; - } - break; - - case UFBXI_PARSE_SELECTION_NODE: - if (name == ufbxi_VertexIndexArray) { - info->type = 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_EdgeIndexArray) { - info->type = 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_PolygonIndexArray) { - info->type = 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_NORMAL: - if (name == ufbxi_Normals) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_NormalsIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_NormalsW) { - info->type = uc->retain_vertex_w ? 'r' : '-'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_BINORMAL: - if (name == ufbxi_Binormals) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_BinormalsIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_BinormalsW) { - info->type = uc->retain_vertex_w ? 'r' : '-'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_TANGENT: - if (name == ufbxi_Tangents) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_TangentsIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_TangentsW) { - info->type = uc->retain_vertex_w ? 'r' : '-'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_UV: - if (name == ufbxi_UV) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_UVIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_COLOR: - if (name == ufbxi_Colors) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_ColorIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE: - if (name == ufbxi_VertexCrease) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_VertexCreaseIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE: - if (name == ufbxi_EdgeCrease) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING: - if (name == ufbxi_Smoothing) { - info->type = uc->opts.ignore_geometry ? '-' : 'b'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY: - if (name == ufbxi_Visibility) { - info->type = uc->opts.ignore_geometry ? '-' : 'b'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP: - if (name == ufbxi_PolygonGroup) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_HOLE: - if (name == ufbxi_Hole) { - info->type = uc->opts.ignore_geometry ? '-' : 'b'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_MATERIAL: - if (name == ufbxi_Materials) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_LAYER_ELEMENT_OTHER: - if (name == ufbxi_TextureId) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF; - return true; - } else if (name == ufbxi_UV) { - info->type = uc->opts.retain_dom ? 'r' : '-'; - return true; - } else if (name == ufbxi_UVIndex) { - info->type = uc->opts.retain_dom ? 'i' : '-'; - return true; - } - break; - - case UFBXI_PARSE_GEOMETRY_UV_INFO: - if (name == ufbxi_TextureUV) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } else if (name == ufbxi_TextureUVVerticeIndex) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_SHAPE: - if (name == ufbxi_Indexes) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - if (name == ufbxi_Vertices) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - if (name == ufbxi_Normals) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; - return true; - } - break; - - case UFBXI_PARSE_DEFORMER: - if (name == ufbxi_Transform) { - info->type = 'r'; - return true; - } else if (name == ufbxi_TransformLink) { - info->type = 'r'; - return true; - } else if (name == ufbxi_Indexes) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Weights) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_BlendWeights) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_FullWeights) { - info->type = 'r'; - info->flags = (uint8_t)(info->flags | (uc->blender_full_weights ? UFBXI_ARRAY_FLAG_RESULT : UFBXI_ARRAY_FLAG_TMP_BUF)); - return true; - } else if (!strcmp(name, "TransformAssociateModel")) { - info->type = uc->opts.retain_dom ? 'r' : '-'; - return true; - } - break; - - case UFBXI_PARSE_ASSOCIATE_MODEL: - if (name == ufbxi_Transform) { - info->type = uc->opts.retain_dom ? 'r' : '-'; - return true; - } - break; - - case UFBXI_PARSE_LEGACY_LINK: - if (name == ufbxi_Transform) { - info->type = 'r'; - return true; - } else if (name == ufbxi_TransformLink) { - info->type = 'r'; - return true; - } else if (name == ufbxi_Indexes) { - info->type = uc->opts.ignore_geometry ? '-' : 'i'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } else if (name == ufbxi_Weights) { - info->type = uc->opts.ignore_geometry ? '-' : 'r'; - info->flags = UFBXI_ARRAY_FLAG_RESULT; - return true; - } - break; - - case UFBXI_PARSE_POSE_NODE: - if (name == ufbxi_Matrix) { - info->type = 'r'; - return true; - } - break; - - case UFBXI_PARSE_CHANNEL: - if (name == ufbxi_Key) { - info->type = uc->opts.ignore_animation ? '-' : 'd'; - return true; - } - break; - - case UFBXI_PARSE_AUDIO: - if (name == ufbxi_Content) { - info->type = uc->opts.ignore_embedded ? '-' : 'C'; - return true; - } - break; - - default: - if (name == ufbxi_BinaryData) { - info->type = uc->opts.ignore_embedded ? '-' : 'C'; - return true; - } - break; - - } - - return false; -} - -static ufbxi_noinline bool ufbxi_is_raw_string(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, size_t index) -{ - (void)index; - - switch (parent) { - - case UFBXI_PARSE_ROOT: - if (name == ufbxi_Model) return true; - if (!strcmp(name, "FileId")) return true; - break; - - case UFBXI_PARSE_FBX_HEADER_EXTENSION: - if (name == ufbxi_SceneInfo) return true; - break; - - case UFBXI_PARSE_OBJECTS: - return true; - - case UFBXI_PARSE_CONNECTIONS: - case UFBXI_PARSE_RELATIONS: - // Pre-7000 needs raw strings for "Name\x00\x01Type" pairs, post-7000 uses it only - // for properties that are non-raw by default. - return uc->version < 7000; - - case UFBXI_PARSE_MODEL: - if (name == ufbxi_NodeAttributeName) return true; - if (name == ufbxi_Name) return true; - break; - - case UFBXI_PARSE_VIDEO: - if (name == ufbxi_Content) return true; - break; - - case UFBXI_PARSE_TEXTURE: - if (!strcmp(name, "TextureName")) return true; - if (name == ufbxi_Media) return true; - break; - - case UFBXI_PARSE_GEOMETRY: - if (name == ufbxi_NodeAttributeName) return true; - if (name == ufbxi_Name) return true; - break; - - case UFBXI_PARSE_NODE_ATTRIBUTE: - if (name == ufbxi_NodeAttributeName) return true; - if (name == ufbxi_Name) return true; - break; - - case UFBXI_PARSE_POSE_NODE: - if (name == ufbxi_Node) return true; - break; - - case UFBXI_PARSE_SELECTION_NODE: - if (name == ufbxi_Node) return true; - break; - - case UFBXI_PARSE_UNKNOWN_OBJECT: - if (name == ufbxi_NodeAttributeName) return true; - if (name == ufbxi_Name) return true; - break; - - case UFBXI_PARSE_COLLECTION: - if (!strcmp(name, "Member")) return true; - break; - - case UFBXI_PARSE_AUDIO: - if (name == ufbxi_Content) return true; - break; - - case UFBXI_PARSE_LEGACY_MODEL: - if (name == ufbxi_Material) return true; - if (name == ufbxi_Link) return true; - if (name == ufbxi_Name) return true; - break; - - case UFBXI_PARSE_LEGACY_SWITCHER: - if (!strcmp(name, "CameraIndexName")) return true; - break; - - case UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE: - if (name == ufbxi_SceneInfo) return true; - break; - - case UFBXI_PARSE_REFERENCE: - if (!strcmp(name, "Object")) return true; - break; - - case UFBXI_PARSE_TAKE: - if (name == ufbxi_Model) return true; - break; - - default: - break; - - } - - return false; -} - -// -- Binary parsing - -ufbxi_nodiscard static ufbxi_noinline char *ufbxi_swap_endian(ufbxi_context *uc, const void *src, size_t count, size_t elem_size) -{ - ufbxi_dev_assert(elem_size > 1); - size_t total_size = count * elem_size; - ufbxi_check_return(!ufbxi_does_overflow(total_size, count, elem_size), NULL); - if (uc->swap_arr_size < total_size) { - ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->swap_arr, &uc->swap_arr_size, total_size), NULL); - } - char *dst = uc->swap_arr, *d = dst; - - const char *s = (const char*)src; - switch (elem_size) { - case 2: - ufbxi_nounroll for (size_t i = 0; i < count; i++) { - d[0] = s[1]; d[1] = s[0]; - d += 2; s += 2; - } - break; - case 4: - ufbxi_nounroll for (size_t i = 0; i < count; i++) { - d[0] = s[3]; d[1] = s[2]; d[2] = s[1]; d[3] = s[0]; - d += 4; s += 4; - } - break; - case 8: - ufbxi_nounroll for (size_t i = 0; i < count; i++) { - d[0] = s[7]; d[1] = s[6]; d[2] = s[5]; d[3] = s[4]; - d[4] = s[3]; d[5] = s[2]; d[6] = s[1]; d[7] = s[0]; - d += 8; s += 8; - } - break; - default: - ufbxi_unreachable("Bad endian swap size"); - } - - return dst; -} - -// Swap the endianness of an array typed with a lowercase letter -ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_array(ufbxi_context *uc, const void *src, size_t count, char type) -{ - switch (type) { - case 'i': case 'f': return ufbxi_swap_endian(uc, src, count, 4); - case 'l': case 'd': return ufbxi_swap_endian(uc, src, count, 8); - default: return (const char*)src; - } -} - -// Swap the endianness of a single value (shallow, swaps string/array header words) -ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_value(ufbxi_context *uc, const void *src, char type) -{ - switch (type) { - case 'Y': return ufbxi_swap_endian(uc, src, 1, 2); - case 'I': case 'F': return ufbxi_swap_endian(uc, src, 1, 4); - case 'L': case 'D': return ufbxi_swap_endian(uc, src, 1, 8); - case 'S': case 'R': return ufbxi_swap_endian(uc, src, 1, 4); - case 'i': case 'l': case 'f': case 'd': case 'b': return ufbxi_swap_endian(uc, src, 3, 4); - default: return (const char*)src; - } -} - -// Read and convert a post-7000 FBX data array into a different format. `src_type` may be equal to `dst_type` -// if the platform is not binary compatible with the FBX data representation. -ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_convert_array(ufbxi_context *maybe_uc, char src_type, char dst_type, const void *src, void *dst, size_t size) -{ - // TODO: We might want to use the slow path if the machine float/double doesn't match IEEE 754! - // Convert commented out lines under some `#if UFBX_NON_IEE754` define or something. - if (src_type == dst_type) { - ufbx_assert(maybe_uc && maybe_uc->file_big_endian != maybe_uc->local_big_endian); - src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type); - ufbxi_check_err(&maybe_uc->error, src); - memcpy(dst, src, size * ufbxi_array_type_size(dst_type)); - return 1; - } - - if (maybe_uc && maybe_uc->file_big_endian) { - src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type); - ufbxi_check_err(&maybe_uc->error, src); - } - - switch (dst_type) - { - - #define ufbxi_convert_loop_fast(m_dst, m_cast, m_size, m_expr) do { \ - const char *val = (const char*)src, *val_end = val + size*m_size; \ - m_dst *d = (m_dst*)dst; \ - while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \ - } while (0) - - #define ufbxi_convert_loop_slow(m_dst, m_cast, m_size, m_expr) do { \ - const char *val = (const char*)src, *val_end = val + size*m_size; \ - m_dst *d = (m_dst*)dst; \ - ufbxi_nounroll while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \ - } while (0) - - case 'c': - switch (src_type) { - // case 'c': ufbxi_convert_loop_fast(char, (char), 1, *val != 0); break; - case 'i': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_i32(val)); break; - case 'l': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_i64(val)); break; - case 'f': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_f32(val)); break; - case 'd': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_f64(val)); break; - default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; - } - break; - - case 'i': - switch (src_type) { - case 'c': ufbxi_convert_loop_slow(int32_t, (int32_t), 1, *val); break; - // case 'i': ufbxi_convert_loop_slow(int32_t, (int32_t), 4, ufbxi_read_i32(val)); break; - case 'l': ufbxi_convert_loop_slow(int32_t, (int32_t), 8, ufbxi_read_i64(val)); break; - case 'f': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 4, ufbxi_read_f32(val)); break; - case 'd': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 8, ufbxi_read_f64(val)); break; - default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; - } - break; - - case 'l': - switch (src_type) { - case 'c': ufbxi_convert_loop_slow(int64_t, (int64_t), 1, *val); break; - case 'i': ufbxi_convert_loop_slow(int64_t, (int64_t), 4, ufbxi_read_i32(val)); break; - // case 'l': ufbxi_convert_loop_slow(int64_t, (int64_t), 8, ufbxi_read_i64(val)); break; - case 'f': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 4, ufbxi_read_f32(val)); break; - case 'd': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 8, ufbxi_read_f64(val)); break; - default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; - } - break; - - case 'f': - switch (src_type) { - case 'c': ufbxi_convert_loop_slow(float, (float), 1, *val); break; - case 'i': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_i32(val)); break; - case 'l': ufbxi_convert_loop_slow(float, (float), 8, ufbxi_read_i64(val)); break; - // case 'f': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_f32(val)); break; - case 'd': ufbxi_convert_loop_fast(float, (float), 8, ufbxi_read_f64(val)); break; - default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; - } - break; - - case 'd': - switch (src_type) { - case 'c': ufbxi_convert_loop_slow(double, (double), 1, *val); break; - case 'i': ufbxi_convert_loop_slow(double, (double), 4, ufbxi_read_i32(val)); break; - case 'l': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_i64(val)); break; - case 'f': ufbxi_convert_loop_fast(double, (double), 4, ufbxi_read_f32(val)); break; - // case 'd': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_f64(val)); break; - default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0; - } - break; - - default: return 0; - - } - - return 1; -} - -// Read pre-7000 separate properties as an array. -ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_parse_multivalue_array(ufbxi_context *uc, char dst_type, void *dst, size_t size, ufbxi_buf *tmp_buf) -{ - if (size == 0) return 1; - const char *val; - size_t val_size; - - bool file_big_endian = uc->file_big_endian; - - #define ufbxi_convert_parse_fast(m_dst, m_type, m_expr) do { \ - m_dst *d = (m_dst*)dst; \ - for (; base < size; base++) { \ - val = ufbxi_peek_bytes(uc, 13); \ - ufbxi_check(val); \ - if (*val != m_type) break; \ - val++; \ - *d++ = (m_dst)(m_expr); \ - ufbxi_consume_bytes(uc, 1 + sizeof(m_dst)); \ - } \ - } while (0) - - // String array special case - if (dst_type == 's' || dst_type == 'S' || dst_type == 'C') { - bool raw = dst_type == 's'; - ufbx_string *d = (ufbx_string*)dst; - for (size_t i = 0; i < size; i++) { - val = ufbxi_peek_bytes(uc, 13); - ufbxi_check(val); - char type = *val++; - ufbxi_check(type == 'S' || type == 'R'); - if (file_big_endian) { - val = ufbxi_swap_endian_value(uc, val, type); - ufbxi_check(val); - } - size_t len = ufbxi_read_u32(val); - ufbxi_consume_bytes(uc, 5); - d->data = ufbxi_read_bytes(uc, len); - d->length = len; - ufbxi_check(d->data); - if (dst_type == 'C') { - ufbxi_buf *buf = size == 1 || uc->opts.retain_dom ? &uc->result : tmp_buf; - d->data = ufbxi_push_copy(buf, char, len, d->data); - ufbxi_check(d->data); - } else { - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, d, raw)); - } - d++; - } - return 1; - } - - // Optimize a couple of common cases - size_t base = 0; - if (!file_big_endian) { - switch (dst_type) { - case 'i': ufbxi_convert_parse_fast(int32_t, 'I', ufbxi_read_i32(val)); break; - case 'l': ufbxi_convert_parse_fast(int64_t, 'L', ufbxi_read_i64(val)); break; - case 'f': ufbxi_convert_parse_fast(float, 'F', ufbxi_read_f32(val)); break; - case 'd': ufbxi_convert_parse_fast(double, 'D', ufbxi_read_f64(val)); break; - default: break; // Fallthrough to rest - } - - // Early return if we handled everything - if (base == size) return 1; - } - - switch (dst_type) - { - - #define ufbxi_convert_parse(m_cast, m_size, m_expr) \ - *d++ = m_cast(m_expr); val_size = m_size + 1; \ - - #define ufbxi_convert_parse_switch(m_dst, m_cast_int, m_cast_float) do { \ - m_dst *d = (m_dst*)dst + base; \ - for (size_t i = base; i < size; i++) { \ - val = ufbxi_peek_bytes(uc, 13); \ - ufbxi_check(val); \ - char type = *val++; \ - if (file_big_endian) { \ - val = ufbxi_swap_endian_value(uc, val, type); \ - ufbxi_check(val); \ - } \ - switch (type) { \ - case 'C': \ - case 'B': ufbxi_convert_parse(m_cast_int, 1, *val); break; \ - case 'Y': ufbxi_convert_parse(m_cast_int, 2, ufbxi_read_i16(val)); break; \ - case 'I': ufbxi_convert_parse(m_cast_int, 4, ufbxi_read_i32(val)); break; \ - case 'L': ufbxi_convert_parse(m_cast_int, 8, ufbxi_read_i64(val)); break; \ - case 'F': ufbxi_convert_parse(m_cast_float, 4, ufbxi_read_f32(val)); break; \ - case 'D': ufbxi_convert_parse(m_cast_float, 8, ufbxi_read_f64(val)); break; \ - default: ufbxi_fail("Bad multivalue array type"); \ - } \ - ufbxi_consume_bytes(uc, val_size); \ - } \ - } while (0) - - case 'c': ufbxi_convert_parse_switch(uint8_t, (uint8_t), (uint8_t)); break; - case 'i': ufbxi_convert_parse_switch(int32_t, (int32_t), ufbxi_f64_to_i32); break; - case 'l': ufbxi_convert_parse_switch(int64_t, (int64_t), ufbxi_f64_to_i64); break; - case 'f': ufbxi_convert_parse_switch(float, (float), (float)); break; - case 'd': ufbxi_convert_parse_switch(double, (double), (double)); break; - - default: return 0; - - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static void *ufbxi_push_array_data(ufbxi_context *uc, const ufbxi_array_info *info, size_t size, ufbxi_buf *tmp_buf) -{ - size_t elem_size = ufbxi_array_type_size(info->type); - uint32_t flags = info->flags; - if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) size += 4; - - // The array may be pushed either to the result or temporary buffer depending - // if it's already in the right format - ufbxi_buf *arr_buf = tmp_buf; - if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result; - else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp; - char *data = (char*)ufbxi_push_size(arr_buf, elem_size, size); - ufbxi_check_return(data, NULL); - - if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) { - memset(data, 0, elem_size * 4); - data += elem_size * 4; - } - - return data; -} - -ufbxi_noinline static void ufbxi_postprocess_bool_array(char *data, size_t size) -{ - ufbxi_for(char, b, (char*)data, size) { - *b = (char)(*b != 0); - } -} - -typedef struct { - size_t encoded_size; - size_t src_elem_size; - size_t array_size; - char src_type; - char dst_type; - char arr_type; - const void *encoded_data; - void *decoded_data; - void *dst_data; - ufbx_inflate_retain *inflate_retain; -} ufbxi_deflate_task; - -static bool ufbxi_deflate_task_fn(ufbxi_task *task) -{ - ufbxi_deflate_task *t = (ufbxi_deflate_task*)task->data; - - ufbx_inflate_input input; // ufbxi_uninit - input.total_size = t->encoded_size; - input.data = t->encoded_data; - input.data_size = t->encoded_size; - input.no_header = false; - input.no_checksum = false; - input.internal_fast_bits = 0; - input.progress_cb.fn = NULL; - input.progress_cb.user = NULL; - input.progress_size_before = 0; - input.progress_size_after = 0; - input.progress_interval_hint = 0; - input.buffer = NULL; - input.buffer_size = 0; - input.read_fn = NULL; - input.read_user = NULL; - - size_t decoded_data_size = t->src_elem_size * t->array_size; - ptrdiff_t res = ufbx_inflate(t->decoded_data, decoded_data_size, &input, t->inflate_retain); - if (res == -28) { - task->error = "Cancelled"; - return false; - } else if (res != (ptrdiff_t)decoded_data_size) { - task->error = "Bad DEFLATE data"; - return false; - } - - if (t->decoded_data != t->dst_data) { - int ok = ufbxi_binary_convert_array(NULL, t->src_type, t->dst_type, t->decoded_data, t->dst_data, t->array_size); - if (!ok) { - task->error = "Failed to convert array"; - return false; - } - } - - if (t->arr_type == 'b') { - ufbxi_postprocess_bool_array((char*)t->dst_data, t->array_size); - } - - return true; -} - -// Recursion limited by check at the start -ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive) - ufbxi_recursive_function(int, ufbxi_binary_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1, - (ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)) -{ - // https://code.blender.org/2013/08/fbx-binary-file-format-specification - // Parse an FBX document node in the binary format - ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH); - - // Parse the node header, post-7500 versions use 64-bit values for most - // header fields. - uint64_t end_offset, num_values64, values_len; - uint8_t name_len; - size_t header_size = (uc->version >= 7500) ? 25 : 13; - const char *header = ufbxi_read_bytes(uc, header_size), *header_words = header; - ufbxi_check(header); - if (uc->version >= 7500) { - if (uc->file_big_endian) { - header_words = ufbxi_swap_endian(uc, header_words, 3, 8); - ufbxi_check(header_words); - } - end_offset = ufbxi_read_u64(header_words + 0); - num_values64 = ufbxi_read_u64(header_words + 8); - values_len = ufbxi_read_u64(header_words + 16); - name_len = ufbxi_read_u8(header + 24); - } else { - if (uc->file_big_endian) { - header_words = ufbxi_swap_endian(uc, header_words, 3, 4); - ufbxi_check(header_words); - } - end_offset = ufbxi_read_u32(header_words + 0); - num_values64 = ufbxi_read_u32(header_words + 4); - values_len = ufbxi_read_u32(header_words + 8); - name_len = ufbxi_read_u8(header + 12); - } - - ufbxi_check(num_values64 <= UINT32_MAX); - uint32_t num_values = (uint32_t)num_values64; - - // If `end_offset` and `name_len` is zero we treat as the node as a NULL-sentinel - // that terminates a node list. - if (end_offset == 0 && name_len == 0) { - *p_end = true; - return 1; - } - - // Update estimated end offset if possible - if (end_offset > uc->progress_bytes_total) { - uc->progress_bytes_total = end_offset; - } - - // Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by - // calling code after its done parsing all of it's children. - ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1); - ufbxi_check(node); - - // Parse and intern the name to the string pool. - const char *name = ufbxi_read_bytes(uc, name_len); - ufbxi_check(name); - name = ufbxi_push_string(&uc->string_pool, name, name_len, NULL, true); - ufbxi_check(name); - node->name_len = name_len; - node->name = name; - - uint64_t values_end_offset = ufbxi_get_read_offset(uc) + values_len; - - // Check if the values of the node we're parsing currently should be - // treated as an array. - ufbxi_array_info arr_info; - if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) { - - // Normalize the array type (eg. 'r' to 'f'/'d' depending on the build) - // and get the per-element size of the array. - // Boolean arrays 'b' are normalized to 'c' as they are postprocessed - // below based on `arr_info.type`. - char dst_type = ufbxi_normalize_array_type(arr_info.type, 'c'); - - ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1); - ufbxi_check(arr); - - node->value_type_mask = UFBXI_VALUE_ARRAY; - node->array = arr; - arr->type = ufbxi_normalize_array_type(arr_info.type, 'b'); - - // Peek the first bytes of the array. We can always look at least 13 bytes - // ahead safely as valid FBX files must end in a 13/25 byte NULL record. - const char *data = ufbxi_peek_bytes(uc, 13); - ufbxi_check(data); - - // Check if the data type is one of the explicit array types (post-7000). - // Otherwise we form the array by concatenating all the normal values of the - // node (pre-7000) - char c = data[0]; - - // HACK: Override the "type" if either the array is empty or we want to - // specifically ignore the contents. - if (num_values == 0) c = '0'; - if (dst_type == '-') c = '-'; - - bool deferred = false; - - if (c=='c' || c=='b' || c=='i' || c=='l' || c =='f' || c=='d') { - - const char *arr_words = data + 1; - if (uc->file_big_endian) { - arr_words = ufbxi_swap_endian(uc, arr_words, 3, 4); - ufbxi_check(arr_words); - } - - // Parse the array header from the prefix we already peeked above. - char src_type = data[0]; - uint32_t size = ufbxi_read_u32(arr_words + 0); - uint32_t encoding = ufbxi_read_u32(arr_words + 4); - uint32_t encoded_size = ufbxi_read_u32(arr_words + 8); - ufbxi_consume_bytes(uc, 13); - - // Normalize the source type as well, but don't convert UFBX-specific - // 'r' to 'f'/'d', but fail later instead. - if (src_type != 'r') src_type = ufbxi_normalize_array_type(src_type, 'c'); - size_t src_elem_size = ufbxi_array_type_size(src_type); - size_t decoded_data_size = src_elem_size * size; - - // Allocate `size` elements for the array. - char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, size, tmp_buf); - ufbxi_check(arr_data); - - uint64_t arr_begin = ufbxi_get_read_offset(uc); - ufbxi_check(UINT64_MAX - encoded_size > arr_begin); - uint64_t arr_end = arr_begin + encoded_size; - if (arr_end > uc->progress_bytes_total) { - uc->progress_bytes_total = arr_end; - } - - // Threading - if (uc->parse_threaded && encoding == 1 && encoded_size >= UFBXI_MIN_THREADED_DEFLATE_BYTES && !uc->file_big_endian && !uc->local_big_endian) { - ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_deflate_task_fn); - if (task) { - ufbxi_deflate_task *t = ufbxi_push_zero(tmp_buf, ufbxi_deflate_task, 1); - ufbxi_check(t); - - ufbxi_inflate_init_retain(uc->inflate_retain); - - t->src_elem_size = src_elem_size; - t->encoded_size = encoded_size; - t->array_size = size; - t->src_type = src_type; - t->dst_type = dst_type; - t->arr_type = arr->type; - t->dst_data = arr_data; - t->inflate_retain = uc->inflate_retain; - - if (!uc->read_fn) { - // From memory, no need to copy - t->encoded_data = uc->data; - } else { - void *encoded_data = ufbxi_push(tmp_buf, char, encoded_size); - ufbxi_check(encoded_data); - ufbxi_check(ufbxi_read_to(uc, encoded_data, encoded_size)); - t->encoded_data = encoded_data; - } - - if (src_type != dst_type) { - t->decoded_data = ufbxi_push_size(tmp_buf, src_elem_size, size); - ufbxi_check(t->decoded_data); - } else { - t->decoded_data = arr_data; - } - - task->data = t; - ufbxi_thread_pool_run_task(&uc->thread_pool, task); - deferred = true; - } - } - - // If the source and destination types are equal and our build is binary-compatible - // with the FBX format we can read the decoded data directly into the array buffer. - // Otherwise we need a temporary buffer to decode the array into before conversion. - void *decoded_data = arr_data; - if (!deferred && (src_type != dst_type || uc->local_big_endian != uc->file_big_endian)) { - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, decoded_data_size)); - decoded_data = uc->tmp_arr; - } - - if (deferred) { - // Nop - } else if (encoding == 0) { - // Encoding 0: Plain binary data. - ufbxi_check(encoded_size == decoded_data_size); - - // If the array is contained in the current read buffer and we need to convert - // the data anyway we can use the read buffer as the decoded array source, otherwise - // do a plain byte copy to the array/conversion buffer. - if (uc->yield_size + uc->data_size >= encoded_size && decoded_data != arr_data) { - // Yield right after this if we crossed the yield threshold - if (encoded_size > uc->yield_size) { - uc->data_size += uc->yield_size; - uc->yield_size = encoded_size; - uc->data_size -= uc->yield_size; - } - - decoded_data = (void*)uc->data; - ufbxi_consume_bytes(uc, encoded_size); - } else { - ufbxi_check(ufbxi_read_to(uc, decoded_data, encoded_size)); - } - } else if (encoding == 1) { - // Encoding 1: DEFLATE - - ufbxi_pause_progress(uc); - - // Inflate the data from the user-provided IO buffer / read callbacks - ufbx_inflate_input input; - input.total_size = encoded_size; - input.data = uc->data; - input.data_size = uc->data_size; - input.no_header = false; - input.no_checksum = false; - input.internal_fast_bits = 0; - - if (uc->opts.progress_cb.fn) { - input.progress_cb = uc->opts.progress_cb; - input.progress_size_before = arr_begin; - input.progress_size_after = uc->progress_bytes_total - arr_end; - input.progress_interval_hint = uc->progress_interval; - } else { - input.progress_cb.fn = NULL; - input.progress_cb.user = NULL; - input.progress_size_before = 0; - input.progress_size_after = 0; - input.progress_interval_hint = 0; - } - - // If the encoded array is larger than the data we have currently buffered - // we need to allow `ufbx_inflate()` to read from the IO callback. We can - // let `ufbx_inflate()` freely clobber our `read_buffer` as all the data - // in the buffer will be consumed. `ufbx_inflate()` always reads exactly - // the amount of bytes needed so we can continue reading from `read_fn` as - // usual (given that we clear the `uc->data/_size` buffer below). - // NOTE: We _cannot_ share `read_buffer` if we plan to read later from it - // as `ufbx_inflate()` overwrites parts of it with zeroes. - if (encoded_size > input.data_size) { - input.buffer = uc->read_buffer; - input.buffer_size = uc->read_buffer_size; - input.read_fn = uc->read_fn; - input.read_user = uc->read_user; - uc->data_offset += encoded_size - input.data_size; - uc->data += input.data_size; - uc->data_size = 0; - } else { - input.buffer = NULL; - input.buffer_size = 0; - input.read_fn = NULL; - input.read_user = NULL; - uc->data += encoded_size; - uc->data_size -= encoded_size; - ufbxi_check(ufbxi_resume_progress(uc)); - } - - ptrdiff_t res = ufbx_inflate(decoded_data, decoded_data_size, &input, uc->inflate_retain); - ufbxi_check_msg(res != -28, "Cancelled"); - ufbxi_check_msg(res == (ptrdiff_t)decoded_data_size, "Bad DEFLATE data"); - - } else { - ufbxi_fail("Bad array encoding"); - } - - // Convert the decoded array if necessary. - if (!deferred && decoded_data != arr_data) { - ufbxi_check(ufbxi_binary_convert_array(uc, src_type, dst_type, decoded_data, arr_data, size)); - } - - arr->data = arr_data; - arr->size = size; - - } else if (c == '0' || c == '-') { - // Ignore the array - arr->type = c == '-' ? '-' : dst_type; - arr->data = (char*)ufbxi_zero_size_buffer + 32; - arr->size = 0; - } else { - // Allocate `num_values` elements for the array and parse single values into it. - char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, num_values, tmp_buf); - ufbxi_check(arr_data); - ufbxi_check(ufbxi_binary_parse_multivalue_array(uc, dst_type, arr_data, num_values, tmp_buf)); - arr->data = arr_data; - arr->size = num_values; - } - - // Post-process boolean arrays - if (!deferred && arr_info.type == 'b') { - ufbxi_postprocess_bool_array((char*)arr->data, arr->size); - } - - } else { - // Parse up to UFBXI_MAX_NON_ARRAY_VALUES as plain values - num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES); - ufbxi_value *vals = ufbxi_push(tmp_buf, ufbxi_value, num_values); - ufbxi_check(vals); - node->vals = vals; - - uint32_t type_mask = 0; - for (size_t i = 0; i < (size_t)num_values; i++) { - // The file must end in a 13/25 byte NULL record, so we can peek - // up to 13 bytes safely here. - const char *data = ufbxi_peek_bytes(uc, 13); - ufbxi_check(data); - - const char *value = data + 1; - - char type = data[0]; - if (uc->file_big_endian) { - value = ufbxi_swap_endian_value(uc, value, type); - ufbxi_check(value); - } - - switch (type) { - - case 'C': case 'B': case 'Z': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].f = (double)(vals[i].i = (int64_t)(uint8_t)value[0]); - ufbxi_consume_bytes(uc, 2); - break; - - case 'Y': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].f = (double)(vals[i].i = ufbxi_read_i16(value)); - ufbxi_consume_bytes(uc, 3); - break; - - case 'I': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].f = (double)(vals[i].i = ufbxi_read_i32(value)); - ufbxi_consume_bytes(uc, 5); - break; - - case 'L': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].f = (double)(vals[i].i = ufbxi_read_i64(value)); - ufbxi_consume_bytes(uc, 9); - break; - - case 'F': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f32(value)); - ufbxi_consume_bytes(uc, 5); - break; - - case 'D': - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f64(value)); - ufbxi_consume_bytes(uc, 9); - break; - - case 'S': case 'R': - { - uint32_t length = ufbxi_read_u32(value); - ufbxi_consume_bytes(uc, 5); - const char *str = ufbxi_read_bytes(uc, length); - ufbxi_check(str); - - if (length == 0) { - vals[i].s.raw_data = ufbxi_empty_char; - vals[i].s.raw_length = 0; - vals[i].s.utf8_length = 0; - } else { - bool non_ascii = false; - uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); - bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, i); - ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &vals[i].s, str, length, hash, raw)); - - // Mark the data as invalid UTF-8 - if (non_ascii && raw) vals[i].s.utf8_length = UINT32_MAX; - } - - type_mask |= (uint32_t)UFBXI_VALUE_STRING << (i*2); - } - break; - - // Treat arrays as non-values and skip them - case 'c': case 'b': case 'i': case 'l': case 'f': case 'd': - { - uint32_t encoded_size = ufbxi_read_u32(value + 8); - ufbxi_consume_bytes(uc, 13); - ufbxi_check(ufbxi_skip_bytes(uc, encoded_size)); - } - break; - - default: - ufbxi_fail("Bad value type"); - - } - } - - node->value_type_mask = (uint16_t)type_mask; - } - - // Skip over remaining values if necessary if we for example truncated - // the list of values or if there are values after an array - uint64_t offset = ufbxi_get_read_offset(uc); - ufbxi_check(offset <= values_end_offset); - if (offset < values_end_offset) { - ufbxi_check(ufbxi_skip_bytes(uc, values_end_offset - offset)); - } - - if (recursive) { - // Recursively parse the children of this node. Update the parse state - // to provide context for child node parsing. - ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name); - uint32_t num_children = 0; - for (;;) { - // Stop at end offset - uint64_t current_offset = ufbxi_get_read_offset(uc); - if (current_offset >= end_offset) { - ufbxi_check(current_offset == end_offset || end_offset == 0); - break; - } - - bool end = false; - ufbxi_check(ufbxi_binary_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, true)); - if (end) break; - num_children++; - } - - // Pop children from `tmp_stack` to a contiguous array - node->num_children = num_children; - if (num_children > 0) { - node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children); - ufbxi_check(node->children); - } - } else { - uint64_t current_offset = ufbxi_get_read_offset(uc); - uc->has_next_child = (current_offset < end_offset); - } - - return 1; -} - -#define UFBXI_BINARY_MAGIC_SIZE 22 -#define UFBXI_BINARY_HEADER_SIZE 27 -static const char ufbxi_binary_magic[] = "Kaydara FBX Binary \x00\x1a"; - -// -- ASCII parsing - -#define UFBXI_ASCII_END '\0' -#define UFBXI_ASCII_NAME 'N' -#define UFBXI_ASCII_BARE_WORD 'B' -#define UFBXI_ASCII_INT 'I' -#define UFBXI_ASCII_FLOAT 'F' -#define UFBXI_ASCII_STRING 'S' - -static ufbxi_noinline char ufbxi_ascii_refill(ufbxi_context *uc) -{ - ufbxi_ascii *ua = &uc->ascii; - uc->data_offset += ufbxi_to_size(ua->src - uc->data_begin); - if (uc->read_fn) { - char *dst_buffer = NULL; - size_t dst_size = 0; - - if (ua->retain_buf != NULL) { - dst_size = uc->opts.read_buffer_size; - dst_buffer = ufbxi_push(ua->retain_buf, char, dst_size); - ufbxi_check_return(dst_buffer, '\0'); - ua->src_is_retained = true; - ua->src_buf = ua->retain_buf; - } else { - // Grow the read buffer if necessary - if (uc->read_buffer_size < uc->opts.read_buffer_size) { - size_t new_size = uc->opts.read_buffer_size; - ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, new_size), '\0'); - } - dst_buffer = uc->read_buffer; - dst_size = uc->read_buffer_size; - ua->src_is_retained = false; - ua->src_buf = NULL; - } - - // Read user data, return '\0' on EOF - // TODO: Very unoptimal for non-full-size reads in some cases - size_t num_read = uc->read_fn(uc->read_user, dst_buffer, dst_size); - ufbxi_check_return_msg(num_read != SIZE_MAX, '\0', "IO error"); - ufbxi_check_return(num_read <= dst_size, '\0'); - if (num_read == 0) return '\0'; - - uc->data = uc->data_begin = ua->src = dst_buffer; - ua->src_end = dst_buffer + num_read; - return *ua->src; - } else { - // If the user didn't specify a `read_fn()` treat anything - // past the initial data buffer as EOF. - uc->data = uc->data_begin = ua->src = ""; - ua->src_end = ua->src + 1; - return '\0'; - } -} - -static ufbxi_noinline char ufbxi_ascii_yield(ufbxi_context *uc) -{ - ufbxi_ascii *ua = &uc->ascii; - - char ret; - if (ua->src == ua->src_end) { - ret = ufbxi_ascii_refill(uc); - } else { - ret = *ua->src; - } - - if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) { - ua->src_yield = ua->src_end; - } else { - ua->src_yield = ua->src + uc->progress_interval; - } - - // TODO: Unify these properly - uc->data = ua->src; - ufbxi_check_return(ufbxi_report_progress(uc), '\0'); - return ret; -} - -static ufbxi_forceinline char ufbxi_ascii_peek(ufbxi_context *uc) -{ - ufbxi_ascii *ua = &uc->ascii; - if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); - return *ua->src; -} - -static ufbxi_forceinline char ufbxi_ascii_next(ufbxi_context *uc) -{ - ufbxi_ascii *ua = &uc->ascii; - if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); - ua->src++; - if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc); - return *ua->src; -} - -static ufbxi_noinline uint32_t ufbxi_ascii_parse_version(ufbxi_context *uc) -{ - uint8_t digits[3]; - uint32_t num_digits = 0; - - char c = ufbxi_ascii_next(uc); - - const char fmt[] = " FBX ?.?.?"; - uint32_t ix = 0; - while (num_digits < 3) { - char ref = fmt[ix++]; - switch (ref) { - - // Digit - case '?': - if (c < '0' || c > '9') return 0; - digits[num_digits++] = (uint8_t)(c - '0'); - c = ufbxi_ascii_next(uc); - break; - - // Whitespace - case ' ': - while (c == ' ' || c == '\t') { - c = ufbxi_ascii_next(uc); - } - break; - - // Literal character - default: - if (c != ref) return 0; - c = ufbxi_ascii_next(uc); - break; - } - } - - if (num_digits != 3) return 0; - return 1000u*(uint32_t)digits[0] + 100u*(uint32_t)digits[1] + 10u*(uint32_t)digits[2]; -} - -static const uint32_t ufbxi_space_mask = - (1u << ((uint32_t)' ' - 1)) | - (1u << ((uint32_t)'\t' - 1)) | - (1u << ((uint32_t)'\r' - 1)) | - (1u << ((uint32_t)'\n' - 1)) ; - -ufbx_static_assert(space_codepoint, - (uint32_t)' ' <= 32u && (uint32_t)'\t' <= 32u && - (uint32_t)'\r' <= 32u && (uint32_t)'\n' <= 32u); - -static ufbxi_forceinline bool ufbxi_is_space(char c) -{ - uint32_t v = (uint32_t)(uint8_t)c - 1; - return v < 32 && ((ufbxi_space_mask >> v) & 0x1) != 0; -} - -static ufbxi_noinline char ufbxi_ascii_skip_whitespace(ufbxi_context *uc) -{ - ufbxi_ascii *ua = &uc->ascii; - - // Ignore whitespace - char c = ufbxi_ascii_peek(uc); - for (;;) { - while (ufbxi_is_space(c)) { - c = ufbxi_ascii_next(uc); - } - - // Line comment - if (c == ';') { - - bool read_magic = false; - // FBX ASCII files begin with a magic comment of form "; FBX 7.7.0 project file" - // Try to extract the version number from the magic comment - if (!ua->read_first_comment) { - ua->read_first_comment = true; - uint32_t version = ufbxi_ascii_parse_version(uc); - if (version) { - uc->version = version; - ua->found_version = true; - read_magic = true; - } - } - - c = ufbxi_ascii_next(uc); - while (c != '\n' && c != '\0') { - c = ufbxi_ascii_next(uc); - } - c = ufbxi_ascii_next(uc); - - // Try to determine if this is a Blender 6100 ASCII file - if (read_magic) { - if (c == ';') { - char line[32]; - size_t line_len = 0; - - c = ufbxi_ascii_next(uc); - while (c != '\n' && c != '\0') { - if (line_len < sizeof(line)) { - line[line_len++] = c; - } - c = ufbxi_ascii_next(uc); - } - - if (line_len >= 19 && !memcmp(line, " Created by Blender", 19)) { - uc->exporter = UFBX_EXPORTER_BLENDER_ASCII; - } - } - } - - } else { - break; - } - } - return c; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_char(ufbxi_context *uc, ufbxi_ascii_token *token, char c) -{ - // Grow the string data buffer if necessary - if (token->str_len == token->str_cap) { - size_t len = ufbxi_max_sz(token->str_len + 1, 256); - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len)); - } - - token->str_data[token->str_len++] = c; - - return 1; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_string(ufbxi_context *uc, ufbxi_ascii_token *token, const char *data, size_t length) -{ - // Grow the string data buffer if necessary - if (token->str_len + length >= token->str_cap) { - size_t len = ufbxi_max_sz(token->str_len + length, 256); - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len)); - } - - memcpy(token->str_data + token->str_len, data, length); - token->str_len += length; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_skip_until(ufbxi_context *uc, char dst) -{ - ufbxi_ascii *ua = &uc->ascii; - - for (;;) { - size_t buffered = ufbxi_to_size(ua->src_yield - ua->src); - const char *match = (const char*)memchr(ua->src, dst, buffered); - if (match) { - ua->src = match; - break; - } else { - ua->src += buffered; - } - if (buffered == 0) { - char c = ufbxi_ascii_yield(uc); - ufbxi_check(c != '\0'); - } - } - - return 1; -} - -typedef struct { - const char *source; - size_t length; -} ufbxi_ascii_span; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_store_array(ufbxi_context *uc, ufbxi_buf *tmp_buf) -{ - ufbxi_ascii *ua = &uc->ascii; - - ua->retain_buf = tmp_buf; - - for (;;) { - size_t buffered = ufbxi_to_size(ua->src_yield - ua->src); - if (buffered == 0) { - char c = ufbxi_ascii_yield(uc); - ufbxi_check(c != '\0'); - continue; - } - - const char *begin = ua->src, *end; - const char *match = (const char*)memchr(begin, '}', buffered); - if (match) { - end = match; - } else { - end = begin + buffered; - } - ua->src = end; - - size_t length = ufbxi_to_size(end - begin); - ufbxi_ascii_span *span = ufbxi_push(&uc->tmp_ascii_spans, ufbxi_ascii_span, 1); - ufbxi_check(span); - // Store the trailing '}' for parsing - if (match) length += 1; - span->length = length; - if (ua->src_is_retained || !uc->read_fn) { - span->source = begin; - } else { - span->source = ufbxi_push_copy(tmp_buf, char, length, begin); - ufbxi_check(span->source); - } - - if (match) break; - } - - ua->retain_buf = NULL; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_try_ignore_string(ufbxi_context *uc, ufbxi_ascii_token *token) -{ - ufbxi_ascii *ua = &uc->ascii; - - char c = ufbxi_ascii_skip_whitespace(uc); - token->str_len = 0; - - if (c == '"') { - // Replace `prev_token` with `token` but swap the buffers so `token` uses - // the now-unused string buffer of the old `prev_token`. - char *swap_data = ua->prev_token.str_data; - size_t swap_cap = ua->prev_token.str_cap; - ua->prev_token = ua->token; - ua->token.str_data = swap_data; - ua->token.str_cap = swap_cap; - - token->type = UFBXI_ASCII_STRING; - // Skip opening quote - ufbxi_ascii_next(uc); - ufbxi_check(ufbxi_ascii_skip_until(uc, '"')); - // Skip closing quote - ufbxi_ascii_next(uc); - return true; - } - - return false; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_next_token(ufbxi_context *uc, ufbxi_ascii_token *token) -{ - ufbxi_ascii *ua = &uc->ascii; - - // Replace `prev_token` with `token` but swap the buffers so `token` uses - // the now-unused string buffer of the old `prev_token`. - char *swap_data = ua->prev_token.str_data; - size_t swap_cap = ua->prev_token.str_cap; - ua->prev_token = ua->token; - ua->token.str_data = swap_data; - ua->token.str_cap = swap_cap; - - char c = ufbxi_ascii_skip_whitespace(uc); - token->str_len = 0; - - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { - token->type = UFBXI_ASCII_BARE_WORD; - while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '(' || c == ')') { - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); - c = ufbxi_ascii_next(uc); - } - - // Skip whitespace to find if there's a following ':' - c = ufbxi_ascii_skip_whitespace(uc); - if (c == ':') { - token->value.name_len = token->str_len; - token->type = UFBXI_ASCII_NAME; - ufbxi_ascii_next(uc); - } - } else if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') { - token->type = UFBXI_ASCII_INT; - - token->negative = c == '-'; - while ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { - if (c == '.' || c == 'e' || c == 'E') { - token->type = UFBXI_ASCII_FLOAT; - } - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); - c = ufbxi_ascii_next(uc); - } - - bool nan_like = false; - while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '#' || c == '(' || c == ')') { - nan_like = true; - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); - c = ufbxi_ascii_next(uc); - } - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, '\0')); - if (nan_like) { - token->type = UFBXI_ASCII_FLOAT; - } - - char *end; - if (token->type == UFBXI_ASCII_INT) { - token->value.i64 = ufbxi_parse_int64(token->str_data, &end); - ufbxi_check(end == token->str_data + token->str_len - 1); - } else if (token->type == UFBXI_ASCII_FLOAT) { - uint32_t flags = uc->double_parse_flags; - if (ua->parse_as_f32) flags = UFBXI_PARSE_DOUBLE_AS_BINARY32; - token->value.f64 = ufbxi_parse_double(token->str_data, token->str_len, &end, flags); - ufbxi_check(end == token->str_data + token->str_len - 1); - } - } else if (c == '"') { - token->type = UFBXI_ASCII_STRING; - c = ufbxi_ascii_next(uc); - while (c != '"') { - - // Optimized string parsing for non-special characters - if (ua->src + 1 < ua->src_yield) { - const char *begin = ua->src; - const char *end = ua->src_yield; - const char *quot = (const char*)memchr(begin, '"', ufbxi_to_size(end - begin)); - if (quot) end = quot; - const char *esc = (const char*)memchr(begin, '&', ufbxi_to_size(end - begin)); - if (esc) end = esc; - - if (begin < end) { - ufbxi_check(ufbxi_ascii_push_token_string(uc, token, begin, ufbxi_to_size(end - begin))); - ua->src = end; - c = ufbxi_ascii_peek(uc); - continue; - } - } - - // Escape XML-like elements, funny enough there is no way to escape '&' itself, there is no `&`. - // '"' -> '"' - // '&cr;' -> '\r' - // '&lf;' -> '\n' - if (c == '&') { - const char *entity = NULL; - char replacement = '\0'; - - c = ufbxi_ascii_next(uc); - switch (c) { - case 'q': - entity = """; - replacement = '"'; - break; - case 'c': - entity = "&cr;"; - replacement = '\r'; - break; - case 'l': - entity = "&lf;"; - replacement = '\n'; - break; - default: - // As '&' is not escaped in any way just map '&' -> '&' - entity = "&"; - replacement = '&'; - break; - } - - size_t step = 1; - - ufbxi_dev_assert(entity && *entity); - // `entity` is a NULL terminated string longer than a single character - // cppcheck-suppress arrayIndexOutOfBounds - for (; entity[step]; step++) { - if (c != entity[step]) break; - c = ufbxi_ascii_next(uc); - } - - if (entity[step] == '\0') { - // Full match: Push the replacement character - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, replacement)); - } else { - // Partial match: Push the prefix we have skipped already - for (size_t i = 0; i < step; i++) { - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, entity[i])); - } - } - continue; - } - - ufbxi_check(c != '\0'); - ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); - c = ufbxi_ascii_next(uc); - } - // Skip closing quote - ufbxi_ascii_next(uc); - } else { - // Single character token - token->type = c; - ufbxi_ascii_next(uc); - } - - return 1; -} - -ufbxi_nodiscard static int ufbxi_ascii_accept(ufbxi_context *uc, char type) -{ - ufbxi_ascii *ua = &uc->ascii; - - if (ua->token.type == type) { - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - return 1; - } else { - return 0; - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_int_array(ufbxi_context *uc, char type, size_t *p_num_read) -{ - ufbxi_ascii *ua = &uc->ascii; - if (ua->parse_as_f32) return 1; - size_t initial_items = uc->tmp_stack.num_items; - - int64_t val; - if (ua->token.type == UFBXI_ASCII_INT) { - val = ua->token.value.i64; - } else { - return 1; - } - - const char *src = ua->src; - const char *end = ua->src_yield; - const char *src_scan = src; - - for (;;) { - - // Skip '\s*,\s*' between array elements. If we don't find a comma after an element - // don't push it as we can't be 100% certain whether it's a part of the array. - while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; - if (src_scan == end || *src_scan != ',') break; - src_scan++; - while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; - - // Found comma, commit to the position and push the previous value to the array - src = src_scan; - if (type == 'i') { - int32_t *v = ufbxi_push_fast(&uc->tmp_stack, int32_t, 1); - ufbxi_check(v); - *v = (int32_t)val; - } else if (type == 'l') { - int64_t *v = ufbxi_push_fast(&uc->tmp_stack, int64_t, 1); - ufbxi_check(v); - *v = (int64_t)val; - } - - // Try to parse the next value, we don't commit this until we find a comma after it above. - size_t left = ufbxi_to_size(end - src_scan); - if (left < 32) break; - - val = ufbxi_parse_int64(src_scan, (char**)&src_scan); - if (!src_scan) break; - } - - // Resume conventional parsing if we moved `src`. - if (src != ua->src) { - ua->src = src; - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - } - - *p_num_read = uc->tmp_stack.num_items - initial_items; - return 1; -} - -typedef struct { - void *arr_data; - char arr_type; - size_t arr_size; - const ufbxi_ascii_span *spans; - size_t num_spans; - size_t offset; -} ufbxi_ascii_array_task; - -ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_floats(ufbxi_ascii_array_task *t, const char *src, const char *src_end, uint32_t parse_flags) -{ - size_t offset = t->offset; - float *dst_float = t->arr_type == 'f' ? (float*)t->arr_data + offset : NULL; - double *dst_double = t->arr_type == 'd' ? (double*)t->arr_data + offset : NULL; - ufbx_assert(dst_float || dst_double); - const char *src_begin = src; - - while (src != src_end) { - while (ufbxi_is_space(*src)) src++; - - // Try to parse the next value, we don't commit this until we find a comma after it above. - char *num_end = NULL; - double val = ufbxi_parse_double(src, ufbxi_to_size(src_end - src), &num_end, parse_flags); - if (!num_end) return src_begin; - src = num_end; - - while (ufbxi_is_space(*src)) src++; - if (*src != ',') break; - src++; - src_begin = src; - - if (offset >= t->arr_size) return NULL; - if (dst_double) { - *dst_double++ = val; - } else { - *dst_float++ = (float)val; - } - offset++; - } - - t->offset = offset; - return src_begin; -} - -ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_ints(ufbxi_ascii_array_task *t, const char *src, const char *src_end) -{ - size_t offset = t->offset; - int32_t *dst32 = t->arr_type == 'i' ? (int32_t*)t->arr_data + offset : NULL; - int64_t *dst64 = t->arr_type == 'l' ? (int64_t*)t->arr_data + offset : NULL; - ufbx_assert(dst32 || dst64); - const char *src_begin = src; - - while (src != src_end) { - while (ufbxi_is_space(*src)) src++; - - int64_t val = ufbxi_parse_int64(src, (char**)&src); - if (!src) return NULL; - - while (ufbxi_is_space(*src)) src++; - if (*src != ',') break; - src++; - src_begin = src; - - if (offset >= t->arr_size) return NULL; - if (dst32) { - *dst32++ = (int32_t)val; - } else { - *dst64++ = val; - } - offset++; - } - - t->offset = offset; - return src_begin; -} - -ufbxi_noinline static const char *ufbxi_ascii_array_task_parse(ufbxi_ascii_array_task *t, const char *src, const char *src_end) -{ - if (t->arr_type == 'f' || t->arr_type == 'd') { - uint32_t flags = ufbxi_parse_double_init_flags(); - return ufbxi_ascii_array_task_parse_floats(t, src, src_end, flags); - } else { - return ufbxi_ascii_array_task_parse_ints(t, src, src_end); - } -} - -typedef enum { - UFBXI_ASCII_SCAN_STATE_VALUE, - UFBXI_ASCII_SCAN_STATE_WHITESPACE, - UFBXI_ASCII_SCAN_STATE_COMMENT, - UFBXI_ASCII_SCAN_STATE_COMMA, -} ufbxi_ascii_scan_state; - -ufbxi_noinline static bool ufbxi_ascii_array_task_imp(ufbxi_ascii_array_task *t) -{ - // Temporary buffer for parsing between spans - char buffer[128]; // ufbxi_uninit - size_t buffer_len = 0; - bool buffer_value = false; - - ufbxi_ascii_scan_state state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; - ufbxi_for(const ufbxi_ascii_span, span, t->spans, t->num_spans) { - const char *src = span->source; - const char *end = src + span->length; - - while (src != end) { - - // State machine for skipping whitespace and comments, potentially - // between multiple spans. - while (src != end) { - char c = *src; - if (state == UFBXI_ASCII_SCAN_STATE_VALUE) { - if (buffer_len >= sizeof(buffer) - 1) return false; - if (c == '"') { - return false; - } else if (c == ';' || ufbxi_is_space(c)) { - state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; - buffer[buffer_len] = ' '; - buffer_len++; - } else if (c == ',' || c == '}') { - state = UFBXI_ASCII_SCAN_STATE_COMMA; - buffer[buffer_len] = ','; - buffer_len++; - src++; - break; - } else { - buffer_value = true; - buffer[buffer_len] = c; - buffer_len++; - src++; - } - } else if (state == UFBXI_ASCII_SCAN_STATE_WHITESPACE) { - if (c == ';') { - state = UFBXI_ASCII_SCAN_STATE_COMMENT; - } else if (ufbxi_is_space(c)) { - src++; - } else { - state = UFBXI_ASCII_SCAN_STATE_VALUE; - } - } else if (state == UFBXI_ASCII_SCAN_STATE_COMMENT) { - if (c == '\n') { - state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; - } else { - src++; - } - } else if (state == UFBXI_ASCII_SCAN_STATE_COMMA) { - state = UFBXI_ASCII_SCAN_STATE_WHITESPACE; - } - } - - if (state == UFBXI_ASCII_SCAN_STATE_COMMA) { - // Parse a value from the buffer - if (buffer_value) { - const char *buffer_end = ufbxi_ascii_array_task_parse(t, buffer, buffer + buffer_len); - if (buffer_end == NULL || buffer_end == buffer) { - return false; - } - } - - // If not at end, we are past the last comma, so try to find a - // safe range to parse. - if (src != end) { - const char *parse_end = end; - while (parse_end > src) { - if (parse_end[-1] == ',') break; - parse_end--; - } - if (src < parse_end) { - src = ufbxi_ascii_array_task_parse(t, src, parse_end); - if (src == NULL) return false; - } - } - - buffer_len = 0; - buffer_value = false; - } - } - } - - if (t->offset != t->arr_size) return false; - - return true; -} - -ufbxi_noinline static bool ufbxi_ascii_array_task_fn(ufbxi_task *task) -{ - ufbxi_ascii_array_task *t = (ufbxi_ascii_array_task *)task->data; - if (!ufbxi_ascii_array_task_imp(t)) { - task->error = "Threaded ASCII parse error"; - return false; - } - return true; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_float_array(ufbxi_context *uc, char type, size_t *p_num_read) -{ - ufbxi_ascii *ua = &uc->ascii; - if (ua->parse_as_f32) return 1; - - double val; - if (ua->token.type == UFBXI_ASCII_FLOAT) { - val = ua->token.value.f64; - } else if (ua->token.type == UFBXI_ASCII_INT) { - double fsign = !ua->token.value.i64 && ua->token.negative ? -1.0 : 1.0; - val = (double)ua->token.value.i64 * fsign; - } else { - return 1; - } - - const char *src = ua->src; - const char *end = ua->src_yield; - - uint32_t parse_flags = uc->double_parse_flags; - - size_t initial_items = uc->tmp_stack.num_items; - const char *src_scan = src; - for (;;) { - - // Skip '\s*,\s*' between array elements. If we don't find a comma after an element - // don't push it as we can't be 100% certain whether it's a part of the array. - while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; - if (src_scan == end || *src_scan != ',') break; - src_scan++; - while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++; - - // Found comma, commit to the position and push the previous value to the array - src = src_scan; - if (type == 'd') { - double *v = ufbxi_push_fast(&uc->tmp_stack, double, 1); - ufbxi_check(v); - *v = (double)val; - } else if (type == 'f') { - float *v = ufbxi_push_fast(&uc->tmp_stack, float, 1); - ufbxi_check(v); - *v = (float)val; - } - - // Try to parse the next value, we don't commit this until we find a comma after it above. - char *num_end = NULL; - size_t left = ufbxi_to_size(end - src_scan); - val = ufbxi_parse_double(src_scan, left, &num_end, parse_flags); - if (!num_end || num_end == src_scan || num_end >= end) { - break; - } - - src_scan = num_end; - } - - // Resume conventional parsing if we moved `src`. - if (src != ua->src) { - ua->src = src; - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - } - - *p_num_read = uc->tmp_stack.num_items - initial_items; - return 1; -} - -ufbxi_noinline static int ufbxi_setup_base64(ufbxi_context *uc) -{ - uint8_t *table = ufbxi_push(&uc->tmp, uint8_t, 256); - ufbxi_check(table); - uc->base64_table = table; - - memset(table, 0x80, 256); - ufbxi_nounroll for (char c = 'A'; c <= 'Z'; c++) table[(size_t)c] = (uint8_t)(c - 'A'); - ufbxi_nounroll for (char c = 'a'; c <= 'z'; c++) table[(size_t)c] = (uint8_t)(26 + (c - 'a')); - ufbxi_nounroll for (char c = '0'; c <= '9'; c++) table[(size_t)c] = (uint8_t)(52 + (c - '0')); - table[(size_t)'+'] = 62; - table[(size_t)'/'] = 63; - table[(size_t)'='] = 0x40; - - return 1; -} - -ufbxi_noinline static int ufbxi_decode_base64(ufbxi_context *uc, ufbx_string *p_result, const char *src, size_t src_length, bool *p_failed) -{ - if (!uc->base64_table) ufbxi_check(ufbxi_setup_base64(uc)); - - uint8_t *table = uc->base64_table; - uint32_t error_mask = 0, pad_error = 0; - - char *p = (char*)p_result->data; - for (size_t i = 0; i + 4 <= src_length; i += 4) { - uint32_t a = table[(size_t)(uint8_t)src[i + 0]]; - uint32_t b = table[(size_t)(uint8_t)src[i + 1]]; - uint32_t c = table[(size_t)(uint8_t)src[i + 2]]; - uint32_t d = table[(size_t)(uint8_t)src[i + 3]]; - pad_error = error_mask; - error_mask |= a | b | c | d; - - p[0] = (char)(uint8_t)(a << 2 | b >> 4); - p[1] = (char)(uint8_t)(b << 4 | c >> 2); - p[2] = (char)(uint8_t)(c << 6 | d); - p += 3; - } - - if (src_length >= 4) { - const char *end = src + src_length - 4; - uint32_t padding = 0; - padding |= end[0] == '=' ? 0x8 : 0x0; - padding |= end[1] == '=' ? 0x4 : 0x0; - padding |= end[2] == '=' ? 0x2 : 0x0; - padding |= end[3] == '=' ? 0x1 : 0x0; - if (padding <= 0x1) p -= padding; // "xxx=" or "xxxx" - else if (padding == 0x3) p -= 2; // "xx==" - else pad_error |= 0x40; // anything else - } - - if (((error_mask & 0x80) != 0 || (pad_error & 0x40) != 0 || src_length % 4 != 0) && !*p_failed) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_BAD_BASE64_CONTENT, "Ignored bad base64 embedded content")); - *p_failed = true; - } - - p_result->length = ufbxi_to_size(p - p_result->data); - return 1; -} - -// Recursion limited by check at the start -ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive) - ufbxi_recursive_function(int, ufbxi_ascii_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1, - (ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)) -{ - ufbxi_ascii *ua = &uc->ascii; - - if (ua->token.type == '}') { - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - *p_end = true; - return 1; - } - - if (ua->token.type == UFBXI_ASCII_END) { - ufbxi_check_msg(depth == 0, "Truncated file"); - *p_end = true; - return 1; - } - - // Parse the name eg. "Node:" token and intern the name - ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH); - if (!uc->sure_fbx && depth == 0 && ua->token.type != UFBXI_ASCII_NAME) { - ufbxi_fail_msg("Expected a 'Name:' token", "Not an FBX file"); - } - ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME)); - size_t name_len = ua->prev_token.value.name_len; - ufbxi_check(name_len <= 0xff); - const char *name = ufbxi_push_string(&uc->string_pool, ua->prev_token.str_data, ua->prev_token.str_len, NULL, true); - ufbxi_check(name); - - // Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by - // calling code after its done parsing all of it's children. - ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1); - ufbxi_check(node); - node->name = name; - node->name_len = (uint8_t)name_len; - - bool in_ascii_array = false; - - uint32_t num_values = 0; - uint32_t type_mask = 0; - - int arr_type = 0; - ufbxi_buf *arr_buf = NULL; - size_t arr_elem_size = 0; - bool arr_error = false; - - // Check if the values of the node we're parsing currently should be - // treated as an array. - ufbxi_array_info arr_info; - if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) { - uint32_t flags = arr_info.flags; - arr_type = ufbxi_normalize_array_type(arr_info.type, 'b'); - arr_buf = tmp_buf; - if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result; - else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp; - - ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1); - ufbxi_check(arr); - node->value_type_mask = UFBXI_VALUE_ARRAY; - node->array = arr; - arr->type = (char)arr_type; - - // Parse array values using strtof() if the array destination is 32-bit float - // since KeyAttrDataFloat packs integer data (!) into floating point values so we - // should try to be as exact as possible. - if (arr_info.flags & UFBXI_ARRAY_FLAG_ACCURATE_F32) { - ua->parse_as_f32 = true; - } - - arr_elem_size = ufbxi_array_type_size((char)arr_type); - - if (arr_type != '-') { - // Force alignment for array contents: This allows us to use `ufbxi_push_fast()` - // in fast parsing functions. - ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, 8, 1)); - - // Pad with 4 zero elements to make indexing with `-1` safe. - if ((flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) != 0) { - ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, arr_elem_size, 4)); - num_values += 4; - } - } - } - - // Some fields in ASCII may have leading commas eg. `Content: , "base64-string"` - if (ua->token.type == ',') { - // HACK: If we are parsing an "array" that should be ignored, ie. `Content` when - // `opts.ignore_embedded == true` try to skip the next token string if possible. - if (arr_type == '-') { - if (!ufbxi_ascii_try_ignore_string(uc, &ua->token)) { - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - } - } else { - ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token)); - } - } - - ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name); - ufbxi_value vals[UFBXI_MAX_NON_ARRAY_VALUES]; - - uint32_t deferred_size = 0; - - // NOTE: Infinite loop to allow skipping the comma parsing via `continue`. - for (;;) { - ufbxi_ascii_token *tok = &ua->prev_token; - - if (arr_type) { - size_t num_read = 0; - if (arr_type == 'f' || arr_type == 'd') { - ufbxi_check(ufbxi_ascii_read_float_array(uc, (char)arr_type, &num_read)); - } else if (arr_type == 'i' || arr_type == 'l') { - ufbxi_check(ufbxi_ascii_read_int_array(uc, (char)arr_type, &num_read)); - } - ufbxi_check(UINT32_MAX - num_values > num_read); - num_values += (uint32_t)num_read; - } - - if (ufbxi_ascii_accept(uc, UFBXI_ASCII_STRING)) { - - if (arr_type) { - - if (arr_type == 's' || arr_type == 'S' || arr_type == 'C') { - bool raw = arr_type == 's'; - ufbx_string *v = ufbxi_push(&uc->tmp_stack, ufbx_string, 1); - ufbxi_check(v); - if (arr_type == 'C') { - ufbxi_buf *buf = uc->opts.retain_dom ? &uc->result : tmp_buf; - size_t capacity = tok->str_len / 4 * 3 + 3; - v->data = ufbxi_push(buf, char, capacity); - ufbxi_check(v->data); - ufbxi_check(ufbxi_decode_base64(uc, v, tok->str_data, tok->str_len, &arr_error)); - ufbx_assert(v->length <= capacity); - } else { - v->data = tok->str_data; - v->length = tok->str_len; - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, v, raw)); - } - } else { - // Ignore strings in non-string arrays, decrement `num_values` as it will be - // incremented after the loop iteration is done to ignore it. - num_values--; - } - - } else if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { - type_mask |= (uint32_t)UFBXI_VALUE_STRING << (num_values*2); - ufbxi_value *v = &vals[num_values]; - - const char *str = tok->str_data; - size_t length = tok->str_len; - ufbxi_check(str); - - if (length == 0) { - v->s.raw_data = ufbxi_empty_char; - v->s.raw_length = 0; - v->s.utf8_length = 0; - } else { - bool non_ascii = false; - uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii); - bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, num_values); - ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &v->s, str, length, hash, raw)); - if (non_ascii && raw) v->s.utf8_length = UINT32_MAX; - } - } - - } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_INT)) { - int64_t val = tok->value.i64; - ufbx_real fsign = !val && tok->negative ? (ufbx_real)-1.0f : (ufbx_real)1.0f; - - switch (arr_type) { - - case 0: - // Parse version from comment if there was no magic comment - if (!ua->found_version && parse_state == UFBXI_PARSE_FBX_VERSION && num_values == 0) { - if (val >= 6000 && val <= 10000) { - ua->found_version = true; - uc->version = (uint32_t)val; - } - } - - if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); - ufbxi_value *v = &vals[num_values]; - // False positive: `v->f` and `v->i` do not overlap in the union. - // cppcheck-suppress overlappingWriteUnion - v->f = (double)(v->i = val) * (double)fsign; - } - break; - - case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; - case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; - case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break; - case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break; - case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val * (float)fsign; } break; - case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val * (double)fsign; } break; - case '-': num_values--; break; - - default: - ufbxi_fail("Bad array dst type"); - - } - - } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_FLOAT)) { - double val = tok->value.f64; - - switch (arr_type) { - - case 0: - if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); - ufbxi_value *v = &vals[num_values]; - // False positive: `v->f` and `v->i` do not overlap in the union. - // cppcheck-suppress overlappingWriteUnion - v->i = ufbxi_f64_to_i64(v->f = val); - } - break; - - case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; - case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; - case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i32(val); } break; - case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i64(val); } break; - case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val; } break; - case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val; } break; - case '-': num_values--; break; - - default: - ufbxi_fail("Bad array dst type"); - - } - - } else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_BARE_WORD)) { - - int64_t val = 0; - double val_f = 0.0; - if (tok->str_len >= 1) { - val = (int64_t)tok->str_data[0]; - val_f = (double)val; - if (tok->str_len > 1 && tok->str_len < 64) { - // Try to parse the bare word as NAN/INF - char str_data[64]; // ufbxi_uninit - size_t str_len = tok->str_len; - memcpy(str_data, tok->str_data, str_len); - str_data[str_len] = '\0'; - double inf_nan; - char *end = NULL; - if (ufbxi_parse_inf_nan(&inf_nan, str_data, str_len, &end) && end == str_data + str_len) { - val = 0; - val_f = inf_nan; - } - } - } - - switch (arr_type) { - - case 0: - if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) { - type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2); - ufbxi_value *v = &vals[num_values]; - // False positive: `v->f` and `v->i` do not overlap in the union. - // cppcheck-suppress overlappingWriteUnion - v->i = val; - v->f = val_f; - } - break; - - case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break; - case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break; - case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break; - case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break; - case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val_f; } break; - case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val_f; } break; - case '-': num_values--; break; - - default: - ufbxi_fail("Bad array dst type"); - } - - } else if (ufbxi_ascii_accept(uc, '*')) { - // Parse a post-7000 ASCII array eg. "*3 { 1,2,3 }" - ufbxi_check(!in_ascii_array); - ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_INT)); - int64_t count = ua->prev_token.value.i64; - - if (ufbxi_ascii_accept(uc, '{')) { - ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME)); - in_ascii_array = true; - - // Optimized array skipping and threaded parsing - if (arr_type == '-') { - ufbxi_check(ufbxi_ascii_skip_until(uc, '}')); - } else if (uc->parse_threaded && !uc->opts.force_single_thread_ascii_parsing - && !ua->parse_as_f32 - && (arr_type == 'i' || arr_type == 'l' || arr_type == 'f' || arr_type == 'd')) { - // Don't bother with small arrays due to fixed overhead - if (count >= UFBXI_MIN_THREADED_ASCII_VALUES && count <= UINT32_MAX) { - deferred_size = (uint32_t)count - 1; - ufbxi_check(ufbxi_ascii_store_array(uc, tmp_buf)); - } - } - } - - // NOTE: This `continue` skips incrementing `num_values` and parsing - // a comma, continuing to parse the values in the array. - continue; - } else { - break; - } - - // Add value and keep parsing if there's a comma. This part may be - // skipped if we enter an array block. - num_values++; - ufbxi_check(num_values < UINT32_MAX); - if (!ufbxi_ascii_accept(uc, ',')) break; - } - - // Close the ASCII array if we are in one - if (in_ascii_array) { - ufbxi_check(ufbxi_ascii_accept(uc, '}')); - } - - ua->parse_as_f32 = false; - - if (arr_type) { - if (arr_type == '-') { - node->array->data = NULL; - node->array->size = 0; - } else { - void *arr_data = NULL; - - if (deferred_size > 0) { - arr_data = ufbxi_push_size(arr_buf, arr_elem_size, num_values + deferred_size); - // Pop any previously pushed values - if (num_values > 0) { - ufbxi_pop_size(&uc->tmp_stack, arr_elem_size, num_values, arr_data, false); - } - } else if (arr_error) { - ufbxi_pop_size(&uc->tmp_stack, arr_elem_size, num_values, NULL, false); - num_values = 0; - arr_data = (void*)ufbxi_zero_size_buffer; - } else { - arr_data = ufbxi_push_pop_size(arr_buf, &uc->tmp_stack, arr_elem_size, num_values); - } - ufbxi_check(arr_data); - if (arr_info.flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) { - node->array->data = (char*)arr_data + 4*arr_elem_size; - node->array->size = num_values + deferred_size - 4; - } else { - node->array->data = arr_data; - node->array->size = num_values + deferred_size; - } - - // Pop alignment helper - ufbxi_pop_size(&uc->tmp_stack, 8, 1, NULL, false); - - // Deferred parsing - if (deferred_size > 0) { - size_t num_spans = uc->tmp_ascii_spans.num_items; - ufbxi_ascii_span *spans = ufbxi_push_pop(tmp_buf, &uc->tmp_ascii_spans, ufbxi_ascii_span, num_spans); - ufbxi_check(spans); - - ufbxi_ascii_array_task t; // ufbxi_uninit - t.arr_data = (char*)arr_data + num_values * arr_elem_size; - t.arr_type = (char)arr_type; - t.arr_size = deferred_size; - t.num_spans = num_spans; - t.spans = spans; - t.offset = 0; - - // TODO: Split these further - ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_ascii_array_task_fn); - if (task) { - task->data = ufbxi_push_copy(tmp_buf, ufbxi_ascii_array_task, 1, &t); - ufbxi_check(task->data); - ufbxi_thread_pool_run_task(&uc->thread_pool, task); - } else { - ufbxi_check_msg(ufbxi_ascii_array_task_imp(&t), "Threaded ASCII parse error"); - } - } - } - } else { - num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES); - node->value_type_mask = (uint16_t)type_mask; - node->vals = ufbxi_push_copy(tmp_buf, ufbxi_value, num_values, vals); - ufbxi_check(node->vals); - } - - // Recursively parse the children of this node. Update the parse state - // to provide context for child node parsing. - if (ufbxi_ascii_accept(uc, '{')) { - if (recursive) { - size_t num_children = 0; - for (;;) { - bool end = false; - ufbxi_check(ufbxi_ascii_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, recursive)); - if (end) break; - num_children++; - } - - // Pop children from `tmp_stack` to a contiguous array - node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children); - ufbxi_check(node->children); - node->num_children = (uint32_t)num_children; - } - - uc->has_next_child = true; - } else { - uc->has_next_child = false; - } - - return 1; -} - -// -- DOM retention - -typedef struct { - uintptr_t node_ptr; - ufbx_dom_node *dom_node; -} ufbxi_dom_mapping; - -ufbxi_nodiscard static ufbxi_noinline ufbx_dom_node *ufbxi_get_dom_node_imp(ufbxi_context *uc, ufbxi_node *node) -{ - if (!node) return NULL; - ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL }; - uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr); - ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); - return result ? result->dom_node : NULL; -} - -ufbxi_nodiscard static ufbxi_forceinline ufbx_dom_node *ufbxi_get_dom_node(ufbxi_context *uc, ufbxi_node *node) -{ - if (!uc->opts.retain_dom) return NULL; - return ufbxi_get_dom_node_imp(uc, node); -} - -// Recursion limited by check in ufbxi_[binary/ascii]_parse_node() -ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_dom_node(ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node) - ufbxi_recursive_function(int, ufbxi_retain_dom_node, (uc, node, p_dom_node), UFBXI_MAX_NODE_DEPTH + 1, - (ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node)) -{ - ufbx_dom_node *dst = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); - ufbxi_check(dst); - ufbxi_check(ufbxi_push_copy(&uc->tmp_dom_nodes, ufbx_dom_node*, 1, &dst)); - - if (p_dom_node) { - *p_dom_node = dst; - } - - dst->name.data = node->name; - dst->name.length = node->name_len; - - { - ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL }; - uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr); - ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); - if (!result) { - result = ufbxi_map_insert(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping); - ufbxi_check(result); - } - result->node_ptr = (uintptr_t)node; - result->dom_node = dst; - } - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst->name, false)); - - if (node->value_type_mask == UFBXI_VALUE_ARRAY) { - ufbxi_value_array *arr = node->array; - ufbx_dom_value *val = ufbxi_push_zero(&uc->result, ufbx_dom_value, 1); - ufbxi_check(val); - - dst->values.data = val; - dst->values.count = 1; - - size_t elem_size = ufbxi_array_type_size(arr->type); - val->value_str.data = ufbxi_empty_char; - val->value_blob.data = arr->data; - val->value_blob.size = arr->size * elem_size; - val->value_float = (double)(val->value_int = (int64_t)arr->size); - - switch (arr->type) { - case 'c': val->type = UFBX_DOM_VALUE_BLOB; break; - case 'b': val->type = UFBX_DOM_VALUE_BLOB; break; - case 'i': val->type = UFBX_DOM_VALUE_ARRAY_I32; break; - case 'l': val->type = UFBX_DOM_VALUE_ARRAY_I64; break; - case 'f': val->type = UFBX_DOM_VALUE_ARRAY_F32; break; - case 'd': val->type = UFBX_DOM_VALUE_ARRAY_F64; break; - case 's': val->type = UFBX_DOM_VALUE_ARRAY_BLOB; break; - case 'C': val->type = UFBX_DOM_VALUE_ARRAY_BLOB; break; - case '-': val->type = UFBX_DOM_VALUE_ARRAY_IGNORED; break; - default: ufbxi_fail("Bad array type"); - } - } else { - size_t ix; - for (ix = 0; ix < UFBXI_MAX_NON_ARRAY_VALUES; ix++) { - uint32_t mask = (node->value_type_mask >> (2*ix)) & 0x3; - if (!mask) break; - ufbx_dom_value *val = ufbxi_push_zero(&uc->tmp_stack, ufbx_dom_value, 1); - ufbxi_check(val); - val->value_str.data = ufbxi_empty_char; - - if (mask == UFBXI_VALUE_STRING) { - val->type = UFBX_DOM_VALUE_STRING; - ufbxi_ignore(ufbxi_get_val_at(node, ix, 'S', &val->value_str)); - ufbxi_ignore(ufbxi_get_val_at(node, ix, 'b', &val->value_blob)); - } else { - ufbx_assert(mask == UFBXI_VALUE_NUMBER); - val->type = UFBX_DOM_VALUE_NUMBER; - val->value_int = node->vals[ix].i; - val->value_float = node->vals[ix].f; - } - } - - dst->values.count = ix; - dst->values.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_dom_value, ix); - ufbxi_check(dst->values.data); - } - - if (node->num_children > 0) { - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL)); - } - - dst->children.count = node->num_children; - dst->children.data = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, node->num_children); - ufbxi_check(dst->children.data); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel(ufbxi_context *uc, ufbxi_node *node) -{ - if (uc->dom_parse_num_children > 0) { - ufbx_dom_node **children = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, uc->dom_parse_num_children); - ufbxi_check(children); - uc->dom_parse_toplevel->children.data = children; - uc->dom_parse_toplevel->children.count = uc->dom_parse_num_children; - uc->dom_parse_num_children = 0; - } - - if (node) { - ufbxi_check(ufbxi_retain_dom_node(uc, node, &uc->dom_parse_toplevel)); - } else { - uc->dom_parse_toplevel = NULL; - - // Called with NULL argument to finish retaining DOM, collect the final nodes to `ufbx_scene`. - size_t num_top_nodes = uc->tmp_dom_nodes.num_items; - ufbx_dom_node **nodes = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, num_top_nodes); - ufbxi_check(nodes); - - ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); - ufbxi_check(dom_root); - - dom_root->name.data = ufbxi_empty_char; - dom_root->children.data = nodes; - dom_root->children.count = num_top_nodes; - - uc->scene.dom_root = dom_root; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel_child(ufbxi_context *uc, ufbxi_node *child) -{ - ufbx_assert(uc->dom_parse_toplevel); - ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL)); - uc->dom_parse_num_children++; - - return 1; -} - -// -- General parsing - -static ufbxi_noinline bool ufbxi_next_line(ufbx_string *line, ufbx_string *buf, bool skip_space) -{ - if (buf->length == 0) return false; - const char *newline = (const char*)memchr(buf->data, '\n', buf->length); - size_t length = newline ? ufbxi_to_size(newline - buf->data) + 1 : buf->length; - - line->data = buf->data; - line->length = length; - buf->data += length; - buf->length -= length; - - if (skip_space) { - while (line->length > 0 && ufbxi_is_space(line->data[0])) { - line->data++; - line->length--; - } - while (line->length > 0 && ufbxi_is_space(line->data[line->length - 1])) { - line->length--; - } - } - - return true; -} - -// Recursion limited by compile time patterns -static ufbxi_noinline const char *ufbxi_match_skip(const char *fmt, bool alternation) - ufbxi_recursive_function(const char *, ufbxi_match_skip, (fmt, alternation), 4, - (const char *fmt, bool alternation)) -{ - for (;;) { - char c = *fmt++; - switch (c) { - case '(': - fmt = ufbxi_match_skip(fmt, false) + 1; - break; - case '\\': - fmt++; - break; - case '[': - c = *fmt; - while (c != ']') { - c = *fmt++; - if (c == '\\') { - c = *fmt++; - } - } - fmt++; - break; - case '|': - if (alternation) return fmt - 1; - break; - case ')': - case '\0': - return fmt - 1; - default: break; - } - } -} - -// Recursion limited by compile time patterns -static ufbxi_noinline bool ufbxi_match_imp(const char **p_str, const char *end, const char **p_fmt) - ufbxi_recursive_function(bool, ufbxi_match_imp, (p_str, end, p_fmt), 4, - (const char **p_str, const char *end, const char **p_fmt)) -{ - const char *str_original_begin = *p_str; - const char *str = str_original_begin; - const char *fmt_begin = *p_fmt; - const char *fmt = fmt_begin; - bool case_insensitive = false; - - size_t count = 0; - for (;;) { - char c = *fmt++; - if (!c) { - *p_str = str; - *p_fmt = fmt - 1; - return true; - } - - const char *str_begin = str; - char ref = str != end ? *str : '\0'; - - if (case_insensitive) { - if (ref >= 'A' && ref <= 'Z') { - ref = (char)((int)(ref - 'A') + 'a'); - } - } - - bool ok = false; - switch (c) { - - case '\\': { - const char *macro = NULL; - c = *fmt++; - switch (c) { - case 'd': - macro = "[0-9]"; - break; - case 'F': - macro = "[\\-+]?[0-9]+(\\.[0-9]+)?([eE][\\-+]?[0-9]+)?"; - break; - case 's': - if (ufbxi_is_space(ref)) { - ok = true; - str++; - } - break; - case 'S': - if (!ufbxi_is_space(ref)) { - ok = true; - str++; - } - break; - case 'c': - case 'C': - case_insensitive = c == 'c'; - ok = true; - break; - default: - if (ref == c) { - ok = true; - str++; - } - break; - } - if (macro) { - ok = ufbxi_match_imp(&str, end, ¯o); - } - } break; - - case '[': { - while (fmt[0] != ']') { - if (fmt[0] == '\\') { - if (ref == fmt[1]) ok = true; - fmt += 2; - } else if (fmt[1] == '-') { - if (ref >= fmt[0] && ref <= fmt[2]) { - ok = true; - } - fmt += 3; - } else { - if (ref == fmt[0]) ok = true; - fmt += 1; - } - } - fmt++; - if (ok) str++; - } break; - - case '(': - if (ufbxi_match_imp(&str, end, &fmt)) { - ok = true; - } - break; - - case '|': - fmt = ufbxi_match_skip(fmt, false); - ok = true; - break; - - case ')': - *p_str = str; - *p_fmt = fmt; - return true; - - case '.': - if (ref != '\0') { - ok = true; - str++; - } - break; - - default: - if (c == ref) { - str++; - ok = true; - } - break; - } - - bool did_fail = false; - c = *fmt; - switch (c) { - case '*': - fmt++; - if (ok) { - fmt = fmt_begin; - count++; - continue; - } - break; - case '+': - fmt++; - if (ok) { - fmt = fmt_begin; - count++; - continue; - } else if (count == 0) { - did_fail = true; - } - break; - case '?': - fmt++; - break; - default: - did_fail = !ok; - break; - } - - if (did_fail) { - fmt = ufbxi_match_skip(fmt, true); - if (*fmt == '|') { - fmt++; - str = str_original_begin; - } else { - *p_fmt = ufbxi_match_skip(fmt, false) + 1; - return false; - } - } else { - if (!ok) { - str = str_begin; - } - } - - fmt_begin = fmt; - count = 0; - } -} - -static ufbxi_noinline bool ufbxi_match(const ufbx_string *str, const char *fmt) -{ - const char *ptr = str->data, *end = str->data + str->length; - if (ufbxi_match_imp(&ptr, end, &fmt)) { - return ptr == end; - } else { - return false; - } -} - -static ufbxi_noinline bool ufbxi_is_format(const char *data, size_t size, ufbx_file_format format) -{ - ufbx_string line, buf = { data, size }; - - if (format == UFBX_FILE_FORMAT_FBX) { - if (size >= UFBXI_BINARY_MAGIC_SIZE && !memcmp(data, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) { - return true; - } - - while (ufbxi_next_line(&line, &buf, true)) { - if (ufbxi_match(&line, ";\\s*FBX\\s*\\d+\\.\\d+\\.\\d+\\s*project\\s+file")) return true; - if (ufbxi_match(&line, "FBXHeaderExtension:.*")) return true; - } - } else if (format == UFBX_FILE_FORMAT_OBJ) { - while (ufbxi_next_line(&line, &buf, true)) { - const char *pattern = - "(vn?\\s+\\F|vt)\\s+\\F\\s+\\F.*" "|" - "f\\s+[\\-/0-9]+\\s+[\\-/0-9]+\\s*[\\-/0-9]+.*" "|" - "(usemtl|mtllib)\\s+\\S.*"; - if (ufbxi_match(&line, pattern)) return true; - } - } else if (format == UFBX_FILE_FORMAT_MTL) { - while (ufbxi_next_line(&line, &buf, true)) { - const char *pattern = - "newmtl\\s+\\S.*"; - if (ufbxi_match(&line, pattern)) return true; - } - } else { - ufbxi_unreachable("Unhandled format"); - } - - return false; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_determine_format(ufbxi_context *uc) -{ - ufbx_file_format format = uc->opts.file_format; - - if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_content) { - ufbxi_pause_progress(uc); - - size_t lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD; - while (format == UFBX_FILE_FORMAT_UNKNOWN && lookahead <= uc->opts.file_format_lookahead) { - if (lookahead > uc->data_size) { - if (uc->eof) break; - ufbxi_check(ufbxi_refill(uc, lookahead, false)); - } - - size_t data_size = ufbxi_min_sz(lookahead, uc->data_size); - ufbxi_check_msg(data_size > 0, "Empty file"); - - for (uint32_t fmt = UFBX_FILE_FORMAT_FBX; fmt < UFBX_FILE_FORMAT_COUNT; fmt++) { - if (ufbxi_is_format(uc->data, data_size, (ufbx_file_format)fmt)) { - format = (ufbx_file_format)fmt; - break; - } - } - - if (lookahead >= uc->opts.file_format_lookahead) { - break; - } else if (lookahead < SIZE_MAX / 2) { - lookahead = ufbxi_min_sz(lookahead * 2, uc->opts.file_format_lookahead); - } else { - lookahead = SIZE_MAX; - } - } - - ufbxi_check(ufbxi_resume_progress(uc)); - } - - if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_extension) { - if (uc->opts.filename.length > 0) { - ufbx_string extension = uc->opts.filename; - for (size_t i = extension.length; i > 0; i--) { - if (extension.data[i - 1] == '.') { - extension.data += i - 1; - extension.length -= i - 1; - break; - } - } - - if (ufbxi_match(&extension, "\\c\\.fbx")) { - format = UFBX_FILE_FORMAT_FBX; - } else if (ufbxi_match(&extension, "\\c\\.obj")) { - format = UFBX_FILE_FORMAT_OBJ; - } else if (ufbxi_match(&extension, "\\c\\.mtl")) { - format = UFBX_FILE_FORMAT_MTL; - } - } - } - - ufbxi_check_msg(format != UFBX_FILE_FORMAT_UNKNOWN, "Unrecognized file format"); - uc->scene.metadata.file_format = format; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_begin_parse(ufbxi_context *uc) -{ - const char *header = ufbxi_peek_bytes(uc, UFBXI_BINARY_HEADER_SIZE); - ufbxi_check(header); - - // If the file starts with the binary magic parse it as binary, otherwise - // treat it as an ASCII file. - if (!memcmp(header, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) { - - // The byte after the magic indicates endianness - char endian = header[UFBXI_BINARY_MAGIC_SIZE + 0]; - uc->file_big_endian = endian != 0; - - // Read the version directly from the header - const char *version_word = header + UFBXI_BINARY_MAGIC_SIZE + 1; - if (uc->file_big_endian) { - version_word = ufbxi_swap_endian(uc, version_word, 1, 4); - ufbxi_check(version_word); - } - uc->version = ufbxi_read_u32(version_word); - - // This is quite probably an FBX file.. - uc->sure_fbx = true; - ufbxi_consume_bytes(uc, UFBXI_BINARY_HEADER_SIZE); - - } else { - uc->from_ascii = true; - - // Use the current read buffer as the initial parse buffer - memset(&uc->ascii, 0, sizeof(uc->ascii)); - uc->ascii.src = uc->data; - uc->ascii.src_yield = uc->data + uc->yield_size; - uc->ascii.src_end = uc->data + uc->data_size + uc->yield_size; - - // Initialize the first token - ufbxi_check(ufbxi_ascii_next_token(uc, &uc->ascii.token)); - - // Default to version 7400 if not found in header - if (uc->version > 0) { - uc->sure_fbx = true; - } else { - if (!uc->opts.strict) uc->version = 7400; - ufbxi_check_msg(uc->version > 0, "Not an FBX file"); - } - } - - return 1; -} - -ufbxi_nodiscard static int ufbxi_parse_toplevel_child_imp(ufbxi_context *uc, ufbxi_parse_state state, ufbxi_buf *buf, bool *p_end) -{ - if (uc->from_ascii) { - ufbxi_check(ufbxi_ascii_parse_node(uc, 0, state, p_end, buf, true)); - } else { - ufbxi_check(ufbxi_binary_parse_node(uc, 0, state, p_end, buf, true)); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel(ufbxi_context *uc, const char *name) -{ - ufbxi_for(ufbxi_node, node, uc->top_nodes, uc->top_nodes_len) { - if (node->name == name) { - uc->top_node = node; - uc->top_child_index = 0; - return 1; - } - } - - // Reached end and not found in cache - if (uc->parsed_to_end) { - uc->top_node = NULL; - uc->top_child_index = 0; - return 1; - } - - for (;;) { - // Parse the next top-level node - bool end = false; - if (uc->from_ascii) { - ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false)); - } else { - ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false)); - } - - // Top-level node not found - if (end) { - uc->top_node = NULL; - uc->top_child_index = 0; - uc->parsed_to_end = true; - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_retain_toplevel(uc, NULL)); - } - - // Not needed anymore - ufbxi_buf_free(&uc->tmp_parse); - - return 1; - } - - uc->top_nodes_len++; - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->top_nodes, &uc->top_nodes_cap, uc->top_nodes_len)); - ufbxi_node *node = &uc->top_nodes[uc->top_nodes_len - 1]; - ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, node); - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_retain_toplevel(uc, node)); - } - - // Return if we parsed the right one - if (node->name == name) { - uc->top_node = node; - uc->top_child_index = SIZE_MAX; - return 1; - } - - // If not we need to parse all the children of the node for later - uint32_t num_children = 0; - ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, node->name); - if (uc->has_next_child) { - for (;;) { - ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, &uc->tmp, &end)); - if (end) break; - num_children++; - } - } - - node->num_children = num_children; - node->children = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_node, num_children); - ufbxi_check(node->children); - - if (uc->opts.retain_dom) { - for (size_t i = 0; i < num_children; i++) { - ufbxi_check(ufbxi_retain_toplevel_child(uc, &node->children[i])); - } - } - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel_child(ufbxi_context *uc, ufbxi_node **p_node, ufbxi_buf *tmp_buf) -{ - // Top-level node not found - if (!uc->top_node) { - *p_node = NULL; - return 1; - } - - if (uc->top_child_index == SIZE_MAX) { - // Parse children on demand - if (!tmp_buf) { - ufbxi_buf_clear(&uc->tmp_parse); - } - bool end = false; - ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, uc->top_node->name); - ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, tmp_buf ? tmp_buf : &uc->tmp_parse, &end)); - if (end) { - *p_node = NULL; - } else { - // Parse to either reused `uc->top_child` or push if retaining to `tmp_buf`. - ufbxi_node *dst = &uc->top_child; - if (tmp_buf) { - dst = ufbxi_push_zero(tmp_buf, ufbxi_node, 1); - ufbxi_check(dst); - } - - ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, dst); - *p_node = dst; - - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_retain_toplevel_child(uc, dst)); - } - } - } else { - // Iterate already parsed nodes - size_t child_index = uc->top_child_index; - if (child_index == uc->top_node->num_children) { - *p_node = NULL; - } else { - uc->top_child_index++; - *p_node = &uc->top_node->children[child_index]; - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_legacy_toplevel(ufbxi_context *uc) -{ - ufbx_assert(uc->top_nodes_len == 0); - - bool end = false; - if (uc->from_ascii) { - ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true)); - } else { - ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true)); - } - - // Top-level node not found - if (end) { - uc->top_node = NULL; - uc->top_child_index = 0; - uc->parsed_to_end = true; - return 1; - } - - ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, &uc->legacy_node); - uc->top_child_index = 0; - uc->top_node = &uc->legacy_node; - - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_retain_toplevel(uc, &uc->legacy_node)); - } - - return 1; -} - -// -- Setup - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_strings(ufbxi_context *uc) -{ -#if defined(UFBX_REGRESSION) - ufbx_string reg_prev = ufbx_empty_string; -#endif - - // Push all the global 'ufbxi_*' strings into the pool without copying them - // This allows us to compare name pointers to the global values - ufbxi_for(const ufbx_string, str, ufbxi_strings, ufbxi_arraycount(ufbxi_strings)) { -#if defined(UFBX_REGRESSION) - ufbx_assert(strlen(str->data) == str->length); - ufbx_assert(ufbxi_str_less(reg_prev, *str)); - reg_prev = *str; -#endif - ufbxi_check(ufbxi_push_string_imp(&uc->string_pool, str->data, str->length, NULL, false, true)); - } - - return 1; -} - -typedef struct { - const char *name; - ufbx_prop_type type; -} ufbxi_prop_type_name; - -static const ufbxi_prop_type_name ufbxi_prop_type_names[] = { - { "Boolean", UFBX_PROP_BOOLEAN }, - { "bool", UFBX_PROP_BOOLEAN }, - { "Bool", UFBX_PROP_BOOLEAN }, - { "Integer", UFBX_PROP_INTEGER }, - { "int", UFBX_PROP_INTEGER }, - { "enum", UFBX_PROP_INTEGER }, - { "Enum", UFBX_PROP_INTEGER }, - { "Visibility", UFBX_PROP_INTEGER }, - { "Visibility Inheritance", UFBX_PROP_INTEGER }, - { "KTime", UFBX_PROP_INTEGER }, - { "Number", UFBX_PROP_NUMBER }, - { "double", UFBX_PROP_NUMBER }, - { "Real", UFBX_PROP_NUMBER }, - { "Float", UFBX_PROP_NUMBER }, - { "Intensity", UFBX_PROP_NUMBER }, - { "Vector", UFBX_PROP_VECTOR }, - { "Vector3D", UFBX_PROP_VECTOR }, - { "Color", UFBX_PROP_COLOR }, - { "ColorAndAlpha", UFBX_PROP_COLOR_WITH_ALPHA }, - { "ColorRGB", UFBX_PROP_COLOR }, - { "String", UFBX_PROP_STRING }, - { "KString", UFBX_PROP_STRING }, - { "object", UFBX_PROP_STRING }, - { "DateTime", UFBX_PROP_DATE_TIME }, - { "Lcl Translation", UFBX_PROP_TRANSLATION }, - { "Lcl Rotation", UFBX_PROP_ROTATION }, - { "Lcl Scaling", UFBX_PROP_SCALING }, - { "Distance", UFBX_PROP_DISTANCE }, - { "Compound", UFBX_PROP_COMPOUND }, - { "Blob", UFBX_PROP_BLOB }, - { "Reference", UFBX_PROP_REFERENCE }, -}; - -static ufbx_prop_type ufbxi_get_prop_type(ufbxi_context *uc, const char *name) -{ - uint32_t hash = ufbxi_hash_ptr(name); - ufbxi_prop_type_name *entry = ufbxi_map_find(&uc->prop_type_map, ufbxi_prop_type_name, hash, &name); - if (entry) { - return entry->type; - } - return UFBX_PROP_UNKNOWN; -} - -static ufbxi_noinline ufbx_prop *ufbxi_find_prop_with_key(const ufbx_props *props, const char *name, uint32_t key) -{ - do { - ufbx_prop *prop_data = props->props.data; - size_t begin = 0; - size_t end = props->props.count; - while (end - begin >= 16) { - size_t mid = (begin + end) >> 1; - const ufbx_prop *p = &prop_data[mid]; - if (p->_internal_key < key) { - begin = mid + 1; - } else { - end = mid; - } - } - - end = props->props.count; - for (; begin < end; begin++) { - const ufbx_prop *p = &prop_data[begin]; - if (p->_internal_key > key) break; - if (p->name.data == name && (p->flags & UFBX_PROP_FLAG_NO_VALUE) == 0) { - return (ufbx_prop*)p; - } - } - - props = props->defaults; - } while (props); - - return NULL; -} - -typedef struct { - const char *key; - ufbx_texture_file *file; -} ufbxi_texture_file_entry; - -#define ufbxi_find_prop(props, name) ufbxi_find_prop_with_key((props), (name), \ - ((uint32_t)(uint8_t)name[0] << 24u) | ((uint32_t)(uint8_t)name[1] << 16u) | \ - ((uint32_t)(uint8_t)name[2] << 8u) | (uint32_t)(uint8_t)name[3]) - -static ufbxi_forceinline ufbx_real ufbxi_find_real(const ufbx_props *props, const char *name, ufbx_real def) -{ - ufbx_prop *prop = ufbxi_find_prop(props, name); - if (prop) { - return prop->value_real; - } else { - return def; - } -} - -static ufbxi_forceinline ufbx_vec3 ufbxi_find_vec3(const ufbx_props *props, const char *name, ufbx_real def_x, ufbx_real def_y, ufbx_real def_z) -{ - ufbx_prop *prop = ufbxi_find_prop(props, name); - if (prop) { - return prop->value_vec3; - } else { - ufbx_vec3 def = { def_x, def_y, def_z }; - return def; - } -} - -static ufbxi_forceinline int64_t ufbxi_find_int(const ufbx_props *props, const char *name, int64_t def) -{ - ufbx_prop *prop = ufbxi_find_prop(props, name); - if (prop) { - return prop->value_int; - } else { - return def; - } -} - -static ufbxi_forceinline int64_t ufbxi_find_enum(const ufbx_props *props, const char *name, int64_t def, int64_t max_value) -{ - ufbx_prop *prop = ufbxi_find_prop(props, name); - if (prop) { - int64_t value = prop->value_int; - if (value >= 0 && value <= max_value) { - return value; - } else { - return def; - } - } else { - return def; - } -} - -ufbxi_noinline static bool ufbxi_matrix_all_zero(const ufbx_matrix *matrix) -{ - for (size_t i = 0; i < 12; i++) { - if (matrix->v[i] != 0.0f) return false; - } - return true; -} - -static ufbxi_forceinline bool ufbxi_is_vec3_zero(ufbx_vec3 v) -{ - return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0); -} - -static ufbxi_forceinline bool ufbxi_is_vec4_zero(ufbx_vec4 v) -{ - return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0); -} - -static ufbxi_forceinline bool ufbxi_is_vec3_one(ufbx_vec3 v) -{ - return (v.x == 1.0) & (v.y == 1.0) & (v.z == 1.0); -} - -static ufbxi_forceinline bool ufbxi_is_quat_identity(ufbx_quat v) -{ - return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0) & (v.w == 1.0); -} - -static ufbxi_noinline bool ufbxi_is_transform_identity(const ufbx_transform *t) -{ - return (bool)((int)ufbxi_is_vec3_zero(t->translation) & (int)ufbxi_is_quat_identity(t->rotation) & (int)ufbxi_is_vec3_one(t->scale)); -} - -static ufbxi_forceinline uint32_t ufbxi_get_name_key(const char *name, size_t len) -{ - uint32_t key = 0; - if (len >= 4) { - key = (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16 - | (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3]; - } else { - for (size_t i = 0; i < 4; i++) { - key <<= 8; - if (i < len) key |= (uint8_t)name[i]; - } - } - return key; -} - -static ufbxi_forceinline uint32_t ufbxi_get_name_key_c(const char *name) -{ - if (name[0] == '\0') return 0; - if (name[1] == '\0') return (uint32_t)(uint8_t)name[0]<<24; - if (name[2] == '\0') return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16; - return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16 - | (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3]; -} - -static ufbxi_forceinline bool ufbxi_name_key_less(ufbx_prop *prop, const char *data, size_t name_len, uint32_t key) -{ - if (prop->_internal_key < key) return true; - if (prop->_internal_key > key) return false; - - size_t prop_len = prop->name.length; - size_t len = ufbxi_min_sz(prop_len, name_len); - int cmp = memcmp(prop->name.data, data, len); - if (cmp != 0) return cmp < 0; - return prop_len < name_len; -} - -static const char *const ufbxi_node_prop_names[] = { - "AxisLen", - "DefaultAttributeIndex", - "Freeze", - "GeometricRotation", - "GeometricScaling", - "GeometricTranslation", - "InheritType", - "LODBox", - "Lcl Rotation", - "Lcl Scaling", - "Lcl Translation", - "LookAtProperty", - "MaxDampRangeX", - "MaxDampRangeY", - "MaxDampRangeZ", - "MaxDampStrengthX", - "MaxDampStrengthY", - "MaxDampStrengthZ", - "MinDampRangeX", - "MinDampRangeY", - "MinDampRangeZ", - "MinDampStrengthX", - "MinDampStrengthY", - "MinDampStrengthZ", - "NegativePercentShapeSupport", - "PostRotation", - "PreRotation", - "PreferedAngleX", - "PreferedAngleY", - "PreferedAngleZ", - "QuaternionInterpolate", - "RotationActive", - "RotationMax", - "RotationMaxX", - "RotationMaxY", - "RotationMaxZ", - "RotationMin", - "RotationMinX", - "RotationMinY", - "RotationMinZ", - "RotationOffset", - "RotationOrder", - "RotationPivot", - "RotationSpaceForLimitOnly", - "RotationStiffnessX", - "RotationStiffnessY", - "RotationStiffnessZ", - "ScalingActive", - "ScalingMax", - "ScalingMaxX", - "ScalingMaxY", - "ScalingMaxZ", - "ScalingMin", - "ScalingMinX", - "ScalingMinY", - "ScalingMinZ", - "ScalingOffset", - "ScalingPivot", - "Show", - "TranslationActive", - "TranslationMax", - "TranslationMaxX", - "TranslationMaxY", - "TranslationMaxZ", - "TranslationMin", - "TranslationMinX", - "TranslationMinY", - "TranslationMinZ", - "UpVectorProperty", - "Visibility Inheritance", - "Visibility", - "notes", -}; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_init_node_prop_names(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_map_grow(&uc->node_prop_set, const char*, ufbxi_arraycount(ufbxi_node_prop_names))); - for (size_t i = 0; i < ufbxi_arraycount(ufbxi_node_prop_names); i++) { - const char *name = ufbxi_node_prop_names[i]; - const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name, strlen(name), NULL, false, true); - ufbxi_check(pooled); - uint32_t hash = ufbxi_hash_ptr(pooled); - const char **entry = ufbxi_map_insert(&uc->node_prop_set, const char*, hash, &pooled); - ufbxi_check(entry); - *entry = pooled; - } - - return 1; -} - -static bool ufbxi_is_node_property_name(ufbxi_context *uc, const char *name) -{ - // You need to call `ufbxi_init_node_prop_names()` before calling this - ufbx_assert(uc->node_prop_set.size > 0); - - uint32_t hash = ufbxi_hash_ptr(name); - const char **entry = ufbxi_map_find(&uc->node_prop_set, const char*, hash, &name); - return entry != NULL; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_maps(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_map_grow(&uc->prop_type_map, ufbxi_prop_type_name, ufbxi_arraycount(ufbxi_prop_type_names))); - ufbxi_for(const ufbxi_prop_type_name, name, ufbxi_prop_type_names, ufbxi_arraycount(ufbxi_prop_type_names)) { - const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name->name, strlen(name->name), NULL, false, true); - ufbxi_check(pooled); - uint32_t hash = ufbxi_hash_ptr(pooled); - ufbxi_prop_type_name *entry = ufbxi_map_insert(&uc->prop_type_map, ufbxi_prop_type_name, hash, &pooled); - ufbxi_check(entry); - entry->type = name->type; - entry->name = pooled; - } - - return 1; -} - -// -- Reading the parsed data - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_embedded_blob(ufbxi_context *uc, ufbx_blob *dst_blob, ufbxi_node *node) -{ - if (!node) return 1; - - ufbxi_value_array *content_arr = ufbxi_get_array(node, 'C'); - if (content_arr && content_arr->size > 0) { - ufbx_string content; - size_t num_parts = content_arr->size; - ufbx_string *parts = (ufbx_string*)content_arr->data; - - if (num_parts == 1 && !uc->from_ascii) { - content = parts[0]; - } else { - size_t total_size = 0; - ufbxi_for(ufbx_string, part, parts, num_parts) { - total_size += part->length; - } - char *dst = ufbxi_push(&uc->result, char, total_size); - ufbxi_check(dst); - content.data = dst; - content.length = total_size; - ufbxi_for(ufbx_string, part, parts, num_parts) { - memcpy(dst, part->data, part->length); - dst += part->length; - } - } - - dst_blob->data = content.data; - dst_blob->size = content.length; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_property(ufbxi_context *uc, ufbxi_node *node, ufbx_prop *prop, int version) -{ - const char *type_str = NULL, *subtype_str = NULL; - ufbxi_check(ufbxi_get_val2(node, "SC", &prop->name, (char**)&type_str)); - uint32_t val_ix = 2; - if (version == 70) { - ufbxi_check(ufbxi_get_val_at(node, val_ix++, 'C', (char**)&subtype_str)); - } - - uint32_t flags = 0; - prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); - - ufbx_string flags_str; - if (ufbxi_get_val_at(node, val_ix++, 'S', &flags_str)) { - for (size_t i = 0; i < flags_str.length; i++) { - char next = i + 1 < flags_str.length ? flags_str.data[i + 1] : '0'; - switch (flags_str.data[i]) { - case 'A': flags |= UFBX_PROP_FLAG_ANIMATABLE; break; - case 'U': flags |= UFBX_PROP_FLAG_USER_DEFINED; break; - case 'H': flags |= UFBX_PROP_FLAG_HIDDEN; break; - case 'L': flags |= ((uint32_t)(next - '0') & 0xf) << 4; break; // UFBX_PROP_FLAG_LOCK_* - case 'M': flags |= ((uint32_t)(next - '0') & 0xf) << 8; break; // UFBX_PROP_FLAG_MUTE_* - default: break; // Ignore unknown flags - } - } - } - - prop->type = ufbxi_get_prop_type(uc, type_str); - if (prop->type == UFBX_PROP_UNKNOWN && subtype_str) { - prop->type = ufbxi_get_prop_type(uc, subtype_str); - } - - if (ufbxi_get_val_at(node, val_ix, 'L', &prop->value_int)) { - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT; - } - - size_t real_ix; - for (real_ix = 0; real_ix < 4; real_ix++) { - if (!ufbxi_get_val_at(node, val_ix + real_ix, 'R', &prop->value_real_arr[real_ix])) break; - } - if (real_ix > 0) { - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (real_ix - 1); - } - - // Skip one value forward in case the current value is not a string, as some properties - // contain mixed numbers and strings. Currenltly known cases: - // Lod Distance: P: "Thresholds|Level0", "Distance", "", "",64, "cm" - // User Enum: P: "User_Enum", "Enum", "", "A+U",1, "ValueA~ValueB~ValueC" - if (ufbxi_get_val_type(node, val_ix) != UFBXI_VALUE_STRING) { - val_ix++; - } - - if (ufbxi_get_val_at(node, val_ix, 'S', &prop->value_str)) { - if (prop->value_str.length > 0) { - ufbxi_ignore(ufbxi_get_val_at(node, val_ix, 'b', &prop->value_blob)); - } - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR; - } else { - prop->value_str = ufbx_empty_string; - } - - // Very unlikely, seems to only exist in some "non standard" FBX files - if (node->num_children > 0) { - ufbxi_node *binary = ufbxi_find_child(node, ufbxi_BinaryData); - ufbxi_check(ufbxi_read_embedded_blob(uc, &prop->value_blob, binary)); - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_BLOB; - } - - prop->flags = (ufbx_prop_flags)flags; - - return 1; -} - -static ufbxi_forceinline bool ufbxi_prop_less(ufbx_prop *a, ufbx_prop *b) -{ - if (a->_internal_key < b->_internal_key) return true; - if (a->_internal_key > b->_internal_key) return false; - return strcmp(a->name.data, b->name.data) < 0; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_properties(ufbxi_context *uc, ufbx_prop *props, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_prop))); - ufbxi_macro_stable_sort(ufbx_prop, 32, props, uc->tmp_arr, count, ( ufbxi_prop_less(a, b) )); - return 1; -} - -ufbxi_noinline static void ufbxi_deduplicate_properties(ufbx_prop_list *list) -{ - if (list->count >= 2) { - ufbx_prop *ps = list->data; - size_t dst = 0, src = 0, end = list->count; - while (src < end) { - if (src + 1 < end && ps[src].name.data == ps[src + 1].name.data) { - src++; - } else if (dst != src) { - ps[dst++] = ps[src++]; - } else { - dst++; src++; - } - } - list->count = dst; - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_properties(ufbxi_context *uc, ufbxi_node *parent, ufbx_props *props) -{ - props->defaults = NULL; - - int version = 70; - ufbxi_node *node = ufbxi_find_child(parent, ufbxi_Properties70); - if (!node) { - node = ufbxi_find_child(parent, ufbxi_Properties60); - if (!node) { - // No properties found, not an error - props->props.data = NULL; - props->props.count = 0; - return 1; - } - version = 60; - } - - props->props.data = ufbxi_push_zero(&uc->result, ufbx_prop, node->num_children); - props->props.count = node->num_children; - ufbxi_check(props->props.data); - - for (size_t i = 0; i < props->props.count; i++) { - ufbxi_check(ufbxi_read_property(uc, &node->children[i], &props->props.data[i], version)); - } - - ufbxi_check(ufbxi_sort_properties(uc, props->props.data, props->props.count)); - ufbxi_deduplicate_properties(&props->props); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_thumbnail(ufbxi_context *uc, ufbxi_node *node, ufbx_thumbnail *thumbnail) -{ - ufbxi_check(ufbxi_read_properties(uc, node, &thumbnail->props)); - - int64_t custom_width = ufbx_find_int(&thumbnail->props, "CustomWidth", 0); - int64_t custom_height = ufbx_find_int(&thumbnail->props, "CustomHeight", 0); - - int32_t format; - ufbxi_node *format_node = ufbxi_find_child_strcmp(node, "Format"); - if (format_node && ufbxi_get_val1(format_node, "I", &format)) { - if (format >= 0 && format + 1 < UFBX_THUMBNAIL_FORMAT_COUNT) { - thumbnail->format = (ufbx_thumbnail_format)(format + 1); - } - } - - int32_t size; - if (ufbxi_find_val1(node, ufbxi_Size, "I", &size)) { - if (size > 0) { - thumbnail->width = (uint32_t)size; - thumbnail->height = (uint32_t)size; - } else if (size < 0 && custom_width > 0 && custom_height > 0) { - thumbnail->width = (uint32_t)custom_width; - thumbnail->height = (uint32_t)custom_height; - } - } - - ufbxi_value_array *data_arr = ufbxi_find_array(node, ufbxi_ImageData, 'c'); - if (data_arr) { - thumbnail->data.data = data_arr->data; - thumbnail->data.size = data_arr->size; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_scene_info(ufbxi_context *uc, ufbxi_node *node) -{ - ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.metadata.scene_props)); - - ufbxi_node *thumbnail = ufbxi_find_child(node, ufbxi_Thumbnail); - if (thumbnail) { - ufbxi_check(ufbxi_read_thumbnail(uc, thumbnail, &uc->scene.metadata.thumbnail)); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_header_extension(ufbxi_context *uc) -{ - bool has_tc_definition = false; - int32_t tc_definition = 0; - int32_t header_version = 0; - - for (;;) { - ufbxi_node *child; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL)); - if (!child) break; - - if (child->name == ufbxi_Creator) { - ufbxi_ignore(ufbxi_get_val1(child, "S", &uc->scene.metadata.creator)); - } - - if (uc->version < 6000 && child->name == ufbxi_FBXVersion) { - int32_t version; - if (ufbxi_get_val1(child, "I", &version)) { - if (version > 0 && version < 6000 && (uint32_t)version > uc->version) { - uc->version = (uint32_t)version; - } - } - } - - if (child->name == ufbxi_FBXHeaderVersion) { - ufbxi_ignore(ufbxi_get_val1(child, "I", &header_version)); - } - - if (child->name == ufbxi_OtherFlags) { - if (ufbxi_find_val1(child, ufbxi_TCDefinition, "I", &tc_definition)) { - has_tc_definition = true; - } - } - - if (child->name == ufbxi_SceneInfo) { - ufbxi_check(ufbxi_read_scene_info(uc, child)); - } - - } - - // FBX 8000 will change the KTime units and the new units are opt-in currently via `TCDefinition`. - // `TCDefinition` seems be accounted in all versions, as long as `FBXHeaderVersion >= 1004`. - // The old KTime units are specified as the value `127` and all other values seem to use the new definition. - bool use_v7_ktime = uc->version < 8000; - if (header_version >= 1004 && has_tc_definition) { - use_v7_ktime = tc_definition == 127; - } - - uc->ktime_sec = use_v7_ktime ? 46186158000 : 141120000; - uc->ktime_sec_double = (double)uc->ktime_sec; - - return 1; -} - -static bool ufbxi_match_version_string(const char *fmt, ufbx_string str, uint32_t *p_version) -{ - size_t num_ix = 0; - size_t pos = 0; - while (*fmt) { - char c = *fmt++; - if (c >= 'a' && c <= 'z') { - if (pos >= str.length) return false; - char s = str.data[pos]; - if (s != c && (int)s + (int)('a' - 'A') != (int)c) return false; - pos++; - } else if (c == ' ') { - while (pos < str.length) { - char s = str.data[pos]; - if (s != ' ' && s != '\t') break; - pos++; - } - } else if (c == '-') { - while (pos < str.length) { - char s = str.data[pos]; - if (s == '-') break; - pos++; - } - if (pos >= str.length) return false; - pos++; - } else if (c == '/' || c == '.' || c == '(' || c == ')') { - if (pos >= str.length) return false; - if (str.data[pos] != c) return false; - pos++; - } else if (c == '?') { - uint32_t num = 0; - size_t len = 0; - while (pos < str.length) { - char s = str.data[pos]; - if (!(s >= '0' && s <= '9')) break; - num = num*10 + (uint32_t)(s - '0'); - pos++; - len++; - } - if (len == 0) return false; - p_version[num_ix++] = num; - } else { - ufbxi_unreachable("Unhandled match character"); - } - } - - return true; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_match_exporter(ufbxi_context *uc) -{ - ufbx_string creator = uc->scene.metadata.creator; - uint32_t version[3] = { 0 }; - if (ufbxi_match_version_string("blender-- ?.?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_BLENDER_BINARY; - uc->exporter_version = ufbx_pack_version(version[0], version[1], version[2]); - } else if (ufbxi_match_version_string("blender- ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_BLENDER_BINARY; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } else if (ufbxi_match_version_string("blender version ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_BLENDER_ASCII; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } else if (ufbxi_match_version_string("fbx sdk/fbx plugins version ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_FBX_SDK; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } else if (ufbxi_match_version_string("fbx sdk/fbx plugins build ?", creator, version)) { - uc->exporter = UFBX_EXPORTER_FBX_SDK; - uc->exporter_version = ufbx_pack_version(version[0]/10000u, version[0]/100u%100u, version[0]%100u); - } else if (ufbxi_match_version_string("motionbuilder version ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_MOTION_BUILDER; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } else if (ufbxi_match_version_string("motionbuilder/mocap/online version ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_MOTION_BUILDER; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } - - uc->scene.metadata.exporter = uc->exporter; - uc->scene.metadata.exporter_version = uc->exporter_version; - - // Un-detect the exporter in `ufbxi_context` to disable special cases - if (uc->opts.disable_quirks) { - uc->exporter = UFBX_EXPORTER_UNKNOWN; - uc->exporter_version = 0; - } - - if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { - uc->blender_full_weights = true; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_document(ufbxi_context *uc) -{ - bool found_root_id = 0; - - for (;;) { - ufbxi_node *child; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL)); - if (!child) break; - - if (child->name == ufbxi_Document && !found_root_id) { - // Post-7000: Try to find the first document node and root ID. - // TODO: Multiple documents / roots? - if (ufbxi_find_val1(child, ufbxi_RootNode, "L", &uc->root_id)) { - found_root_id = true; - } - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_definitions(ufbxi_context *uc) -{ - for (;;) { - ufbxi_node *object; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &object, NULL)); - if (!object) break; - - if (object->name != ufbxi_ObjectType) continue; - - ufbxi_template *tmpl = ufbxi_push_zero(&uc->tmp_stack, ufbxi_template, 1); - uc->num_templates++; - ufbxi_check(tmpl); - ufbxi_check(ufbxi_get_val1(object, "C", (char**)&tmpl->type)); - - // Pre-7000 FBX versions don't have property templates, they just have - // the object counts by themselves. - ufbxi_node *props = ufbxi_find_child(object, ufbxi_PropertyTemplate); - if (props) { - ufbxi_check(ufbxi_get_val1(props, "S", &tmpl->sub_type)); - - // Remove the "Fbx" prefix from sub-types, remember to re-intern! - if (tmpl->sub_type.length > 3 && !strncmp(tmpl->sub_type.data, "Fbx", 3)) { - tmpl->sub_type.data += 3; - tmpl->sub_type.length -= 3; - - // HACK: LOD groups use LODGroup for Template, LodGroup for Object? - if (tmpl->sub_type.length == 8 && !memcmp(tmpl->sub_type.data, "LODGroup", 8)) { - tmpl->sub_type.data = "LodGroup"; - } - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tmpl->sub_type, false)); - } - - ufbxi_check(ufbxi_read_properties(uc, props, &tmpl->props)); - } - } - - // TODO: Preserve only the `props` part of the templates - uc->templates = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbxi_template, uc->num_templates); - ufbxi_check(uc->templates); - - return 1; -} - -ufbxi_nodiscard static ufbx_props *ufbxi_find_template(ufbxi_context *uc, const char *name, const char *sub_type) -{ - // TODO: Binary search - ufbxi_for(ufbxi_template, tmpl, uc->templates, uc->num_templates) { - if (tmpl->type == name) { - - // Check that sub_type matches unless the type is Material, Model, AnimationStack, AnimationLayer. - // Those match to all sub-types. - if (tmpl->type != ufbxi_Material && tmpl->type != ufbxi_Model - && tmpl->type != ufbxi_AnimationStack && tmpl->type != ufbxi_AnimationLayer) { - if (tmpl->sub_type.data != sub_type) { - return NULL; - } - } - - if (tmpl->props.props.count > 0) { - return &tmpl->props; - } else { - return NULL; - } - } - } - return NULL; -} - -// Name ID categories -#if defined(UFBX_REGRESSION) - #define UFBXI_MAXIMUM_FAST_POINTER_ID UINT64_C(0x100) -#else - #define UFBXI_MAXIMUM_FAST_POINTER_ID UINT64_C(0x4000000000000000) -#endif -#define UFBXI_POINTER_ID_START UINT64_C(0x8000000000000000) -#define UFBXI_SYNTHETIC_ID_START (UFBXI_POINTER_ID_START + UFBXI_MAXIMUM_FAST_POINTER_ID) - -static ufbxi_forceinline uint64_t ufbxi_push_synthetic_id(ufbxi_context *uc) -{ - return ++uc->synthetic_id_counter; -} - -static ufbxi_noinline uint64_t ufbxi_synthetic_id_from_ptr_id(ufbxi_context *uc, uintptr_t ptr, uint64_t id) -{ - ufbxi_ptr_id ptr_id = { ptr, id }; - uint32_t hash = ufbxi_hash_ptr_id(ptr_id); - ufbxi_ptr_fbx_id_entry *entry = ufbxi_map_find(&uc->ptr_fbx_id_map, ufbxi_ptr_fbx_id_entry, hash, &ptr_id); - - if (!entry) { - entry = ufbxi_map_insert(&uc->ptr_fbx_id_map, ufbxi_ptr_fbx_id_entry, hash, &ptr_id); - ufbxi_check_return(entry, 0); - entry->ptr_id = ptr_id; - entry->fbx_id = ufbxi_push_synthetic_id(uc); - } - - return entry->fbx_id; -} - -static ufbxi_forceinline uint64_t ufbxi_synthetic_id_from_string(ufbxi_context *uc, const char *str) -{ - uintptr_t uptr = (uintptr_t)str; - if (uptr < (UINTPTR_MAX < UFBXI_MAXIMUM_FAST_POINTER_ID ? UINTPTR_MAX : UFBXI_MAXIMUM_FAST_POINTER_ID)) { - return (uint64_t)uptr; - } else { - return ufbxi_synthetic_id_from_ptr_id(uc, uptr, 0); - } -} - -ufbxi_nodiscard ufbxi_forceinline static int ufbxi_validate_fbx_id(ufbxi_context *uc, uint64_t *p_fbx_id) -{ - uint64_t fbx_id = *p_fbx_id; - if (fbx_id >= UFBXI_POINTER_ID_START) { - fbx_id = ufbxi_synthetic_id_from_ptr_id(uc, 0, fbx_id); - ufbxi_check(fbx_id); - *p_fbx_id = fbx_id; - } - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_split_type_and_name(ufbxi_context *uc, ufbx_string type_and_name, ufbx_string *type, ufbx_string *name) -{ - // Name and type are packed in a single property as Type::Name (in ASCII) - // or Name\x00\x01Type (in binary) - const char *sep = uc->from_ascii ? "::" : "\x00\x01"; - size_t type_end = 2; - for (; type_end <= type_and_name.length; type_end++) { - const char *ch = type_and_name.data + type_end - 2; - if (ch[0] == sep[0] && ch[1] == sep[1]) break; - } - - // ???: ASCII and binary store type and name in different order - if (type_end <= type_and_name.length) { - if (uc->from_ascii) { - name->data = type_and_name.data + type_end; - name->length = type_and_name.length - type_end; - type->data = type_and_name.data; - type->length = type_end - 2; - } else { - name->data = type_and_name.data; - name->length = type_end - 2; - type->data = type_and_name.data + type_end; - type->length = type_and_name.length - type_end; - } - } else { - *name = type_and_name; - type->data = ufbxi_empty_char; - type->length = 0; - } - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, type, false)); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, name, false)); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_id(ufbxi_context *uc, uint64_t fbx_id, uint32_t element_id) -{ - uint32_t hash = ufbxi_hash64(fbx_id); - ufbxi_fbx_id_entry *entry = ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); - - if (!entry) { - entry = ufbxi_map_insert(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); - ufbxi_check(entry); - entry->fbx_id = fbx_id; - entry->element_id = element_id; - entry->user_id = 0; - } else { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_DUPLICATE_OBJECT_ID, "Duplicate object ID")); - } - - return 1; -} - -static ufbxi_noinline ufbxi_fbx_id_entry *ufbxi_find_fbx_id(ufbxi_context *uc, uint64_t fbx_id) -{ - uint32_t hash = ufbxi_hash64(fbx_id); - return ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id); -} - -static ufbxi_forceinline bool ufbxi_fbx_id_exists(ufbxi_context *uc, uint64_t fbx_id) -{ - return ufbxi_find_fbx_id(uc, fbx_id) != NULL; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_attr(ufbxi_context *uc, uint64_t fbx_id, uint64_t attrib_fbx_id) -{ - uint32_t hash = ufbxi_hash64(fbx_id); - ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id); - // TODO: Strict / warn about duplicate objects - - if (!entry) { - entry = ufbxi_map_insert(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id); - ufbxi_check(entry); - entry->node_fbx_id = fbx_id; - entry->attr_fbx_id = attrib_fbx_id; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbxi_context *uc, ufbxi_element_info *info, size_t size, ufbx_element_type type) -{ - size_t aligned_size = (size + 7u) & ~0x7u; - - uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items; - uint32_t element_id = uc->num_elements++; - - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, &info->fbx_id), NULL); - uc->tmp_element_byte_offset += aligned_size; - - ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8); - ufbxi_check_return(elem, NULL); - elem->type = type; - elem->element_id = element_id; - elem->typed_id = typed_id; - elem->name = info->name; - elem->props = info->props; - elem->dom_node = info->dom_node; - - if (uc->p_element_id) { - *uc->p_element_id = element_id; - } - - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); - - ufbxi_check_return(ufbxi_insert_fbx_id(uc, info->fbx_id, element_id), NULL); - - return elem; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element_size(ufbxi_context *uc, uint64_t *p_fbx_id, ufbxi_node *node, const char *name, size_t size, ufbx_element_type type) -{ - size_t aligned_size = (size + 7u) & ~0x7u; - - uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items; - uint32_t element_id = uc->num_elements++; - - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); - uc->tmp_element_byte_offset += aligned_size; - - ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8); - ufbxi_check_return(elem, NULL); - elem->type = type; - elem->element_id = element_id; - elem->typed_id = typed_id; - elem->dom_node = ufbxi_get_dom_node(uc, node); - if (name) { - elem->name.data = name; - elem->name.length = strlen(name); - } - - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); - - *p_fbx_id = ufbxi_push_synthetic_id(uc); - - ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, p_fbx_id), NULL); - ufbxi_check_return(ufbxi_insert_fbx_id(uc, *p_fbx_id, element_id), NULL); - - return elem; -} - -#define ufbxi_push_element(uc, info, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_element_size((uc), (info), sizeof(type_name), (type_enum))) -#define ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_synthetic_element_size((uc), (p_fbx_id), (node), (name), sizeof(type_name), (type_enum))) - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_oo(ufbxi_context *uc, uint64_t src, uint64_t dst) -{ - ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); - ufbxi_check(conn); - conn->src = src; - conn->dst = dst; - conn->src_prop = conn->dst_prop = ufbx_empty_string; - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_op(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string prop) -{ - ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); - ufbxi_check(conn); - conn->src = src; - conn->dst = dst; - conn->src_prop = ufbx_empty_string; - conn->dst_prop = prop; - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_pp(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string src_prop, ufbx_string dst_prop) -{ - ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); - ufbxi_check(conn); - conn->src = src; - conn->dst = dst; - conn->src_prop = src_prop; - conn->dst_prop = dst_prop; - return 1; -} - -ufbxi_noinline static void ufbxi_init_synthetic_int_prop(ufbx_prop *dst, const char *name, int64_t value, ufbx_prop_type type) -{ - dst->type = type; - dst->name.data = name; - dst->name.length = strlen(name); - dst->value_real = (ufbx_real)value; - dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_INT); - dst->value_int = value; - dst->value_str.data = ufbxi_empty_char; - - ufbxi_dev_assert(dst->name.length >= 4); - dst->_internal_key = ufbxi_get_name_key(name, 4); -} - -ufbxi_noinline static void ufbxi_init_synthetic_real_prop(ufbx_prop *dst, const char *name, ufbx_real value, ufbx_prop_type type) -{ - dst->type = type; - dst->name.data = name; - dst->name.length = strlen(name); - dst->value_real = value; - dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL); - dst->value_int = (int64_t)value; - dst->value_str.data = ufbxi_empty_char; - - ufbxi_dev_assert(dst->name.length >= 4); - dst->_internal_key = ufbxi_get_name_key(name, 4); -} - -ufbxi_noinline static void ufbxi_init_synthetic_vec3_prop(ufbx_prop *dst, const char *name, const ufbx_vec3 *value, ufbx_prop_type type) -{ - dst->type = type; - dst->name.data = name; - dst->name.length = strlen(name); - dst->value_vec3 = *value; - dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_VEC3); - dst->value_int = ufbxi_f64_to_i64(dst->value_real); - dst->value_str.data = ufbxi_empty_char; - - ufbxi_dev_assert(dst->name.length >= 4); - dst->_internal_key = ufbxi_get_name_key(name, 4); -} - -ufbxi_noinline static void ufbxi_set_own_prop_vec3_uniform(ufbx_props *props, const char *name, ufbx_real value) -{ - ufbx_props local_props = *props; - local_props.defaults = NULL; - ufbx_prop *prop = ufbx_find_prop(&local_props, name); - if (prop) { - prop->value_vec4.x = value; - prop->value_vec4.y = value; - prop->value_vec4.z = value; - prop->value_vec4.w = 0.0f; - prop->value_int = (int64_t)value; - } -} - -typedef struct { - uint32_t geometry_helper_id; - uint32_t scale_helper_id; -} ufbxi_node_extra; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_geometry_transform_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id) -{ - ufbx_vec3 geo_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 geo_rotation = ufbxi_find_vec3(&node->props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 geo_scaling = ufbxi_find_vec3(&node->props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f); - if (!ufbxi_is_vec3_zero(geo_translation) || !ufbxi_is_vec3_zero(geo_rotation) || !ufbxi_is_vec3_one(geo_scaling)) { - - uint64_t geo_fbx_id; - ufbx_node *geo_node = ufbxi_push_synthetic_element(uc, &geo_fbx_id, NULL, uc->opts.geometry_transform_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(geo_node); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &geo_node->element.element_id)); - geo_node->element.dom_node = node->element.dom_node; - - ufbx_prop *props = ufbxi_push_zero(&uc->result, ufbx_prop, 3); - ufbxi_check(props); - ufbxi_init_synthetic_vec3_prop(&props[0], ufbxi_Lcl_Rotation, &geo_rotation, UFBX_PROP_ROTATION); - ufbxi_init_synthetic_vec3_prop(&props[1], ufbxi_Lcl_Scaling, &geo_scaling, UFBX_PROP_SCALING); - ufbxi_init_synthetic_vec3_prop(&props[2], ufbxi_Lcl_Translation, &geo_translation, UFBX_PROP_TRANSLATION); - - geo_node->props.props.data = props; - geo_node->props.props.count = 3; - - node->has_geometry_transform = true; - geo_node->is_geometry_transform_helper = true; - - ufbxi_check(ufbxi_connect_oo(uc, geo_fbx_id, node_fbx_id)); - uc->has_geometry_transform_nodes = true; - - ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element_id, ufbxi_node_extra); - ufbxi_check(extra); - extra->geometry_helper_id = geo_node->element_id; - } - - return 1; -} - -typedef struct { - const char *name; - ufbx_vec3 default_value; -} ufbxi_scale_helper_prop; - -static const ufbxi_scale_helper_prop ufbxi_scale_helper_props[] = { - { ufbxi_GeometricRotation, { 0.0f, 0.0f, 0.0f } }, - { ufbxi_GeometricScaling, { 1.0f, 1.0f, 1.0f } }, - { ufbxi_GeometricTranslation, { 0.0f, 0.0f, 0.0f } }, - { ufbxi_Lcl_Scaling, { 1.0f, 1.0f, 1.0f } }, -}; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_scale_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id) -{ - uint64_t scale_fbx_id; - ufbx_node *scale_node = ufbxi_push_synthetic_element(uc, &scale_fbx_id, NULL, uc->opts.scale_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(scale_node); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &scale_node->element.element_id)); - scale_node->element.dom_node = node->element.dom_node; - - node->scale_helper = scale_node; - scale_node->is_scale_helper = true; - - ufbxi_check(ufbxi_connect_oo(uc, scale_fbx_id, node_fbx_id)); - uc->has_scale_helper_nodes = true; - - ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element.element_id, ufbxi_node_extra); - ufbxi_check(extra); - extra->scale_helper_id = scale_node->element_id; - - size_t max_props = ufbxi_arraycount(ufbxi_scale_helper_props); - ufbx_prop *helper_props = ufbxi_push(&uc->result, ufbx_prop, max_props); - ufbxi_check(helper_props); - - size_t num_props = 0; - ufbx_props props_copy = node->props; - props_copy.defaults = NULL; - for (size_t i = 0; i < max_props; i++) { - const ufbxi_scale_helper_prop *hp = &ufbxi_scale_helper_props[i]; - ufbx_prop *src_prop = ufbxi_find_prop(&props_copy, hp->name); - if (!src_prop) continue; - - helper_props[num_props++] = *src_prop; - src_prop->value_vec3 = hp->default_value; - src_prop->value_int = (int64_t)src_prop->value_vec3.x; - } - - scale_node->props.props.data = helper_props; - scale_node->props.props.count = num_props; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_model(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - (void)node; - ufbx_node *elem_node = ufbxi_push_element(uc, info, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(elem_node); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id)); - - int64_t inherit_type = ufbxi_find_int(&elem_node->props, ufbxi_InheritType, -1); - switch (inherit_type) { - case 0: // RrSs - elem_node->original_inherit_mode = UFBX_INHERIT_MODE_COMPONENTWISE_SCALE; - break; - case 2: // Rrs - elem_node->original_inherit_mode = UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE; - break; - default: break; - } - - if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) { - elem_node->inherit_mode = elem_node->original_inherit_mode; - } else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_IGNORE) { - elem_node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL; - elem_node->inherit_mode = UFBX_INHERIT_MODE_NORMAL; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_element(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, size_t size, ufbx_element_type type) -{ - (void)node; - ufbx_element *elem = ufbxi_push_element_size(uc, info, size, type); - ufbxi_check(elem); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_unknown(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *element, ufbx_string type, ufbx_string sub_type, const char *node_name) -{ - (void)node; - ufbx_unknown *unknown = ufbxi_push_element(uc, element, ufbx_unknown, UFBX_ELEMENT_UNKNOWN); - ufbxi_check(unknown); - unknown->type = type; - unknown->sub_type = sub_type; - unknown->super_type.data = node_name; - unknown->super_type.length = strlen(node_name); - - // `type`, `sub_type` and `node_name` are raw strings so they may need to be sanitized. - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->type, false)); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->sub_type, false)); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->super_type, false)); - - return 1; -} - -typedef struct { - ufbx_vertex_vec3 elem; - uint32_t index; -} ufbxi_tangent_layer; - -static ufbx_real ufbxi_zero_element[8] = { 0 }; - -// Sentinel pointers used for zero/sequential index buffers -static const uint32_t ufbxi_sentinel_index_zero[1] = { 100000000 }; -static const uint32_t ufbxi_sentinel_index_consecutive[1] = { 123456789 }; - -ufbxi_noinline static int ufbxi_fix_index(ufbxi_context *uc, uint32_t *p_dst, uint32_t index, size_t one_past_max_val) -{ - switch (uc->opts.index_error_handling) { - case UFBX_INDEX_ERROR_HANDLING_CLAMP: - ufbxi_check(one_past_max_val > 0); - ufbxi_check(one_past_max_val <= UINT32_MAX); - *p_dst = (uint32_t)one_past_max_val - 1; - ufbxi_check(ufbxi_warnf(UFBX_WARNING_INDEX_CLAMPED, "Clamped index")); - break; - case UFBX_INDEX_ERROR_HANDLING_NO_INDEX: - *p_dst = UFBX_NO_INDEX; - break; - case UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING: - ufbxi_fmt_err_info(&uc->error, "%u (max %u)", index, one_past_max_val ? (one_past_max_val - 1) : 0); - ufbxi_fail_msg("UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING", "Bad index"); - case UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE: - *p_dst = index; - break; - default: - ufbxi_unreachable("Unhandled index_error_handling"); - return 0; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_check_indices(ufbxi_context *uc, uint32_t **p_dst, uint32_t *indices, bool owns_indices, size_t num_indices, size_t num_indexers, size_t num_elems) -{ - // If the indices are truncated extend them with `UFBX_NO_INDEX`, the following normalization pass - // will handle them the same way as other out-of-bounds indices. - if (num_indices < num_indexers) { - uint32_t *new_indices = ufbxi_push(&uc->result, uint32_t, num_indexers); - ufbxi_check(new_indices); - - memcpy(new_indices, indices, sizeof(uint32_t) * num_indices); - for (size_t i = num_indices; i < num_indexers; i++) { - new_indices[i] = UFBX_NO_INDEX; - } - - indices = new_indices; - num_indices = num_indexers; - owns_indices = true; - } - - // Normalize out-of-bounds indices to `invalid_index` - for (size_t i = 0; i < num_indices; i++) { - uint32_t ix = indices[i]; - if (ix >= num_elems) { - // If the indices refer to an external buffer we need to - // allocate a separate buffer for them - if (!owns_indices) { - indices = ufbxi_push_copy(&uc->result, uint32_t, num_indices, indices); - ufbxi_check(indices); - owns_indices = true; - } - ufbxi_check(ufbxi_fix_index(uc, &indices[i], ix, num_elems)); - } - } - - *p_dst = indices; - - return 1; -} - -ufbx_static_assert(vertex_real_size, sizeof(ufbx_vertex_real) == sizeof(ufbx_vertex_attrib)); -ufbx_static_assert(vertex_vec2_size, sizeof(ufbx_vertex_vec2) == sizeof(ufbx_vertex_attrib)); -ufbx_static_assert(vertex_vec3_size, sizeof(ufbx_vertex_vec3) == sizeof(ufbx_vertex_attrib)); -ufbx_static_assert(vertex_vec4_size, sizeof(ufbx_vertex_vec4) == sizeof(ufbx_vertex_attrib)); - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_warn_polygon_mapping(ufbxi_context *uc, const char *data_name, const char *mapping) -{ - ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_POLYGON_MAPPING, "Ignoring geometry '%s' with bad mapping mode '%s'", data_name, mapping)); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_context *uc, ufbx_mesh *mesh, ufbxi_node *node, - ufbx_vertex_attrib *attrib, const char *data_name, const char *index_name, const char *w_name, char data_type, size_t num_components) -{ - ufbx_real **p_dst_data = (ufbx_real**)&attrib->values.data; - - ufbxi_value_array *data = ufbxi_find_array(node, data_name, data_type); - ufbxi_value_array *indices = ufbxi_find_array(node, index_name, 'i'); - - if (!uc->opts.strict) { - if (!data) return 1; - } - - ufbxi_check(data); - ufbxi_check(data->size % num_components == 0); - - size_t num_elems = data->size / num_components; - - // HACK: If there's no elements at all keep the attribute as NULL - // TODO: Strict mode for this? - if (num_elems == 0) { - return 1; - } - - ufbxi_check(num_elems > 0 && num_elems < INT32_MAX); - - attrib->exists = true; - attrib->indices.count = mesh->num_indices; - - const char *mapping = ""; - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_MappingInformationType, "C", (char**)&mapping)); - - attrib->values.count = num_elems ? num_elems : 1; - - // Data array is always used as-is, if empty set the data to a global - // zero buffer so invalid zero index can point to some valid data. - // The zero data is offset by 4 elements to accommodate for invalid index (-1) - if (num_elems > 0) { - *p_dst_data = (ufbx_real*)data->data; - } else { - *p_dst_data = ufbxi_zero_element + 4; - } - - // HACK: Some old exporters seem to use ByPolygon to mean ByPolygonVertex, - // it should be quite safe to remap this - if (mapping == ufbxi_ByPolygon) { - size_t num_indices = indices ? indices->size : num_elems; - if (num_indices == mesh->num_indices) { - mapping = ufbxi_ByPolygonVertex; - } - } - - if (indices) { - size_t num_indices = indices->size; - uint32_t *index_data = (uint32_t*)indices->data; - - if (mapping == ufbxi_ByPolygonVertex) { - - // Indexed by polygon vertex: We can use the provided indices directly. - ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, num_indices, mesh->num_indices, num_elems)); - - } else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) { - - // Indexed by vertex: Follow through the position index mapping to get the final indices. - uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); - ufbxi_check(new_index_data); - - uint32_t *vert_ix = mesh->vertex_indices.data; - for (size_t i = 0; i < mesh->num_indices; i++) { - uint32_t ix = vert_ix[i]; - if (ix < num_indices) { - new_index_data[i] = index_data[ix]; - } else { - ufbxi_check(ufbxi_fix_index(uc, &new_index_data[i], ix, num_elems)); - } - } - - ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); - attrib->unique_per_vertex = true; - - } else if (mapping == ufbxi_ByPolygon) { - - // Indexed by polygon: Generate new indices based on polygons - uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); - ufbxi_check(new_index_data); - - size_t num_faces = mesh->num_faces; - for (size_t face_ix = 0; face_ix < num_faces; face_ix++) { - ufbx_face face = mesh->faces.data[face_ix]; - uint32_t index = UFBX_NO_INDEX; - if (face_ix < num_indices) { - index = index_data[face_ix]; - } - if (index >= num_elems) { - ufbxi_check(ufbxi_fix_index(uc, &index, index, num_elems)); - } - for (size_t i = 0; i < face.num_indices; i++) { - new_index_data[face.index_begin + i] = index; - } - } - - attrib->indices.data = new_index_data; - - } else if (mapping == ufbxi_AllSame) { - - // Indexed by all same: ??? This could be possibly used for making - // holes with invalid indices, but that seems really fringe. - // Just use the shared zero index buffer for this. - uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices); - attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero; - attrib->unique_per_vertex = true; - - } else { - memset(attrib, 0, sizeof(ufbx_vertex_attrib)); - ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); - return 1; - } - - } else { - - if (mapping == ufbxi_ByPolygonVertex) { - - // Direct by polygon index: Use shared consecutive array if there's enough - // elements, otherwise use a unique truncated consecutive index array. - if (num_elems >= mesh->num_indices) { - uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices); - attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; - } else { - uint32_t *index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); - ufbxi_check(index_data); - for (size_t i = 0; i < mesh->num_indices; i++) { - index_data[i] = (uint32_t)i; - } - ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); - } - - } else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) { - - // Direct by vertex: We can re-use the position indices.. - ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, mesh->vertex_position.indices.data, false, mesh->num_indices, mesh->num_indices, num_elems)); - attrib->unique_per_vertex = true; - - } else if (mapping == ufbxi_ByPolygon) { - - // Direct by polygon: Generate new indices based on polygons - uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices); - ufbxi_check(new_index_data); - - uint32_t num_faces = (uint32_t)mesh->num_faces; - for (uint32_t face_ix = 0; face_ix < num_faces; face_ix++) { - ufbx_face face = mesh->faces.data[face_ix]; - for (size_t i = 0; i < face.num_indices; i++) { - new_index_data[face.index_begin + i] = face_ix; - } - } - - ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems)); - - } else if (mapping == ufbxi_AllSame) { - - // Direct by all same: This cannot fail as the index list is just zero. - uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices); - attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero; - attrib->unique_per_vertex = true; - - } else { - memset(attrib, 0, sizeof(ufbx_vertex_attrib)); - ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); - return 1; - } - } - - if (uc->opts.retain_vertex_attrib_w && w_name) { - ufbxi_value_array *w_data = ufbxi_find_array(node, w_name, 'r'); - if (w_data) { - if (w_data->size == num_elems) { - attrib->values_w.count = w_data->size; - attrib->values_w.data = (ufbx_real*)w_data->data; - } else { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, "Bad W array size %s=%zu, %s=%zu", - w_name, w_data->size, data_name, num_elems)); - } - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_truncated_array(ufbxi_context *uc, void *p_data, size_t *p_count, ufbxi_node *node, const char *name, char fmt, size_t size) -{ - ufbxi_value_array *arr = ufbxi_find_array(node, name, fmt); - if (!arr) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_GEOMETRY_DATA, "Missing geometry data: %s", name)); - return 1; - } - - *p_count = size; - - void *data = arr->data; - if (arr->size < size) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_TRUNCATED_ARRAY, "Truncated array: %s", name)); - - size_t elem_size = ufbxi_array_type_size(fmt); - void *new_data = ufbxi_push_size(&uc->result, elem_size, size); - ufbxi_check(new_data); - memcpy(new_data, data, arr->size * elem_size); - // Extend the array with the last element if possible - if (arr->size > 0) { - char *first_elem = (char*)data + (arr->size - 1) * elem_size; - for (size_t i = arr->size; i < size; i++) { - memcpy((char*)new_data + i * elem_size, first_elem, elem_size); - } - } else { - memset(new_data, 0, size * elem_size); - } - data = new_data; - } - - *(void**)p_data = data; - return 1; -} - -ufbxi_noinline static bool ufbxi_uv_set_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_uv_set *a = (const ufbx_uv_set *)va, *b = (const ufbx_uv_set *)vb; - return a->index < b->index; -} - -ufbxi_noinline static bool ufbxi_color_set_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_color_set *a = (const ufbx_color_set *)va, *b = (const ufbx_color_set *)vb; - return a->index < b->index; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_uv_sets(ufbxi_context *uc, ufbx_uv_set *sets, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_uv_set))); - ufbxi_stable_sort(sizeof(ufbx_uv_set), 32, sets, uc->tmp_arr, count, &ufbxi_uv_set_less, NULL); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_color_sets(ufbxi_context *uc, ufbx_color_set *sets, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_color_set))); - ufbxi_stable_sort(sizeof(ufbx_color_set), 32, sets, uc->tmp_arr, count, &ufbxi_color_set_less, NULL); - return 1; -} - -typedef struct ufbxi_blend_offset { - uint32_t vertex; - ufbx_vec3 position_offset; - ufbx_vec3 normal_offset; -} ufbxi_blend_offset; - -static ufbxi_noinline bool ufbxi_blend_offset_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_blend_offset *a = (const ufbxi_blend_offset*)va, *b = (const ufbxi_blend_offset*)vb; - return a->vertex < b->vertex; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_offsets(ufbxi_context *uc, ufbxi_blend_offset *offsets, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_blend_offset))); - ufbxi_stable_sort(sizeof(ufbxi_blend_offset), 16, offsets, uc->tmp_arr, count, &ufbxi_blend_offset_less, NULL); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_shape(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); - ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_Indexes); - ufbxi_node *node_normals = ufbxi_find_child(node, ufbxi_Normals); - if (!node_vertices || !node_indices) return 1; - - ufbx_blend_shape *shape = ufbxi_push_element(uc, info, ufbx_blend_shape, UFBX_ELEMENT_BLEND_SHAPE); - ufbxi_check(shape); - - if (uc->opts.ignore_geometry) return 1; - - ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); - ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i'); - - ufbxi_check(vertices && indices); - ufbxi_check(vertices->size % 3 == 0); - ufbxi_check(indices->size == vertices->size / 3); - - size_t num_offsets = indices->size; - uint32_t *vertex_indices = (uint32_t*)indices->data; - - shape->num_offsets = num_offsets; - shape->position_offsets.data = (ufbx_vec3*)vertices->data; - shape->offset_vertices.data = vertex_indices; - shape->position_offsets.count = num_offsets; - shape->offset_vertices.count = num_offsets; - - if (node_normals) { - ufbxi_value_array *normals = ufbxi_get_array(node_normals, 'r'); - ufbxi_check(normals && normals->size == vertices->size); - shape->normal_offsets.data = (ufbx_vec3*)normals->data; - shape->normal_offsets.count = num_offsets; - } - - // Sort the blend shape vertices only if absolutely necessary - bool sorted = true; - for (size_t i = 1; i < num_offsets; i++) { - if (vertex_indices[i - 1] > vertex_indices[i]) { - sorted = false; - break; - } - } - - if (!sorted) { - ufbxi_blend_offset *offsets = ufbxi_push(&uc->tmp_stack, ufbxi_blend_offset, num_offsets); - ufbxi_check(offsets); - - for (size_t i = 0; i < num_offsets; i++) { - offsets[i].vertex = shape->offset_vertices.data[i]; - offsets[i].position_offset = shape->position_offsets.data[i]; - if (node_normals) offsets[i].normal_offset = shape->normal_offsets.data[i]; - } - - ufbxi_check(ufbxi_sort_blend_offsets(uc, offsets, num_offsets)); - - for (size_t i = 0; i < num_offsets; i++) { - shape->offset_vertices.data[i] = offsets[i].vertex; - shape->position_offsets.data[i] = offsets[i].position_offset; - if (node_normals) shape->normal_offsets.data[i] = offsets[i].normal_offset; - } - ufbxi_pop(&uc->tmp_stack, ufbxi_blend_offset, num_offsets, NULL); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_blend_shapes(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_blend_deformer *deformer = NULL; - uint64_t deformer_fbx_id = 0; - - ufbxi_for (ufbxi_node, n, node->children, node->num_children) { - if (n->name != ufbxi_Shape) continue; - - ufbx_string name; - ufbxi_check(ufbxi_get_val1(n, "S", &name)); - - if (deformer == NULL) { - deformer = ufbxi_push_synthetic_element(uc, &deformer_fbx_id, n, name.data, ufbx_blend_deformer, UFBX_ELEMENT_BLEND_DEFORMER); - ufbxi_check(deformer); - ufbxi_check(ufbxi_connect_oo(uc, deformer_fbx_id, info->fbx_id)); - } - - uint64_t channel_fbx_id = 0; - ufbx_blend_channel *channel = ufbxi_push_synthetic_element(uc, &channel_fbx_id, n, name.data, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL); - ufbxi_check(channel); - - ufbx_real_list weight_list = { NULL, 0 }; - ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &weight_list)); - - size_t num_shape_props = 1; - ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props); - ufbxi_check(shape_props); - shape_props[0].name.data = ufbxi_DeformPercent; - shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1; - shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent); - shape_props[0].type = UFBX_PROP_NUMBER; - shape_props[0].value_real = (ufbx_real)0.0; - shape_props[0].value_str = ufbx_empty_string; - shape_props[0].value_blob = ufbx_empty_blob; - - ufbx_prop *self_prop = ufbx_find_prop_len(&info->props, name.data, name.length); - if (self_prop && (self_prop->type == UFBX_PROP_NUMBER || self_prop->type == UFBX_PROP_INTEGER)) { - shape_props[0].value_real = self_prop->value_real; - ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name)); - } else if (uc->version < 6000) { - ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name)); - } - - channel->name = name; - channel->props.props.data = shape_props; - channel->props.props.count = num_shape_props; - - ufbxi_element_info shape_info = { 0 }; - - shape_info.fbx_id = ufbxi_push_synthetic_id(uc); - shape_info.name = name; - shape_info.dom_node = ufbxi_get_dom_node(uc, n); - - ufbxi_check(ufbxi_read_shape(uc, n, &shape_info)); - - ufbxi_check(ufbxi_connect_oo(uc, channel_fbx_id, deformer_fbx_id)); - ufbxi_check(ufbxi_connect_oo(uc, shape_info.fbx_id, channel_fbx_id)); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_process_indices(ufbxi_context *uc, ufbx_mesh *mesh, uint32_t *index_data) -{ - // Count the number of faces and allocate the index list - // Indices less than zero (~actual_index) ends a polygon - size_t num_total_faces = 0; - ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) { - num_total_faces += ((int32_t)*p_ix < 0) ? 1u : 0u; - } - mesh->faces.data = ufbxi_push(&uc->result, ufbx_face, num_total_faces); - ufbxi_check(mesh->faces.data); - - size_t num_triangles = 0; - size_t max_face_triangles = 0; - size_t num_bad_faces[3] = { 0 }; - - ufbx_face *dst_face = mesh->faces.data; - uint32_t *p_face_begin = index_data; - ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) { - uint32_t ix = *p_ix; - // Un-negate final indices of polygons - if ((int32_t)ix < 0) { - ix = ~ix; - *p_ix = ix; - uint32_t num_indices = (uint32_t)((p_ix - p_face_begin) + 1); - dst_face->index_begin = (uint32_t)(p_face_begin - index_data); - dst_face->num_indices = num_indices; - if (num_indices >= 3) { - num_triangles += num_indices - 2; - max_face_triangles = ufbxi_max_sz(max_face_triangles, num_indices - 2); - } else { - num_bad_faces[num_indices]++; - } - dst_face++; - p_face_begin = p_ix + 1; - } - ufbxi_check((size_t)ix < mesh->num_vertices); - } - - mesh->vertex_position.indices.data = index_data; - mesh->num_faces = ufbxi_to_size(dst_face - mesh->faces.data); - mesh->faces.count = mesh->num_faces; - mesh->num_triangles = num_triangles; - mesh->max_face_triangles = max_face_triangles; - mesh->num_empty_faces = num_bad_faces[0]; - mesh->num_point_faces = num_bad_faces[1]; - mesh->num_line_faces = num_bad_faces[2]; - - mesh->vertex_first_index.count = mesh->num_vertices; - mesh->vertex_first_index.data = ufbxi_push(&uc->result, uint32_t, mesh->num_vertices); - ufbxi_check(mesh->vertex_first_index.data); - - ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) { - *p_vx_ix = UFBX_NO_INDEX; - } - - { - size_t num_indices = mesh->num_indices; - size_t num_vertices = mesh->num_vertices; - uint32_t *vertex_indices = mesh->vertex_indices.data; - uint32_t *vertex_first_index = mesh->vertex_first_index.data; - for (size_t ix = 0; ix < num_indices; ix++) { - uint32_t vx = vertex_indices[ix]; - if (vx < num_vertices) { - if (vertex_first_index[vx] == UFBX_NO_INDEX) { - vertex_first_index[vx] = (uint32_t)ix; - } - } else { - ufbxi_check(ufbxi_fix_index(uc, &vertex_indices[ix], vx, mesh->num_vertices)); - } - } - } - - // HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero - // index buffer for face materials.. - uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces); - uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_faces); - - return 1; -} - -ufbxi_noinline static void ufbxi_patch_mesh_reals(ufbx_mesh *mesh) -{ - mesh->vertex_position.value_reals = 3; - mesh->vertex_normal.value_reals = 3; - mesh->vertex_uv.value_reals = 2; - mesh->vertex_tangent.value_reals = 3; - mesh->vertex_bitangent.value_reals = 3; - mesh->vertex_color.value_reals = 4; - mesh->vertex_crease.value_reals = 1; - mesh->skinned_position.value_reals = 3; - mesh->skinned_normal.value_reals = 3; - - ufbxi_nounroll ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - set->vertex_uv.value_reals = 2; - set->vertex_tangent.value_reals = 3; - set->vertex_bitangent.value_reals = 3; - } - - ufbxi_nounroll ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { - set->vertex_color.value_reals = 4; - } -} - -typedef struct { - uint32_t id, index; -} ufbxi_id_group; - -static bool ufbxi_less_int32(void *user, const void *va, const void *vb) -{ - (void)user; - const int32_t a = *(const int32_t*)va, b = *(const int32_t*)vb; - return a < b; -} - -ufbx_static_assert(mesh_mat_point_faces, offsetof(ufbx_mesh_part, num_point_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 1 * sizeof(size_t)); -ufbx_static_assert(mesh_mat_line_faces, offsetof(ufbx_mesh_part, num_line_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 2 * sizeof(size_t)); -static ufbxi_forceinline void ufbxi_mesh_part_add_face(ufbx_mesh_part *part, uint32_t num_indices) -{ - part->num_faces++; - if (num_indices >= 3) { - part->num_triangles += num_indices - 2; - } else { - // `num_empty/point/line_faces` are consecutive, see static asserts above. - // cppcheck-suppress objectIndex - (&part->num_empty_faces)[num_indices]++; - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_assign_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, size_t *p_consecutive_indices, bool retain_parts) -{ - size_t num_faces = mesh->num_faces; - ufbxi_check_err(error, num_faces > 0); - ufbxi_check_err(error, num_faces < UINT32_MAX); - ufbxi_check_err(error, mesh->face_group.count == num_faces); - - uint32_t *ids = ufbxi_push(buf, uint32_t, num_faces); - ufbxi_check_err(error, ids); - - uint32_t num_ids = 0; - - ufbxi_id_group seen_ids[1 << UFBXI_FACE_GROUP_HASH_BITS]; - memset(seen_ids, 0, sizeof(seen_ids)); - - uint32_t seed = 2654435769u; - uint32_t rehash_threshold = 256; - - // Loosely deduplicate group IDs - ufbxi_for_list(uint32_t, p_id, mesh->face_group) { - uint32_t id = *p_id; - uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS); - if (seen_ids[id_hash].id != id || seen_ids[id_hash].index == 0) { - seen_ids[id_hash].id = id; - if (++seen_ids[id_hash].index > rehash_threshold) { - seed *= seed; - rehash_threshold *= 2; - } - ids[num_ids++] = id; - } - } - - // Sort and deduplicate remaining IDs - ufbxi_unstable_sort(ids, num_ids, sizeof(uint32_t), &ufbxi_less_int32, NULL); - - size_t num_groups = 0; - for (size_t i = 0; i < num_ids; ) { - uint32_t id = ids[i]; - ids[num_groups++] = id; - do { i++; } while (i < num_ids && ids[i] == id); - } - - // Allocate group info structs - ufbx_face_group *groups = ufbxi_push_zero(buf, ufbx_face_group, num_groups); - ufbxi_check_err(error, groups); - for (size_t i = 0; i < num_groups; i++) { - groups[i].id = (int32_t)ids[i]; - groups[i].name.data = ufbxi_empty_char; - } - - mesh->face_groups.data = groups; - mesh->face_groups.count = num_groups; - - ufbx_mesh_part *parts = NULL; - if (retain_parts) { - parts = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups); - ufbxi_check_err(error, parts); - mesh->face_group_parts.data = parts; - mesh->face_group_parts.count = num_groups; - } - - // Optimization: Use `consecutive_indices` for a single group - if (p_consecutive_indices && num_groups == 1) { - memset(mesh->face_group.data, 0, sizeof(uint32_t) * num_faces); - - if (parts) { - parts[0].face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; - parts[0].face_indices.count = num_faces; - parts[0].num_empty_faces = mesh->num_empty_faces; - parts[0].num_point_faces = mesh->num_point_faces; - parts[0].num_line_faces = mesh->num_line_faces; - parts[0].num_faces = num_faces; - parts[0].num_triangles = mesh->num_triangles; - } - - *p_consecutive_indices = ufbxi_max_sz(*p_consecutive_indices, num_faces); - return 1; - } - - memset(seen_ids, 0, sizeof(seen_ids)); - - // Count faces and triangles per group and reassign IDs - const ufbx_face *p_face = mesh->faces.data; - ufbxi_for_list(uint32_t, p_id, mesh->face_group) { - uint32_t id = *p_id; - uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS); - - uint32_t num_indices = p_face->num_indices; - - size_t index; - if (seen_ids[id_hash].id == id && seen_ids[id_hash].index > 0) { - index = seen_ids[id_hash].index - 1; - *p_id = (uint32_t)index; - } else { - int32_t signed_id = (int32_t)id; - index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_face_group, 8, &index, groups, 0, num_groups, ( a->id < signed_id ), ( a->id == signed_id )); - ufbx_assert(index < num_groups); - seen_ids[id_hash].id = id; - seen_ids[id_hash].index = (uint32_t)index + 1; - } - - if (parts) { - ufbxi_mesh_part_add_face(&parts[index], num_indices); - } - - *p_id = (uint32_t)index; - p_face++; - } - - if (!parts) return 1; - - // Subdivide `ids` for per-group `face_indices` - uint32_t *face_indices = ids; - uint32_t part_index = 0; - ufbxi_for(ufbx_mesh_part, part, parts, num_groups) { - part->index = part_index++; - part->face_indices.data = face_indices; - face_indices += part->num_faces; - } - ufbx_assert(face_indices == ids + num_faces); - - // Collect per-group faces - uint32_t face_index = 0; - ufbxi_for_list(uint32_t, p_id, mesh->face_group) { - ufbx_mesh_part *part = &parts[*p_id]; - part->face_indices.data[part->face_indices.count++] = face_index++; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_update_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, bool need_copy) -{ - size_t num_faces = mesh->faces.count; - size_t num_groups = mesh->face_group_parts.count; - if (num_groups == 0) return 1; - - if (need_copy) { - mesh->face_group_parts.data = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups); - ufbxi_check_err(error, mesh->face_group_parts.data); - } - - uint32_t *face_indices = ufbxi_push(buf, uint32_t, num_faces); - ufbxi_check_err(error, face_indices); - - ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { - ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]]; - ufbxi_mesh_part_add_face(part, mesh->faces.data[i].num_indices); - } - - uint32_t part_index = 0; - ufbxi_for_list(ufbx_mesh_part, part, mesh->face_group_parts) { - part->index = part_index++; - part->face_indices.data = face_indices; - part->face_indices.count = 0; - face_indices += part->num_faces; - } - - ufbxi_nounroll for (uint32_t i = 0; i < num_faces; i++) { - ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]]; - part->face_indices.data[part->face_indices.count++] = i; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH); - ufbxi_check(mesh); - - // In up to version 7100 FBX files blend shapes are contained within the same geometry node - if (uc->version <= 7100) { - ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info)); - } - - ufbxi_patch_mesh_reals(mesh); - - // Sometimes there are empty meshes in FBX files? - // TODO: Should these be included in output? option? strict mode? - ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); - ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex); - if (!node_vertices) return 1; - - if (uc->opts.ignore_geometry) return 1; - - ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); - ufbxi_value_array *indices = node_indices ? ufbxi_get_array(node_indices, 'i') : NULL; - ufbxi_value_array *edge_indices = ufbxi_find_array(node, ufbxi_Edges, 'i'); - ufbxi_check(vertices); - ufbxi_check(!node_indices || indices); // If node_indices exists, it must be an array - ufbxi_check(vertices->size % 3 == 0); - - mesh->num_vertices = vertices->size / 3; - mesh->num_indices = indices ? indices->size : 0; - - uint32_t *index_data = indices ? (uint32_t*)indices->data : NULL; - - // Duplicate `index_data` for modification if we retain DOM - if (uc->opts.retain_dom) { - index_data = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, index_data); - ufbxi_check(index_data); - } - - mesh->vertices.data = (ufbx_vec3*)vertices->data; - mesh->vertices.count = mesh->num_vertices; - mesh->vertex_indices.data = index_data; - mesh->vertex_indices.count = mesh->num_indices; - - mesh->vertex_position.exists = true; - mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data; - mesh->vertex_position.values.count = mesh->num_vertices; - mesh->vertex_position.indices.data = index_data; - mesh->vertex_position.indices.count = mesh->num_indices; - mesh->vertex_position.unique_per_vertex = true; - - // Check/make sure that the last index is negated (last of polygon) - if (mesh->num_indices > 0) { - if ((int32_t)index_data[mesh->num_indices - 1] >= 0) { - if (uc->opts.strict) ufbxi_fail("Non-negated last index"); - index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1]; - } - } - - // Read edges before un-negating the indices - if (edge_indices) { - size_t num_edges = edge_indices->size; - ufbx_edge *edges = ufbxi_push(&uc->result, ufbx_edge, num_edges); - ufbxi_check(edges); - - size_t dst_ix = 0; - - // Edges are represented using a single index into PolygonVertexIndex. - // The edge is between two consecutive vertices in the polygon. - uint32_t *edge_data = (uint32_t*)edge_indices->data; - for (size_t i = 0; i < num_edges; i++) { - uint32_t index_ix = edge_data[i]; - if (index_ix >= mesh->num_indices) { - if (uc->opts.strict) ufbxi_fail("Edge index out of bounds"); - continue; - } - edges[dst_ix].a = index_ix; - if ((int32_t)index_data[index_ix] < 0) { - // Previous index is the last one of this polygon, rewind to first index. - while (index_ix > 0 && (int32_t)index_data[index_ix - 1] >= 0) { - index_ix--; - } - } else { - // Connect to the next index in the same polygon - index_ix++; - } - ufbxi_check(index_ix < mesh->num_indices); - edges[dst_ix].b = index_ix; - dst_ix++; - } - - mesh->edges.data = edges; - mesh->edges.count = dst_ix; - mesh->num_edges = mesh->edges.count; - } - - ufbxi_check(ufbxi_process_indices(uc, mesh, index_data)); - - // Count the number of UV/color sets - size_t num_uv = 0, num_color = 0, num_bitangents = 0, num_tangents = 0; - ufbxi_for (ufbxi_node, n, node->children, node->num_children) { - if (n->name == ufbxi_LayerElementUV) num_uv++; - if (n->name == ufbxi_LayerElementColor) num_color++; - if (n->name == ufbxi_LayerElementBinormal) num_bitangents++; - if (n->name == ufbxi_LayerElementTangent) num_tangents++; - } - - size_t num_textures = 0; - - ufbxi_tangent_layer *bitangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_bitangents); - ufbxi_tangent_layer *tangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_tangents); - ufbxi_check(bitangents); - ufbxi_check(tangents); - - mesh->uv_sets.data = ufbxi_push_zero(&uc->result, ufbx_uv_set, num_uv); - mesh->color_sets.data = ufbxi_push_zero(&uc->result, ufbx_color_set, num_color); - ufbxi_check(mesh->uv_sets.data); - ufbxi_check(mesh->color_sets.data); - - size_t num_bitangents_read = 0, num_tangents_read = 0; - ufbxi_for (ufbxi_node, n, node->children, node->num_children) { - if (n->name[0] != 'L') continue; // All names start with 'LayerElement*' - - if (n->name == ufbxi_LayerElementNormal) { - if (mesh->vertex_normal.exists) continue; - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_normal, - ufbxi_Normals, ufbxi_NormalsIndex, ufbxi_NormalsW, 'r', 3)); - } else if (n->name == ufbxi_LayerElementBinormal) { - ufbxi_tangent_layer *layer = &bitangents[num_bitangents_read++]; - - ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index)); - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem, - ufbxi_Binormals, ufbxi_BinormalsIndex, ufbxi_BinormalsW, 'r', 3)); - if (!layer->elem.exists) num_bitangents_read--; - - } else if (n->name == ufbxi_LayerElementTangent) { - ufbxi_tangent_layer *layer = &tangents[num_tangents_read++]; - - ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index)); - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem, - ufbxi_Tangents, ufbxi_TangentsIndex, ufbxi_TangentsW, 'r', 3)); - if (!layer->elem.exists) num_tangents_read--; - - } else if (n->name == ufbxi_LayerElementUV) { - ufbx_uv_set *set = &mesh->uv_sets.data[mesh->uv_sets.count++]; - - ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index)); - if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) { - set->name = ufbx_empty_string; - } - - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_uv, - ufbxi_UV, ufbxi_UVIndex, NULL, 'r', 2)); - if (!set->vertex_uv.exists) mesh->uv_sets.count--; - - } else if (n->name == ufbxi_LayerElementColor) { - ufbx_color_set *set = &mesh->color_sets.data[mesh->color_sets.count++]; - - ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index)); - if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) { - set->name = ufbx_empty_string; - } - - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_color, - ufbxi_Colors, ufbxi_ColorIndex, NULL, 'r', 4)); - if (!set->vertex_color.exists) mesh->color_sets.count--; - - } else if (n->name == ufbxi_LayerElementVertexCrease) { - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_crease, - ufbxi_VertexCrease, ufbxi_VertexCreaseIndex, NULL, 'r', 1)); - } else if (n->name == ufbxi_LayerElementEdgeCrease) { - const char *mapping = ""; - ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByEdge) { - if (mesh->edge_crease.count) continue; - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_crease.data, &mesh->edge_crease.count, n, ufbxi_EdgeCrease, 'r', mesh->num_edges)); - } else { - ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_EdgeCrease, mapping)); - } - } else if (n->name == ufbxi_LayerElementSmoothing) { - const char *mapping = ""; - ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByEdge) { - if (mesh->edge_smoothing.count) continue; - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_smoothing.data, &mesh->edge_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_edges)); - } else if (mapping == ufbxi_ByPolygon) { - if (mesh->face_smoothing.count) continue; - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_smoothing.data, &mesh->face_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_faces)); - } else { - ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Smoothing, mapping)); - } - } else if (n->name == ufbxi_LayerElementVisibility) { - const char *mapping = ""; - ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByEdge) { - if (mesh->edge_visibility.count) continue; - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_visibility.data, &mesh->edge_visibility.count, n, ufbxi_Visibility, 'b', mesh->num_edges)); - } else { - ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Visibility, mapping)); - } - } else if (n->name == ufbxi_LayerElementMaterial) { - if (mesh->face_material.count) continue; - const char *mapping = ""; - ufbxi_ignore(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByPolygon) { - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, n, ufbxi_Materials, 'i', mesh->num_faces)); - } else if (mapping == ufbxi_AllSame) { - ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_Materials, 'i'); - ufbxi_check(arr && arr->size >= 1); - uint32_t material = *(uint32_t*)arr->data; - mesh->face_material.count = mesh->num_faces; - if (material == 0) { - mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; - } else { - mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces); - ufbxi_check(mesh->face_material.data); - ufbxi_for_list(uint32_t, p_mat, mesh->face_material) { - *p_mat = material; - } - } - } else { - ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Materials, mapping)); - } - } else if (n->name == ufbxi_LayerElementPolygonGroup) { - if (mesh->face_group.count) continue; - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByPolygon) { - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_group.data, &mesh->face_group.count, n, ufbxi_PolygonGroup, 'i', mesh->num_faces)); - } - } else if (n->name == ufbxi_LayerElementHole) { - if (mesh->face_group.count) continue; - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); - if (mapping == ufbxi_ByPolygon) { - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_hole.data, &mesh->face_hole.count, n, ufbxi_Hole, 'b', mesh->num_faces)); - } - } else if (!strncmp(n->name, "LayerElement", 12)) { - - // Make sure the name has no internal zero bytes - ufbxi_check(!memchr(n->name, '\0', n->name_len)); - - // What?! 6x00 stores textures in mesh geometry, eg. "LayerElementTexture", - // "LayerElementDiffuseFactorTextures", "LayerElementEmissive_Textures"... - ufbx_string prop_name = ufbx_empty_string; - if (n->name_len > 20 && !strcmp(n->name + n->name_len - 8, "Textures")) { - prop_name.data = n->name + 12; - prop_name.length = (size_t)n->name_len - 20; - if (prop_name.data[prop_name.length - 1] == '_') { - prop_name.length -= 1; - } - } else if (!strcmp(n->name, "LayerElementTexture")) { - prop_name.data = "Diffuse"; - prop_name.length = 7; - } - - if (prop_name.length > 0) { - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop_name, false)); - const char *mapping = NULL; - if (ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)) { - ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_TextureId, 'i'); - - ufbxi_tmp_mesh_texture *tex = ufbxi_push_zero(&uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, 1); - ufbxi_check(tex); - if (arr) { - tex->face_texture = (uint32_t*)arr->data; - tex->num_faces = arr->size; - } - tex->prop_name = prop_name; - tex->all_same = (mapping == ufbxi_AllSame); - num_textures++; - } - } - } - } - - // Always use a default zero material, this will be removed if no materials are found - if (!mesh->face_material.count) { - uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces); - mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; - mesh->face_material.count = mesh->num_faces; - } - - if (uc->opts.strict) { - ufbxi_check(mesh->uv_sets.count == num_uv); - ufbxi_check(mesh->color_sets.count == num_color); - ufbxi_check(num_bitangents_read == num_bitangents); - ufbxi_check(num_tangents_read == num_tangents); - } - - // Connect bitangents/tangents to UV sets - ufbxi_for (ufbxi_node, n, node->children, node->num_children) { - if (n->name != ufbxi_Layer) continue; - ufbx_uv_set *uv_set = NULL; - ufbxi_tangent_layer *bitangent_layer = NULL; - ufbxi_tangent_layer *tangent_layer = NULL; - - ufbxi_for (ufbxi_node, c, n->children, n->num_children) { - uint32_t index; - const char *type; - if (c->name != ufbxi_LayerElement) continue; - if (!ufbxi_find_val1(c, ufbxi_TypedIndex, "I", &index)) continue; - if (!ufbxi_find_val1(c, ufbxi_Type, "C", (char**)&type)) continue; - - if (type == ufbxi_LayerElementUV) { - ufbxi_for(ufbx_uv_set, set, mesh->uv_sets.data, mesh->uv_sets.count) { - if (set->index == index) { - uv_set = set; - break; - } - } - } else if (type == ufbxi_LayerElementBinormal) { - ufbxi_for(ufbxi_tangent_layer, layer, bitangents, num_bitangents_read) { - if (layer->index == index) { - bitangent_layer = layer; - break; - } - } - } else if (type == ufbxi_LayerElementTangent) { - ufbxi_for(ufbxi_tangent_layer, layer, tangents, num_tangents_read) { - if (layer->index == index) { - tangent_layer = layer; - break; - } - } - } - } - - if (uv_set) { - if (bitangent_layer) { - uv_set->vertex_bitangent = bitangent_layer->elem; - } - if (tangent_layer) { - uv_set->vertex_tangent = tangent_layer->elem; - } - } - } - - mesh->skinned_is_local = true; - mesh->skinned_position = mesh->vertex_position; - mesh->skinned_normal = mesh->vertex_normal; - - ufbxi_patch_mesh_reals(mesh); - - if (mesh->face_group.count > 0 && mesh->face_groups.count == 0) { - ufbxi_check(ufbxi_assign_face_groups(&uc->result, &uc->error, mesh, &uc->max_consecutive_indices, uc->retain_mesh_parts)); - } - - // Sort UV and color sets by set index - ufbxi_check(ufbxi_sort_uv_sets(uc, mesh->uv_sets.data, mesh->uv_sets.count)); - ufbxi_check(ufbxi_sort_color_sets(uc, mesh->color_sets.data, mesh->color_sets.count)); - - if (num_textures > 0) { - ufbxi_mesh_extra *extra = ufbxi_push_element_extra(uc, mesh->element.element_id, ufbxi_mesh_extra); - ufbxi_check(extra); - extra->texture_count = num_textures; - extra->texture_arr = ufbxi_push_pop(&uc->tmp, &uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, num_textures); - ufbxi_check(extra->texture_arr); - } - - // Subdivision - - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_PreviewDivisionLevels, "I", &mesh->subdivision_preview_levels)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RenderDivisionLevels, "I", &mesh->subdivision_render_levels)); - - int32_t smoothness, boundary; - if (ufbxi_find_val1(node, ufbxi_Smoothness, "I", &smoothness)) { - if (smoothness >= 0 && smoothness <= UFBX_SUBDIVISION_DISPLAY_SMOOTH) { - mesh->subdivision_display_mode = (ufbx_subdivision_display_mode)smoothness; - } - } - if (ufbxi_find_val1(node, ufbxi_BoundaryRule, "I", &boundary)) { - if (boundary >= 0 && boundary <= UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS - 1) { - mesh->subdivision_boundary = (ufbx_subdivision_boundary)(boundary + 1); - } - } - - return 1; -} - -ufbxi_noinline static ufbx_nurbs_topology ufbxi_read_nurbs_topology(const char *form) -{ - if (!strcmp(form, "Open")) { - return UFBX_NURBS_TOPOLOGY_OPEN; - } else if (!strcmp(form, "Closed")) { - return UFBX_NURBS_TOPOLOGY_CLOSED; - } else if (!strcmp(form, "Periodic")) { - return UFBX_NURBS_TOPOLOGY_PERIODIC; - } - return UFBX_NURBS_TOPOLOGY_OPEN; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_nurbs_curve *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_curve, UFBX_ELEMENT_NURBS_CURVE); - ufbxi_check(nurbs); - - int32_t dimension = 3; - - const char *form = NULL; - ufbxi_check(ufbxi_find_val1(node, ufbxi_Order, "I", &nurbs->basis.order)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Dimension, "I", &dimension)); - ufbxi_check(ufbxi_find_val1(node, ufbxi_Form, "C", (char**)&form)); - nurbs->basis.topology = ufbxi_read_nurbs_topology(form); - nurbs->basis.is_2d = dimension == 2; - - if (!uc->opts.ignore_geometry) { - ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); - ufbxi_value_array *knot = ufbxi_find_array(node, ufbxi_KnotVector, 'r'); - ufbxi_check(points); - ufbxi_check(knot); - ufbxi_check(points->size % 4 == 0); - - nurbs->control_points.count = points->size / 4; - nurbs->control_points.data = (ufbx_vec4*)points->data; - nurbs->basis.knot_vector.data = (ufbx_real*)knot->data; - nurbs->basis.knot_vector.count = knot->size; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_surface(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_nurbs_surface *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_surface, UFBX_ELEMENT_NURBS_SURFACE); - ufbxi_check(nurbs); - - const char *form_u = NULL, *form_v = NULL; - size_t dimension_u = 0, dimension_v = 0; - int32_t step_u = 0, step_v = 0; - ufbxi_check(ufbxi_find_val2(node, ufbxi_NurbsSurfaceOrder, "II", &nurbs->basis_u.order, &nurbs->basis_v.order)); - ufbxi_check(ufbxi_find_val2(node, ufbxi_Dimensions, "ZZ", &dimension_u, &dimension_v)); - ufbxi_check(ufbxi_find_val2(node, ufbxi_Step, "II", &step_u, &step_v)); - ufbxi_check(ufbxi_find_val2(node, ufbxi_Form, "CC", (char**)&form_u, (char**)&form_v)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FlipNormals, "B", &nurbs->flip_normals)); - nurbs->basis_u.topology = ufbxi_read_nurbs_topology(form_u); - nurbs->basis_v.topology = ufbxi_read_nurbs_topology(form_v); - nurbs->num_control_points_u = dimension_u; - nurbs->num_control_points_v = dimension_v; - nurbs->span_subdivision_u = step_u > 0 ? (uint32_t)step_u : 4u; - nurbs->span_subdivision_v = step_v > 0 ? (uint32_t)step_v : 4u; - - if (!uc->opts.ignore_geometry) { - ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); - ufbxi_value_array *knot_u = ufbxi_find_array(node, ufbxi_KnotVectorU, 'r'); - ufbxi_value_array *knot_v = ufbxi_find_array(node, ufbxi_KnotVectorV, 'r'); - ufbxi_check(points); - ufbxi_check(knot_u); - ufbxi_check(knot_v); - ufbxi_check(points->size % 4 == 0); - ufbxi_check(points->size / 4 == (size_t)dimension_u * (size_t)dimension_v); - - nurbs->control_points.count = points->size / 4; - nurbs->control_points.data = (ufbx_vec4*)points->data; - nurbs->basis_u.knot_vector.data = (ufbx_real*)knot_u->data; - nurbs->basis_u.knot_vector.count = knot_u->size; - nurbs->basis_v.knot_vector.data = (ufbx_real*)knot_v->data; - nurbs->basis_v.knot_vector.count = knot_v->size; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_line(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_line_curve *line = ufbxi_push_element(uc, info, ufbx_line_curve, UFBX_ELEMENT_LINE_CURVE); - ufbxi_check(line); - - if (!uc->opts.ignore_geometry) { - ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r'); - ufbxi_value_array *points_index = ufbxi_find_array(node, ufbxi_PointsIndex, 'i'); - ufbxi_check(points); - ufbxi_check(points_index); - ufbxi_check(points->size % 3 == 0); - - if (points->size > 0) { - line->control_points.count = points->size / 3; - line->control_points.data = (ufbx_vec3*)points->data; - line->point_indices.count = points_index->size; - line->point_indices.data = (uint32_t*)points_index->data; - - ufbxi_check(line->control_points.count < INT32_MAX); - - // Count end points - size_t num_segments = 1; - if (line->point_indices.count > 0) { - for (size_t i = 0; i < line->point_indices.count - 1; i++) { - uint32_t ix = line->point_indices.data[i]; - num_segments += (int32_t)ix < 0 ? 1u : 0u; - } - } - - size_t prev_end = 0; - line->segments.data = ufbxi_push(&uc->result, ufbx_line_segment, num_segments); - ufbxi_check(line->segments.data); - for (size_t i = 0; i < line->point_indices.count; i++) { - uint32_t ix = line->point_indices.data[i]; - if ((int32_t)ix < 0) { - ix = ~ix; - if (i + 1 < line->point_indices.count) { - ufbx_line_segment *segment = &line->segments.data[line->segments.count++]; - segment->index_begin = (uint32_t)prev_end; - segment->num_indices = (uint32_t)(i - prev_end); - prev_end = i; - } - } - - if (ix < line->control_points.count) { - line->point_indices.data[i] = ix; - } else { - ufbxi_check(ufbxi_fix_index(uc, &line->point_indices.data[i], ix, line->control_points.count)); - } - } - - ufbx_line_segment *segment = &line->segments.data[line->segments.count++]; - segment->index_begin = (uint32_t)prev_end; - segment->num_indices = (uint32_t)ufbxi_to_size(line->point_indices.count - prev_end); - ufbx_assert(line->segments.count == num_segments); - } - } - - return 1; -} - -ufbxi_noinline static void ufbxi_read_transform_matrix(ufbx_matrix *m, ufbx_real *data) -{ - m->m00 = data[ 0]; m->m10 = data[ 1]; m->m20 = data[ 2]; - m->m01 = data[ 4]; m->m11 = data[ 5]; m->m21 = data[ 6]; - m->m02 = data[ 8]; m->m12 = data[ 9]; m->m22 = data[10]; - m->m03 = data[12]; m->m13 = data[13]; m->m23 = data[14]; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_bone(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type) -{ - (void)node; - - ufbx_bone *bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE); - ufbxi_check(bone); - - if (sub_type == ufbxi_Root) { - bone->is_root = true; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_marker(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type, ufbx_marker_type type) -{ - (void)node; - (void)sub_type; - - ufbx_marker *marker = ufbxi_push_element(uc, info, ufbx_marker, UFBX_ELEMENT_MARKER); - ufbxi_check(marker); - - marker->type = type; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_skin_deformer *skin = ufbxi_push_element(uc, info, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER); - ufbxi_check(skin); - - const char *skinning_type = NULL; - if (ufbxi_find_val1(node, ufbxi_SkinningType, "C", (char**)&skinning_type)) { - if (!strcmp(skinning_type, "Rigid")) { - skin->skinning_method = UFBX_SKINNING_METHOD_RIGID; - } else if (!strcmp(skinning_type, "Linear")) { - skin->skinning_method = UFBX_SKINNING_METHOD_LINEAR; - } else if (!strcmp(skinning_type, "DualQuaternion")) { - skin->skinning_method = UFBX_SKINNING_METHOD_DUAL_QUATERNION; - } else if (!strcmp(skinning_type, "Blend")) { - skin->skinning_method = UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR; - } - } - - ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); - ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_BlendWeights, 'r'); - if (indices && weights) { - // TODO strict: ufbxi_check(indices->size == weights->size); - skin->num_dq_weights = ufbxi_min_sz(indices->size, weights->size); - skin->dq_vertices.data = (uint32_t*)indices->data; - skin->dq_weights.data = (ufbx_real*)weights->data; - skin->dq_vertices.count = skin->num_dq_weights; - skin->dq_weights.count = skin->num_dq_weights; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin_cluster(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_skin_cluster *cluster = ufbxi_push_element(uc, info, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER); - ufbxi_check(cluster); - - ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); - ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r'); - - if (indices && weights) { - ufbxi_check(indices->size == weights->size); - cluster->num_weights = indices->size; - cluster->vertices.data = (uint32_t*)indices->data; - cluster->weights.data = (ufbx_real*)weights->data; - cluster->vertices.count = cluster->num_weights; - cluster->weights.count = cluster->num_weights; - } - - ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r'); - ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r'); - if (transform && transform_link) { - ufbxi_check(transform->size >= 16); - ufbxi_check(transform_link->size >= 16); - - ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data); - ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_blend_channel(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_blend_channel *channel = ufbxi_push_element(uc, info, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL); - ufbxi_check(channel); - - ufbx_real_list list = { NULL, 0 }; - ufbxi_value_array *full_weights = ufbxi_find_array(node, ufbxi_FullWeights, 'r'); - if (full_weights) { - list.data = (ufbx_real*)full_weights->data; - list.count = full_weights->size; - } - ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &list)); - - // Blender saves blend shapes with DeformPercent as a field, not a property. - // However, the animations are mapped to the DeformPercent property. - ufbxi_node *deform_percent = ufbxi_find_child(node, ufbxi_DeformPercent); - if (channel->props.props.count == 0 && deform_percent) { - size_t num_shape_props = 1; - ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props); - ufbxi_check(shape_props); - shape_props[0].name.data = ufbxi_DeformPercent; - shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1; - shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent); - shape_props[0].type = UFBX_PROP_NUMBER; - shape_props[0].value_str = ufbx_empty_string; - shape_props[0].value_real = 100.0f; - ufbxi_ignore(ufbxi_get_val1(deform_percent, "R", &shape_props[0].value_real)); - channel->props.props.data = shape_props; - channel->props.props.count = num_shape_props; - } - - return 1; -} - -typedef enum { - UFBXI_KEY_INTERPOLATION_CONSTANT = 0x2, - UFBXI_KEY_INTERPOLATION_LINEAR = 0x4, - UFBXI_KEY_INTERPOLATION_CUBIC = 0x8, - UFBXI_KEY_TANGENT_AUTO = 0x100, - UFBXI_KEY_TANGENT_TCB = 0x200, - UFBXI_KEY_TANGENT_USER = 0x400, - UFBXI_KEY_TANGENT_BROKEN = 0x800, - UFBXI_KEY_CONSTANT_NEXT = 0x100, - UFBXI_KEY_CLAMP = 0x1000, - UFBXI_KEY_TIME_INDEPENDENT = 0x2000, - UFBXI_KEY_CLAMP_PROGRESSIVE = 0x4000, - UFBXI_KEY_WEIGHTED_RIGHT = 0x1000000, - UFBXI_KEY_WEIGHTED_NEXT_LEFT = 0x2000000, - UFBXI_KEY_VELOCITY_RIGHT = 0x10000000, - UFBXI_KEY_VELOCITY_NEXT_LEFT = 0x20000000, -} ufbxi_key_flags; - -static ufbxi_noinline float ufbxi_solve_auto_tangent(ufbxi_context *uc, double prev_time, double time, double next_time, ufbx_real prev_value, ufbx_real value, ufbx_real next_value, float weight_left, float weight_right, float auto_bias, uint32_t flags) -{ - // Clamp tangent to zero if near either left or right key - if (flags & UFBXI_KEY_CLAMP) { - if (ufbx_fmin(ufbx_fabs(prev_value - value), ufbx_fabs(next_value - value)) <= uc->opts.key_clamp_threshold) { - return 0.0f; - } - } - - // Time-independent: Set the initial slope to be the difference between the two keyframes. - double slope = (next_value - prev_value) / (next_time - prev_time); - - // Non-time-independent tangents seem to blend between left/right tangent and the total difference. - if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { - double slope_left = (value - prev_value) / (time - prev_time); - double slope_right = (next_value - value) / (next_time - time); - double delta = (time - prev_time) / (next_time - prev_time); - slope = slope * 0.5 + (slope_left * (1.0 - delta) + slope_right * delta) * 0.5; - - double bias_weight = ufbx_fabs(auto_bias) / 100.0; - if (bias_weight > 0.0001) { - double bias_target = auto_bias > 0.0 ? slope_right : slope_left; - double bias_delta = bias_target - slope; - slope = slope * (1.0 - bias_weight) + bias_target * bias_weight; - - // Auto bias larger than 500 (positive or negative) adds an absolute - // value to the slope, determined by `((bias-500) / 100)^2 * 40`. - double abs_bias_weight = bias_weight - 5.0; - if (abs_bias_weight > 0.0) { - double bias_sign = ufbx_fabs(bias_delta) > 0.00001 ? bias_delta : auto_bias; - bias_sign = bias_sign > 0.0 ? 1.0 : -1.0; - slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; - } - } - } - - // Prevent overshooting by clamping the slope in case either - // tangent goes above/below the endpoints. - if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) { - // Split the slope to sign and a non-negative absolute value - double slope_sign = slope >= 0.0 ? 1.0 : -1.0; - double abs_slope = slope_sign * slope; - - // Find limits for the absolute value of the slope - double range_left = weight_left * (time - prev_time); - double range_right = weight_right * (next_time - time); - double max_left = range_left > 0.0 ? slope_sign * (value - prev_value) / range_left : 0.0; - double max_right = range_right > 0.0 ? slope_sign * (next_value - value) / range_right : 0.0; - - // Clamp negative values and NaNs to zero - if (!(max_left > 0.0)) max_left = 0.0; - if (!(max_right > 0.0)) max_right = 0.0; - - // Clamp the absolute slope from both sides - if (abs_slope > max_left) abs_slope = max_left; - if (abs_slope > max_right) abs_slope = max_right; - - slope = (slope_sign * abs_slope); - } - - return (float)slope; -} - -static float ufbxi_solve_auto_tangent_left(ufbxi_context *uc, double prev_time, double time, ufbx_real prev_value, ufbx_real value, float weight_left, float auto_bias, uint32_t flags) -{ - (void)weight_left; - if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f; - if (flags & UFBXI_KEY_CLAMP) { - if (ufbx_fabs(prev_value - value) <= uc->opts.key_clamp_threshold) { - return 0.0f; - } - } - - double slope = (value - prev_value) / (time - prev_time); - - if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { - double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0; - if (abs_bias_weight > 0.0) { - double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0; - slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; - } - } - - return (float)slope; -} - -static float ufbxi_solve_auto_tangent_right(ufbxi_context *uc, double time, double next_time, ufbx_real value, ufbx_real next_value, float weight_right, float auto_bias, uint32_t flags) -{ - (void)weight_right; - if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f; - if (flags & UFBXI_KEY_CLAMP) { - if (ufbx_fabs(next_value - value) <= uc->opts.key_clamp_threshold) { - return 0.0f; - } - } - - double slope = (next_value - value) / (next_time - time); - - if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) { - double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0; - if (abs_bias_weight > 0.0) { - double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0; - slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0; - } - } - - return (float)slope; -} - -static void ufbxi_solve_tcb(float *p_slope_left, float *p_slope_right, double tension, double continuity, double bias, double slope_left, double slope_right, bool edge) -{ - double factor = edge ? 1.0 : 0.5; - double d00 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 - continuity); - double d01 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 + continuity); - double d10 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 + continuity); - double d11 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 - continuity); - - *p_slope_left = (float)(d00 * slope_left + d01 * slope_right); - *p_slope_right = (float)(d10 * slope_left + d11 * slope_right); -} - -ufbxi_noinline static void ufbxi_read_extrapolation(ufbx_extrapolation *p_extrapolation, ufbxi_node *node, const char *name) -{ - ufbxi_node *child = ufbxi_find_child(node, name); - ufbx_extrapolation_mode mode = UFBX_EXTRAPOLATION_CONSTANT; - int32_t repeat_count = -1; - - if (child) { - int32_t mode_ch; - if (ufbxi_find_val1(child, ufbxi_Type, "I", &mode_ch)) { - - switch (mode_ch) { - case 'A': mode = UFBX_EXTRAPOLATION_REPEAT_RELATIVE; break; - case 'C': mode = UFBX_EXTRAPOLATION_CONSTANT; break; - case 'K': mode = UFBX_EXTRAPOLATION_SLOPE; break; - case 'M': mode = UFBX_EXTRAPOLATION_MIRROR; break; - case 'R': mode = UFBX_EXTRAPOLATION_REPEAT; break; - default: /* Unknown */ break; - } - if (ufbxi_find_val1(child, ufbxi_Repetition, "I", &repeat_count)) { - if (repeat_count < 0) { - repeat_count = -1; - } - } - } - } - - p_extrapolation->mode = mode; - p_extrapolation->repeat_count = repeat_count; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_animation_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_anim_curve *curve = ufbxi_push_element(uc, info, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE); - ufbxi_check(curve); - - ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); - ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); - - if (uc->opts.ignore_animation) return 1; - - ufbxi_value_array *times, *values, *attr_flags, *attrs, *refs; - ufbxi_check(times = ufbxi_find_array(node, ufbxi_KeyTime, 'l')); - ufbxi_check(values = ufbxi_find_array(node, ufbxi_KeyValueFloat, 'r')); - ufbxi_check(attr_flags = ufbxi_find_array(node, ufbxi_KeyAttrFlags, 'i')); - ufbxi_check(attrs = ufbxi_find_array(node, ufbxi_KeyAttrDataFloat, '?')); - ufbxi_check(refs = ufbxi_find_array(node, ufbxi_KeyAttrRefCount, 'i')); - - // Time and value arrays that define the keyframes should be parallel - ufbxi_check(times->size == values->size); - - // Flags and attributes are run-length encoded where KeyAttrRefCount (refs) - // is an array that describes how many times to repeat a given flag/attribute. - // Attributes consist of 4 32-bit floating point values per key. - ufbxi_check(attr_flags->size == refs->size); - ufbxi_check(attrs->size == refs->size * 4u); - - size_t num_keys = times->size; - ufbx_keyframe *keys = ufbxi_push(&uc->result, ufbx_keyframe, num_keys); - ufbxi_check(keys); - - curve->keyframes.data = keys; - curve->keyframes.count = num_keys; - - int64_t *p_time = (int64_t*)times->data; - ufbx_real *p_value = (ufbx_real*)values->data; - int32_t *p_flag = (int32_t*)attr_flags->data; - float *p_attr = (float*)attrs->data; - int32_t *p_ref = (int32_t*)refs->data, *p_ref_end = p_ref + refs->size; - - // The previous key defines the weight/slope of the left tangent - float slope_left = 0.0f; - float weight_left = 0.333333f; - // float velocity_left = 0.0f; - - double prev_time = 0.0; - double next_time = 0.0; - - int32_t refs_left = 0; - if (num_keys > 0) { - next_time = (double)p_time[0] / uc->ktime_sec_double; - if (p_ref < p_ref_end) refs_left = *p_ref; - } - - for (size_t i = 0; i < num_keys; i++) { - ufbx_keyframe *key = &keys[i]; - ufbxi_check(refs_left > 0); - - ufbx_real value = *p_value; - if (i == 0) { - curve->min_value = value; - curve->max_value = value; - } else { - curve->min_value = ufbxi_min_real(curve->min_value, value); - curve->max_value = ufbxi_max_real(curve->max_value, value); - } - - key->time = next_time; - key->value = value; - - if (i + 1 < num_keys) { - next_time = (double)p_time[1] / uc->ktime_sec_double; - } - - uint32_t flags = (uint32_t)*p_flag; - - float slope_right = p_attr[0]; - float weight_right = 0.333333f; - //float velocity_right = 0.0f; - float next_slope_left = p_attr[1]; - float next_weight_left = 0.333333f; - // float next_velocity_left = 0.0f; - - if ((flags & (UFBXI_KEY_WEIGHTED_RIGHT|UFBXI_KEY_WEIGHTED_NEXT_LEFT)) != 0) { - // At least one of the tangents is weighted. The weights are encoded as - // two 0.4 _decimal_ fixed point values that are packed into 32 bits and - // interpreted as a 32-bit float. - uint32_t packed_weights; - memcpy(&packed_weights, &p_attr[2], sizeof(uint32_t)); - - if (flags & UFBXI_KEY_WEIGHTED_RIGHT) { - // Right tangent is weighted - weight_right = (float)(packed_weights & 0xffff) * 0.0001f; - } - - if (flags & UFBXI_KEY_WEIGHTED_NEXT_LEFT) { - // Next left tangent is weighted - next_weight_left = (float)(packed_weights >> 16) * 0.0001f; - } - } -#if 0 - if ((flags & (UFBXI_KEY_VELOCITY_RIGHT|UFBXI_KEY_VELOCITY_NEXT_LEFT)) != 0) { - // Velocities are encoded in the same way as weights, see above. - uint32_t packed_velocities; - memcpy(&packed_velocities, &p_attr[3], sizeof(uint32_t)); - - if (flags & UFBXI_KEY_VELOCITY_RIGHT) { - // Right tangent has velocity - velocity_right = (float)(int16_t)(packed_velocities & 0xffff) * 0.0001f; - } - - if (flags & UFBXI_KEY_VELOCITY_NEXT_LEFT) { - // Next left tangent has velocity - next_velocity_left = (float)(int16_t)(packed_velocities >> 16) * 0.0001f; - } - } -#endif - - if (flags & UFBXI_KEY_INTERPOLATION_CONSTANT) { - // Constant interpolation: Set cubic tangents to flat. - - if (flags & UFBXI_KEY_CONSTANT_NEXT) { - // Take constant value from next key - key->interpolation = UFBX_INTERPOLATION_CONSTANT_NEXT; - - } else { - // Take constant value from the previous key - key->interpolation = UFBX_INTERPOLATION_CONSTANT_PREV; - } - - weight_right = next_weight_left = 0.333333f; - slope_right = next_slope_left = 0.0f; - - } else if (flags & UFBXI_KEY_INTERPOLATION_CUBIC) { - // Cubic interpolation - key->interpolation = UFBX_INTERPOLATION_CUBIC; - - if (flags & UFBXI_KEY_TANGENT_TCB) { - double tcb_slope_left = 0.0; - double tcb_slope_right = 0.0; - bool tcb_edge = false; - if (i > 0 && key->time > prev_time) { - tcb_slope_left = (key->value - p_value[-1]) / (key->time - prev_time); - } else { - tcb_edge = true; - } - if (i + 1 < num_keys && next_time > key->time) { - tcb_slope_right = (p_value[1] - key->value) / (next_time - key->time); - } else { - tcb_edge = true; - } - - ufbxi_solve_tcb(&slope_left, &slope_right, p_attr[0], p_attr[1], p_attr[2], tcb_slope_left, tcb_slope_right, tcb_edge); - - // TODO: How to handle these? - next_slope_left = 0.0f; - next_weight_left = 0.333333f; - // next_velocity_left = 0.0f; - } else if (flags & UFBXI_KEY_TANGENT_USER) { - // User tangents - - if (flags & UFBXI_KEY_TANGENT_BROKEN) { - // Broken tangents: No need to modify slopes - } else { - // Unified tangents: Use right slope for both sides - // TODO: ??? slope_left = slope_right; - } - - } else { - // TODO: Auto break (0x800) - - if (i > 0 && i + 1 < num_keys && key->time > prev_time && next_time > key->time) { - if (ufbx_fabs(slope_left + slope_right) <= 0.0001f) { - slope_left = slope_right = ufbxi_solve_auto_tangent(uc, - prev_time, key->time, next_time, - p_value[-1], key->value, p_value[1], - weight_left, weight_right, slope_right, flags); - } else { - slope_left = ufbxi_solve_auto_tangent(uc, - prev_time, key->time, next_time, - p_value[-1], key->value, p_value[1], - weight_left, weight_right, -slope_left, flags); - slope_right = ufbxi_solve_auto_tangent(uc, - prev_time, key->time, next_time, - p_value[-1], key->value, p_value[1], - weight_left, weight_right, slope_right, flags); - } - } else if (i > 0 && key->time > prev_time) { - slope_left = slope_right = ufbxi_solve_auto_tangent_left(uc, - prev_time, key->time, - p_value[-1], key->value, - weight_left, -slope_left, flags); - } else if (i + 1 < num_keys && next_time > key->time) { - slope_left = slope_right = ufbxi_solve_auto_tangent_right(uc, - key->time, next_time, - key->value, p_value[1], - weight_right, slope_right, flags); - } else { - // Only / invalid keyframe: Set both slopes to zero - slope_left = slope_right = 0.0f; - } - - - // ??? Looks like at least MotionBuilder adjusts weight and auto bias to - // implement velocity and the velocity information in the file is purely - // for UI (?) If auto bias is not accounted for the velocity computation - // below results in the correct tangents, but with auto bias the velocity - // seems to be accounted for twice resulting in incorrect values... -#if 0 - if (weight_left >= UFBX_EPSILON) { - slope_left *= (float)(1.0 - ufbx_fmin(velocity_left / weight_left, 1.0)); - } - if (weight_right >= UFBX_EPSILON) { - slope_right *= (float)(1.0 - ufbx_fmin(velocity_right / weight_right, 1.0)); - } -#endif - } - - } else { - // Linear or unknown interpolation: Set cubic tangents to match - // the linear interpolation with weights of 1/3. - key->interpolation = UFBX_INTERPOLATION_LINEAR; - - weight_right = 0.333333f; - next_weight_left = 0.333333f; - - if (next_time > key->time) { - double delta_time = next_time - key->time; - if (delta_time > 0.0) { - double slope = (p_value[1] - key->value) / delta_time; - slope_right = next_slope_left = (float)slope; - } else { - slope_right = next_slope_left = 0.0f; - } - } else { - slope_right = next_slope_left = 0.0f; - } - } - - // Set the tangents based on weights (dx relative to the time difference - // between the previous/next key) and slope (simply d = slope * dx) - if (key->time > prev_time) { - double delta = key->time - prev_time; - key->left.dx = (float)(weight_left * delta); - key->left.dy = key->left.dx * slope_left; - } else { - key->left.dx = 0.0f; - key->left.dy = 0.0f; - } - - if (next_time > key->time) { - double delta = next_time - key->time; - key->right.dx = (float)(weight_right * delta); - key->right.dy = key->right.dx * slope_right; - } else { - key->right.dx = 0.0f; - key->right.dy = 0.0f; - } - - slope_left = next_slope_left; - weight_left = next_weight_left; - // velocity_left = next_velocity_left; - prev_time = key->time; - - // Decrement attribute refcount and potentially move to the next one. - if (--refs_left == 0) { - p_flag++; - p_attr += 4; - p_ref++; - if (p_ref < p_ref_end) refs_left = *p_ref; - } - p_time++; - p_value++; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_material(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_material *material = ufbxi_push_element(uc, info, ufbx_material, UFBX_ELEMENT_MATERIAL); - ufbxi_check(material); - - if (!ufbxi_find_val1(node, ufbxi_ShadingModel, "S", &material->shading_model_name)) { - material->shading_model_name = ufbx_empty_string; - } - - material->shader_prop_prefix = ufbx_empty_string; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE); - ufbxi_check(texture); - - texture->type = UFBX_TEXTURE_FILE; - - texture->filename = ufbx_empty_string; - texture->absolute_filename = ufbx_empty_string; - texture->relative_filename = ufbx_empty_string; - - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &texture->absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &texture->absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &texture->relative_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &texture->relative_filename)); - - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &texture->raw_absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &texture->raw_absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &texture->raw_relative_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &texture->raw_relative_filename)); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_layered_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE); - ufbxi_check(texture); - - texture->type = UFBX_TEXTURE_LAYERED; - - texture->filename = ufbx_empty_string; - texture->absolute_filename = ufbx_empty_string; - texture->relative_filename = ufbx_empty_string; - - ufbxi_texture_extra *extra = ufbxi_push_element_extra(uc, texture->element.element_id, ufbxi_texture_extra); - ufbxi_check(extra); - - ufbxi_value_array *alphas = ufbxi_find_array(node, ufbxi_Alphas, 'r'); - if (alphas) { - extra->alphas = (ufbx_real*)alphas->data; - extra->num_alphas = alphas->size; - } - - ufbxi_value_array *blend_modes = ufbxi_find_array(node, ufbxi_BlendModes, 'i'); - if (blend_modes) { - extra->blend_modes = (int32_t*)blend_modes->data; - extra->num_blend_modes = blend_modes->size; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_video(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_video *video = ufbxi_push_element(uc, info, ufbx_video, UFBX_ELEMENT_VIDEO); - ufbxi_check(video); - - video->filename = ufbx_empty_string; - video->absolute_filename = ufbx_empty_string; - video->relative_filename = ufbx_empty_string; - - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &video->absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &video->absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &video->relative_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &video->relative_filename)); - - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &video->raw_absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &video->raw_absolute_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &video->raw_relative_filename)); - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &video->raw_relative_filename)); - - ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content); - ufbxi_check(ufbxi_read_embedded_blob(uc, &video->content, content_node)); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_anim_stack(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - (void)node; - - ufbx_anim_stack *stack = ufbxi_push_element(uc, info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); - ufbxi_check(stack); - - uint32_t hash = ufbxi_hash_ptr(info->name.data); - ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data); - if (!entry) { - entry = ufbxi_map_insert(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data); - ufbxi_check(entry); - entry->name = info->name.data; - entry->stack = stack; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_pose(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type) -{ - ufbx_pose *pose = ufbxi_push_element(uc, info, ufbx_pose, UFBX_ELEMENT_POSE); - ufbxi_check(pose); - - // TODO: What are the actual other types? - pose->is_bind_pose = sub_type == ufbxi_BindPose; - - size_t num_bones = 0; - ufbxi_for(ufbxi_node, n, node->children, node->num_children) { - if (n->name != ufbxi_PoseNode) continue; - - // Bones are linked with FBX names/IDs bypassing the connection system (!?) - uint64_t fbx_id = 0; - if (uc->version < 7000) { - char *name = NULL; - if (!ufbxi_find_val1(n, ufbxi_Node, "c", &name)) continue; - fbx_id = ufbxi_synthetic_id_from_string(uc, name); - ufbxi_check(fbx_id); - } else { - if (!ufbxi_find_val1(n, ufbxi_Node, "L", &fbx_id)) continue; - ufbxi_check(ufbxi_validate_fbx_id(uc, &fbx_id)); - } - - ufbxi_value_array *matrix = ufbxi_find_array(n, ufbxi_Matrix, 'r'); - if (!matrix) continue; - ufbxi_check(matrix->size >= 16); - - ufbxi_tmp_bone_pose *tmp_pose = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_bone_pose, 1); - ufbxi_check(tmp_pose); - - num_bones++; - tmp_pose->bone_fbx_id = fbx_id; - ufbxi_read_transform_matrix(&tmp_pose->bone_to_world, (ufbx_real*)matrix->data); - } - - // HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer - pose->bone_poses.count = num_bones; - pose->bone_poses.data = (ufbx_bone_pose*)ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_bone_pose, num_bones); - ufbxi_check(pose->bone_poses.data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_shader_prop_bindings(ufbxi_context *uc, ufbx_shader_prop_binding *bindings, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_shader_prop_binding))); - ufbxi_macro_stable_sort(ufbx_shader_prop_binding, 32, bindings, uc->tmp_arr, count, - ( ufbxi_str_less(a->shader_prop, b->shader_prop) ) ); - return 1; -} - - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_binding_table(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_shader_binding *bindings = ufbxi_push_element(uc, info, ufbx_shader_binding, UFBX_ELEMENT_SHADER_BINDING); - ufbxi_check(bindings); - - size_t num_entries = 0; - ufbxi_for (ufbxi_node, n, node->children, node->num_children) { - if (n->name != ufbxi_Entry) continue; - - ufbx_string src, dst; - const char *src_type = NULL, *dst_type = NULL; - if (!ufbxi_get_val4(n, "SCSC", &src, (char**)&src_type, &dst, (char**)&dst_type)) { - continue; - } - - if (src_type == ufbxi_FbxPropertyEntry && dst_type == ufbxi_FbxSemanticEntry) { - ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1); - ufbxi_check(bind); - bind->material_prop = src; - bind->shader_prop = dst; - num_entries++; - } else if (src_type == ufbxi_FbxSemanticEntry && dst_type == ufbxi_FbxPropertyEntry) { - ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1); - ufbxi_check(bind); - bind->material_prop = dst; - bind->shader_prop = src; - num_entries++; - } - } - - bindings->prop_bindings.count = num_entries; - bindings->prop_bindings.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_shader_prop_binding, num_entries); - ufbxi_check(bindings->prop_bindings.data); - - ufbxi_check(ufbxi_sort_shader_prop_bindings(uc, bindings->prop_bindings.data, bindings->prop_bindings.count)); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_set(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - (void)node; - - ufbx_selection_set *set = ufbxi_push_element(uc, info, ufbx_selection_set, UFBX_ELEMENT_SELECTION_SET); - ufbxi_check(set); - - return 1; -} - -ufbxi_noinline static void ufbxi_find_uint32_list(ufbx_uint32_list *dst, ufbxi_node *node, const char *name) -{ - ufbxi_value_array *arr = ufbxi_find_array(node, name, 'i'); - if (arr) { - dst->data = (uint32_t*)arr->data; - dst->count = arr->size; - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_selection_node *sel = ufbxi_push_element(uc, info, ufbx_selection_node, UFBX_ELEMENT_SELECTION_NODE); - ufbxi_check(sel); - - int32_t in_set = 0; - if (ufbxi_find_val1(node, ufbxi_IsTheNodeInSet, "I", &in_set) && in_set) { - sel->include_node = true; - } - - ufbxi_find_uint32_list(&sel->vertices, node, ufbxi_VertexIndexArray); - ufbxi_find_uint32_list(&sel->edges, node, ufbxi_EdgeIndexArray); - ufbxi_find_uint32_list(&sel->faces, node, ufbxi_PolygonIndexArray); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_character(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - (void)node; - - ufbx_character *character = ufbxi_push_element(uc, info, ufbx_character, UFBX_ELEMENT_CHARACTER); - ufbxi_check(character); - - // TODO: There's some extremely cursed all-caps data in characters - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_audio_clip(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_audio_clip *audio = ufbxi_push_element(uc, info, ufbx_audio_clip, UFBX_ELEMENT_AUDIO_CLIP); - ufbxi_check(audio); - - audio->filename = ufbx_empty_string; - audio->absolute_filename = ufbx_empty_string; - audio->relative_filename = ufbx_empty_string; - - ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content); - ufbxi_check(ufbxi_read_embedded_blob(uc, &audio->content, content_node)); - - return 1; -} - -typedef struct { - ufbx_constraint_type type; - const char *name; -} ufbxi_constraint_type; - -static const ufbxi_constraint_type ufbxi_constraint_types[] = { - { UFBX_CONSTRAINT_AIM, "Aim" }, - { UFBX_CONSTRAINT_PARENT, "Parent-Child" }, - { UFBX_CONSTRAINT_POSITION, "Position From Positions" }, - { UFBX_CONSTRAINT_ROTATION, "Rotation From Rotations" }, - { UFBX_CONSTRAINT_SCALE, "Scale From Scales" }, - { UFBX_CONSTRAINT_SINGLE_CHAIN_IK, "Single Chain IK" }, -}; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_constraint(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - (void)node; - - ufbx_constraint *constraint = ufbxi_push_element(uc, info, ufbx_constraint, UFBX_ELEMENT_CONSTRAINT); - ufbxi_check(constraint); - - if (!ufbxi_find_val1(node, ufbxi_Type, "S", &constraint->type_name)) { - constraint->type_name = ufbx_empty_string; - } - - ufbxi_for(const ufbxi_constraint_type, ctype, ufbxi_constraint_types, ufbxi_arraycount(ufbxi_constraint_types)) { - if (!strcmp(constraint->type_name.data, ctype->name)) { - constraint->type = ctype->type; - break; - } - } - - // TODO: There's some extremely cursed all-caps data in characters - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_attribute(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, ufbx_string type_str, const char *sub_type, const char *super_type) -{ - if ((sub_type == ufbxi_empty_char || sub_type == ufbxi_Model) && type_str.data == ufbxi_Model) { - // Plain model - return 1; - } - - ufbxi_element_info attrib_info = *info; - - attrib_info.fbx_id = ufbxi_push_synthetic_id(uc); - - // Use type and name from NodeAttributeName if it exists *uniquely* - ufbx_string type_and_name; - if (ufbxi_find_val1(node, ufbxi_NodeAttributeName, "s", &type_and_name)) { - ufbx_string attrib_type_str, attrib_name_str; - ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &attrib_type_str, &attrib_name_str)); - if (attrib_name_str.length > 0) { - attrib_info.name = attrib_name_str; - uint64_t attrib_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); - ufbxi_check(attrib_id); - if (info->fbx_id != attrib_id && !ufbxi_fbx_id_exists(uc, attrib_id)) { - attrib_info.fbx_id = attrib_id; - } - } - } - - // 6x00: Link the node to the node attribute so property connections can be - // redirected from connections if necessary. - ufbxi_check(ufbxi_insert_fbx_attr(uc, info->fbx_id, attrib_info.fbx_id)); - - // Split properties between the node and the attribute. - // Consider all user properties as node properties. - ufbx_prop *ps = info->props.props.data; - size_t dst = 0, src = 0, end = info->props.props.count; - while (src < end) { - if (!ufbxi_is_node_property_name(uc, ps[src].name.data) && (ps[src].flags & UFBX_PROP_FLAG_USER_DEFINED) == 0) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, 1, &ps[src])); - src++; - } else if (dst != src) { - ps[dst++] = ps[src++]; - } else { - dst++; src++; - } - } - attrib_info.props.props.count = end - dst; - attrib_info.props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, attrib_info.props.props.count); - ufbxi_check(attrib_info.props.props.data); - info->props.props.count = dst; - - if (sub_type == ufbxi_Mesh) { - ufbxi_check(ufbxi_read_mesh(uc, node, &attrib_info)); - } else if (sub_type == ufbxi_Light) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT)); - } else if (sub_type == ufbxi_Camera) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA)); - } else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) { - ufbxi_check(ufbxi_read_bone(uc, node, &attrib_info, sub_type)); - } else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY)); - } else if (sub_type == ufbxi_NurbsCurve) { - if (!ufbxi_find_child(node, ufbxi_KnotVector)) return 1; - ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &attrib_info)); - } else if (sub_type == ufbxi_NurbsSurface) { - if (!ufbxi_find_child(node, ufbxi_KnotVectorU)) return 1; - if (!ufbxi_find_child(node, ufbxi_KnotVectorV)) return 1; - ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &attrib_info)); - } else if (sub_type == ufbxi_Line) { - if (!ufbxi_find_child(node, ufbxi_Points)) return 1; - if (!ufbxi_find_child(node, ufbxi_PointsIndex)) return 1; - ufbxi_check(ufbxi_read_line(uc, node, &attrib_info)); - } else if (sub_type == ufbxi_TrimNurbsSurface) { - if (!ufbxi_find_child(node, ufbxi_Layer)) return 1; - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE)); - } else if (sub_type == ufbxi_Boundary) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY)); - } else if (sub_type == ufbxi_CameraStereo) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA)); - } else if (sub_type == ufbxi_CameraSwitcher) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER)); - } else if (sub_type == ufbxi_FKEffector) { - ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_FK_EFFECTOR)); - } else if (sub_type == ufbxi_IKEffector) { - ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_IK_EFFECTOR)); - } else if (sub_type == ufbxi_LodGroup) { - ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP)); - } else { - ufbx_string sub_type_str = { sub_type, strlen(sub_type) }; - ufbxi_check(ufbxi_read_unknown(uc, node, &attrib_info, type_str, sub_type_str, super_type)); - } - - ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info->fbx_id)); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_global_settings(ufbxi_context *uc, ufbxi_node *node) -{ - ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.settings.props)); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_object(ufbxi_context *uc, ufbxi_node *node) -{ - ufbxi_element_info info = { 0 }; - info.dom_node = ufbxi_get_dom_node(uc, node); - - if (node->name == ufbxi_GlobalSettings) { - ufbxi_check(ufbxi_read_global_settings(uc, node)); - return 1; - } - - ufbx_string type_and_name, sub_type_str; - - // Failing to parse the object properties is not an error since - // there's some weird objects mixed in every now and then. - // FBX version 7000 and up uses 64-bit unique IDs per object, - // older FBX versions just use name/type pairs, which we can - // use as IDs since all strings are interned into a string pool. - if (uc->version >= 7000) { - if (!ufbxi_get_val3(node, "Lss", &info.fbx_id, &type_and_name, &sub_type_str)) return 1; - ufbxi_check(ufbxi_validate_fbx_id(uc, &info.fbx_id)); - } else { - if (!ufbxi_get_val2(node, "ss", &type_and_name, &sub_type_str)) return 1; - info.fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); - ufbxi_check(info.fbx_id); - } - - // Remove the "Fbx" prefix from sub-types, remember to re-intern! - if (sub_type_str.length > 3 && !memcmp(sub_type_str.data, "Fbx", 3)) { - sub_type_str.data += 3; - sub_type_str.length -= 3; - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &sub_type_str, false)); - } - - ufbx_string type_str; - ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type_str, &info.name)); - - const char *name = node->name, *sub_type = sub_type_str.data; - ufbxi_check(ufbxi_read_properties(uc, node, &info.props)); - info.props.defaults = ufbxi_find_template(uc, name, sub_type); - - if (name == ufbxi_Model) { - if (uc->version < 7000) { - ufbxi_check(ufbxi_read_synthetic_attribute(uc, node, &info, type_str, sub_type, name)); - } - ufbxi_check(ufbxi_read_model(uc, node, &info)); - } else if (name == ufbxi_NodeAttribute) { - if (sub_type == ufbxi_Light) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT)); - } else if (sub_type == ufbxi_Camera) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA)); - } else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) { - ufbxi_check(ufbxi_read_bone(uc, node, &info, sub_type)); - } else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY)); - } else if (sub_type == ufbxi_CameraStereo) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA)); - } else if (sub_type == ufbxi_CameraSwitcher) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER)); - } else if (sub_type == ufbxi_FKEffector) { - ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_FK_EFFECTOR)); - } else if (sub_type == ufbxi_IKEffector) { - ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_IK_EFFECTOR)); - } else if (sub_type == ufbxi_LodGroup) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP)); - } else { - ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); - } - } else if (name == ufbxi_Geometry) { - if (sub_type == ufbxi_Mesh) { - ufbxi_check(ufbxi_read_mesh(uc, node, &info)); - } else if (sub_type == ufbxi_Shape) { - ufbxi_check(ufbxi_read_shape(uc, node, &info)); - } else if (sub_type == ufbxi_NurbsCurve) { - ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &info)); - } else if (sub_type == ufbxi_NurbsSurface) { - ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &info)); - } else if (sub_type == ufbxi_Line) { - ufbxi_check(ufbxi_read_line(uc, node, &info)); - } else if (sub_type == ufbxi_TrimNurbsSurface) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE)); - } else if (sub_type == ufbxi_Boundary) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY)); - } else { - ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); - } - } else if (name == ufbxi_Deformer) { - if (sub_type == ufbxi_Skin) { - ufbxi_check(ufbxi_read_skin(uc, node, &info)); - } else if (sub_type == ufbxi_Cluster) { - ufbxi_check(ufbxi_read_skin_cluster(uc, node, &info)); - } else if (sub_type == ufbxi_BlendShape) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_blend_deformer), UFBX_ELEMENT_BLEND_DEFORMER)); - } else if (sub_type == ufbxi_BlendShapeChannel) { - ufbxi_check(ufbxi_read_blend_channel(uc, node, &info)); - } else if (sub_type == ufbxi_VertexCacheDeformer) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_deformer), UFBX_ELEMENT_CACHE_DEFORMER)); - } else { - ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); - } - } else if (name == ufbxi_Material) { - ufbxi_check(ufbxi_read_material(uc, node, &info)); - } else if (name == ufbxi_Texture) { - ufbxi_check(ufbxi_read_texture(uc, node, &info)); - } else if (name == ufbxi_LayeredTexture) { - ufbxi_check(ufbxi_read_layered_texture(uc, node, &info)); - } else if (name == ufbxi_Video) { - ufbxi_check(ufbxi_read_video(uc, node, &info)); - } else if (name == ufbxi_AnimationStack) { - ufbxi_check(ufbxi_read_anim_stack(uc, node, &info)); - } else if (name == ufbxi_AnimationLayer) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_layer), UFBX_ELEMENT_ANIM_LAYER)); - } else if (name == ufbxi_AnimationCurveNode) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_value), UFBX_ELEMENT_ANIM_VALUE)); - } else if (name == ufbxi_AnimationCurve) { - ufbxi_check(ufbxi_read_animation_curve(uc, node, &info)); - } else if (name == ufbxi_Pose) { - ufbxi_check(ufbxi_read_pose(uc, node, &info, sub_type)); - } else if (name == ufbxi_Implementation) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_shader), UFBX_ELEMENT_SHADER)); - } else if (name == ufbxi_BindingTable) { - ufbxi_check(ufbxi_read_binding_table(uc, node, &info)); - } else if (name == ufbxi_Collection) { - if (sub_type == ufbxi_SelectionSet) { - ufbxi_check(ufbxi_read_selection_set(uc, node, &info)); - } - } else if (name == ufbxi_CollectionExclusive) { - if (sub_type == ufbxi_DisplayLayer) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_display_layer), UFBX_ELEMENT_DISPLAY_LAYER)); - } - } else if (name == ufbxi_SelectionNode) { - ufbxi_check(ufbxi_read_selection_node(uc, node, &info)); - } else if (name == ufbxi_Constraint) { - if (sub_type == ufbxi_Character) { - ufbxi_check(ufbxi_read_character(uc, node, &info)); - } else { - ufbxi_check(ufbxi_read_constraint(uc, node, &info)); - } - } else if (name == ufbxi_SceneInfo) { - ufbxi_check(ufbxi_read_scene_info(uc, node)); - } else if (name == ufbxi_Cache) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_file), UFBX_ELEMENT_CACHE_FILE)); - } else if (name == ufbxi_ObjectMetaData) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_metadata_object), UFBX_ELEMENT_METADATA_OBJECT)); - } else if (name == ufbxi_AudioLayer) { - ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_audio_layer), UFBX_ELEMENT_AUDIO_LAYER)); - } else if (name == ufbxi_Audio) { - ufbxi_check(ufbxi_read_audio_clip(uc, node, &info)); - } else { - ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name)); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects(ufbxi_context *uc) -{ - for (;;) { - // Push a deferred element ID for tagging warnings - uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1); - ufbxi_check(uc->p_element_id); - *uc->p_element_id = UFBX_NO_INDEX; - uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items; - - ufbxi_node *node; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); - if (!node) break; - - ufbxi_check(ufbxi_read_object(uc, node)); - - uc->warnings.deferred_element_id_plus_one = 0; - uc->p_element_id = NULL; - } - - return 1; -} - -typedef struct { - ufbxi_node **nodes; - size_t num_nodes; - uint32_t task_index; -} ufbxi_object_batch; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_context *uc) -{ - uc->parse_threaded = true; - - bool parsed_to_end = false; - ufbxi_object_batch batches[UFBX_THREAD_GROUP_COUNT]; // ufbxi_uninit - memset(batches, 0, sizeof(batches)); - - size_t empty_count = 0; - size_t batch_index = 0; - while (empty_count < UFBX_THREAD_GROUP_COUNT) { - ufbxi_object_batch *batch = &batches[batch_index]; - - ufbxi_check(ufbxi_thread_pool_wait_group(&uc->thread_pool)); - - if (batch->num_nodes > 0) { - ufbxi_for_ptr(ufbxi_node, p_node, batch->nodes, batch->num_nodes) { - ufbxi_buf_clear(&uc->tmp_parse); - - // Push a deferred element ID for tagging warnings - uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1); - ufbxi_check(uc->p_element_id); - *uc->p_element_id = UFBX_NO_INDEX; - uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items; - - ufbxi_check(ufbxi_read_object(uc, *p_node)); - - uc->warnings.deferred_element_id_plus_one = 0; - uc->p_element_id = NULL; - } - batch->num_nodes = 0; - } - - ufbxi_buf *tmp_buf = &uc->tmp_thread_parse[batch_index]; - - // ASCII data may be in `tmp_buf`, so copy it to safety in case - if (uc->ascii.src_buf == tmp_buf) { - ufbxi_ascii *ua = &uc->ascii; - size_t size = ufbxi_to_size(ua->src_end - ua->src); - if (uc->read_buffer_size < size) { - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, size)); - } - memcpy(uc->read_buffer, ua->src, size); - uc->data = uc->data_begin = ua->src = uc->read_buffer; - ua->src_end = uc->read_buffer + size; - ua->src_is_retained = false; - ua->src_buf = NULL; - if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) { - ua->src_yield = ua->src_end; - } else { - ua->src_yield = ua->src + uc->progress_interval; - } - uc->data = ua->src; - } - - ufbxi_buf_clear(tmp_buf); - - if (!parsed_to_end) { - size_t num_nodes = 0; - uint32_t task_start = uc->thread_pool.start_index; - uint32_t max_tasks = uc->thread_pool.num_tasks / UFBX_THREAD_GROUP_COUNT; - max_tasks = ufbxi_min32(max_tasks, ufbxi_thread_pool_available_tasks(&uc->thread_pool)); - size_t max_memory = uc->opts.thread_opts.memory_limit / UFBX_THREAD_GROUP_COUNT; - - for (;;) { - ufbxi_node *node = NULL; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, tmp_buf)); - if (!node) { - parsed_to_end = true; - break; - } - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbxi_node*, 1, &node)); - num_nodes++; - - uint32_t num_tasks = uc->thread_pool.start_index - task_start; - if (num_tasks >= max_tasks) break; - - size_t memory_used = tmp_buf->pushed_size + tmp_buf->pos; - if (memory_used >= max_memory) break; - } - - batch->num_nodes = num_nodes; - batch->nodes = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node*, num_nodes); - ufbxi_check(batch->nodes); - batch->task_index = uc->thread_pool.start_index; - - } - - // Not safe to refer to this buffer anymore - uc->ascii.src_is_retained = false; - - ufbxi_thread_pool_flush_group(&uc->thread_pool); - - if (batch->num_nodes == 0) { - empty_count += 1; - } - - batch_index = (batch_index + 1) % UFBX_THREAD_GROUP_COUNT; - } - - ufbxi_check(ufbxi_thread_pool_wait_all(&uc->thread_pool)); - - uc->parse_threaded = false; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_connections(ufbxi_context *uc) -{ - // Read the connections to the list first - for (;;) { - ufbxi_node *node; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); - if (!node) break; - - char *type; - - uint64_t src_id = 0, dst_id = 0; - ufbx_string src_prop = ufbx_empty_string, dst_prop = ufbx_empty_string; - - if (uc->version < 7000) { - char *src_name = NULL, *dst_name = NULL; - // Pre-7000 versions use Type::Name pairs as identifiers - - if (!ufbxi_get_val1(node, "c", &type)) continue; - - if (type == ufbxi_OO) { - if (!ufbxi_get_val3(node, "_cc", NULL, &src_name, &dst_name)) continue; - } else if (type == ufbxi_OP) { - if (!ufbxi_get_val4(node, "_ccs", NULL, &src_name, &dst_name, &dst_prop)) continue; - } else if (type == ufbxi_PO) { - if (!ufbxi_get_val4(node, "_csc", NULL, &src_name, &src_prop, &dst_name)) continue; - } else if (type == ufbxi_PP) { - if (!ufbxi_get_val5(node, "_cscs", NULL, &src_name, &src_prop, &dst_name, &dst_prop)) continue; - } else { - // TODO: Strict mode? - continue; - } - - if (src_prop.length > 0) { - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &src_prop, false)); - } - if (dst_prop.length > 0) { - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst_prop, false)); - } - - src_id = ufbxi_synthetic_id_from_string(uc, src_name); - dst_id = ufbxi_synthetic_id_from_string(uc, dst_name); - ufbxi_check(src_id && dst_id); - - } else { - // Post-7000 versions use proper unique 64-bit IDs - - if (!ufbxi_get_val1(node, "C", &type)) continue; - - if (type == ufbxi_OO) { - if (!ufbxi_get_val3(node, "_LL", NULL, &src_id, &dst_id)) continue; - } else if (type == ufbxi_OP) { - if (!ufbxi_get_val4(node, "_LLS", NULL, &src_id, &dst_id, &dst_prop)) continue; - } else if (type == ufbxi_PO) { - if (!ufbxi_get_val4(node, "_LSL", NULL, &src_id, &src_prop, &dst_id)) continue; - } else if (type == ufbxi_PP) { - if (!ufbxi_get_val5(node, "_LSLS", NULL, &src_id, &src_prop, &dst_id, &dst_prop)) continue; - } else { - // TODO: Strict mode? - continue; - } - - ufbxi_check(ufbxi_validate_fbx_id(uc, &src_id)); - ufbxi_check(ufbxi_validate_fbx_id(uc, &dst_id)); - } - - ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1); - ufbxi_check(conn); - conn->src = src_id; - conn->dst = dst_id; - conn->src_prop = src_prop; - conn->dst_prop = dst_prop; - } - - return 1; -} - -// -- Pre-7000 "Take" based animation - -ufbxi_forceinline static char ufbxi_double_to_char(double value) -{ - if (value >= 0.0 && value <= 127.0) { - return (char)(int)value; - } else { - return 0; - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_anim_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t value_fbx_id, const char *name, ufbx_real *p_default) -{ - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Default, "R", p_default)); - - // Find the key array, early return with success if not found as we may have only a default - ufbxi_value_array *keys = ufbxi_find_array(node, ufbxi_Key, 'd'); - if (!keys) return 1; - - uint64_t curve_fbx_id = 0; - ufbx_anim_curve *curve = ufbxi_push_synthetic_element(uc, &curve_fbx_id, node, name, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE); - ufbxi_check(curve); - - ufbxi_check(ufbxi_connect_op(uc, curve_fbx_id, value_fbx_id, curve->name)); - - ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); - ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); - - if (uc->opts.ignore_animation) return 1; - - size_t num_keys = 0; - ufbxi_check(ufbxi_find_val1(node, ufbxi_KeyCount, "Z", &num_keys)); - curve->keyframes.data = ufbxi_push(&uc->result, ufbx_keyframe, num_keys); - curve->keyframes.count = num_keys; - ufbxi_check(curve->keyframes.data); - - float slope_left = 0.0f; - float weight_left = 0.333333f; - - double next_time = 0.0; - double next_value = 0.0; - double prev_time = 0.0; - - // The pre-7000 keyframe data is stored as a _heterogenous_ array containing 64-bit integers, - // floating point values, and _bare characters_. We cast all values to double and interpret them. - double *data = (double*)keys->data, *data_end = data + keys->size; - - if (num_keys > 0) { - ufbxi_check(data_end - data >= 2); - next_time = data[0] / uc->ktime_sec_double; - next_value = data[1]; - } - - for (size_t i = 0; i < num_keys; i++) { - ufbx_keyframe *key = &curve->keyframes.data[i]; - - if (i == 0) { - curve->min_value = (ufbx_real)next_value; - curve->max_value = (ufbx_real)next_value; - } else { - curve->min_value = ufbxi_min_real(curve->min_value, (ufbx_real)next_value); - curve->max_value = ufbxi_max_real(curve->max_value, (ufbx_real)next_value); - } - - // First three values: Time, Value, InterpolationMode - ufbxi_check(data_end - data >= 3); - key->time = next_time; - key->value = (ufbx_real)next_value; - char mode = ufbxi_double_to_char(data[2]); - data += 3; - - float slope_right = 0.0f; - float weight_right = 0.333333f; - float next_slope_left = 0.0f; - float next_weight_left = 0.333333f; - bool auto_slope = false; - - if (mode == 'U') { - // Cubic interpolation - key->interpolation = UFBX_INTERPOLATION_CUBIC; - - ufbxi_check(data_end - data >= 1); - char slope_mode = ufbxi_double_to_char(data[0]); - data += 1; - - size_t num_weights = 1; - if (slope_mode == 's' || slope_mode == 'b') { - // Slope mode 's'/'b' (standard? broken?) always have two explicit slopes - // TODO: `b` might actually be some kind of TCB curve - ufbxi_check(data_end - data >= 2); - slope_right = (float)data[0]; - next_slope_left = (float)data[1]; - data += 2; - } else if (slope_mode == 'a') { - // Parameterless slope mode 'a' seems to appear in baked animations. Let's just assume - // automatic tangents for now as they're the least likely to break with - // objectionable artifacts. We need to defer the automatic tangent resolve - // until we have read the next time/value. - // TODO: Solve what this is more throroughly - auto_slope = true; - if (uc->version == 5000) { - num_weights = 0; - } - } else if (slope_mode == 'p') { - // TODO: What is this mode? It seems to have negative values sometimes? - // Also it seems to have _two_ trailing weights values, currently observed: - // `n,n` and `a,X,Y,n`... - // Ignore unknown values for now - ufbxi_check(data_end - data >= 2); - data += 2; - num_weights = 2; - } else if (slope_mode == 't') { - // TODO: What is this mode? It seems that it does not have any weights and the - // third value seems _tiny_ (around 1e-30?) - ufbxi_check(data_end - data >= 3); - data += 3; - num_weights = 0; - } else { - ufbxi_fail("Unknown slope mode"); - } - - for (; num_weights > 0; num_weights--) { - ufbxi_check(data_end - data >= 1); - char weight_mode = ufbxi_double_to_char(data[0]); - data += 1; - - if (weight_mode == 'n') { - // Automatic weights (0.3333...) - } else if (weight_mode == 'a') { - // Manual weights: RightWeight, NextLeftWeight - ufbxi_check(data_end - data >= 2); - weight_right = (float)data[0]; - next_weight_left = (float)data[1]; - data += 2; - } else if (weight_mode == 'l') { - // Next left tangent is weighted - ufbxi_check(data_end - data >= 1); - next_weight_left = (float)data[0]; - data += 1; - } else if (weight_mode == 'r') { - // Right tangent is weighted - ufbxi_check(data_end - data >= 1); - weight_right = (float)data[0]; - data += 1; - } else if (weight_mode == 'c') { - // TODO: What is this mode? At least it has no parameters so let's - // just assume automatic weights for the time being (0.3333...) - } else { - ufbxi_fail("Unknown weight mode"); - } - } - - } else if (mode == 'L') { - // Linear interpolation: No parameters - key->interpolation = UFBX_INTERPOLATION_LINEAR; - } else if (mode == 'C') { - // Constant interpolation: Single parameter (use prev/next) - ufbxi_check(data_end - data >= 1); - key->interpolation = ufbxi_double_to_char(data[0]) == 'n' ? UFBX_INTERPOLATION_CONSTANT_NEXT : UFBX_INTERPOLATION_CONSTANT_PREV; - data += 1; - } else { - ufbxi_fail("Unknown key mode"); - } - - // Retrieve next key and value - if (i + 1 < num_keys) { - ufbxi_check(data_end - data >= 2); - next_time = data[0] / uc->ktime_sec_double; - next_value = data[1]; - } - - if (auto_slope) { - if (i > 0) { - slope_left = slope_right = ufbxi_solve_auto_tangent(uc, - prev_time, key->time, next_time, - key[-1].value, key->value, (ufbx_real)next_value, - weight_left, weight_right, 0.0f, UFBXI_KEY_CLAMP_PROGRESSIVE|UFBXI_KEY_TIME_INDEPENDENT); - } else { - slope_left = slope_right = 0.0f; - } - } - - // Set up linear cubic tangents if necessary - if (key->interpolation == UFBX_INTERPOLATION_LINEAR) { - if (next_time > key->time) { - double slope = (next_value - key->value) / (next_time - key->time); - slope_right = next_slope_left = (float)slope; - } else { - slope_right = next_slope_left = 0.0f; - } - } - - if (key->time > prev_time) { - double delta = key->time - prev_time; - key->left.dx = (float)(weight_left * delta); - key->left.dy = key->left.dx * slope_left; - } else { - key->left.dx = 0.0f; - key->left.dy = 0.0f; - } - - if (next_time > key->time) { - double delta = next_time - key->time; - key->right.dx = (float)(weight_right * delta); - key->right.dy = key->right.dx * slope_right; - } else { - key->right.dx = 0.0f; - key->right.dy = 0.0f; - } - - slope_left = next_slope_left; - weight_left = next_weight_left; - prev_time = key->time; - } - - ufbxi_check(data == data_end); - - return 1; -} - -// Recursion limited as it is further called only for `name="T"/"R"/"S"` and -// cannot enter the `name=="Transform"` branch. -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_prop_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name) - ufbxi_recursive_function(int, ufbxi_read_take_prop_channel, (uc, node, target_fbx_id, layer_fbx_id, name), 2, - (ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name)) -{ - if (name.data == ufbxi_Transform) { - // Pre-7000 have transform keyframes in a deeply nested structure, - // flatten it to make it resemble post-7000 structure a bit closer: - // old: Model: { Channel: "Transform" { Channel: "T" { Channel "X": { ... } } } } - // new: Model: { Channel: "Lcl Translation" { Channel "X": { ... } } } - - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - if (child->name != ufbxi_Channel) continue; - - const char *old_name = NULL; - ufbxi_check(ufbxi_get_val1(child, "C", (char**)&old_name)); - - ufbx_string new_name; - if (old_name == ufbxi_T) { new_name.data = ufbxi_Lcl_Translation; new_name.length = sizeof(ufbxi_Lcl_Translation) - 1; } - else if (old_name == ufbxi_R) { new_name.data = ufbxi_Lcl_Rotation; new_name.length = sizeof(ufbxi_Lcl_Rotation) - 1; } - else if (old_name == ufbxi_S) { new_name.data = ufbxi_Lcl_Scaling; new_name.length = sizeof(ufbxi_Lcl_Scaling) - 1; } - else { - continue; - } - - // Read child as a top-level property channel - ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, new_name)); - } - - } else { - - // Pre-6000 FBX files store blend shape keys with a " (Shape)" suffix - if (uc->version < 6000) { - const char *const suffix = " (Shape)"; - size_t suffix_len = strlen(suffix); - if (name.length > suffix_len && !memcmp(name.data + name.length - suffix_len, suffix, suffix_len)) { - name.length -= suffix_len; - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false)); - } - } - - // Find 1-3 channel nodes that contain a `Key:` node - ufbxi_node *channel_nodes[3] = { 0 }; - const char *channel_names[3] = { 0 }; - size_t num_channel_nodes = 0; - - if (ufbxi_find_child(node, ufbxi_Key) || ufbxi_find_child(node, ufbxi_Default)) { - // Channel has only a single curve - channel_nodes[0] = node; - channel_names[0] = name.data; - num_channel_nodes = 1; - } else { - // Channel is a compound of multiple curves - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - if (child->name != ufbxi_Channel) continue; - if (!ufbxi_find_child(child, ufbxi_Key) && !ufbxi_find_child(child, ufbxi_Default)) continue; - if (!ufbxi_get_val1(child, "C", (char**)&channel_names[num_channel_nodes])) continue; - channel_nodes[num_channel_nodes] = child; - if (++num_channel_nodes == 3) break; - } - } - - // Early return: No valid channels found, not an error - if (num_channel_nodes == 0) return 1; - - uint64_t value_fbx_id = 0; - ufbx_anim_value *value = ufbxi_push_synthetic_element(uc, &value_fbx_id, node, name.data, ufbx_anim_value, UFBX_ELEMENT_ANIM_VALUE); - - // Add a "virtual" connection between the animated property and the layer/target - ufbxi_check(ufbxi_connect_oo(uc, value_fbx_id, layer_fbx_id)); - ufbxi_check(ufbxi_connect_op(uc, value_fbx_id, target_fbx_id, name)); - - for (size_t i = 0; i < num_channel_nodes; i++) { - ufbxi_check(ufbxi_read_take_anim_channel(uc, channel_nodes[i], value_fbx_id, channel_names[i], &value->default_value.v[i])); - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_object(ufbxi_context *uc, ufbxi_node *node, uint64_t layer_fbx_id) -{ - // Takes are used only in pre-7000 FBX versions so objects are identified - // by their unique Type::Name pair that we use as unique IDs through the - // pooled interned string pointers. - const char *type_and_name = NULL; - ufbxi_check(ufbxi_get_val1(node, "c", (char**)&type_and_name)); - uint64_t target_fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name); - ufbxi_check(target_fbx_id); - - // Add all suitable Channels as animated properties - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - ufbx_string name; - if (child->name != ufbxi_Channel) continue; - if (!ufbxi_get_val1(child, "S", &name)) continue; - - ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, name)); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take(ufbxi_context *uc, ufbxi_node *node) -{ - ufbx_prop tmp_props[4]; - uint32_t num_props = 0; - memset(tmp_props, 0, sizeof(tmp_props)); - - int64_t start = 0, stop = 0; - if (ufbxi_find_val2(node, ufbxi_LocalTime, "LL", &start, &stop)) { - ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStart, start, UFBX_PROP_INTEGER); - ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStop, stop, UFBX_PROP_INTEGER); - } - if (ufbxi_find_val2(node, ufbxi_ReferenceTime, "LL", &start, &stop)) { - ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStart, start, UFBX_PROP_INTEGER); - ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStop, stop, UFBX_PROP_INTEGER); - } - - const char *name; - ufbxi_check(ufbxi_get_val1(node, "C", (char**)&name)); - - // Hack: For post-7000 files we are only interested in the animation times - // for fallback in case the information is missing in the stacks. - if (uc->version >= 7000) { - uint32_t hash = ufbxi_hash_ptr(name); - ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &name); - - if (entry) { - ufbx_anim_stack *stack = entry->stack; - if (stack->props.props.count == 0) { - stack->props.props.count = num_props; - stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(stack->props.props.data); - } - } - - return 1; - } - - uint64_t stack_fbx_id = 0, layer_fbx_id = 0; - - // Treat the Take as a post-7000 version animation stack and layer. - ufbx_anim_stack *stack = ufbxi_push_synthetic_element(uc, &stack_fbx_id, node, name, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); - ufbxi_check(stack); - - stack->props.props.count = num_props; - stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(stack->props.props.data); - - ufbx_anim_layer *layer = ufbxi_push_synthetic_element(uc, &layer_fbx_id, node, ufbxi_BaseLayer, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER); - ufbxi_check(layer); - - ufbxi_check(ufbxi_connect_oo(uc, layer_fbx_id, stack_fbx_id)); - - // Read all properties of objects included in the take - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - // TODO: Do some object types have another name? - if (child->name != ufbxi_Model) continue; - - ufbxi_check(ufbxi_read_take_object(uc, child, layer_fbx_id)); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_takes(ufbxi_context *uc) -{ - for (;;) { - ufbxi_node *node; - ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL)); - if (!node) break; - - if (node->name == ufbxi_Take) { - ufbxi_check(ufbxi_read_take(uc, node)); - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_settings(ufbxi_context *uc, ufbxi_node *node) -{ - if (uc->read_legacy_settings) return 1; - uc->read_legacy_settings = true; - - ufbx_prop tmp_props[2]; - uint32_t num_props = 0; - memset(tmp_props, 0, sizeof(tmp_props)); - - ufbxi_node *frame_rate = ufbxi_find_child_strcmp(node, "FrameRate"); - if (frame_rate) { - double fps = 0.0; - if (!ufbxi_get_val1(frame_rate, "D", &fps)) { - ufbx_string str; - if (ufbxi_get_val1(frame_rate, "S", &str)) { - char *end; - double val = ufbxi_parse_double(str.data, str.length, &end, uc->double_parse_flags); - if (end == str.data + str.length) { - fps = val; - } - } - } - if (fps > 0.0) { - ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_CustomFrameRate, (ufbx_real)fps, UFBX_PROP_NUMBER); - ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_TimeMode, UFBX_TIME_MODE_CUSTOM, UFBX_PROP_INTEGER); - } - } - - if (num_props > 0) { - ufbx_props *props = &uc->scene.settings.props; - size_t num_existing = props->props.count; - - size_t new_count = num_props + num_existing; - ufbx_prop *new_props = ufbxi_push(&uc->result, ufbx_prop, new_count); - ufbxi_check(new_props); - - memcpy(new_props, tmp_props, num_props * sizeof(ufbx_prop)); - if (num_existing > 0) { - memcpy(new_props + num_props, props->props.data, num_existing * sizeof(ufbx_prop)); - } - - ufbxi_check(ufbxi_sort_properties(uc, new_props, new_count)); - props->props.data = new_props; - props->props.count = new_count; - ufbxi_deduplicate_properties(&props->props); - - ufbxi_check(uc->scene.settings.props.props.data); - } - - return 1; -} - -ufbxi_noinline static ufbx_matrix ufbxi_unscaled_transform_to_matrix(const ufbx_transform *t) -{ - ufbx_transform transform = *t; - transform.scale.x = 1.0f; - transform.scale.y = 1.0f; - transform.scale.z = 1.0f; - return ufbx_transform_to_matrix(&transform); -} - -ufbxi_noinline static void ufbxi_setup_root_node(ufbxi_context *uc, ufbx_node *root) -{ - if (uc->opts.use_root_transform) { - root->local_transform = uc->opts.root_transform; - root->node_to_parent = ufbx_transform_to_matrix(&uc->opts.root_transform); - } else { - root->local_transform = ufbx_identity_transform; - root->node_to_parent = ufbx_identity_matrix; - } - root->is_root = true; -} - -static ufbxi_forceinline bool ufbxi_supports_version(uint32_t version) -{ - return version >= 3000 && version <= 7700; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_root(ufbxi_context *uc) -{ - // FBXHeaderExtension: Some metadata (optional) - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_FBXHeaderExtension)); - ufbxi_check(ufbxi_read_header_extension(uc)); - - // The ASCII exporter version is stored in top-level - if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Creator)); - if (uc->top_node) { - ufbxi_ignore(ufbxi_get_val1(uc->top_node, "S", &uc->scene.metadata.creator)); - } - } - - // Resolve the exporter before continuing - ufbxi_check(ufbxi_match_exporter(uc)); - if (uc->version < 7000) { - ufbxi_check(ufbxi_init_node_prop_names(uc)); - } - // Don't allow changing version from this point onwards - uc->ascii.found_version = true; - - // Document: Read root ID - if (uc->version >= 7000) { - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Documents)); - ufbxi_check(ufbxi_read_document(uc)); - } else { - // Pre-7000: Root node has a specific type-name pair "Model::Scene" - // (or reversed in binary). Use the interned name as ID as usual. - const char *root_name = uc->from_ascii ? "Model::Scene" : "Scene\x00\x01Model"; - root_name = ufbxi_push_string_imp(&uc->string_pool, root_name, 12, NULL, false, true); - ufbxi_check(root_name); - uc->root_id = ufbxi_synthetic_id_from_string(uc, root_name); - ufbxi_check(uc->root_id); - } - - // Add a nameless root node with the root ID - { - ufbxi_element_info root_info = { uc->root_id }; - root_info.name = ufbx_empty_string; - ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(root); - ufbxi_setup_root_node(uc, root); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); - } - - // Definitions: Object type counts and property templates (optional) - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Definitions)); - ufbxi_check(ufbxi_read_definitions(uc)); - - // Objects: Actual scene data - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Objects)); - if (!uc->sure_fbx) { - // If the file is a bit iffy about being a real FBX file reject it if - // even the objects are not found. - ufbxi_check_msg(uc->top_node, "Not an FBX file"); - } - if (uc->thread_pool.enabled) { - ufbxi_check(ufbxi_read_objects_threaded(uc)); - } else { - ufbxi_check(ufbxi_read_objects(uc)); - } - - // Connections: Relationships between nodes - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Connections)); - ufbxi_check(ufbxi_read_connections(uc)); - - // Takes: Pre-7000 animation data - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Takes)); - ufbxi_check(ufbxi_read_takes(uc)); - - // Check if there's a top-level GlobalSettings that we skimmed over - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_GlobalSettings)); - if (uc->top_node) { - ufbxi_check(ufbxi_read_global_settings(uc, uc->top_node)); - } - - // Version5: Pre-6000 settings - ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Version5)); - if (uc->top_node) { - ufbxi_node *settings = ufbxi_find_child_strcmp(uc->top_node, "Settings"); - if (settings) { - ufbxi_check(ufbxi_read_legacy_settings(uc, settings)); - } - } - - // Force parsing all the nodes by parsing a toplevel that cannot be found - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_parse_toplevel(uc, NULL)); - } - - return 1; -} - -typedef struct { - const char *prop_name; - ufbx_prop_type prop_type; - const char *node_name; - const char *node_fmt; -} ufbxi_legacy_prop; - -// Must be alphabetically sorted! -static const ufbxi_legacy_prop ufbxi_legacy_light_props[] = { - { ufbxi_CastLight, UFBX_PROP_BOOLEAN, ufbxi_CastLight, "L" }, - { ufbxi_CastShadows, UFBX_PROP_BOOLEAN, ufbxi_CastShadows, "L" }, - { ufbxi_Color, UFBX_PROP_COLOR, ufbxi_Color, "RRR" }, - { ufbxi_ConeAngle, UFBX_PROP_NUMBER, ufbxi_ConeAngle, "R" }, - { ufbxi_HotSpot, UFBX_PROP_NUMBER, ufbxi_HotSpot, "R" }, - { ufbxi_Intensity, UFBX_PROP_NUMBER, ufbxi_Intensity, "R" }, - { ufbxi_LightType, UFBX_PROP_INTEGER, ufbxi_LightType, "L" }, -}; - -// Must be alphabetically sorted! -static const ufbxi_legacy_prop ufbxi_legacy_camera_props[] = { - { ufbxi_ApertureMode, UFBX_PROP_INTEGER, ufbxi_ApertureMode, "L" }, - { ufbxi_AspectH, UFBX_PROP_NUMBER, ufbxi_AspectH, "R" }, - { ufbxi_AspectRatioMode, UFBX_PROP_INTEGER, "AspectType", "L" }, - { ufbxi_AspectW, UFBX_PROP_NUMBER, ufbxi_AspectW, "R" }, - { ufbxi_FieldOfView, UFBX_PROP_NUMBER, "Aperture", "R" }, - { ufbxi_FieldOfViewX, UFBX_PROP_NUMBER, "FieldOfViewXProperty", "R" }, - { ufbxi_FieldOfViewY, UFBX_PROP_NUMBER, "FieldOfViewYProperty", "R" }, - { ufbxi_FilmHeight, UFBX_PROP_NUMBER, "CameraAperture", "_R" }, - { ufbxi_FilmSqueezeRatio, UFBX_PROP_NUMBER, "SqueezeRatio", "R" }, - { ufbxi_FilmWidth, UFBX_PROP_NUMBER, "CameraAperture", "R_" }, - { ufbxi_FocalLength, UFBX_PROP_NUMBER, ufbxi_FocalLength, "R" }, -}; - -// Must be alphabetically sorted! -static const ufbxi_legacy_prop ufbxi_legacy_bone_props[] = { - { ufbxi_Size, UFBX_PROP_NUMBER, ufbxi_Size, "R" }, -}; - -// Must be alphabetically sorted! -static const ufbxi_legacy_prop ufbxi_legacy_material_props[] = { - { ufbxi_AmbientColor, UFBX_PROP_COLOR, "Ambient", "RRR" }, - { ufbxi_DiffuseColor, UFBX_PROP_COLOR, "Diffuse", "RRR" }, - { ufbxi_EmissiveColor, UFBX_PROP_COLOR, "Emissive", "RRR" }, - { ufbxi_ShadingModel, UFBX_PROP_COLOR, ufbxi_ShadingModel, "S" }, - { ufbxi_Shininess, UFBX_PROP_NUMBER, "Shininess", "R" }, - { ufbxi_SpecularColor, UFBX_PROP_COLOR, "Specular", "RRR" }, -}; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_prop(ufbxi_node *node, ufbx_prop *prop, const ufbxi_legacy_prop *legacy_prop) -{ - size_t value_ix = 0; - uint32_t flags = 0; - - const char *fmt = legacy_prop->node_fmt; - for (size_t fmt_ix = 0; fmt[fmt_ix]; fmt_ix++) { - char c = fmt[fmt_ix]; - switch (c) { - case 'L': - ufbx_assert(value_ix == 0); - if (!ufbxi_get_val_at(node, fmt_ix, 'L', &prop->value_int)) return 0; - prop->value_real = (ufbx_real)prop->value_int; - prop->value_real_arr[1] = 0.0f; - prop->value_real_arr[2] = 0.0f; - prop->value_real_arr[3] = 0.0f; - prop->value_str = ufbx_empty_string; - prop->value_blob = ufbx_empty_blob; - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT; - value_ix++; - break; - case 'R': - ufbx_assert(value_ix < 4); - if (!ufbxi_get_val_at(node, fmt_ix, 'R', &prop->value_real_arr[value_ix])) return 0; - if (value_ix == 0) { - prop->value_int = ufbxi_f64_to_i64(prop->value_real); - prop->value_real_arr[1] = 0.0f; - prop->value_real_arr[2] = 0.0f; - prop->value_real_arr[3] = 0.0f; - prop->value_str = ufbx_empty_string; - prop->value_blob = ufbx_empty_blob; - } - flags &= ~(uint32_t)(UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_VEC2|UFBX_PROP_FLAG_VALUE_VEC3|UFBX_PROP_FLAG_VALUE_VEC4); - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << value_ix; - value_ix++; - break; - case 'S': - ufbx_assert(value_ix == 0); - if (!ufbxi_get_val_at(node, fmt_ix, 'S', &prop->value_str)) return 0; - if (prop->value_str.length > 0) { - int found = ufbxi_get_val_at(node, fmt_ix, 'b', &prop->value_blob); - ufbxi_ignore(found); - ufbx_assert(found); - } else { - prop->value_blob = ufbx_empty_blob; - } - prop->value_real = 0.0f; - prop->value_real_arr[1] = 0.0f; - prop->value_real_arr[2] = 0.0f; - prop->value_real_arr[3] = 0.0f; - prop->value_int = 0; - flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR; - value_ix++; - break; - case '_': - break; - default: - ufbxi_unreachable("Unhandled legacy fmt"); - } - } - - prop->flags = (ufbx_prop_flags)flags; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_read_legacy_props(ufbxi_node *node, ufbx_prop *props, const ufbxi_legacy_prop *legacy_props, size_t num_legacy) -{ - size_t num_props = 0; - for (size_t legacy_ix = 0; legacy_ix < num_legacy; legacy_ix++) { - const ufbxi_legacy_prop *legacy_prop = &legacy_props[legacy_ix]; - ufbx_prop *prop = &props[num_props]; - - ufbxi_node *n = ufbxi_find_child_strcmp(node, legacy_prop->node_name); - if (!n) continue; - if (!ufbxi_read_legacy_prop(n, prop, legacy_prop)) continue; - - prop->name.data = legacy_prop->prop_name; - prop->name.length = strlen(legacy_prop->prop_name); - prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); - prop->flags = (ufbx_prop_flags)0; - prop->type = legacy_prop->prop_type; - num_props++; - } - - return num_props; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_material(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name) -{ - ufbx_material *ufbxi_restrict material = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_material, UFBX_ELEMENT_MATERIAL); - ufbxi_check(material); - - ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_material_props)]; - size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_material_props, ufbxi_arraycount(ufbxi_legacy_material_props)); - - material->shading_model_name = ufbx_empty_string; - material->props.props.count = num_props; - material->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(material->props.props.data); - - material->shader_prop_prefix = ufbx_empty_string; - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_link(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name) -{ - ufbx_skin_cluster *ufbxi_restrict cluster = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER); - ufbxi_check(cluster); - - // TODO: Merge with ufbxi_read_skin_cluster(), at least partially? - ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i'); - ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r'); - - if (indices && weights) { - ufbxi_check(indices->size == weights->size); - cluster->num_weights = indices->size; - cluster->vertices.data = (uint32_t*)indices->data; - cluster->weights.data = (ufbx_real*)weights->data; - cluster->vertices.count = cluster->num_weights; - cluster->weights.count = cluster->num_weights; - } - - ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r'); - ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r'); - if (transform && transform_link) { - ufbxi_check(transform->size >= 16); - ufbxi_check(transform_link->size >= 16); - - ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data); - ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_light(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_light *ufbxi_restrict light = ufbxi_push_element(uc, info, ufbx_light, UFBX_ELEMENT_LIGHT); - ufbxi_check(light); - - ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_light_props)]; - size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_light_props, ufbxi_arraycount(ufbxi_legacy_light_props)); - - light->props.props.count = num_props; - light->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(light->props.props.data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_camera(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_camera *ufbxi_restrict camera = ufbxi_push_element(uc, info, ufbx_camera, UFBX_ELEMENT_CAMERA); - ufbxi_check(camera); - - ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_camera_props)]; - size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_camera_props, ufbxi_arraycount(ufbxi_legacy_camera_props)); - - camera->props.props.count = num_props; - camera->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(camera->props.props.data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_limb_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - ufbx_bone *ufbxi_restrict bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE); - ufbxi_check(bone); - - ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_bone_props)]; - size_t num_props = 0; - - ufbxi_node *prop_node = ufbxi_find_child_strcmp(node, "Properties"); - if (prop_node) { - num_props = ufbxi_read_legacy_props(prop_node, tmp_props, ufbxi_legacy_bone_props, ufbxi_arraycount(ufbxi_legacy_bone_props)); - } - - bone->props.props.count = num_props; - bone->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props); - ufbxi_check(bone->props.props.data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) -{ - // Only read polygon meshes, ignore eg. NURBS without error - ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); - ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex); - if (!node_vertices || !node_indices) return 1; - - ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH); - ufbxi_check(mesh); - - ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info)); - - ufbxi_patch_mesh_reals(mesh); - - if (uc->opts.ignore_geometry) return 1; - - ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); - ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i'); - ufbxi_check(vertices && indices); - ufbxi_check(vertices->size % 3 == 0); - - mesh->num_vertices = vertices->size / 3; - mesh->num_indices = indices->size; - - uint32_t *index_data = (uint32_t*)indices->data; - - // Duplicate `index_data` for modification if we retain DOM - if (uc->opts.retain_dom) { - index_data = ufbxi_push_copy(&uc->result, uint32_t, indices->size, index_data); - ufbxi_check(index_data); - } - - mesh->vertices.data = (ufbx_vec3*)vertices->data; - mesh->vertex_indices.data = index_data; - mesh->vertices.count = mesh->num_vertices; - mesh->vertex_indices.count = mesh->num_indices; - - mesh->vertex_position.exists = true; - mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data; - mesh->vertex_position.values.count = mesh->num_vertices; - mesh->vertex_position.indices.data = index_data; - mesh->vertex_position.indices.count = mesh->num_indices; - mesh->vertex_position.unique_per_vertex = true; - - // Check/make sure that the last index is negated (last of polygon) - if (mesh->num_indices > 0) { - if ((int32_t)index_data[mesh->num_indices - 1] >= 0) { - if (uc->opts.strict) ufbxi_fail("Non-negated last index"); - index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1]; - } - } - - ufbxi_check(ufbxi_process_indices(uc, mesh, index_data)); - - // Normals are either per-vertex or per-index in legacy FBX files? - // If the version is 5000 prefer per-vertex, otherwise per-index... - ufbxi_value_array *normals = ufbxi_find_array(node, ufbxi_Normals, 'r'); - if (normals) { - size_t num_normals = normals->size / 3; - bool per_vertex = num_normals == mesh->num_vertices; - bool per_index = num_normals == mesh->num_indices; - if (per_vertex && (!per_index || uc->version == 5000)) { - mesh->vertex_normal.exists = true; - mesh->vertex_normal.values.count = num_normals; - mesh->vertex_normal.indices.count = mesh->num_indices; - mesh->vertex_normal.unique_per_vertex = true; - mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data; - mesh->vertex_normal.indices.data = mesh->vertex_indices.data; - } else if (per_index) { - uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices); - mesh->vertex_normal.exists = true; - mesh->vertex_normal.values.count = num_normals; - mesh->vertex_normal.indices.count = mesh->num_indices; - mesh->vertex_normal.unique_per_vertex = false; - mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data; - mesh->vertex_normal.indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; - } - } - - // Optional UV values are stored pretty much like a modern vertex element - ufbxi_node *uv_info = ufbxi_find_child(node, ufbxi_GeometryUVInfo); - if (uv_info) { - ufbx_uv_set *set = ufbxi_push_zero(&uc->result, ufbx_uv_set, 1); - ufbxi_check(set); - set->index = 0; - set->name.data = ufbxi_empty_char; - ufbxi_check(ufbxi_read_vertex_element(uc, mesh, uv_info, (ufbx_vertex_attrib*)&set->vertex_uv, - ufbxi_TextureUV, ufbxi_TextureUVVerticeIndex, NULL, 'r', 2)); - - mesh->uv_sets.data = set; - mesh->uv_sets.count = 1; - mesh->vertex_uv = set->vertex_uv; - } - - // Material indices - { - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(node, ufbxi_MaterialAssignation, "C", (char**)&mapping)); - if (mapping == ufbxi_ByPolygon) { - ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, node, ufbxi_Materials, 'i', mesh->num_faces)); - } else if (mapping == ufbxi_AllSame) { - ufbxi_value_array *arr = ufbxi_find_array(node, ufbxi_Materials, 'i'); - uint32_t material = 0; - if (arr && arr->size >= 1) { - material = ((uint32_t*)arr->data)[0]; - } - - mesh->face_material.count = mesh->num_faces; - if (material == 0) { - mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero; - } else { - mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces); - ufbxi_check(mesh->face_material.data); - ufbxi_for_list(uint32_t, p_mat, mesh->face_material) { - *p_mat = material; - } - } - } - } - - uint64_t skin_fbx_id = 0; - ufbx_skin_deformer *skin = NULL; - - // Materials, Skin Clusters - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - if (child->name == ufbxi_Material) { - uint64_t fbx_id = 0; - ufbx_string type_and_name, type, name; - ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name)); - ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); - ufbxi_check(ufbxi_read_legacy_material(uc, child, &fbx_id, name.data)); - ufbxi_check(ufbxi_connect_oo(uc, fbx_id, info->fbx_id)); - } else if (child->name == ufbxi_Link) { - uint64_t fbx_id = 0; - ufbx_string type_and_name, type, name; - ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name)); - ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); - ufbxi_check(ufbxi_read_legacy_link(uc, child, &fbx_id, name.data)); - - uint64_t node_fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); - ufbxi_check(node_fbx_id); - ufbxi_check(ufbxi_connect_oo(uc, node_fbx_id, fbx_id)); - if (!skin) { - skin = ufbxi_push_synthetic_element(uc, &skin_fbx_id, NULL, info->name.data, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER); - ufbxi_check(skin); - ufbxi_check(ufbxi_connect_oo(uc, skin_fbx_id, info->fbx_id)); - } - ufbxi_check(ufbxi_connect_oo(uc, fbx_id, skin_fbx_id)); - } - } - - mesh->skinned_is_local = true; - mesh->skinned_position = mesh->vertex_position; - mesh->skinned_normal = mesh->vertex_normal; - - ufbxi_patch_mesh_reals(mesh); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_media(ufbxi_context *uc, ufbxi_node *node) -{ - ufbxi_node *videos = ufbxi_find_child(node, ufbxi_Video); - if (videos) { - ufbxi_for(ufbxi_node, child, videos->children, videos->num_children) { - ufbxi_element_info video_info = { 0 }; - ufbxi_check(ufbxi_get_val1(child, "S", &video_info.name)); - video_info.fbx_id = ufbxi_push_synthetic_id(uc); - video_info.dom_node = ufbxi_get_dom_node(uc, node); - - ufbxi_check(ufbxi_read_video(uc, child, &video_info)); - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_model(ufbxi_context *uc, ufbxi_node *node) -{ - ufbx_string type_and_name, type, name; - ufbxi_check(ufbxi_get_val1(node, "s", &type_and_name)); - ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name)); - - ufbxi_element_info info = { 0 }; - info.fbx_id = ufbxi_synthetic_id_from_string(uc, type_and_name.data); - ufbxi_check(info.fbx_id); - info.name = name; - info.dom_node = ufbxi_get_dom_node(uc, node); - - ufbx_node *elem_node = ufbxi_push_element(uc, &info, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(elem_node); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id)); - - ufbxi_element_info attrib_info = { 0 }; - attrib_info.fbx_id = ufbxi_push_synthetic_id(uc); - attrib_info.name = name; - attrib_info.dom_node = info.dom_node; - - // If we make unused connections it doesn't matter.. - ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info.fbx_id)); - - const char *attrib_type = ufbxi_empty_char; - ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Type, "C", (char**)&attrib_type)); - - bool has_attrib = true; - if (attrib_type == ufbxi_Light) { - ufbxi_check(ufbxi_read_legacy_light(uc, node, &attrib_info)); - } else if (attrib_type == ufbxi_Camera) { - ufbxi_check(ufbxi_read_legacy_camera(uc, node, &attrib_info)); - } else if (attrib_type == ufbxi_LimbNode) { - ufbxi_check(ufbxi_read_legacy_limb_node(uc, node, &attrib_info)); - } else if (ufbxi_find_child(node, ufbxi_Vertices)) { - ufbxi_check(ufbxi_read_legacy_mesh(uc, node, &attrib_info)); - } else { - has_attrib = false; - } - - // Mark the node as having an attribute so property connections can be forwarded - if (has_attrib) { - ufbxi_check(ufbxi_insert_fbx_attr(uc, info.fbx_id, attrib_info.fbx_id)); - } - - // Children are represented as an array of strings - ufbxi_value_array *children = ufbxi_find_array(node, ufbxi_Children, 's'); - if (children) { - ufbx_string *names = (ufbx_string*)children->data; - for (size_t i = 0; i < children->size; i++) { - uint64_t child_fbx_id = ufbxi_synthetic_id_from_string(uc, names[i].data); - ufbxi_check(child_fbx_id); - ufbxi_check(ufbxi_connect_oo(uc, child_fbx_id, info.fbx_id)); - } - } - - // Non-take animation channels - ufbxi_for(ufbxi_node, child, node->children, node->num_children) { - if (child->name == ufbxi_Channel) { - ufbx_string channel_name; - if (ufbxi_get_val1(child, "S", &channel_name)) { - if (uc->legacy_implicit_anim_layer_id == 0) { - // Defer creation so we won't be the first animation stack.. - uc->legacy_implicit_anim_layer_id = ufbxi_push_synthetic_id(uc); - } - ufbxi_check(ufbxi_read_take_prop_channel(uc, child, info.fbx_id, uc->legacy_implicit_anim_layer_id, channel_name)); - } - } - } - - return 1; -} - -// Read a pre-6000 FBX file where everything is stored at the root level -ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_legacy_root(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_init_node_prop_names(uc)); - - // Some legacy FBX files have an `Fbx_Root` node that could be used as the - // root node. However no other formats have root node with transforms so it - // might be better to leave it as-is and create an empty one. - { - ufbx_node *root = ufbxi_push_synthetic_element(uc, &uc->root_id, NULL, ufbxi_empty_char, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(root); - ufbxi_setup_root_node(uc, root); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); - } - - // NOTE: `ufbxi_read_header_extension()` is optional so use default KTime definition - uc->ktime_sec = 46186158000; - uc->ktime_sec_double = (double)uc->ktime_sec; - - for (;;) { - ufbxi_check(ufbxi_parse_legacy_toplevel(uc)); - if (!uc->top_node) break; - - ufbxi_node *node = uc->top_node; - if (node->name == ufbxi_FBXHeaderExtension) { - ufbxi_check(ufbxi_read_header_extension(uc)); - } else if (node->name == ufbxi_Media) { - ufbxi_check(ufbxi_read_legacy_media(uc, node)); - } else if (node->name == ufbxi_Takes) { - ufbxi_check(ufbxi_read_takes(uc)); - } else if (node->name == ufbxi_Model) { - ufbxi_check(ufbxi_read_legacy_model(uc, node)); - } else if (!strcmp(node->name, "Settings")) { - ufbxi_check(ufbxi_read_legacy_settings(uc, node)); - } - } - - if (uc->opts.retain_dom) { - ufbxi_check(ufbxi_retain_toplevel(uc, NULL)); - } - - // Create the implicit animation stack if necessary - if (uc->legacy_implicit_anim_layer_id) { - ufbxi_element_info layer_info = { 0 }; - layer_info.fbx_id = uc->legacy_implicit_anim_layer_id; - layer_info.name.data = "(internal)"; - layer_info.name.length = strlen(layer_info.name.data); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &layer_info.name, true)); - ufbx_anim_layer *layer = ufbxi_push_element(uc, &layer_info, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER); - ufbxi_check(layer); - - ufbxi_element_info stack_info = layer_info; - stack_info.fbx_id = ufbxi_push_synthetic_id(uc); - ufbx_anim_stack *stack = ufbxi_push_element(uc, &stack_info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK); - ufbxi_check(stack); - - ufbxi_check(ufbxi_connect_oo(uc, layer_info.fbx_id, stack_info.fbx_id)); - } - - return 1; -} - -// Filename manipulation - -ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_trim_delimiters(ufbxi_context *uc, const char *data, size_t length) -{ - for (; length > 0; length--) { - char c = data[length - 1]; - bool is_separator = c == '/' || c == uc->opts.path_separator; - if (is_separator) { - length--; - break; - } - } - return length; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_init_file_paths(ufbxi_context *uc) -{ - if (uc->opts.filename.length > 0) { - uc->scene.metadata.filename = uc->opts.filename; - } else if (uc->opts.raw_filename.size > 0) { - uc->scene.metadata.filename.data = (const char*)uc->opts.raw_filename.data; - uc->scene.metadata.filename.length = uc->opts.raw_filename.size; - } - - if (uc->opts.raw_filename.size > 0) { - uc->scene.metadata.raw_filename = uc->opts.raw_filename; - } else if (uc->opts.filename.length > 0) { - uc->scene.metadata.raw_filename.data = uc->opts.filename.data; - uc->scene.metadata.raw_filename.size = uc->opts.filename.length; - } - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.filename, false)); - ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_filename, true)); - - uc->scene.metadata.relative_root.data = uc->scene.metadata.filename.data; - uc->scene.metadata.relative_root.length = ufbxi_trim_delimiters(uc, uc->scene.metadata.filename.data, uc->scene.metadata.filename.length); - - uc->scene.metadata.raw_relative_root.data = uc->scene.metadata.raw_filename.data; - uc->scene.metadata.raw_relative_root.size = ufbxi_trim_delimiters(uc, (const char*)uc->scene.metadata.raw_filename.data, uc->scene.metadata.raw_filename.size); - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.relative_root, false)); - ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_relative_root, true)); - - return 1; -} - -typedef union { - ufbx_string str; - ufbx_blob blob; -} ufbxi_strblob; - -static ufbxi_noinline void ufbxi_strblob_set(ufbxi_strblob *dst, const char *data, size_t length, bool raw) -{ - if (raw) { - dst->blob.data = data; - dst->blob.size = length; - } else { - dst->str.data = length == 0 ? ufbxi_empty_char : data; - dst->str.length = length; - } -} - -static ufbxi_forceinline const char *ufbxi_strblob_data(const ufbxi_strblob *strblob, bool raw) -{ - return raw ? (const char*)strblob->blob.data : strblob->str.data; -} - -static ufbxi_forceinline size_t ufbxi_strblob_length(const ufbxi_strblob *strblob, bool raw) -{ - return raw ? strblob->blob.size : strblob->str.length; -} - -ufbxi_nodiscard ufbxi_noinline static bool ufbxi_is_absolute_path(const char *path, size_t length) -{ - if (length > 0 && (path[0] == '/' || path[0] == '\\')) { - return true; - } else if (length > 2 && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { - return true; - } - return false; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_src, bool raw) -{ - const char *src = ufbxi_strblob_data(p_src, raw); - size_t src_length = ufbxi_strblob_length(p_src, raw); - - // Skip leading directory separators and early return if the relative path is empty - while (src_length > 0 && (src[0] == '/' || src[0] == '\\')) { - src++; - src_length--; - } - if (src_length == 0) { - ufbxi_strblob_set(p_dst, NULL, 0, raw); - return 1; - } - - const char *prefix_data; - size_t prefix_length; - if (raw) { - prefix_data = (const char*)uc->scene.metadata.raw_relative_root.data; - prefix_length = uc->scene.metadata.raw_relative_root.size; - } else { - prefix_data = (const char*)uc->scene.metadata.relative_root.data; - prefix_length = uc->scene.metadata.relative_root.length; - } - - // Retain absolute paths - if (ufbxi_is_absolute_path(src, src_length)) { - prefix_length = 0; - } - - // Undo directories from `prefix` for every `..` - while (prefix_length > 0 && src_length >= 3 && src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) { - size_t part_start = prefix_length; - while (part_start > 0 && !(prefix_data[part_start - 1] == '/' || prefix_data[part_start - 1] == '\\')) { - part_start--; - } - size_t part_len = prefix_length - part_start; - - if (part_len == 2 && prefix_data[part_start] == '.' && prefix_data[part_start + 1] == '.') { - // Prefix itself ends in `..`, cannot cancel out a leading `../` - break; - } - - // Eat the leading '/' before the part segment - prefix_length = part_start > 0 ? part_start - 1 : 0; - - if (part_len == 1 && prefix_data[part_start] == '.') { - // Single '.' -> remove and continue without cancelling out a leading `../` - continue; - } - - src += 3; - src_length -= 3; - } - - size_t result_cap = prefix_length + src_length + 1; - char *result = ufbxi_push(&uc->tmp_stack, char, result_cap); - ufbxi_check(result); - char *ptr = result; - - // Copy prefix and suffix converting separators in the process - if (prefix_length > 0) { - memcpy(ptr, prefix_data, prefix_length); - ptr[prefix_length] = uc->opts.path_separator; - ptr += prefix_length + 1; - } - for (size_t i = 0; i < src_length; i++) { - char c = src[i]; - if (c == '/' || c == '\\') { - c = uc->opts.path_separator; - } - *ptr++ = c; - } - - // Intern the string and pop the temporary buffer - ufbx_string dst = { result, ufbxi_to_size(ptr - result) }; - ufbx_assert(dst.length <= result_cap); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst, raw)); - ufbxi_pop(&uc->tmp_stack, char, result_cap, NULL); - - ufbxi_strblob_set(p_dst, dst.data, dst.length, raw); - - return 1; -} - -// Open file utility - -static ufbxi_noinline bool ufbxi_open_file(const ufbx_open_file_cb *cb, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_blob *original_filename, ufbxi_allocator *ator, ufbx_open_file_type type) -{ - if (!cb || !cb->fn) return false; - - ufbx_open_file_info info; // ufbxi_uninit - info.context = (uintptr_t)ator; - if (original_filename) { - info.original_filename = *original_filename; - } else { - info.original_filename.data = path; - info.original_filename.size = path_len; - } - info.type = type; - - return cb->fn(cb->user, stream, path, path_len, &info); -} - -#define ufbxi_patch_zero(dst, src) do { \ - ufbx_assert((dst) == 0 || (dst) == (src)); \ - (dst) = (src); \ - } while (0) - -static void ufbxi_update_vertex_first_index(ufbx_mesh *mesh) -{ - ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) { - *p_vx_ix = UFBX_NO_INDEX; - } - - uint32_t num_vertices = (uint32_t)mesh->num_vertices; - for (size_t ix = 0; ix < mesh->num_indices; ix++) { - uint32_t vx = mesh->vertex_indices.data[ix]; - if (vx < num_vertices && mesh->vertex_first_index.data[vx] == UFBX_NO_INDEX) { - mesh->vertex_first_index.data[vx] = (uint32_t)ix; - } - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh) -{ - if (mesh->vertices.count == 0) { - mesh->vertices = mesh->vertex_position.values; - } - if (mesh->vertex_indices.count == 0) { - mesh->vertex_indices = mesh->vertex_position.indices; - } - - ufbxi_patch_zero(mesh->num_vertices, mesh->vertices.count); - ufbxi_patch_zero(mesh->num_indices, mesh->vertex_indices.count); - ufbxi_patch_zero(mesh->num_faces, mesh->faces.count); - - if (mesh->num_triangles == 0 || mesh->max_face_triangles == 0) { - size_t num_triangles = 0; - size_t max_face_triangles = 0; - size_t num_bad_faces[3] = { 0 }; - ufbxi_nounroll ufbxi_for_list(ufbx_face, face, mesh->faces) { - if (face->num_indices >= 3) { - size_t tris = face->num_indices - 2; - num_triangles += tris; - max_face_triangles = ufbxi_max_sz(max_face_triangles, tris); - } else { - num_bad_faces[face->num_indices]++; - } - } - - ufbxi_patch_zero(mesh->num_triangles, num_triangles); - ufbxi_patch_zero(mesh->max_face_triangles, max_face_triangles); - ufbxi_patch_zero(mesh->num_empty_faces, num_bad_faces[0]); - ufbxi_patch_zero(mesh->num_point_faces, num_bad_faces[1]); - ufbxi_patch_zero(mesh->num_line_faces, num_bad_faces[2]); - } - - if (!mesh->skinned_position.exists) { - mesh->skinned_is_local = true; - mesh->skinned_position = mesh->vertex_position; - mesh->skinned_normal = mesh->vertex_normal; - } - - if (mesh->vertex_first_index.count == 0) { - mesh->vertex_first_index.count = mesh->num_vertices; - mesh->vertex_first_index.data = ufbxi_push(buf, uint32_t, mesh->num_vertices); - ufbxi_check_err(error, mesh->vertex_first_index.data); - ufbxi_update_vertex_first_index(mesh); - } - - if (mesh->uv_sets.count == 0 && mesh->vertex_uv.exists) { - ufbx_uv_set *uv_set = ufbxi_push_zero(buf, ufbx_uv_set, 1); - ufbxi_check_err(error, uv_set); - - uv_set->name.data = ufbxi_empty_char; - uv_set->vertex_uv = mesh->vertex_uv; - uv_set->vertex_tangent = mesh->vertex_tangent; - uv_set->vertex_bitangent = mesh->vertex_bitangent; - - mesh->uv_sets.data = uv_set; - mesh->uv_sets.count = 1; - } - - if (mesh->color_sets.count == 0 && mesh->vertex_color.exists) { - ufbx_color_set *color_set = ufbxi_push_zero(buf, ufbx_color_set, 1); - ufbxi_check_err(error, color_set); - - color_set->name.data = ufbxi_empty_char; - color_set->vertex_color = mesh->vertex_color; - - mesh->color_sets.data = color_set; - mesh->color_sets.count = 1; - } - - ufbxi_patch_mesh_reals(mesh); - - return 1; -} - -// -- .obj file - -#if UFBXI_FEATURE_FORMAT_OBJ - -static const uint8_t ufbxi_obj_attrib_stride[] = { - 3, 2, 3, 4, -}; - -ufbx_static_assert(obj_attrib_strides, ufbxi_arraycount(ufbxi_obj_attrib_stride) == UFBXI_OBJ_NUM_ATTRIBS_EXT); - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_props(ufbxi_context *uc, ufbx_prop_list *dst, size_t count) -{ - ufbx_prop_list props; // ufbxi_uninit - props.count = count; - props.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_props, ufbx_prop, count); - ufbxi_check(props.data); - - ufbxi_for_list(ufbx_prop, prop, props) { - prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length); - if (prop->value_str.length == 0) { - prop->value_str.data = ufbxi_empty_char; - } - if (!prop->value_int) { - prop->value_int = ufbxi_f64_to_i64(prop->value_real); - } - if (prop->value_blob.size == 0 && prop->value_str.length > 0) { - prop->value_blob.data = prop->value_str.data; - prop->value_blob.size = prop->value_str.length; - } - } - - if (props.count > 1) { - ufbxi_check(ufbxi_sort_properties(uc, props.data, props.count)); - ufbxi_deduplicate_properties(&props); - } - - *dst = props; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_push_mesh(ufbxi_context *uc) -{ - ufbxi_obj_mesh *mesh = ufbxi_push_zero(&uc->obj.tmp_meshes, ufbxi_obj_mesh, 1); - ufbxi_check(mesh); - uc->obj.mesh = mesh; - - ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) { - mesh->vertex_range[i].min_ix = UINT64_MAX; - } - - const char *name = ""; - if (uc->opts.obj_split_groups && uc->obj.group.length > 0) { - name = uc->obj.group.data; - } else if (!uc->opts.obj_merge_objects && uc->obj.object.length > 0) { - name = uc->obj.object.data; - } else if (!uc->opts.obj_merge_groups && uc->obj.group.length > 0) { - name = uc->obj.group.data; - } - - mesh->fbx_node = ufbxi_push_synthetic_element(uc, &mesh->fbx_node_id, NULL, name, ufbx_node, UFBX_ELEMENT_NODE); - mesh->fbx_mesh = ufbxi_push_synthetic_element(uc, &mesh->fbx_mesh_id, NULL, name, ufbx_mesh, UFBX_ELEMENT_MESH); - ufbxi_check(mesh->fbx_node && mesh->fbx_mesh); - - mesh->fbx_mesh->vertex_position.unique_per_vertex = true; - - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &mesh->fbx_node->element_id)); - - uc->obj.face_material = UFBX_NO_INDEX; - uc->obj.face_group = 0; - uc->obj.face_group_dirty = true; - uc->obj.material_dirty = true; - - ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_mesh_id, mesh->fbx_node_id)); - ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_node_id, 0)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_mesh(ufbxi_context *uc) -{ - if (!uc->obj.mesh) return 1; - - size_t num_props = uc->obj.tmp_props.num_items; - ufbxi_check(ufbxi_obj_pop_props(uc, &uc->obj.mesh->fbx_mesh->props.props, num_props)); - - size_t num_groups = uc->obj.tmp_face_group_infos.num_items; - ufbx_face_group *groups = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group_infos, ufbx_face_group, num_groups); - ufbxi_check(groups); - - uc->obj.mesh->fbx_mesh->face_groups.data = groups; - uc->obj.mesh->fbx_mesh->face_groups.count = num_groups; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_init(ufbxi_context *uc) -{ - uc->from_ascii = true; - uc->obj.initialized = true; - - - ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) { - uc->obj.tmp_vertices[i].ator = &uc->ator_tmp; - uc->obj.tmp_indices[i].ator = &uc->ator_tmp; - } - uc->obj.tmp_color_valid.ator = &uc->ator_tmp; - uc->obj.tmp_faces.ator = &uc->ator_tmp; - uc->obj.tmp_face_material.ator = &uc->ator_tmp; - uc->obj.tmp_face_smoothing.ator = &uc->ator_tmp; - uc->obj.tmp_face_group.ator = &uc->ator_tmp; - uc->obj.tmp_face_group_infos.ator = &uc->ator_tmp; - uc->obj.tmp_meshes.ator = &uc->ator_tmp; - uc->obj.tmp_props.ator = &uc->ator_tmp; - - // .obj parsing does its own yield logic - uc->data_size += uc->yield_size; - - uc->obj.object.data = ufbxi_empty_char; - uc->obj.group.data = ufbxi_empty_char; - - ufbxi_map_init(&uc->obj.group_map, &uc->ator_tmp, ufbxi_map_cmp_const_char_ptr, NULL); - - // Add a nameless root node with the root ID - { - ufbxi_element_info root_info = { uc->root_id }; - root_info.name = ufbx_empty_string; - ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE); - ufbxi_check(root); - ufbxi_setup_root_node(uc, root); - ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id)); - } - - return 1; -} - -static ufbxi_noinline void ufbxi_obj_free(ufbxi_context *uc) -{ - if (!uc->obj.initialized) return; - - ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) { - ufbxi_buf_free(&uc->obj.tmp_vertices[i]); - ufbxi_buf_free(&uc->obj.tmp_indices[i]); - } - ufbxi_buf_free(&uc->obj.tmp_color_valid); - ufbxi_buf_free(&uc->obj.tmp_faces); - ufbxi_buf_free(&uc->obj.tmp_face_material); - ufbxi_buf_free(&uc->obj.tmp_face_smoothing); - ufbxi_buf_free(&uc->obj.tmp_face_group); - ufbxi_buf_free(&uc->obj.tmp_face_group_infos); - ufbxi_buf_free(&uc->obj.tmp_meshes); - ufbxi_buf_free(&uc->obj.tmp_props); - - ufbxi_map_free(&uc->obj.group_map); - - ufbxi_free(&uc->ator_tmp, ufbx_string, uc->obj.tokens, uc->obj.tokens_cap); - ufbxi_free(&uc->ator_tmp, ufbx_material*, uc->obj.tmp_materials, uc->obj.tmp_materials_cap); -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_read_line(ufbxi_context *uc) -{ - ufbxi_dev_assert(!uc->obj.eof); - - size_t offset = 0; - - for (;;) { - const char *begin = ufbxi_add_ptr(uc->data, offset); - const char *end = begin ? (const char*)memchr(begin, '\n', uc->data_size - offset) : NULL; - if (!end) { - if (uc->eof) { - offset = uc->data_size; - uc->obj.eof = true; - break; - } else { - size_t new_cap = ufbxi_max_sz(1, uc->data_size * 2); - ufbxi_check(ufbxi_refill(uc, new_cap, false)); - continue; - } - } - - offset += ufbxi_to_size(end - begin) + 1; - - // Handle line continuations - const char *esc = end; - if (esc > begin && esc[-1] == '\r') esc--; - if (esc > begin && esc[-1] == '\\') { - continue; - } - - break; - } - - size_t line_len = offset; - - uc->obj.line.data = uc->data; - uc->obj.line.length = line_len; - uc->data += line_len; - uc->data_size -= line_len; - - uc->obj.read_progress += line_len; - if (uc->obj.read_progress >= uc->progress_interval) { - ufbxi_check(ufbxi_report_progress(uc)); - uc->obj.read_progress %= uc->progress_interval; - } - - if (uc->obj.eof) { - char *new_data = ufbxi_push(&uc->tmp, char, line_len + 1); - ufbxi_check(new_data); - memcpy(new_data, uc->obj.line.data, line_len); - new_data[line_len] = '\n'; - uc->obj.line.data = new_data; - uc->obj.line.length++; - } - - return 1; -} - -static ufbxi_noinline ufbx_string ufbxi_obj_span_token(ufbxi_context *uc, size_t start_token, size_t end_token) -{ - ufbx_assert(start_token < uc->obj.num_tokens); - end_token = ufbxi_min_sz(end_token, uc->obj.num_tokens - 1); - - ufbx_assert(start_token <= end_token); - ufbx_string start = uc->obj.tokens[start_token]; - ufbx_string end = uc->obj.tokens[end_token]; - size_t num_between = ufbxi_to_size(end.data - start.data); - - ufbx_string result; - result.data = start.data; - result.length = num_between + end.length; - return result; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize(ufbxi_context *uc) -{ - const char *ptr = uc->obj.line.data, *end = ptr + uc->obj.line.length; - uc->obj.num_tokens = 0; - - for (;;) { - char c; - - // Skip whitespace - for (;;) { - c = *ptr; - if (c == ' ' || c == '\t' || c == '\r') { - ptr++; - continue; - } - - // Treat line continuations as whitespace - if (c == '\\') { - const char *p = ptr + 1; - if (*p == '\r') p++; - if (*p == '\n' && p < end - 1) { - ptr = p + 1; - continue; - } - } - - break; - } - - c = *ptr; - if (c == '\n') break; - if (c == '#' && uc->obj.num_tokens > 0) break; - - size_t index = uc->obj.num_tokens++; - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tokens, &uc->obj.tokens_cap, index + 1)); - - ufbx_string *tok = &uc->obj.tokens[index]; - tok->data = ptr; - - // Treat comment start as a single token - if (c == '#') { - ptr++; - tok->length = 1; - continue; - } - - for (;;) { - c = *++ptr; - - if (ufbxi_is_space(c)) { - break; - } - - if (c == '\\') { - const char *p = ptr + 1; - if (*p == '\r') p++; - if (*p == '\n' && p < end - 1) { - break; - } - } - } - - tok->length = ufbxi_to_size(ptr - tok->data); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize_line(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_obj_read_line(uc)); - ufbxi_check(ufbxi_obj_tokenize(uc)); - return 1; -} - -static ufbxi_noinline int ufbxi_obj_parse_vertex(ufbxi_context *uc, ufbxi_obj_attrib attrib, size_t offset) -{ - if (uc->opts.ignore_geometry) return 1; - - ufbxi_buf *dst = &uc->obj.tmp_vertices[attrib]; - size_t num_values = ufbxi_obj_attrib_stride[attrib]; - uc->obj.vertex_count[attrib]++; - - size_t read_values = num_values; - if (attrib == UFBXI_OBJ_ATTRIB_COLOR) { - if (offset + read_values > uc->obj.num_tokens) { - read_values = 3; - } - } - ufbxi_check(offset + read_values <= uc->obj.num_tokens); - - uint32_t parse_flags = uc->double_parse_flags; - ufbx_real *vals = ufbxi_push_fast(dst, ufbx_real, num_values); - ufbxi_check(vals); - for (size_t i = 0; i < read_values; i++) { - ufbx_string str = uc->obj.tokens[offset + i]; - char *end; // ufbxi_uninit - double val = ufbxi_parse_double(str.data, str.length, &end, parse_flags); - ufbxi_check(end == str.data + str.length); - vals[i] = (ufbx_real)val; - } - - if (read_values < num_values) { - ufbx_assert(read_values + 1 == num_values); - ufbx_assert(attrib == UFBXI_OBJ_ATTRIB_COLOR); - vals[read_values] = 1.0f; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_index(ufbxi_context *uc, ufbx_string *s, uint32_t attrib) -{ - const char *ptr = s->data, *end = ptr + s->length; - - bool negative = false; - if (*ptr == '-') { - negative = true; - ptr++; - } - - // As .obj indices are never zero we can detect missing indices - // by simply not writing to it. - uint64_t index = 0; - for (; ptr != end; ptr++) { - char c = *ptr; - if (c >= '0' && c <= '9') { - ufbxi_check(index < UINT64_MAX / 10 - 10); - index = index * 10 + (uint64_t)(c - '0'); - } else if (c == '/') { - ptr++; - break; - } - } - - if (negative) { - size_t count = uc->obj.vertex_count[attrib]; - index = index <= count ? count - index : UINT64_MAX; - } else { - // Corrects to zero based indices and wraps 0 to UINT64_MAX (missing) - index -= 1; - } - - ufbxi_obj_fast_indices *fast_indices = &uc->obj.fast_indices[attrib]; - if (fast_indices->num_left == 0) { - size_t num_push = 128; - uint64_t *dst = ufbxi_push(&uc->obj.tmp_indices[attrib], uint64_t, num_push); - ufbxi_check(dst); - uc->obj.fast_indices[attrib].indices = dst; - uc->obj.fast_indices[attrib].num_left = num_push; - } - - *fast_indices->indices++ = index; - fast_indices->num_left--; - - ufbxi_obj_mesh *mesh = uc->obj.mesh; - - if (index != UINT64_MAX) { - ufbxi_obj_index_range *range = &mesh->vertex_range[attrib]; - range->min_ix = ufbxi_min64(range->min_ix, index); - range->max_ix = ufbxi_max64(range->max_ix, index); - } - - s->data = ptr; - s->length = ufbxi_to_size(end - ptr); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_indices(ufbxi_context *uc, size_t token_begin, size_t num_tokens) -{ - bool flush_mesh = false; - if (uc->obj.object_dirty) { - if (!uc->opts.obj_merge_objects) { - flush_mesh = true; - } - uc->obj.object_dirty = false; - } - - if (uc->obj.group_dirty) { - if (((uc->obj.object.length == 0 || uc->opts.obj_merge_objects) && !uc->opts.obj_merge_groups) || uc->opts.obj_split_groups) { - flush_mesh = true; - } - uc->obj.group_dirty = false; - uc->obj.face_group_dirty = true; - } - - if (!uc->obj.mesh || flush_mesh) { - ufbxi_check(ufbxi_obj_flush_mesh(uc)); - ufbxi_check(ufbxi_obj_push_mesh(uc)); - } - ufbxi_obj_mesh *mesh = uc->obj.mesh; - - if (uc->obj.material_dirty) { - if (uc->obj.usemtl_fbx_id != 0) { - ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id); - ufbx_assert(entry); - if (mesh->usemtl_base == 0 || entry->user_id < mesh->usemtl_base) { - ufbxi_check(ufbxi_connect_oo(uc, uc->obj.usemtl_fbx_id, mesh->fbx_node_id)); - - uint32_t index = ++uc->obj.usemtl_index; - ufbxi_check(index < UINT32_MAX); - entry->user_id = index; - - if (mesh->usemtl_base == 0) { - mesh->usemtl_base = index; - } - uc->obj.face_material = index - mesh->usemtl_base; - } - uc->obj.face_material = entry->user_id - mesh->usemtl_base; - } else { - uc->obj.face_material = UFBX_NO_INDEX; - } - } - - // EARLY RETURN: Rest of the function should only be related to geometry! - if (uc->opts.ignore_geometry) return 1; - - if (num_tokens == 0 && !uc->opts.allow_empty_faces) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_EMPTY_FACE_REMOVED, "Empty face has been removed")); - return 1; - } - - if (uc->obj.face_group_dirty) { - ufbx_string name = ufbx_empty_string; - if (uc->obj.group.length > 0 && (uc->obj.object.length > 0 || uc->opts.obj_merge_groups) && !uc->opts.obj_split_groups) { - name = uc->obj.group; - } - - uint32_t hash = ufbxi_hash_ptr(name.data); - ufbxi_obj_group_entry *entry = ufbxi_map_find(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data); - if (!entry) { - entry = ufbxi_map_insert(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data); - ufbxi_check(entry); - entry->name = name.data; - entry->mesh_id = 0; - entry->local_id = 0; - } - - uint32_t mesh_id = mesh->fbx_mesh->element_id; - if (entry->mesh_id != mesh_id) { - uint32_t id = mesh->num_groups++; - entry->mesh_id = mesh_id; - entry->local_id = id; - - ufbx_face_group *group = ufbxi_push_zero(&uc->obj.tmp_face_group_infos, ufbx_face_group, 1); - ufbxi_check(group); - group->id = 0; - group->name = name; - } - - uc->obj.face_group = entry->local_id; - - if (!uc->obj.has_face_group) { - uc->obj.has_face_group = true; - ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_group, uint32_t, uc->obj.tmp_faces.num_items)); - } - - uc->obj.face_group_dirty = false; - } - - size_t num_indices = num_tokens; - ufbxi_check(UINT32_MAX - mesh->num_indices >= num_indices); - - ufbx_face *face = ufbxi_push_fast(&uc->obj.tmp_faces, ufbx_face, 1); - ufbxi_check(face); - - face->index_begin = (uint32_t)mesh->num_indices; - face->num_indices = (uint32_t)num_indices; - - mesh->num_faces++; - mesh->num_indices += num_indices; - - uint32_t *p_face_mat = ufbxi_push_fast(&uc->obj.tmp_face_material, uint32_t, 1); - ufbxi_check(p_face_mat); - *p_face_mat = uc->obj.face_material; - - if (uc->obj.has_face_smoothing) { - bool *p_face_smooth = ufbxi_push_fast(&uc->obj.tmp_face_smoothing, bool, 1); - ufbxi_check(p_face_smooth); - *p_face_smooth = uc->obj.face_smoothing; - } - - if (uc->obj.has_face_group) { - uint32_t *p_face_group = ufbxi_push_fast(&uc->obj.tmp_face_group, uint32_t, 1); - ufbxi_check(p_face_group); - *p_face_group = uc->obj.face_group; - } - - for (size_t ix = 0; ix < num_indices; ix++) { - ufbx_string tok = uc->obj.tokens[token_begin + ix]; - for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { - ufbxi_check(ufbxi_obj_parse_index(uc, &tok, attrib)); - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_multi_indices(ufbxi_context *uc, size_t window) -{ - for (size_t begin = 1; begin + window <= uc->obj.num_tokens; begin++) { - ufbxi_check(ufbxi_obj_parse_indices(uc, begin, window)); - } - return 1; -} - -static ufbxi_noinline uint32_t ufbxi_parse_hex(const char *digits, size_t length) -{ - uint32_t value = 0; - - for (size_t i = 0; i < length; i++) { - char c = digits[i]; - uint32_t v = 0; - if (c >= '0' && c <= '9') { - v = (uint32_t)(c - '0'); - } else if (c >= 'A' && c <= 'F') { - v = (uint32_t)(c - 'A') + 10; - } else if (c >= 'a' && c <= 'f') { - v = (uint32_t)(c - 'a') + 10; - } - value = (value << 4) | v; - } - - return value; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_comment(ufbxi_context *uc) -{ - if (uc->obj.num_tokens >= 3 && ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("MRGB"))) { - size_t num_color = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR]; - - // Pop standard vertex colors and replace them with MRGB colors - if (num_color > uc->obj.mrgb_vertex_count) { - size_t num_pop = num_color - uc->obj.mrgb_vertex_count; - ufbxi_pop(&uc->obj.tmp_color_valid, bool, num_pop, NULL); - ufbxi_pop(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pop * 4, NULL); - uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] -= num_pop; - } - - ufbx_string mrgb = uc->obj.tokens[2]; - for (size_t i = 0; i + 8 <= mrgb.length; i += 8) { - ufbx_real *p_rgba = ufbxi_push(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, 4); - bool *p_valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1); - ufbxi_check(p_rgba && p_valid); - *p_valid = true; - - uint32_t hex = ufbxi_parse_hex(mrgb.data + i, 8); - p_rgba[0] = (ufbx_real)((hex >> 16u) & 0xff) / 255.0f; - p_rgba[1] = (ufbx_real)((hex >> 8u) & 0xff) / 255.0f; - p_rgba[2] = (ufbx_real)((hex >> 0u) & 0xff) / 255.0f; - p_rgba[3] = (ufbx_real)((hex >> 24u) & 0xff) / 255.0f; - } - - uc->obj.has_vertex_color = true; - } - - if (!uc->opts.disable_quirks) { - if (ufbxi_match(&uc->obj.line, "\\s*#\\s*File exported by ZBrush.*")) { - if (!uc->obj.mesh) { - uc->opts.obj_merge_groups = true; - } - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_material(ufbxi_context *uc) -{ - uc->obj.material_dirty = true; - - // Allow empty `usemtl` lines to specify "no material". - if (uc->obj.num_tokens < 2) { - uc->obj.usemtl_fbx_id = 0; - return 1; - } - - ufbx_string name = ufbxi_obj_span_token(uc, 1, SIZE_MAX); - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false)); - - uint64_t fbx_id = ufbxi_synthetic_id_from_string(uc, name.data); - ufbxi_check(fbx_id); - - ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id); - - uc->obj.usemtl_fbx_id = fbx_id; - - if (!entry) { - ufbxi_element_info info = { 0 }; - info.fbx_id = fbx_id; - info.name = name; - - ufbx_material *material = ufbxi_push_element(uc, &info, ufbx_material, UFBX_ELEMENT_MATERIAL); - ufbxi_check(material); - - material->shader_type = UFBX_SHADER_WAVEFRONT_MTL; - material->shading_model_name.data = ufbxi_empty_char; - material->shader_prop_prefix.data = ufbxi_empty_char; - - size_t id = material->element_id; - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tmp_materials, &uc->obj.tmp_materials_cap, id + 1)); - uc->obj.tmp_materials[id] = material; - } - - return 1; -} - -#define ufbxi_obj_cmd1(a) ((uint32_t)(a)<<24u) -#define ufbxi_obj_cmd2(a,b) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16) -#define ufbxi_obj_cmd3(a,b,c) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u) - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_vertices(ufbxi_context *uc, ufbx_real_list *dst, uint32_t attrib, uint64_t min_index) -{ - size_t stride = ufbxi_obj_attrib_stride[attrib]; - ufbxi_check(min_index < uc->obj.tmp_vertices[attrib].num_items / stride); - - size_t count = uc->obj.tmp_vertices[attrib].num_items - (size_t)min_index * stride; - ufbx_real *data = ufbxi_push(&uc->result, ufbx_real, count + 4); - ufbxi_check(data); - - data[0] = 0.0f; - data[1] = 0.0f; - data[2] = 0.0f; - data[3] = 0.0f; - data += 4; - - ufbxi_pop(&uc->obj.tmp_vertices[attrib], ufbx_real, count, data); - - dst->data = data; - dst->count = count; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_setup_attrib(ufbxi_context *uc, ufbxi_obj_mesh *mesh, uint64_t *tmp_indices, - ufbx_vertex_attrib *dst, const ufbx_real_list *p_data, uint32_t attrib, bool non_disjoint, bool required) -{ - ufbx_real_list data = *p_data; - - size_t num_indices = mesh->num_indices; - size_t stride = ufbxi_obj_attrib_stride[attrib]; - size_t num_values = data.count / stride; - - uint64_t mesh_min_ix = mesh->vertex_range[attrib].min_ix; - if (num_indices == 0 || num_values == 0 || mesh_min_ix == UINT64_MAX) { - ufbxi_check(num_indices == 0 || !required); - - // Pop indices without copying if the attribute is not used - ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, NULL); - return 1; - } - - uint64_t min_index = non_disjoint ? 0 : mesh_min_ix; - - ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, tmp_indices); - - uint32_t *dst_indices = ufbxi_push(&uc->result, uint32_t, num_indices); - ufbxi_check(dst_indices); - - dst->exists = true; - - dst->values.data = data.data; - dst->values.count = num_values; - - dst->indices.data = dst_indices; - dst->indices.count = num_indices; - - ufbxi_nounroll for (size_t i = 0; i < num_indices; i++) { - uint64_t ix = tmp_indices[i]; - if (ix != UINT64_MAX) { - ix -= min_index; - ufbxi_check(ix < UINT32_MAX); - } - if (ix < num_values) { - dst_indices[i] = (uint32_t)ix; - } else { - ufbxi_check(ufbxi_fix_index(uc, &dst_indices[i], (uint32_t)ix, num_values)); - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pad_colors(ufbxi_context *uc, size_t num_vertices) -{ - if (uc->opts.ignore_geometry) return 1; - - size_t num_colors = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR]; - if (num_vertices > num_colors) { - size_t num_pad = num_vertices - num_colors; - ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pad * 4)); - ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_color_valid, bool, num_pad)); - uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] += num_pad; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_meshes(ufbxi_context *uc) -{ - size_t num_meshes = uc->obj.tmp_meshes.num_items; - ufbxi_obj_mesh *meshes = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_meshes, ufbxi_obj_mesh, num_meshes); - ufbxi_check(meshes); - - if (uc->obj.has_vertex_color) { - ufbxi_check(ufbxi_obj_pad_colors(uc, uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION])); - } - - // Pop unused fast indices - for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) { - ufbxi_pop(&uc->obj.tmp_indices[i], uint64_t, uc->obj.fast_indices[i].num_left, NULL); - } - - // Check if the file has disjoint vertices - bool non_disjoint[UFBXI_OBJ_NUM_ATTRIBS] = { 0 }; - uint64_t next_min[UFBXI_OBJ_NUM_ATTRIBS] = { 0 }; - ufbx_real_list vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT] = { 0 }; - bool *color_valid = NULL; - - size_t max_indices = 0; - - for (size_t i = 0; i < num_meshes; i++) { - ufbxi_obj_mesh *mesh = &meshes[i]; - max_indices = ufbxi_max_sz(max_indices, mesh->num_indices); - ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { - ufbxi_obj_index_range range = mesh->vertex_range[attrib]; - if (range.min_ix > range.max_ix) continue; - if (range.min_ix < next_min[attrib]) { - non_disjoint[attrib] = true; - } - next_min[attrib] = range.max_ix + 1; - } - } - - uint64_t *tmp_indices = ufbxi_push(&uc->tmp, uint64_t, max_indices); - ufbxi_check(tmp_indices); - - ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { - if (!non_disjoint[attrib]) continue; - ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, 0)); - } - if (uc->obj.has_vertex_color && non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) { - ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, 0)); - color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4); - ufbxi_check(color_valid); - } - - for (size_t i = num_meshes; i > 0; i--) { - ufbxi_obj_mesh *mesh = &meshes[i - 1]; - - ufbx_mesh *fbx_mesh = mesh->fbx_mesh; - - size_t num_faces = mesh->num_faces; - - if (!uc->opts.ignore_geometry) { - ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) { - if (non_disjoint[attrib]) continue; - uint64_t min_ix = mesh->vertex_range[attrib].min_ix; - if (min_ix < UINT64_MAX) { - ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, min_ix)); - } - } - if (uc->obj.has_vertex_color && !non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) { - uint64_t min_ix = mesh->vertex_range[UFBXI_OBJ_ATTRIB_POSITION].min_ix; - ufbxi_check(min_ix < UINT64_MAX); - ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, min_ix)); - color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4); - ufbxi_check(color_valid); - } - - fbx_mesh->faces.count = num_faces; - fbx_mesh->face_material.count = num_faces; - - fbx_mesh->faces.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_faces, ufbx_face, num_faces); - fbx_mesh->face_material.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_material, uint32_t, num_faces); - - ufbxi_check(fbx_mesh->faces.data); - ufbxi_check(fbx_mesh->face_material.data); - - if (uc->obj.has_face_smoothing) { - fbx_mesh->face_smoothing.count = num_faces; - fbx_mesh->face_smoothing.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_smoothing, bool, num_faces); - ufbxi_check(fbx_mesh->face_smoothing.data); - } - - if (uc->obj.has_face_group) { - if (mesh->num_groups > 1) { - fbx_mesh->face_group.count = num_faces; - fbx_mesh->face_group.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group, uint32_t, num_faces); - ufbxi_check(fbx_mesh->face_group.data); - } else { - ufbxi_pop(&uc->obj.tmp_face_group, uint32_t, num_faces, NULL); - } - } - - ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_position, - &vertices[UFBXI_OBJ_ATTRIB_POSITION], UFBXI_OBJ_ATTRIB_POSITION, non_disjoint[UFBXI_OBJ_ATTRIB_POSITION], true)); - - ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_uv, - &vertices[UFBXI_OBJ_ATTRIB_UV], UFBXI_OBJ_ATTRIB_UV, non_disjoint[UFBXI_OBJ_ATTRIB_UV], false)); - - ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_normal, - &vertices[UFBXI_OBJ_ATTRIB_NORMAL], UFBXI_OBJ_ATTRIB_NORMAL, non_disjoint[UFBXI_OBJ_ATTRIB_NORMAL], false)); - - if (uc->obj.has_vertex_color) { - ufbx_assert(color_valid); - bool has_color = false; - bool all_valid = true; - size_t max_index = fbx_mesh->vertex_position.values.count; - ufbxi_for_list(uint32_t, p_ix, fbx_mesh->vertex_position.indices) { - if (*p_ix < max_index) { - if (color_valid[*p_ix]) { - has_color = true; - } else { - all_valid = false; - } - } - } - - if (has_color) { - fbx_mesh->vertex_color.exists = true; - fbx_mesh->vertex_color.values.data = (ufbx_vec4*)vertices[UFBXI_OBJ_ATTRIB_COLOR].data; - fbx_mesh->vertex_color.values.count = vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4; - fbx_mesh->vertex_color.indices = fbx_mesh->vertex_position.indices; - fbx_mesh->vertex_color.unique_per_vertex = true; - - if (!all_valid) { - uint32_t *indices = fbx_mesh->vertex_color.indices.data; - indices = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, indices); - ufbxi_check(indices); - - size_t num_values = fbx_mesh->vertex_color.values.count; - ufbxi_for(uint32_t, p_ix, indices, mesh->num_indices) { - if (*p_ix >= num_values || !color_valid[*p_ix]) { - ufbxi_check(ufbxi_fix_index(uc, p_ix, *p_ix, num_values)); - } - } - - fbx_mesh->vertex_color.indices.data = indices; - } - } - } - } - - ufbxi_check(ufbxi_finalize_mesh(&uc->result, &uc->error, fbx_mesh)); - - if (uc->retain_mesh_parts) { - fbx_mesh->face_group_parts.count = mesh->num_groups; - fbx_mesh->face_group_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, mesh->num_groups); - ufbxi_check(fbx_mesh->face_group_parts.data); - } - - if (mesh->num_groups > 1) { - ufbxi_check(ufbxi_update_face_groups(&uc->result, &uc->error, fbx_mesh, false)); - } else if (mesh->num_groups == 1) { - fbx_mesh->face_group.data = (uint32_t*)ufbxi_sentinel_index_zero; - fbx_mesh->face_group.count = num_faces; - // NOTE: Consecutive and zero indices are always allocated so we can skip doing it here, - // see HACK(consecutiv-faces).. - if (fbx_mesh->face_group_parts.count > 0) { - ufbx_mesh_part *part = &fbx_mesh->face_group_parts.data[0]; - part->num_faces = fbx_mesh->num_faces; - part->num_faces = num_faces; - part->num_empty_faces = fbx_mesh->num_empty_faces; - part->num_point_faces = fbx_mesh->num_point_faces; - part->num_line_faces = fbx_mesh->num_line_faces; - part->num_triangles = fbx_mesh->num_triangles; - part->face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive; - part->face_indices.count = num_faces; - } - } - - // HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero - // index buffer for face materials.. - uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, num_faces); - uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, num_faces); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_file(ufbxi_context *uc) -{ - while (!uc->obj.eof) { - ufbxi_check(ufbxi_obj_tokenize_line(uc)); - size_t num_tokens = uc->obj.num_tokens; - if (num_tokens == 0) continue; - - ufbx_string cmd = uc->obj.tokens[0]; - uint32_t key = ufbxi_get_name_key(cmd.data, cmd.length); - if (key == ufbxi_obj_cmd1('v')) { - ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_POSITION, 1)); - if (num_tokens >= 7) { - size_t num_vertices = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION]; - uc->obj.has_vertex_color = true; - ufbxi_check(ufbxi_obj_pad_colors(uc, num_vertices - 1)); - if (uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] < num_vertices) { - ufbx_assert(uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] == num_vertices - 1); - ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_COLOR, 4)); - bool *valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1); - ufbxi_check(valid); - *valid = true; - } - } - } else if (key == ufbxi_obj_cmd2('v','t')) { - ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_UV, 1)); - } else if (key == ufbxi_obj_cmd2('v','n')) { - ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_NORMAL, 1)); - } else if (key == ufbxi_obj_cmd1('f')) { - ufbxi_check(ufbxi_obj_parse_indices(uc, 1, uc->obj.num_tokens - 1)); - } else if (key == ufbxi_obj_cmd1('p')) { - ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 1)); - } else if (key == ufbxi_obj_cmd1('l')) { - ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 2)); - } else if (key == ufbxi_obj_cmd1('s')) { - if (num_tokens >= 2) { - uc->obj.has_face_smoothing = true; - uc->obj.face_smoothing = !ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("off")); - - // Fill in previously missed face smoothing data - if (uc->obj.tmp_face_smoothing.num_items == 0 && uc->obj.tmp_faces.num_items > 0) { - ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_smoothing, bool, uc->obj.tmp_faces.num_items)); - } - } - } else if (key == ufbxi_obj_cmd1('o')) { - if (num_tokens >= 2) { - uc->obj.object = ufbxi_obj_span_token(uc, 1, SIZE_MAX); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.object, false)); - uc->obj.object_dirty = true; - } - } else if (key == ufbxi_obj_cmd1('g')) { - if (num_tokens >= 2) { - uc->obj.group = ufbxi_obj_span_token(uc, 1, SIZE_MAX); - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.group, false)); - uc->obj.group_dirty = true; - } else { - uc->obj.group = ufbx_empty_string; - uc->obj.group_dirty = true; - } - } else if (key == ufbxi_obj_cmd1('#')) { - ufbxi_check(ufbxi_obj_parse_comment(uc)); - } else if (ufbxi_str_equal(cmd, ufbxi_str_c("mtllib"))) { - ufbxi_check(uc->obj.num_tokens >= 2); - ufbx_string lib = ufbxi_obj_span_token(uc, 1, SIZE_MAX); - lib.data = ufbxi_push_copy(&uc->tmp, char, lib.length + 1, lib.data); - ufbxi_check(lib.data); - uc->obj.mtllib_relative_path.data = lib.data; - uc->obj.mtllib_relative_path.size = lib.length; - } else if (ufbxi_str_equal(cmd, ufbxi_str_c("usemtl"))) { - ufbxi_check(ufbxi_obj_parse_material(uc)); - } else if (!uc->opts.disable_quirks && key == 0) { - // ZBrush exporter seems to end the files with '\0', sometimes.. - } else { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, "Unknown .obj directive, skipped line")); - } - } - - ufbxi_check(ufbxi_obj_flush_mesh(uc)); - ufbxi_check(ufbxi_obj_pop_meshes(uc)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_material(ufbxi_context *uc) -{ - if (uc->obj.usemtl_fbx_id == 0) return 1; - - ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id); - ufbx_assert(entry); - ufbx_material *material = uc->obj.tmp_materials[entry->element_id]; - - size_t num_props = uc->obj.tmp_props.num_items; - ufbxi_check(ufbxi_obj_pop_props(uc, &material->props.props, num_props)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_prop(ufbxi_context *uc, ufbx_string name, size_t start, bool include_rest, size_t *p_next) -{ - if (start >= uc->obj.num_tokens) { - if (p_next) { - *p_next = start; - } - return 1; - } - - ufbx_prop *prop = ufbxi_push_zero(&uc->obj.tmp_props, ufbx_prop, 1); - ufbxi_check(prop); - prop->name = name; - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->name, false)); - - uint32_t flags = UFBX_PROP_FLAG_VALUE_STR; - - size_t num_reals = 0; - for (; num_reals < 4; num_reals++) { - if (start + num_reals >= uc->obj.num_tokens) break; - ufbx_string tok = uc->obj.tokens[start + num_reals]; - - char *end; // ufbxi_uninit - double val = ufbxi_parse_double(tok.data, tok.length, &end, uc->double_parse_flags); - if (end != tok.data + tok.length) break; - - prop->value_real_arr[num_reals] = (ufbx_real)val; - if (num_reals == 0) { - prop->value_int = ufbxi_f64_to_i64(val); - flags |= UFBX_PROP_FLAG_VALUE_INT; - } - } - - size_t num_args = 0; - if (!include_rest) { - for (; start + num_args < uc->obj.num_tokens - 1; num_args++) { - if (ufbxi_match(&uc->obj.tokens[start + num_args], "-[A-Za-z][\\-A-Za-z0-9_]*")) break; - } - } - - if (num_args > 0 || include_rest) { - ufbx_string span = ufbxi_obj_span_token(uc, start, include_rest ? SIZE_MAX : start + num_args - 1); - prop->value_str = span; - prop->value_blob.data = span.data; - prop->value_blob.size = span.length; - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->value_str, false)); - ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &prop->value_blob, true)); - } else { - prop->value_str.data = ufbxi_empty_char; - } - - if (num_reals > 0) { - flags = (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (num_reals - 1); - } else { - if (!strcmp(prop->value_str.data, "on")) { - prop->value_int = 1; - prop->value_real = 1.0f; - flags |= UFBX_PROP_FLAG_VALUE_INT; - } else if (!strcmp(prop->value_str.data, "off")) { - prop->value_int = 0; - prop->value_real = 0.0f; - flags |= UFBX_PROP_FLAG_VALUE_INT; - } - } - - prop->flags = (ufbx_prop_flags)flags; - - if (p_next) { - *p_next = start + num_args; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl_map(ufbxi_context *uc, size_t prefix_len) -{ - if (uc->obj.num_tokens < 2) return 1; - - size_t num_props = 1; - ufbxi_check(ufbxi_obj_parse_prop(uc, ufbxi_str_c("obj|args"), 1, true, NULL)); - - size_t start = 1; - for (; start + 1 < uc->obj.num_tokens; ) { - ufbx_string tok = uc->obj.tokens[start]; - if (ufbxi_match(&tok, "-[A-Za-z][\\-A-Za-z0-9_]*")) { - tok.data += 1; - tok.length -= 1; - ufbxi_check(ufbxi_obj_parse_prop(uc, tok, start + 1, false, &start)); - num_props++; - } else { - break; - } - } - - ufbx_string tex_str = ufbxi_obj_span_token(uc, start, SIZE_MAX); - ufbx_blob tex_raw = { tex_str.data, tex_str.length }; - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tex_str, false)); - ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &tex_raw, true)); - - uint64_t fbx_id = 0; - ufbx_texture *texture = ufbxi_push_synthetic_element(uc, &fbx_id, NULL, "", ufbx_texture, UFBX_ELEMENT_TEXTURE); - ufbxi_check(texture); - - texture->filename.data = ufbxi_empty_char; - texture->absolute_filename.data = ufbxi_empty_char; - texture->uv_set.data = ufbxi_empty_char; - - texture->relative_filename = tex_str; - texture->raw_relative_filename = tex_raw; - - ufbxi_check(ufbxi_obj_pop_props(uc, &texture->props.props, num_props)); - - ufbx_string prop = uc->obj.tokens[0]; - ufbx_assert(prop.length >= prefix_len); - prop.data += prefix_len; - prop.length -= prefix_len; - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop, false)); - - if (uc->obj.usemtl_fbx_id != 0) { - ufbxi_check(ufbxi_connect_op(uc, fbx_id, uc->obj.usemtl_fbx_id, prop)); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl(ufbxi_context *uc) -{ - uc->obj.mesh = NULL; - uc->obj.usemtl_fbx_id = 0; - - while (!uc->obj.eof) { - ufbxi_check(ufbxi_obj_tokenize_line(uc)); - size_t num_tokens = uc->obj.num_tokens; - if (num_tokens == 0) continue; - - ufbx_string cmd = uc->obj.tokens[0]; - if (ufbxi_str_equal(cmd, ufbxi_str_c("newmtl"))) { - // HACK: Reuse mesh material parsing, but don't allow for empty material name - ufbxi_check(uc->obj.num_tokens >= 2); - ufbxi_check(ufbxi_obj_flush_material(uc)); - ufbxi_check(ufbxi_obj_parse_material(uc)); - } else if (cmd.length > 4 && !memcmp(cmd.data, "map_", 4)) { - ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 4)); - } else if (cmd.length == 4 && (!memcmp(cmd.data, "bump", 4) || !memcmp(cmd.data, "disp", 4) || !memcmp(cmd.data, "norm", 4))) { - ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 0)); - } else if (cmd.length == 1 && cmd.data[0] == '#') { - // Implement .mtl magic comment handling here if necessary - } else { - ufbxi_check(ufbxi_obj_parse_prop(uc, uc->obj.tokens[0], 1, true, NULL)); - } - } - - ufbxi_check(ufbxi_obj_flush_material(uc)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load_mtl(ufbxi_context *uc) -{ - // HACK: Reset everything and switch to loading the .mtl file globally - if (uc->close_fn) { - uc->close_fn(uc->read_user); - } - - uc->read_fn = NULL; - uc->close_fn = NULL; - uc->read_user = NULL; - uc->data_begin = NULL; - uc->data = NULL; - uc->data_size = 0; - uc->yield_size = 0; - uc->eof = false; - uc->obj.eof = false; - - if (uc->opts.obj_mtl_data.size > 0) { - uc->data_begin = uc->data = (const char*)uc->opts.obj_mtl_data.data; - uc->data_size = uc->opts.obj_mtl_data.size; - ufbxi_check(ufbxi_obj_parse_mtl(uc)); - return 1; - } - - ufbx_stream stream = { 0 }; - bool has_stream = false; - bool needs_stream = false; - ufbx_blob stream_path = { 0 }; - - if (uc->opts.open_file_cb.fn) { - if (uc->opts.obj_mtl_path.length > 0) { - has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->opts.obj_mtl_path.data, uc->opts.obj_mtl_path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); - stream_path.data = uc->opts.obj_mtl_path.data; - stream_path.size = uc->opts.obj_mtl_path.length; - needs_stream = true; - if (!has_stream) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", uc->opts.obj_mtl_path.data)); - } - } - - if (!has_stream && uc->opts.load_external_files && uc->obj.mtllib_relative_path.size > 0) { - ufbx_blob dst; // ufbxi_uninit - ufbxi_check(ufbxi_resolve_relative_filename(uc, (ufbxi_strblob*)&dst, (const ufbxi_strblob*)&uc->obj.mtllib_relative_path, true)); - has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, (const char*)dst.data, dst.size, &uc->obj.mtllib_relative_path, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); - stream_path = uc->obj.mtllib_relative_path; - needs_stream = true; - if (!has_stream) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", dst.data)); - } - } - - ufbx_string path = uc->scene.metadata.filename; - if (!has_stream && uc->opts.load_external_files && uc->opts.obj_search_mtl_by_filename && path.length > 4) { - ufbx_string ext = { path.data + path.length - 4, 4 }; - if (ufbxi_match(&ext, "\\c.obj")) { - ufbxi_analysis_assert(path.length < SIZE_MAX - 1); - char *copy = ufbxi_push_copy(&uc->tmp, char, path.length + 1, path.data); - ufbxi_check(copy); - copy[path.length - 3] = copy[path.length - 3] == 'O' ? 'M' : 'm'; - copy[path.length - 2] = copy[path.length - 2] == 'B' ? 'T' : 't'; - copy[path.length - 1] = copy[path.length - 1] == 'J' ? 'L' : 'l'; - has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, copy, path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); - if (has_stream) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_IMPLICIT_MTL, "Opened .mtl file derived from .obj filename: %s", copy)); - } - } - } - } - - if (has_stream) { - // Adopt `stream` to ufbx read callbacks - uc->read_fn = stream.read_fn; - uc->close_fn = stream.close_fn; - uc->read_user = stream.user; - - int ok = ufbxi_obj_parse_mtl(uc); - - if (uc->close_fn) { - uc->close_fn(uc->read_user); - } - uc->read_fn = NULL; - uc->close_fn = NULL; - uc->read_user = NULL; - - ufbxi_check(ok); - } else if (needs_stream && !uc->opts.ignore_missing_external_files) { - ufbxi_set_err_info(&uc->error, (const char*)stream_path.data, stream_path.size); - ufbxi_fail_msg("ufbxi_obj_load_mtl()", "External file not found"); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_obj_init(uc)); - ufbxi_check(ufbxi_obj_parse_file(uc)); - ufbxi_check(ufbxi_init_file_paths(uc)); - ufbxi_check(ufbxi_obj_load_mtl(uc)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_mtl_load(ufbxi_context *uc) -{ - ufbxi_check(ufbxi_obj_init(uc)); - ufbxi_check(ufbxi_init_file_paths(uc)); - ufbxi_check(ufbxi_obj_parse_mtl(uc)); - - return 1; -} - -#else -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_obj_load(ufbxi_context *uc) -{ - ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ"); - ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled"); -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_mtl_load(ufbxi_context *uc) -{ - ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ"); - ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled"); -} - -static ufbxi_forceinline void ufbxi_obj_free(ufbxi_context *uc) -{ -} -#endif - -// -- Scene pre-processing - -typedef struct { - ufbx_element *src, *dst; -} ufbxi_pre_connection; - -typedef struct { - bool has_constant_scale; - bool has_recursive_scale_helper; - bool has_skin_deformer; - ufbx_vec3 constant_scale; - uint32_t element_id; - uint32_t first_child; - uint32_t next_child; - uint32_t parent; -} ufbxi_pre_node; - -typedef struct { - bool has_skin_deformer; -} ufbxi_pre_mesh; - -typedef struct { - bool has_constant_value; - ufbx_vec3 constant_value; -} ufbxi_pre_anim_value; - -static bool ufbxi_pivot_nonzero(ufbx_vec3 offset) -{ - // TODO: Expose this as a setting? - const double epsilon = 0.0009765625; - return ufbx_fabs(offset.x) >= epsilon || ufbx_fabs(offset.y) >= epsilon || ufbx_fabs(offset.z) >= epsilon; -} - -static ufbx_real ufbxi_pivot_div(ufbx_real offset, ufbx_real initial_scale) -{ - const double epsilon = 0.0078125; - if (ufbx_fabs(initial_scale) >= epsilon) { - return offset / initial_scale; - } else { - return offset; - } -} - -// Called between parsing and `ufbxi_finalize_scene()`. -// This is a very messy function reminiscent of the _old_ ufbx, where we do -// multiple passes over connections without having a proper scene graph. -// This, however gives us the advantage of allowing us to modify elements -// and connections. We can, for example, add new helper nodes and redirect -// animated properties from source nodes to the helpers. The rest of ufbx -// will treat these as if they were a part of the source file. -ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context *uc) -{ - bool required = false; - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES || uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) required = true; - if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_HELPER_NODES || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) required = true; - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT || uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) required = true; -#if defined(UFBX_REGRESSION) - required = true; -#endif - - if (!required) return 1; - - uint32_t num_elements = uc->num_elements; - size_t num_nodes = uc->tmp_node_ids.num_items; - ufbx_element **elements = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_ptrs, ufbx_element*, num_elements); - ufbxi_check(elements); - - size_t num_connections = uc->tmp_connections.num_items; - ufbxi_tmp_connection *tmp_connections = ufbxi_push_peek(&uc->tmp_parse, &uc->tmp_connections, ufbxi_tmp_connection, num_connections); - ufbxi_check(tmp_connections); - - ufbxi_pre_connection *pre_connections = ufbxi_push(&uc->tmp_parse, ufbxi_pre_connection, num_connections); - ufbxi_check(pre_connections); - - uint32_t *instance_counts = ufbxi_push_zero(&uc->tmp_parse, uint32_t, num_elements); - ufbxi_check(instance_counts); - - bool *modify_not_supported = ufbxi_push_zero(&uc->tmp_parse, bool, num_elements); - ufbxi_check(modify_not_supported); - - ufbx_element_type *node_attrib_type = ufbxi_push_zero(&uc->tmp_parse, ufbx_element_type, num_nodes); - ufbxi_check(node_attrib_type); - - bool *has_unscaled_children = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes); - ufbxi_check(has_unscaled_children); - - bool *has_scale_animation = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes); - ufbxi_check(has_scale_animation); - - ufbxi_pre_node *pre_nodes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_node, num_nodes); - ufbxi_check(pre_nodes); - - size_t num_meshes = uc->tmp_typed_element_offsets[UFBX_ELEMENT_MESH].num_items; - ufbxi_pre_mesh *pre_meshes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_mesh, num_meshes); - ufbxi_check(pre_meshes); - - size_t num_anim_values = uc->tmp_typed_element_offsets[UFBX_ELEMENT_ANIM_VALUE].num_items; - ufbxi_pre_anim_value *pre_anim_values = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_anim_value, num_anim_values); - ufbxi_check(pre_anim_values); - - uint64_t *fbx_ids = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_fbx_ids, uint64_t, num_elements); - ufbxi_check(fbx_ids); - - // TODO - const ufbx_real scale_epsilon = 0.001f; - const ufbx_real pivot_epsilon = 0.001f; - const ufbx_real compensate_epsilon = 0.01f; - - for (size_t i = 0; i < num_elements; i++) { - ufbx_element *element = elements[i]; - uint32_t id = element->typed_id; - - if (element->type == UFBX_ELEMENT_NODE) { - ufbxi_pre_node *pre_node = &pre_nodes[id]; - pre_node->has_constant_scale = true; - pre_node->constant_scale = ufbxi_find_vec3(&element->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); - pre_node->element_id = element->element_id; - pre_node->first_child = ~0u; - pre_node->next_child = ~0u; - pre_node->parent = ~0u; - } if (element->type == UFBX_ELEMENT_ANIM_VALUE) { - ufbxi_pre_anim_value *pre_value = &pre_anim_values[id]; - pre_value->has_constant_value = true; - pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_X, UFBX_NAN); - pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_d_X, pre_value->constant_value.x); - pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_Y, UFBX_NAN); - pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_d_Y, pre_value->constant_value.y); - pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_Z, UFBX_NAN); - pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_d_Z, pre_value->constant_value.z); - } - } - - for (size_t i = 0; i < num_connections; i++) { - ufbxi_tmp_connection *tmp = &tmp_connections[i]; - ufbxi_pre_connection *pre = &pre_connections[i]; - - ufbxi_fbx_id_entry *src_entry = ufbxi_find_fbx_id(uc, tmp->src); - ufbxi_fbx_id_entry *dst_entry = ufbxi_find_fbx_id(uc, tmp->dst); - - ufbx_element *src = src_entry ? elements[src_entry->element_id] : NULL; - ufbx_element *dst = dst_entry ? elements[dst_entry->element_id] : NULL; - pre->src = src; - pre->dst = dst; - if (!src || !dst) continue; - - if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) { - // Count number of instances of each attribute - if (dst->type == UFBX_ELEMENT_NODE) { - ufbx_node *dst_node = (ufbx_node*)dst; - - if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { - uint32_t count = ++instance_counts[src->element_id]; - node_attrib_type[dst->typed_id] = count == 1 ? src->type : UFBX_ELEMENT_UNKNOWN; - - // These must match what can be trasnsformed in `ufbxi_modify_geometry()` - switch (src->type) { - case UFBX_ELEMENT_MESH: - case UFBX_ELEMENT_LINE_CURVE: - case UFBX_ELEMENT_NURBS_CURVE: - case UFBX_ELEMENT_NURBS_SURFACE: - break; // Nop, supported - default: - modify_not_supported[dst->element_id] = true; - break; - } - } - - if (src->type == UFBX_ELEMENT_NODE) { - ufbx_node *src_node = (ufbx_node*)src; - ufbxi_pre_node *pre_dst = &pre_nodes[dst_node->typed_id]; - ufbxi_pre_node *pre_src = &pre_nodes[src_node->typed_id]; - - // Remember parent and add children into a linked list - if (pre_src->parent == ~0u) { - pre_src->parent = dst_node->typed_id; - pre_src->next_child = pre_dst->first_child; - pre_dst->first_child = src_node->typed_id; - } - - if (uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_PRESERVE) { - if (!dst_node->is_root && src_node->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) { - has_unscaled_children[dst->typed_id] = true; - } - } - } - } else if (dst->type == UFBX_ELEMENT_MESH) { - if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) { - ufbxi_pre_mesh *pre_mesh = &pre_meshes[dst->typed_id]; - pre_mesh->has_skin_deformer = true; - } - } - } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { - const char *dst_prop = tmp->dst_prop.data; - if (dst->type == UFBX_ELEMENT_ANIM_VALUE && src->type == UFBX_ELEMENT_ANIM_CURVE) { - ufbx_anim_curve *src_curve = (ufbx_anim_curve*)src; - uint32_t index = 0; - if (dst_prop == ufbxi_Y || dst_prop == ufbxi_d_Y) { - index = 1; - } else if (dst_prop == ufbxi_Z || dst_prop == ufbxi_d_Z) { - index = 2; - } - - ufbxi_pre_anim_value *pre_value = &pre_anim_values[dst->typed_id]; - if (src_curve->max_value - src_curve->min_value >= scale_epsilon) { - pre_value->has_constant_value = false; - } else { - ufbx_real constant_value = (src_curve->min_value + src_curve->max_value) * 0.5f; - if (ufbx_isnan(pre_value->constant_value.v[index])) { - pre_value->constant_value.v[index] = constant_value; - } - if ((ufbx_real)ufbx_fabs(pre_value->constant_value.v[index] - constant_value) > scale_epsilon) { - pre_value->has_constant_value = false; - } - } - } - } - } - - for (size_t i = 0; i < num_connections; i++) { - ufbxi_tmp_connection *tmp = &tmp_connections[i]; - ufbxi_pre_connection *pre = &pre_connections[i]; - ufbx_element *src = pre->src, *dst = pre->dst; - if (!src || !dst) continue; - - if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) { - // Count maximum number of instanced attributes in a node - if (dst->type == UFBX_ELEMENT_NODE) { - if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { - instance_counts[dst->element_id] = ufbxi_max32(instance_counts[dst->element_id], instance_counts[src->element_id]); - if (src->type == UFBX_ELEMENT_MESH) { - ufbxi_pre_mesh *pre_mesh = &pre_meshes[src->typed_id]; - if (pre_mesh->has_skin_deformer) { - pre_nodes[dst->typed_id].has_skin_deformer = true; - } - } - } else if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) { - pre_nodes[dst->typed_id].has_skin_deformer = true; - } - } - } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { - if (dst->type == UFBX_ELEMENT_NODE) { - if (src->type == UFBX_ELEMENT_ANIM_VALUE) { - if (tmp->dst_prop.data == ufbxi_Lcl_Scaling) { - ufbxi_pre_node *pre_node = &pre_nodes[dst->typed_id]; - if (pre_node->has_constant_scale) { - ufbxi_pre_anim_value *pre_value = &pre_anim_values[src->typed_id]; - if (!pre_value->has_constant_value) { - pre_node->has_constant_scale = false; - } else { - ufbx_real error = 0.0f; - error += (ufbx_real)ufbx_fabs(pre_value->constant_value.x - pre_node->constant_scale.x); - error += (ufbx_real)ufbx_fabs(pre_value->constant_value.y - pre_node->constant_scale.y); - error += (ufbx_real)ufbx_fabs(pre_value->constant_value.z - pre_node->constant_scale.z); - if (error >= scale_epsilon) { - pre_node->has_constant_scale = false; - } - } - } - } - } - } - } - } - - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT || uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { - for (size_t i = 0; i < num_nodes; i++) { - ufbxi_pre_node *pre_node = &pre_nodes[i]; - ufbx_node *node = (ufbx_node*)elements[pre_node->element_id]; - - ufbx_vec3 rotation_pivot = ufbxi_find_vec3(&node->props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling_pivot = ufbxi_find_vec3(&node->props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling_offset = ufbxi_find_vec3(&node->props, ufbxi_ScalingOffset, 0.0f, 0.0f, 0.0f); - - bool should_modify_pivot = false; - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { - should_modify_pivot = !ufbxi_is_vec3_zero(rotation_pivot); - } else if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { - should_modify_pivot = ufbxi_pivot_nonzero(rotation_pivot) || ufbxi_pivot_nonzero(scaling_pivot) || ufbxi_pivot_nonzero(scaling_offset); - } - - if (should_modify_pivot) { - bool skip_geometry_transform = false; - bool can_modify_geometry_transform = true; - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { - if (node_attrib_type[node->typed_id] == UFBX_ELEMENT_EMPTY) { - if (!uc->opts.pivot_handling_retain_empties) { - skip_geometry_transform = true; - } else { - can_modify_geometry_transform = false; - } - } - } - - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) { - if (instance_counts[node->element_id] > 1 || modify_not_supported[node->element_id]) { - can_modify_geometry_transform = false; - } - } - // Currently, geometry transform messes up skinning - if (pre_node->has_skin_deformer) { - can_modify_geometry_transform = false; - } - - bool can_modify_pivot = true; - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { - ufbx_real err = 0.0f; - err += (ufbx_real)ufbx_fabs(rotation_pivot.x - scaling_pivot.x); - err += (ufbx_real)ufbx_fabs(rotation_pivot.y - scaling_pivot.y); - err += (ufbx_real)ufbx_fabs(rotation_pivot.z - scaling_pivot.z); - if (err > pivot_epsilon) { - can_modify_pivot = false; - } - } - - if (can_modify_pivot && (can_modify_geometry_transform || skip_geometry_transform)) { - ufbx_vec3 geometric_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); - - ufbx_vec3 child_offset = { 0.0f }; - ufbx_prop *new_props = NULL; - size_t num_props = node->props.props.count; - size_t new_prop_count = num_props; - if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { - ufbx_assert(!skip_geometry_transform); // not supporeted in legacy mode - child_offset = ufbxi_neg3(rotation_pivot); - geometric_translation = ufbxi_add3(geometric_translation, child_offset); - - new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 3); - ufbxi_check(new_props); - memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop)); - - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR); - } else if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT) { - // We can eliminate the post-rotation translation and move it to the geometry/children as follows. - // Let Z be the initial value of S in the transform (aka `initial_scale`): - // - // (Rp-1+Soff+Sp) + S * (Sp-1) - // S * (Sp-1 + (Rp-1+Soff+Sp)/S) - // S * (Sp-1 + (Rp-1+Soff+Sp)/S - (Rp-1+Soff+Sp)/Z + (Rp-1+Soff+Sp)/Z) - // - // (Rp-1 + Soff + Sp) + S * (-(Rp-1 + Soff + Sp)/Z + (Sp-1 + (Rp-1 + Soff + Sp)/Z)) - // ^-scaled_offset--^ ^-unscaled_offset--^ ^-unscaled_offset--^ - // ^---------------- 0, when S=Z ----------------^ ^------- child_offset ------^ - // - // We need to be careful when doing this in case any component of Z is 0. Fortunately, - // the above holds for all `Z != 0`, it will just result in non-zero translation in the parent. - ufbx_vec3 initial_scale = ufbxi_find_vec3(&node->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); - ufbx_vec3 scaled_offset = ufbxi_sub3(ufbxi_add3(scaling_offset, scaling_pivot), rotation_pivot); - ufbx_vec3 unscaled_offset; - unscaled_offset.x = ufbxi_pivot_div(scaled_offset.x, initial_scale.x); - unscaled_offset.y = ufbxi_pivot_div(scaled_offset.y, initial_scale.y); - unscaled_offset.z = ufbxi_pivot_div(scaled_offset.z, initial_scale.z); - - // Convert `scaled_offset + S*unscaled_offset` to FBX scaling pivot and offset. - ufbx_vec3 new_scaling_pivot = unscaled_offset; - ufbx_vec3 new_scaling_offset = ufbxi_sub3(scaled_offset, new_scaling_pivot); - child_offset = ufbxi_sub3(unscaled_offset, scaling_pivot); - - new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 4); - ufbxi_check(new_props); - memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop)); - - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingPivot, &new_scaling_pivot, UFBX_PROP_VECTOR); - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_ScalingOffset, &new_scaling_offset, UFBX_PROP_VECTOR); - if (!skip_geometry_transform) { - geometric_translation = ufbxi_add3(geometric_translation, child_offset); - ufbxi_init_synthetic_vec3_prop(&new_props[new_prop_count++], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR); - } - } - - node->props.props.data = new_props; - node->props.props.count = new_prop_count; - ufbxi_check(ufbxi_sort_properties(uc, node->props.props.data, node->props.props.count)); - ufbxi_deduplicate_properties(&node->props.props); - - node->adjust_pre_translation = ufbxi_add3(node->adjust_pre_translation, rotation_pivot); - node->has_adjust_transform = true; - uint32_t ix = pre_node->first_child; - while (ix != ~0u) { - ufbxi_pre_node *pre_child = &pre_nodes[ix]; - ufbx_node *child = (ufbx_node*)elements[pre_child->element_id]; - - child->adjust_pre_translation = ufbxi_add3(child->adjust_pre_translation, child_offset); - child->has_adjust_transform = true; - - ix = pre_child->next_child; - } - } - } - } - } - - for (size_t i = 0; i < num_elements; i++) { - ufbx_element *element = elements[i]; - uint64_t fbx_id = fbx_ids[i]; - - if (element->type == UFBX_ELEMENT_NODE) { - ufbx_node *node = (ufbx_node*)element; - bool requires_helper_node = false; - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES) { - requires_helper_node = true; - } else if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) { - // Setup a geometry transform helper for nodes that have instanced attributes - requires_helper_node = instance_counts[i] > 1 || modify_not_supported[i]; - } - if (requires_helper_node) { - ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, node, fbx_id)); - } - } - } - - for (size_t i = 0; i < num_elements; i++) { - ufbx_element *element = elements[i]; - uint64_t fbx_id = fbx_ids[i]; - - if (element->type == UFBX_ELEMENT_NODE) { - ufbx_node *node = (ufbx_node*)element; - if (has_unscaled_children[node->typed_id] && !node->scale_helper) { - ufbxi_pre_node *pre_node = &pre_nodes[node->typed_id]; - ufbx_real ref = uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE - ? pre_node->constant_scale.x : (ufbx_real)1.0f; - ufbx_vec3 scale = pre_node->constant_scale; - ufbx_real dx = (ufbx_real)ufbx_fabs(scale.x - ref); - ufbx_real dy = (ufbx_real)ufbx_fabs(scale.y - ref); - ufbx_real dz = (ufbx_real)ufbx_fabs(scale.z - ref); - if ((dx + dy + dz >= scale_epsilon || !pre_node->has_constant_scale || (ufbx_real)ufbx_fabs(scale.x) <= compensate_epsilon) - && uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) { - ufbxi_check(ufbxi_setup_scale_helper(uc, node, fbx_id)); - - // If we added a geometry transform helper that may scale further helpers - // recursively for all child nodes using `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE` - // This is guaranteed to terminate as `ufbxi_pre_node` may only have one parent, - // meaning any cycles must contain `node` itself. - uint32_t ix = pre_node->first_child; - while (ix != ~0u && ix != node->typed_id) { - ufbxi_pre_node *pre_child = &pre_nodes[ix]; - ufbx_node *child = (ufbx_node*)elements[pre_child->element_id]; - - if (pre_child->parent != node->typed_id || child->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) { - if (!pre_child->has_recursive_scale_helper && child->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) { - pre_child->has_recursive_scale_helper = true; - - uint64_t child_fbx_id = fbx_ids[pre_child->element_id]; - ufbxi_check(ufbxi_setup_scale_helper(uc, child, child_fbx_id)); - child->is_scale_compensate_parent = false; - - // Traverse to children if any - if (pre_child->first_child != ~0u) { - ix = pre_child->first_child; - continue; - } - } - } - - // Move to next child, popping parents until we find one - while (pre_child->next_child == ~0u) { - ix = pre_child->parent; - if (ix == node->typed_id) break; - pre_child = &pre_nodes[ix]; - } - if (ix != node->typed_id) { - ix = pre_child->next_child; - } - } - - } else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) { - if ((ufbx_real)ufbx_fabs(scale.x - 1.0f) >= scale_epsilon) { - node->is_scale_compensate_parent = true; - } - } - } - } - } - - return 1; -} - -// -- Scene processing - -static ufbxi_noinline ufbx_element *ufbxi_find_element_by_fbx_id(ufbxi_context *uc, uint64_t fbx_id) -{ - ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id); - if (entry) { - return uc->scene.elements.data[entry->element_id]; - } - return NULL; -} - -ufbxi_forceinline static bool ufbxi_cmp_name_element_less(const ufbx_name_element *a, const ufbx_name_element *b) -{ - if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; - int cmp = strcmp(a->name.data, b->name.data); - if (cmp != 0) return cmp < 0; - return a->type < b->type; -} - -ufbxi_forceinline static bool ufbxi_cmp_name_element_less_ref(const ufbx_name_element *a, ufbx_string name, ufbx_element_type type, uint32_t key) -{ - if (a->_internal_key != key) return a->_internal_key < key; - int cmp = ufbxi_str_cmp(a->name, name); - if (cmp != 0) return cmp < 0; - return a->type < type; -} - -ufbxi_forceinline static bool ufbxi_cmp_prop_less_ref(const ufbx_prop *a, ufbx_string name, uint32_t key) -{ - if (a->_internal_key != key) return a->_internal_key < key; - return ufbxi_str_less(a->name, name); -} - -ufbxi_forceinline static bool ufbxi_cmp_prop_less_concat(const ufbx_prop *a, const ufbx_string *parts, size_t num_parts, uint32_t key) -{ - if (a->_internal_key != key) return a->_internal_key < key; - return ufbxi_concat_str_cmp(&a->name, parts, num_parts) < 0; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_name_elements(ufbxi_context *uc, ufbx_name_element *name_elems, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_name_element))); - ufbxi_macro_stable_sort(ufbx_name_element, 32, name_elems, uc->tmp_arr, count, - ( ufbxi_cmp_name_element_less(a, b) ) ); - return 1; -} - -ufbxi_noinline static bool ufbxi_cmp_node_less(ufbx_node *a, ufbx_node *b) -{ - if (a->node_depth != b->node_depth) return a->node_depth < b->node_depth; - if (a->parent && b->parent) { - uint32_t a_pid = a->parent->element.element_id, b_pid = b->parent->element.element_id; - if (a_pid != b_pid) return a_pid < b_pid; - } else { - ufbx_assert(a->parent == NULL && b->parent == NULL); - } - if (a->is_geometry_transform_helper != b->is_geometry_transform_helper) { - // Sort geometry transform helpers always before rest of the children. - return (unsigned)a->is_geometry_transform_helper > (unsigned)b->is_geometry_transform_helper; - } - if (a->is_scale_helper != b->is_scale_helper) { - // Sort scale helpers after geometry transform helpers. - return (unsigned)a->is_scale_helper > (unsigned)b->is_scale_helper; - } - return a->element.element_id < b->element.element_id; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_node_ptrs(ufbxi_context *uc, ufbx_node **nodes, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_node*))); - ufbxi_macro_stable_sort(ufbx_node*, 32, nodes, uc->tmp_arr, count, - ( ufbxi_cmp_node_less(*a, *b) ) ); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_cmp_tmp_material_texture_less(const ufbxi_tmp_material_texture *a, const ufbxi_tmp_material_texture *b) -{ - if (a->material_id != b->material_id) return a->material_id < b->material_id; - if (a->texture_id != b->texture_id) return a->texture_id < b->texture_id; - return ufbxi_str_less(a->prop_name, b->prop_name); -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_tmp_material_textures(ufbxi_context *uc, ufbxi_tmp_material_texture *mat_texs, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_tmp_material_texture))); - ufbxi_macro_stable_sort(ufbxi_tmp_material_texture, 32, mat_texs, uc->tmp_arr, count, - ( ufbxi_cmp_tmp_material_texture_less(a, b) )); - return 1; -} - -// We need to be able to assume no padding! -ufbx_static_assert(connection_size, sizeof(ufbx_connection) == sizeof(ufbx_element*)*2 + sizeof(ufbx_string)*2); - -ufbxi_forceinline static bool ufbxi_cmp_connection_less(ufbx_connection *a, ufbx_connection *b, size_t index) -{ - ufbx_element *a_elem = (&a->src)[index], *b_elem = (&b->src)[index]; - if (a_elem != b_elem) return a_elem < b_elem; - int cmp = strcmp((&a->src_prop)[index].data, (&b->src_prop)[index].data); - if (cmp != 0) return cmp < 0; - cmp = strcmp((&a->src_prop)[index ^ 1].data, (&b->src_prop)[index ^ 1].data); - return cmp < 0; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_connections(ufbxi_context *uc, ufbx_connection *connections, size_t count, size_t index) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_connection))); - ufbxi_macro_stable_sort(ufbx_connection, 32, connections, uc->tmp_arr, count, ( ufbxi_cmp_connection_less(a, b, index) )); - return 1; -} - -static uint64_t ufbxi_find_attribute_fbx_id(ufbxi_context *uc, uint64_t node_fbx_id) -{ - uint32_t hash = ufbxi_hash64(node_fbx_id); - ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &node_fbx_id); - if (entry) { - return entry->attr_fbx_id; - } - return node_fbx_id; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_connections(ufbxi_context *uc) -{ - size_t num_connections = uc->tmp_connections.num_items; - ufbxi_tmp_connection *tmp_connections = ufbxi_push_pop(&uc->tmp, &uc->tmp_connections, ufbxi_tmp_connection, num_connections); - ufbxi_buf_free(&uc->tmp_connections); - ufbxi_check(tmp_connections); - - // NOTE: We truncate this array in case not all connections are resolved - uc->scene.connections_src.data = ufbxi_push(&uc->result, ufbx_connection, num_connections); - ufbxi_check(uc->scene.connections_src.data); - - // HACK: Translate property connections from node to attribute if the property name is not included - // in the known node properties and is not a property of the node. - if (uc->version > 0 && uc->version < 7000) { - ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) { - if (tmp_conn->src_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->src_prop.data)) { - ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src); - if (!src || !ufbx_find_prop_len(&src->props, tmp_conn->src_prop.data, tmp_conn->src_prop.length)) { - tmp_conn->src = ufbxi_find_attribute_fbx_id(uc, tmp_conn->src); - } - } - if (tmp_conn->dst_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->dst_prop.data)) { - ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst); - if (!dst || !ufbx_find_prop_len(&dst->props, tmp_conn->dst_prop.data, tmp_conn->dst_prop.length)) { - tmp_conn->dst = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); - } - } - } - } - - ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) { - ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src); - ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst); - if (!src || !dst) continue; - - if (!uc->opts.disable_quirks) { - // Some exporters connect arbitrary non-nodes to root breaking further code, ignore those connections here! - if (dst->type == UFBX_ELEMENT_NODE && src->type != UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_root) { - ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, src->element_id, "Non-node element connected to root")); - continue; - } - } - - // Remap connections to geometry transform helpers if necessary, see `ufbxi_setup_geometry_transform_helper()` for how these are setup. - if (uc->has_geometry_transform_nodes) { - if (dst->type == UFBX_ELEMENT_NODE && src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { - ufbx_node *node = (ufbx_node*)dst; - if (node->has_geometry_transform) { - ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id); - ufbx_assert(extra); - dst = uc->scene.elements.data[extra->geometry_helper_id]; - ufbx_assert(dst->type == UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_geometry_transform_helper); - } - } - } - - // Remap connections to scale helpers if necessary, see `ufbxi_setup_scale_helper()` for how these are setup. - if (uc->has_scale_helper_nodes) { - if (dst->type == UFBX_ELEMENT_NODE) { - ufbx_node *dst_node = (ufbx_node*)dst; - if (dst_node->scale_helper) { - if (src->type == UFBX_ELEMENT_NODE) { - ufbx_node *src_node = (ufbx_node*)src; - if (!src_node->is_scale_helper && src_node->original_inherit_mode == UFBX_INHERIT_MODE_NORMAL) { - dst = &dst_node->scale_helper->element; - } - } else if (src->type == UFBX_ELEMENT_ANIM_VALUE) { - if (tmp_conn->dst_prop.data == ufbxi_Lcl_Scaling) { - dst = &dst_node->scale_helper->element; - } - } else { - dst = &dst_node->scale_helper->element; - } - } - } else if (src->type == UFBX_ELEMENT_NODE) { - ufbx_node *src_node = (ufbx_node*)src; - if (src_node->scale_helper) { - if (dst->type == UFBX_ELEMENT_SKIN_CLUSTER) { - src = &src_node->scale_helper->element; - } - } - } - } - - // Translate deformers to point to the geometry in 6100, we don't need to worry about - // blend shapes here as they're always connected synthetically in older files. - if (uc->version > 0 && uc->version < 7000 && dst->type == UFBX_ELEMENT_NODE) { - if (src->type == UFBX_ELEMENT_SKIN_DEFORMER || src->type == UFBX_ELEMENT_CACHE_DEFORMER) { - uint64_t dst_id = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); - ufbx_element *dst_elem = ufbxi_find_element_by_fbx_id(uc, dst_id); - if (dst_elem) { - dst = dst_elem; - } - } - } - - ufbx_connection *conn = &uc->scene.connections_src.data[uc->scene.connections_src.count++]; - conn->src = src; - conn->dst = dst; - conn->src_prop = tmp_conn->src_prop; - conn->dst_prop = tmp_conn->dst_prop; - } - - uc->scene.connections_dst.count = uc->scene.connections_src.count; - uc->scene.connections_dst.data = ufbxi_push_copy(&uc->result, ufbx_connection, - uc->scene.connections_src.count, uc->scene.connections_src.data); - ufbxi_check(uc->scene.connections_dst.data); - - ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_src.data, uc->scene.connections_src.count, 0)); - ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_dst.data, uc->scene.connections_dst.count, 1)); - - // We don't need the temporary connections at this point anymore - ufbxi_buf_free(&uc->tmp_connections); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_connections_to_elements(ufbxi_context *uc) -{ - ufbx_connection *conn_src = uc->scene.connections_src.data; - ufbx_connection *conn_src_end = ufbxi_add_ptr(conn_src, uc->scene.connections_src.count); - ufbx_connection *conn_dst = uc->scene.connections_dst.data; - ufbx_connection *conn_dst_end = ufbxi_add_ptr(conn_dst, uc->scene.connections_dst.count); - - ufbxi_for_ptr(ufbx_element, p_elem, uc->scene.elements.data, uc->scene.elements.count) { - ufbx_element *elem = *p_elem; - uint32_t id = elem->element_id; - - while (conn_src < conn_src_end && conn_src->src->element_id < id) conn_src++; - while (conn_dst < conn_dst_end && conn_dst->dst->element_id < id) conn_dst++; - ufbx_connection *src_end = conn_src, *dst_end = conn_dst; - - while (src_end < conn_src_end && src_end->src->element_id == id) src_end++; - while (dst_end < conn_dst_end && dst_end->dst->element_id == id) dst_end++; - - elem->connections_src.data = conn_src; - elem->connections_src.count = ufbxi_to_size(src_end - conn_src); - elem->connections_dst.data = conn_dst; - elem->connections_dst.count = ufbxi_to_size(dst_end - conn_dst); - - // Setup animated properties - // TODO: It seems we're invalidating a lot of properties here actually, maybe they - // should be initially pushed to `tmp` instead of result if this happens so much.. - { - ufbx_prop *prop = elem->props.props.data, *prop_end = ufbxi_add_ptr(prop, elem->props.props.count); - ufbx_prop *copy_start = prop; - bool needs_copy = false; - size_t num_animated = 0, num_synthetic = 0; - - for (;;) { - // Scan to the next animation connection - for (; conn_dst < dst_end; conn_dst++) { - if (conn_dst->dst_prop.length == 0) continue; - if (conn_dst->src_prop.length > 0) break; - if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) break; - } - - ufbx_string name = ufbx_empty_string; - if (conn_dst < dst_end) { - name = conn_dst->dst_prop; - } - if (name.length == 0) break; - - // NOTE: "Animated" properties also include connected ones as we need - // to resolve them during evaluation - num_animated++; - - ufbx_anim_value *anim_value = NULL; - uint32_t flags = 0; - for (; conn_dst < dst_end && conn_dst->dst_prop.data == name.data; conn_dst++) { - if (conn_dst->src_prop.length > 0) { - flags |= UFBX_PROP_FLAG_CONNECTED; - } else if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) { - anim_value = (ufbx_anim_value*)conn_dst->src; - flags |= UFBX_PROP_FLAG_ANIMATED; - } - } - - uint32_t key = ufbxi_get_name_key(name.data, name.length); - while (prop != prop_end && ufbxi_name_key_less(prop, name.data, name.length, key)) prop++; - - if (prop != prop_end && prop->name.data == name.data) { - prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags | flags); - } else { - // Animated property that is not in the element property list - // Copy the preceding properties to the stack, then push a - // synthetic property for the animated property. - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop - copy_start), copy_start)); - copy_start = prop; - needs_copy = true; - - // Let's hope we can find the property in the defaults at least - ufbx_prop anim_def_prop; - ufbx_prop *def_prop = NULL; - if (elem->props.defaults) { - def_prop = ufbxi_find_prop_with_key(elem->props.defaults, name.data, key); - } else if (anim_value) { - memset(&anim_def_prop, 0, sizeof(anim_def_prop)); - // Hack a couple of common types - ufbx_prop_type type = UFBX_PROP_UNKNOWN; - if (name.data == ufbxi_Lcl_Translation) type = UFBX_PROP_TRANSLATION; - else if (name.data == ufbxi_Lcl_Rotation) type = UFBX_PROP_ROTATION; - else if (name.data == ufbxi_Lcl_Scaling) { - type = UFBX_PROP_SCALING; - anim_def_prop.value_vec3.x = 1.0f; - anim_def_prop.value_vec3.y = 1.0f; - anim_def_prop.value_vec3.z = 1.0f; - } - // Property values are only defined in anim_props on legacy files - if (uc->version < 6000) { - anim_def_prop.value_vec3 = anim_value->default_value; - } - anim_def_prop.type = type; - def_prop = &anim_def_prop; - } else { - flags |= UFBX_PROP_FLAG_NO_VALUE; - } - - ufbx_prop *new_prop = ufbxi_push_zero(&uc->tmp_stack, ufbx_prop, 1); - ufbxi_check(new_prop); - if (def_prop) *new_prop = *def_prop; - flags |= (uint32_t)new_prop->flags; - new_prop->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_ANIMATABLE | UFBX_PROP_FLAG_SYNTHETIC | flags); - new_prop->name = name; - new_prop->_internal_key = key; - new_prop->value_str = ufbx_empty_string; - new_prop->value_blob = ufbx_empty_blob; - num_synthetic++; - } - } - - // Copy the properties if necessary - if (needs_copy) { - size_t num_new_props = elem->props.props.count + num_synthetic; - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop_end - copy_start), copy_start)); - elem->props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, num_new_props); - ufbxi_check(elem->props.props.data); - elem->props.props.count = num_new_props; - } - elem->props.num_animated = num_animated; - } - - conn_src = src_end; - conn_dst = dst_end; - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_linearize_nodes(ufbxi_context *uc) -{ - size_t num_nodes = uc->tmp_node_ids.num_items; - uint32_t *node_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_node_ids, uint32_t, num_nodes); - ufbxi_buf_free(&uc->tmp_node_ids); - ufbxi_check(node_ids); - - ufbx_node **node_ptrs = ufbxi_push(&uc->tmp_stack, ufbx_node*, num_nodes); - ufbxi_check(node_ptrs); - - // Fetch the node pointers - for (size_t i = 0; i < num_nodes; i++) { - node_ptrs[i] = (ufbx_node*)uc->scene.elements.data[node_ids[i]]; - ufbx_assert(node_ptrs[i]->element.type == UFBX_ELEMENT_NODE); - } - - uc->scene.root_node = node_ptrs[0]; - - size_t *node_offsets = ufbxi_push_pop(&uc->tmp_stack, &uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, num_nodes); - ufbxi_check(node_offsets); - - // Hook up the parent nodes, we'll assume that there's no cycles at this point - ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) { - ufbx_node *node = *p_node; - - // Pre-6000 files don't have any explicit root connections so they must always - // be connected to the root.. - if (node->parent == NULL && !(uc->opts.allow_nodes_out_of_root && uc->version >= 6000)) { - if (node != uc->scene.root_node) { - node->parent = uc->scene.root_node; - } - } - - ufbxi_for_list(ufbx_connection, conn, node->element.connections_dst) { - if (conn->src_prop.length > 0 || conn->dst_prop.length > 0) continue; - if (conn->src->type != UFBX_ELEMENT_NODE) continue; - ((ufbx_node*)conn->src)->parent = node; - } - } - - // Count the parent depths and child amounts - ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) { - ufbx_node *node = *p_node; - uint32_t depth = 0; - - for (ufbx_node *p = node->parent; p; p = p->parent) { - depth += p->node_depth + 1; - if (p->node_depth > 0) break; - ufbxi_check_msg(depth <= num_nodes, "Cyclic node hierarchy"); - } - - if (uc->opts.node_depth_limit > 0) { - ufbxi_check_msg(depth <= uc->opts.node_depth_limit, "Node depth limit exceeded"); - } - node->node_depth = depth; - - // Second pass to cache the depths to avoid O(n^2) - for (ufbx_node *p = node->parent; p; p = p->parent) { - if (--depth <= p->node_depth) break; - p->node_depth = depth; - } - } - - ufbxi_check(ufbxi_sort_node_ptrs(uc, node_ptrs, num_nodes)); - - for (uint32_t i = 0; i < num_nodes; i++) { - size_t *p_offset = ufbxi_push(&uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, 1); - ufbxi_check(p_offset); - ufbx_node *node = node_ptrs[i]; - - uint32_t original_id = node->element.typed_id; - node->element.typed_id = i; - *p_offset = node_offsets[original_id]; - } - - // Pop the temporary arrays - ufbxi_pop(&uc->tmp_stack, size_t, num_nodes, NULL); - ufbxi_pop(&uc->tmp_stack, ufbx_node*, num_nodes, NULL); - - return 1; -} - - -ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_dst_connections(ufbx_element *element, const char *prop) -{ - if (!prop) prop = ufbxi_empty_char; - - size_t begin = element->connections_dst.count, end = begin; - - ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin, - element->connections_dst.data, 0, element->connections_dst.count, - (strcmp(a->dst_prop.data, prop) < 0), - (a->dst_prop.data == prop && a->src_prop.length == 0)); - - ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end, - element->connections_dst.data, begin, element->connections_dst.count, - (a->dst_prop.data == prop && a->src_prop.length == 0)); - - ufbx_connection_list result = { element->connections_dst.data + begin, end - begin }; - return result; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_src_connections(ufbx_element *element, const char *prop) -{ - if (!prop) prop = ufbxi_empty_char; - - size_t begin = element->connections_src.count, end = begin; - - ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin, - element->connections_src.data, 0, element->connections_src.count, - (strcmp(a->src_prop.data, prop) < 0), - (a->src_prop.data == prop && a->dst_prop.length == 0)); - - ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end, - element->connections_src.data, begin, element->connections_src.count, - (a->src_prop.data == prop && a->dst_prop.length == 0)); - - ufbx_connection_list result = { element->connections_src.data + begin, end - begin }; - return result; -} - -ufbxi_nodiscard static ufbx_element *ufbxi_get_element_node(ufbx_element *element) -{ - if (!element) return NULL; - if (element->type == UFBX_ELEMENT_NODE) { - ufbx_node *node = (ufbx_node*)element; - if (node->is_geometry_transform_helper) return (ufbx_element*)node->parent; - return NULL; - } else { - return element->instances.count > 0 ? &element->instances.data[0]->element : NULL; - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_dst_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type src_type) -{ - size_t num_elements = 0; - - do { - ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->src->type == src_type) { - if (ignore_duplicates) { - uint32_t element_id = conn->src->element_id; - if (uc->tmp_element_flag[element_id]) { - ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id)); - continue; - } - uc->tmp_element_flag[element_id] = 1; - } - ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); - ufbxi_check(p_elem); - *p_elem = conn->src; - num_elements++; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - ufbx_element_list *list = (ufbx_element_list*)p_dst_list; - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements); - list->count = num_elements; - ufbxi_check(list->data); - - if (ignore_duplicates) { - ufbxi_for_ptr_list(ufbx_element, p_elem, *list) { - uc->tmp_element_flag[(*p_elem)->element_id] = 0; - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_src_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type dst_type) -{ - size_t num_elements = 0; - - do { - ufbx_connection_list conns = ufbxi_find_src_connections(element, prop); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->dst->type == dst_type) { - if (ignore_duplicates) { - uint32_t element_id = conn->dst->element_id; - if (uc->tmp_element_flag[element_id]) { - ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id)); - continue; - } - uc->tmp_element_flag[element_id] = 1; - } - ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); - ufbxi_check(p_elem); - *p_elem = conn->dst; - num_elements++; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - ufbx_element_list *list = (ufbx_element_list*)p_dst_list; - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements); - list->count = num_elements; - ufbxi_check(list->data); - - if (ignore_duplicates) { - ufbxi_for_ptr_list(ufbx_element, p_elem, *list) { - uc->tmp_element_flag[(*p_elem)->element_id] = 0; - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_dst_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type src_type) -{ - do { - ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->src->type == src_type) { - return conn->src; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - return NULL; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_src_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type dst_type) -{ - do { - ufbx_connection_list conns = ufbxi_find_src_connections(element, prop); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->dst->type == dst_type) { - return conn->dst; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - return NULL; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_textures(ufbxi_context *uc, ufbx_material_texture_list *list, ufbx_element *element, bool search_node) -{ - size_t num_textures = 0; - - do { - ufbxi_for_list(ufbx_connection, conn, element->connections_dst) { - if (conn->src_prop.length > 0) continue; - if (conn->src->type == UFBX_ELEMENT_TEXTURE) { - ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1); - ufbxi_check(tex); - tex->shader_prop = tex->material_prop = conn->dst_prop; - tex->texture = (ufbx_texture*)conn->src; - num_textures++; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures); - list->count = num_textures; - ufbxi_check(list->data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_mesh_materials(ufbxi_context *uc, ufbx_material_list *list, ufbx_element *element, bool search_node) -{ - size_t num_materials = 0; - - do { - ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->src->type == UFBX_ELEMENT_MATERIAL) { - ufbx_material *mat = (ufbx_material*)conn->src; - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_material*, 1, &mat)); - num_materials++; - } - } - - if (num_materials > 0) break; - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material*, num_materials); - list->count = num_materials; - ufbxi_check(list->data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_deformers(ufbxi_context *uc, ufbx_element_list *list, ufbx_element *element, bool search_node) -{ - size_t num_deformers = 0; - - do { - ufbxi_for_list(ufbx_connection, conn, element->connections_dst) { - if (conn->src_prop.length > 0) continue; - ufbx_element_type type = conn->src->type; - if (type == UFBX_ELEMENT_SKIN_DEFORMER || type == UFBX_ELEMENT_BLEND_DEFORMER || type == UFBX_ELEMENT_CACHE_DEFORMER) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &conn->src)); - num_deformers++; - } - } - } while (search_node && (element = ufbxi_get_element_node(element)) != NULL); - - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_deformers); - list->count = num_deformers; - ufbxi_check(list->data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe_list *list, ufbx_element *element) -{ - size_t num_keyframes = 0; - - ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->src->type == UFBX_ELEMENT_BLEND_SHAPE) { - ufbx_blend_keyframe key = { (ufbx_blend_shape*)conn->src }; - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_blend_keyframe, 1, &key)); - num_keyframes++; - } - } - - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_blend_keyframe, num_keyframes); - list->count = num_keyframes; - ufbxi_check(list->data); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_texture_layers(ufbxi_context *uc, ufbx_texture_layer_list *list, ufbx_element *element) -{ - size_t num_layers = 0; - - ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL); - ufbxi_for_list(ufbx_connection, conn, conns) { - if (conn->src->type == UFBX_ELEMENT_TEXTURE) { - ufbx_texture *texture = (ufbx_texture*)conn->src; - ufbx_texture_layer layer = { texture }; - layer.alpha = ufbxi_find_real(&texture->props, ufbxi_Texture_alpha, 1.0f); - layer.blend_mode = (ufbx_blend_mode)ufbxi_find_enum(&texture->props, ufbxi_BlendMode, UFBX_BLEND_REPLACE, UFBX_BLEND_OVERLAY); - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture_layer, 1, &layer)); - num_layers++; - } - } - - list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_texture_layer, num_layers); - list->count = num_layers; - ufbxi_check(list->data); - - return 1; -} - -static ufbxi_forceinline bool ufbxi_prop_connection_less(const ufbx_connection *a, const char *prop) -{ - int cmp = strcmp(a->dst_prop.data, prop); - if (cmp != 0) return cmp < 0; - return a->src_prop.length == 0; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_connection *ufbxi_find_prop_connection(const ufbx_element *element, const char *prop) -{ - if (!prop) prop = ufbxi_empty_char; - - size_t index = SIZE_MAX; - - ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &index, - element->connections_dst.data, 0, element->connections_dst.count, - (ufbxi_prop_connection_less(a, prop)), - (a->dst_prop.data == prop && a->src_prop.length > 0)); - - return index < SIZE_MAX ? &element->connections_dst.data[index] : NULL; -} - -ufbxi_forceinline static void ufbxi_patch_index_pointer(ufbxi_context *uc, uint32_t **p_index) -{ - if (*p_index == ufbxi_sentinel_index_zero) { - *p_index = uc->zero_indices; - } else if (*p_index == ufbxi_sentinel_index_consecutive) { - *p_index = uc->consecutive_indices; - } -} - -ufbxi_nodiscard static bool ufbxi_cmp_anim_prop_less(const ufbx_anim_prop *a, const ufbx_anim_prop *b) -{ - if (a->element != b->element) return a->element < b->element; - if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; - return ufbxi_str_less(a->prop_name, b->prop_name); -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_anim_props(ufbxi_context *uc, ufbx_anim_prop *aprops, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_anim_prop))); - ufbxi_macro_stable_sort(ufbx_anim_prop, 32, aprops, uc->tmp_arr, count, ( ufbxi_cmp_anim_prop_less(a, b) )); - return 1; -} - -ufbxi_noinline static bool ufbxi_material_texture_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_material_texture *a = (const ufbx_material_texture*)va, *b = (const ufbx_material_texture*)vb; - return ufbxi_str_less(a->material_prop, b->material_prop); -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_material_textures(ufbxi_context *uc, ufbx_material_texture *textures, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_material_texture))); - ufbxi_stable_sort(sizeof(ufbx_material_texture), 32, textures, uc->tmp_arr, count, &ufbxi_material_texture_less, NULL); - return 1; -} - -static ufbxi_noinline bool ufbxi_bone_pose_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_bone_pose *a = (const ufbx_bone_pose *)va, *b = (const ufbx_bone_pose *)vb; - return a->bone_node->typed_id < b->bone_node->typed_id; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_anim_prop *ufbxi_find_anim_prop_start(ufbx_anim_layer *layer, const ufbx_element *element) -{ - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &index, layer->anim_props.data, 0, layer->anim_props.count, - (a->element < element), (a->element == element)); - return index != SIZE_MAX ? &layer->anim_props.data[index] : NULL; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_bone_poses(ufbxi_context *uc, ufbx_pose *pose) -{ - size_t count = pose->bone_poses.count; - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, pose->bone_poses.count * sizeof(ufbx_bone_pose))); - ufbxi_stable_sort(sizeof(ufbx_bone_pose), 16, pose->bone_poses.data, uc->tmp_arr, count, &ufbxi_bone_pose_less, NULL); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_skin_weights(ufbxi_context *uc, ufbx_skin_deformer *skin) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, skin->max_weights_per_vertex * sizeof(ufbx_skin_weight))); - - for (size_t i = 0; i < skin->vertices.count; i++) { - ufbx_skin_vertex v = skin->vertices.data[i]; - ufbxi_macro_stable_sort(ufbx_skin_weight, 32, skin->weights.data + v.weight_begin, uc->tmp_arr, v.num_weights, - ( a->weight > b->weight )); - } - - return 1; -} - -ufbxi_noinline static bool ufbxi_blend_keyframe_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_blend_keyframe *a = (const ufbx_blend_keyframe*)va, *b = (const ufbx_blend_keyframe*)vb; - return a->target_weight < b->target_weight; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe *keyframes, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_blend_keyframe))); - ufbxi_stable_sort(sizeof(ufbx_blend_keyframe), 32, keyframes, uc->tmp_arr, count, &ufbxi_blend_keyframe_less, NULL); - return 1; -} - -// Material tables - -typedef void (*ufbxi_mat_transform_fn)(ufbx_vec4 *a); - -static void ufbxi_mat_transform_invert_x(ufbx_vec4 *v) { v->x = 1.0f - v->x; } -static void ufbxi_mat_transform_unknown_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; } -static void ufbxi_mat_transform_blender_opacity(ufbx_vec4 *v) { v->x = 1.0f - v->x; } -static void ufbxi_mat_transform_blender_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; } - -typedef enum { - UFBXI_MAT_TRANSFORM_IDENTITY, - UFBXI_MAT_TRANSFORM_INVERT_X, - UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, - UFBXI_MAT_TRANSFORM_BLENDER_OPACITY, - UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, - - UFBXI_MAT_TRANSFORM_COUNT, -} ufbxi_mat_transform; - -typedef enum { - // Set `value_vec4.w` (usually alpha) to 1.0 if not defined by the property - UFBXI_SHADER_MAPPING_DEFAULT_W_1 = 0x1, - // Widen values to RGB if only a single value is present. - UFBXI_SHADER_MAPPING_WIDEN_TO_RGB = 0x2, - // Multiply the existing value. - UFBXI_SHADER_MAPPING_MULTIPLY_VALUE = 0x4, -} ufbxi_shader_mapping_flag; - -typedef enum { - // Invert the feature flag - UFBXI_SHADER_FEATURE_INVERTED = 0x1, - // Enable the feature if the given property exists - UFBXI_SHADER_FEATURE_IF_EXISTS = 0x2, - // Enable the feature if the given property has a texture - UFBXI_SHADER_FEATURE_IF_TEXTURE = 0x4, - // Enable if the feature is in [0.5, 1.5], (ie. 2 won't enable this feature) - UFBXI_SHADER_FEATURE_IF_AROUND_1 = 0x8, - - UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE = UFBXI_SHADER_FEATURE_IF_EXISTS|UFBXI_SHADER_FEATURE_IF_TEXTURE, -} ufbxi_shader_feature_flag; - -static const ufbxi_mat_transform_fn ufbxi_mat_transform_fns[] = { - NULL, - &ufbxi_mat_transform_invert_x, - &ufbxi_mat_transform_unknown_shininess, - &ufbxi_mat_transform_blender_opacity, - &ufbxi_mat_transform_blender_shininess, -}; - -ufbx_static_assert(transform_count, ufbxi_arraycount(ufbxi_mat_transform_fns) == UFBXI_MAT_TRANSFORM_COUNT); - -typedef struct { - uint8_t index; // < `ufbx_material_(fbx|pbr)_map` - uint8_t flags; // < Combination of `ufbxi_shader_mapping_flag` - uint8_t transform; // < `ufbxi_mat_transform` - uint8_t prop_len; // < Length of `prop` not including NULL terminator - const char *prop; // < Name of FBX material property or shader mapping -} ufbxi_shader_mapping; - -typedef struct { - const ufbxi_shader_mapping *data; - size_t count; - const ufbxi_shader_mapping *features; - size_t feature_count; - uint32_t default_features; - ufbx_string texture_prefix; - ufbx_string texture_suffix; - ufbx_string texture_enabled_prefix; - ufbx_string texture_enabled_suffix; -} ufbxi_shader_mapping_list; - -#define ufbxi_mat_string(str) sizeof(str) - 1, str - -static const ufbxi_shader_mapping ufbxi_base_fbx_mapping[] = { - { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, - { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, - { UFBX_MATERIAL_FBX_DIFFUSE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, - { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, - { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") }, - { UFBX_MATERIAL_FBX_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") }, - { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Shininess") }, - { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("ShininessExponent") }, - { UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Reflection") }, - { UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("ReflectionColor") }, - { UFBX_MATERIAL_FBX_REFLECTION_FACTOR, 0, 0, ufbxi_mat_string("ReflectionFactor") }, - { UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, - { UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, - { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, - { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, - { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, - { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, - { UFBX_MATERIAL_FBX_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, - { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Ambient") }, - { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("AmbientColor") }, - { UFBX_MATERIAL_FBX_AMBIENT_FACTOR, 0, 0, ufbxi_mat_string("AmbientFactor") }, - { UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, - { UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("Bump") }, - { UFBX_MATERIAL_FBX_BUMP_FACTOR, 0, 0, ufbxi_mat_string("BumpFactor") }, - { UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("Displacement") }, - { UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("DisplacementFactor") }, - { UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT, 0, 0, ufbxi_mat_string("VectorDisplacement") }, - { UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("VectorDisplacementFactor") }, -}; - -static const ufbxi_shader_mapping ufbxi_obj_fbx_mapping[] = { - { UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ka") }, - { UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") }, - { UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") }, - { UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") }, - { UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Ns") }, - { UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, UFBXI_MAT_TRANSFORM_INVERT_X, ufbxi_mat_string("d") }, - { UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, - { UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("disp") }, - { UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("bump") }, -}; - -static const ufbxi_shader_mapping ufbxi_fbx_lambert_shader_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, -}; - -static const ufbxi_shader_mapping ufbxi_fbx_phong_shader_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Shininess") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("ShininessExponent") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, -}; - -static const ufbxi_shader_mapping ufbxi_osl_standard_shader_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") }, - { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuse_roughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_IOR") }, - { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_anisotropy") }, - { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specular_rotation") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmission_extra_roughness") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_scale") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_anisotropy") }, - { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") }, - { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheen_color") }, - { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheen_roughness") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, - { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, - { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_IOR") }, - { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_anisotropy") }, - { UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coat_rotation") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal") }, - { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") }, - { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") }, - { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") }, - { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_IOR") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") }, - { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") }, -}; - -static const ufbxi_shader_mapping ufbxi_osl_standard_shader_features[] = { - { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") }, -}; - -static const ufbxi_shader_mapping ufbxi_arnold_shader_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specularRoughness") }, - { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuseRoughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specularColor") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specularIOR") }, - { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specularAnisotropy") }, - { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specularRotation") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmissionColor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmissionDepth") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmissionScatter") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmissionScatterAnisotropy") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmissionDispersion") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmissionExtraRoughness") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurfaceColor") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurfaceRadius") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurfaceScale") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurfaceAnisotropy") }, - { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") }, - { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheenColor") }, - { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheenRoughness") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, - { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coatColor") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coatRoughness") }, - { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coatIOR") }, - { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coatAnisotropy") }, - { UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coatRotation") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coatNormal") }, - { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thinFilmThickness") }, - { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thinFilmIOR") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissionColor") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, - { UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE, 0, 0, ufbxi_mat_string("indirectDiffuse") }, - { UFBX_MATERIAL_PBR_INDIRECT_SPECULAR, 0, 0, ufbxi_mat_string("indirectSpecular") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") }, - { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") }, - { UFBX_MATERIAL_PBR_MATTE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("aiMatteColor") }, - { UFBX_MATERIAL_PBR_MATTE_FACTOR, 0, 0, ufbxi_mat_string("aiMatteColorA") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY, 0, 0, ufbxi_mat_string("dielectricPriority") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV, 0, 0, ufbxi_mat_string("transmitAovs") }, -}; - -static const ufbxi_shader_mapping ufbxi_arnold_shader_features[] = { - { UFBX_MATERIAL_FEATURE_MATTE, 0, 0, ufbxi_mat_string("aiEnableMatte") }, - { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thinWalled") }, - { UFBX_MATERIAL_FEATURE_CAUSTICS, 0, 0, ufbxi_mat_string("caustics") }, - { UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, 0, 0, ufbxi_mat_string("internalReflections") }, - { UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, 0, 0, ufbxi_mat_string("exitToBackground") }, -}; - -static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, - { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_rough") }, - { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_roughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("reflectivity") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("refl_color") }, - { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("anisotropy") }, - { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("aniso_angle") }, - { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisoangle") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("trans_ior") }, // NOTE: Not a typo, IOR is same for transparency/specular - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transparency") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("trans_color") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("trans_depth") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_rough") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_roughness") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("scattering") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_color") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_scatter_color") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("sss_depth") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("sss_scale") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coating") }, - { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_rough") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, - { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("clearcoat_bump_map_amt") }, - { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") }, - { UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("cutout") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") }, -}; - -static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_features[] = { - { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") }, - { UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("material_mode") }, - { UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("material_mode") }, - { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, UFBXI_SHADER_FEATURE_INVERTED, 0, ufbxi_mat_string("trans_roughness_lock") }, - { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("roughness_inv") }, - { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("trans_roughness_inv") }, - { UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("coat_roughness_inv") }, -}; - -static const ufbxi_shader_mapping ufbxi_gltf_material_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|baseColor") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("main|roughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("main|metalness") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("main|normal") }, - { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("main|ambientOcclusion") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emission") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emissionColor") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("main|Alpha") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("extension|clearcoat") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|clearcoatRoughness") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("extension|clearcoatNormal") }, - { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|sheenColor") }, - { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|sheenRoughness") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|Specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularcolor") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularColor") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("extension|transmission") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("extension|indexOfRefraction") }, -}; - -static const ufbxi_shader_mapping ufbxi_openpbr_material_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") }, - { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("base_diffuse_roughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("base_metalness") }, - { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular_weight") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") }, - { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_roughness_anisotropy") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_ior") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission_weight") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion_scale") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface_weight") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius_scale") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_radius") }, - { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_scatter_anisotropy") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat_weight") }, - { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, - { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_roughness_anisotropy") }, - { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal_map") }, - { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("fuzz_weight") }, - { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("fuzz_color") }, - { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("fuzz_roughness") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission_weight") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, UFBXI_SHADER_MAPPING_MULTIPLY_VALUE, 0, ufbxi_mat_string("emission_luminance") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") }, - { UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, 0, 0, ufbxi_mat_string("thin_film_weight") }, - { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") }, - { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_ior") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") }, - { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump_map_amt") }, - { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("geometry_tangent_map") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("geometry_opacity") }, -}; - -static const ufbxi_shader_mapping ufbxi_openpbr_material_features[] = { - { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("geometry_thin_walled") }, -}; - -static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_metal_rough_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Roughness_Map") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") }, - { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, -}; - -static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("glossiness") }, - { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") }, -}; - -static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_features[] = { - { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBXI_SHADER_FEATURE_IF_AROUND_1, 0, ufbxi_mat_string("useGlossiness") }, -}; - -static const ufbxi_shader_mapping ufbxi_gltf_material_features[] = { - { UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, 0, 0, ufbxi_mat_string("main|DoubleSided") }, - { UFBX_MATERIAL_FEATURE_SHEEN, 0, 0, ufbxi_mat_string("extension|enableSheen") }, - { UFBX_MATERIAL_FEATURE_COAT, 0, 0, ufbxi_mat_string("extension|enableClearCoat") }, - { UFBX_MATERIAL_FEATURE_TRANSMISSION, 0, 0, ufbxi_mat_string("extension|enableTransmission") }, - { UFBX_MATERIAL_FEATURE_IOR, 0, 0, ufbxi_mat_string("extension|enableIndexOfRefraction") }, - { UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("extension|enableSpecular") }, - { UFBX_MATERIAL_FEATURE_UNLIT, 0, 0, ufbxi_mat_string("extension|unlit") }, -}; - -// NOTE: These are just the names used by the standard PBS "preset". -// In _theory_ we could walk ShaderGraph but that's a bit out of scope for ufbx. -static const ufbxi_shader_mapping ufbxi_shaderfx_graph_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("color") }, - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metallic") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normal") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emissive_intensity") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissive") }, - { UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") }, -}; - -static const ufbxi_shader_mapping ufbxi_blender_phong_shader_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, UFBXI_MAT_TRANSFORM_BLENDER_OPACITY, ufbxi_mat_string("TransparencyFactor") }, - { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("Shininess") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("ShininessExponent") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("ReflectionFactor") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") }, -}; - -static const ufbxi_shader_mapping ufbxi_obj_pbr_mapping[] = { - { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") }, - { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") }, - { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Ns") }, - { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Pr") }, - { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("Ni") }, - { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("Pm") }, - { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("d") }, - { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Tf") }, - { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("disp") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, - { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") }, - { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ps") }, - { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("Pc") }, - { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("Pcr") }, - { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("aniso") }, - { UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisor") }, -}; - -static const ufbxi_shader_mapping ufbxi_obj_features[] = { - { UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pr") }, - { UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") }, - { UFBX_MATERIAL_FEATURE_SHEEN, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ps") }, - { UFBX_MATERIAL_FEATURE_COAT, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pc") }, - { UFBX_MATERIAL_FEATURE_METALNESS, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") }, - { UFBX_MATERIAL_FEATURE_IOR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ni") }, - { UFBX_MATERIAL_FEATURE_OPACITY, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("d") }, - { UFBX_MATERIAL_FEATURE_TRANSMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Tf") }, - { UFBX_MATERIAL_FEATURE_EMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ke") }, -}; - -enum { - UFBXI_MAT_PBR = 1 << UFBX_MATERIAL_FEATURE_PBR, - UFBXI_MAT_METALNESS = 1 << UFBX_MATERIAL_FEATURE_METALNESS, - UFBXI_MAT_DIFFUSE = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE, - UFBXI_MAT_SPECULAR = 1 << UFBX_MATERIAL_FEATURE_SPECULAR, - UFBXI_MAT_EMISSION = 1 << UFBX_MATERIAL_FEATURE_EMISSION, - UFBXI_MAT_COAT = 1 << UFBX_MATERIAL_FEATURE_COAT, - UFBXI_MAT_SHEEN = 1 << UFBX_MATERIAL_FEATURE_SHEEN, - UFBXI_MAT_TRANSMISSION = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION, - UFBXI_MAT_OPACITY = 1 << UFBX_MATERIAL_FEATURE_OPACITY, - UFBXI_MAT_AMBIENT_OCCLUSION = 1 << UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION, - UFBXI_MAT_MATTE = 1 << UFBX_MATERIAL_FEATURE_MATTE, - UFBXI_MAT_UNLIT = 1 << UFBX_MATERIAL_FEATURE_UNLIT, - UFBXI_MAT_IOR = 1 << UFBX_MATERIAL_FEATURE_IOR, - UFBXI_MAT_DIFFUSE_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, - UFBXI_MAT_TRANSMISSION_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, - UFBXI_MAT_THIN_WALLED = 1 << UFBX_MATERIAL_FEATURE_THIN_WALLED, - UFBXI_MAT_CAUSTICS = 1 << UFBX_MATERIAL_FEATURE_CAUSTICS, - UFBXI_MAT_EXIT_TO_BACKGROUND = 1 << UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, - UFBXI_MAT_INTERNAL_REFLECTIONS = 1 << UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, - UFBXI_MAT_DOUBLE_SIDED = 1 << UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, -}; - -static const ufbxi_shader_mapping_list ufbxi_shader_pbr_mappings[] = { - { // UFBX_SHADER_UNKNOWN - ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping), - NULL, 0, - (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), - }, - { // UFBX_SHADER_FBX_LAMBERT - ufbxi_fbx_lambert_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_lambert_shader_pbr_mapping), - NULL, 0, - (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), - }, - { // UFBX_SHADER_FBX_PHONG - ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping), - NULL, 0, - (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION), - }, - { // UFBX_SHADER_OSL_STANDARD_SURFACE - ufbxi_osl_standard_shader_pbr_mapping, ufbxi_arraycount(ufbxi_osl_standard_shader_pbr_mapping), - ufbxi_osl_standard_shader_features, ufbxi_arraycount(ufbxi_osl_standard_shader_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT - | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), - }, - { // UFBX_SHADER_ARNOLD_STANDARD_SURFACE - ufbxi_arnold_shader_pbr_mapping, ufbxi_arraycount(ufbxi_arnold_shader_pbr_mapping), - ufbxi_arnold_shader_features, ufbxi_arraycount(ufbxi_arnold_shader_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT - | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), - }, - { // UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL - ufbxi_3ds_max_physical_material_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_physical_material_pbr_mapping), - ufbxi_3ds_max_physical_material_features, ufbxi_arraycount(ufbxi_3ds_max_physical_material_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_COAT - | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR), - { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix - { NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH - ufbxi_3ds_max_pbr_metal_rough_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_metal_rough_pbr_mapping), - ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY), - { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix - { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS - ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping), - ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_SPECULAR | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY), - { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix - { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_GLTF_MATERIAL - ufbxi_gltf_material_pbr_mapping, ufbxi_arraycount(ufbxi_gltf_material_pbr_mapping), - ufbxi_gltf_material_features, ufbxi_arraycount(ufbxi_gltf_material_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_AMBIENT_OCCLUSION), - { NULL, 0 }, ufbxi_string_literal("Map"), // texture_prefix/suffix - { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_OPENPBR_MATERIAL - ufbxi_openpbr_material_pbr_mapping, ufbxi_arraycount(ufbxi_openpbr_material_pbr_mapping), - ufbxi_openpbr_material_features, ufbxi_arraycount(ufbxi_openpbr_material_features), - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT - | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), - { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix - { NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_SHADERFX_GRAPH - ufbxi_shaderfx_graph_pbr_mapping, ufbxi_arraycount(ufbxi_shaderfx_graph_pbr_mapping), - NULL, 0, - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_AMBIENT_OCCLUSION), - ufbxi_string_literal("TEX_"), ufbxi_string_literal("_map"), // texture_prefix/suffix - ufbxi_string_literal("use_"), ufbxi_string_literal("_map"), // texture_enabled_prefix/suffix - }, - { // UFBX_SHADER_BLENDER_PHONG - ufbxi_blender_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_blender_phong_shader_pbr_mapping), - NULL, 0, - (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION), - }, - { // UFBX_SHADER_WAVEFRONT_MTL - ufbxi_obj_pbr_mapping, ufbxi_arraycount(ufbxi_obj_pbr_mapping), - ufbxi_obj_features, ufbxi_arraycount(ufbxi_obj_features), - (uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR), - }, -}; - -ufbx_static_assert(shader_pbr_mapping_list, ufbxi_arraycount(ufbxi_shader_pbr_mappings) == UFBX_SHADER_TYPE_COUNT); - -enum { - UFBXI_MAPPING_FETCH_VALUE = 0x1, - UFBXI_MAPPING_FETCH_TEXTURE = 0x2, - UFBXI_MAPPING_FETCH_TEXTURE_ENABLED = 0x4, - UFBXI_MAPPING_FETCH_FEATURE = 0x8, -}; - -ufbxi_noinline static void ufbxi_fetch_mapping_maps(ufbx_material *material, ufbx_material_map *maps, ufbx_material_feature_info *features, - ufbx_shader *shader, const ufbxi_shader_mapping *mappings, size_t count, ufbx_string prefix, ufbx_string prefix2, ufbx_string suffix, uint32_t flags) -{ - char combined_name[512]; - ufbx_shader_prop_binding identity_binding; - - ufbxi_for(const ufbxi_shader_mapping, mapping, mappings, count) { - ufbx_string prop_name = { mapping->prop, mapping->prop_len }; - if (prefix.length > 0 || prefix2.length > 0 || suffix.length > 0) { - if (prop_name.length + prefix.length + prefix2.length + suffix.length <= sizeof(combined_name)) { - char *dst = combined_name; - - if (prefix.length > 0) { - memcpy(dst, prefix.data, prefix.length); - dst += prefix.length; - } - if (prefix2.length > 0) { - memcpy(dst, prefix2.data, prefix2.length); - dst += prefix2.length; - } - if (prop_name.length > 0) { - memcpy(dst, prop_name.data, prop_name.length); - dst += prop_name.length; - } - if (suffix.length > 0) { - memcpy(dst, suffix.data, suffix.length); - dst += suffix.length; - } - - prop_name.data = combined_name; - prop_name.length = ufbxi_to_size(dst - combined_name); - } - } - - ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, prop_name.data, prop_name.length); - if (bindings.count == 0) { - identity_binding.material_prop = prop_name; - identity_binding.shader_prop = ufbx_empty_string; - bindings.data = &identity_binding; - bindings.count = 1; - } - - uint32_t mapping_flags = mapping->flags; - ufbxi_for_list(ufbx_shader_prop_binding, binding, bindings) { - ufbx_string name = binding->material_prop; - - ufbx_prop *prop = ufbx_find_prop_len(&material->props, name.data, name.length); - if (flags & UFBXI_MAPPING_FETCH_FEATURE) { - ufbx_material_feature_info *feature = &features[mapping->index]; - if (prop && prop->type != UFBX_PROP_REFERENCE) { - feature->enabled = prop->value_int != 0; - feature->is_explicit = true; - if (mapping_flags & UFBXI_SHADER_FEATURE_IF_AROUND_1) { - feature->enabled = (prop->value_real >= 0.5f && prop->value_real <= 1.5f); - } - if (mapping_flags & UFBXI_SHADER_FEATURE_INVERTED) { - feature->enabled = !feature->enabled; - } - if (mapping_flags & UFBXI_SHADER_FEATURE_IF_EXISTS) { - feature->enabled = true; - } - } - if (mapping_flags & UFBXI_SHADER_FEATURE_IF_TEXTURE) { - ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length); - if (texture) { - feature->enabled = true; - } - } - continue; - } - - ufbx_material_map *map = &maps[mapping->index]; - - if (flags & UFBXI_MAPPING_FETCH_VALUE) { - if (prop && prop->type != UFBX_PROP_REFERENCE) { - if ((mapping->flags & UFBXI_SHADER_MAPPING_MULTIPLY_VALUE) != 0) { - map->value_vec4.x *= prop->value_vec4.x; - map->value_int = ufbxi_f64_to_i64(map->value_vec4.x); - } else { - map->value_vec4 = prop->value_vec4; - map->value_int = prop->value_int; - } - map->has_value = true; - if (mapping->transform) { - ufbxi_mat_transform_fn transform_fn = ufbxi_mat_transform_fns[mapping->transform]; - transform_fn(&map->value_vec4); - } - - uint32_t prop_flags = (uint32_t)prop->flags; - if ((mapping->flags & UFBXI_SHADER_MAPPING_DEFAULT_W_1) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) == 0) { - map->value_vec4.w = 1.0f; - } - if ((mapping->flags & UFBXI_SHADER_MAPPING_WIDEN_TO_RGB) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) { - map->value_vec3.y = map->value_vec3.x; - map->value_vec3.z = map->value_vec3.x; - } - if ((prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) { - map->value_components = 1; - } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC2) != 0) { - map->value_components = 2; - } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC3) != 0) { - map->value_components = 3; - } else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) != 0) { - map->value_components = 4; - } else { - map->value_components = 0; - } - } - } - - if (flags & UFBXI_MAPPING_FETCH_TEXTURE) { - ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length); - if (texture) { - map->texture = texture; - map->texture_enabled = true; - } - } - - if (flags & UFBXI_MAPPING_FETCH_TEXTURE_ENABLED) { - if (prop) { - map->texture_enabled = prop->value_int != 0; - } - } - } - } -} - -ufbxi_noinline static void ufbxi_update_factor(ufbx_material_map *factor_map, ufbx_material_map *color_map) -{ - if (!factor_map->has_value) { - if (color_map->has_value && !ufbxi_is_vec4_zero(color_map->value_vec4)) { - factor_map->value_real = 1.0f; - factor_map->value_int = 1; - } else { - factor_map->value_real = 0.0f; - factor_map->value_int = 0; - } - } -} - -// Some material modes have toggleable roughness/glossiness mode, we read it initially -// always as roughness and if a matching feature such as `roughness_as_glossiness` is set -// we transfer the data into the glossiness and invert the roughness. -typedef struct { - uint8_t feature; - uint8_t roughness_map; - uint8_t glossiness_map; -} ufbxi_glossiness_remap; - -static const ufbxi_glossiness_remap ufbxi_glossiness_remaps[] = { - { UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_ROUGHNESS, UFBX_MATERIAL_PBR_GLOSSINESS }, - { UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_COAT_ROUGHNESS, UFBX_MATERIAL_PBR_COAT_GLOSSINESS }, - { UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS }, -}; - -ufbxi_noinline static void ufbxi_fetch_maps(ufbx_scene *scene, ufbx_material *material) -{ - (void)scene; - - ufbx_shader *shader = material->shader; - ufbx_assert((uint32_t)material->shader_type < UFBX_SHADER_TYPE_COUNT); - - memset(&material->fbx, 0, sizeof(material->fbx)); - memset(&material->pbr, 0, sizeof(material->pbr)); - memset(&material->features, 0, sizeof(material->features)); - - const ufbxi_shader_mapping *base_mapping = ufbxi_base_fbx_mapping; - size_t num_base_mapping = ufbxi_arraycount(ufbxi_base_fbx_mapping); - - if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ || scene->metadata.file_format == UFBX_FILE_FORMAT_MTL) { - base_mapping = ufbxi_obj_fbx_mapping; - num_base_mapping = ufbxi_arraycount(ufbxi_obj_fbx_mapping); - } - - ufbxi_fetch_mapping_maps(material, material->fbx.maps, NULL, NULL, - base_mapping, num_base_mapping, - ufbx_empty_string, ufbx_empty_string, ufbx_empty_string, - UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE); - - ufbxi_shader_mapping_list list = ufbxi_shader_pbr_mappings[material->shader_type]; - - for (uint32_t i = 0; i < UFBX_MATERIAL_FEATURE_COUNT; i++) { - if ((list.default_features & (1u << i)) != 0) { - material->features.features[i].enabled = true; - } - } - - ufbx_string prefix = ufbx_empty_string; - if (!shader) { - prefix = material->shader_prop_prefix; - } - - if (list.texture_prefix.length > 0 || list.texture_suffix.length > 0) { - ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, - list.data, list.count, prefix, list.texture_prefix, list.texture_suffix, - UFBXI_MAPPING_FETCH_TEXTURE); - } - - ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, - list.data, list.count, prefix, ufbx_empty_string, ufbx_empty_string, - UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE); - - if (list.texture_enabled_prefix.length > 0 || list.texture_enabled_suffix.length > 0) { - ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader, - list.data, list.count, prefix, list.texture_enabled_prefix, list.texture_enabled_suffix, - UFBXI_MAPPING_FETCH_TEXTURE_ENABLED); - } - - ufbxi_fetch_mapping_maps(material, NULL, material->features.features, shader, - list.features, list.feature_count, prefix, ufbx_empty_string, ufbx_empty_string, - UFBXI_MAPPING_FETCH_FEATURE); - - ufbxi_update_factor(&material->fbx.diffuse_factor, &material->fbx.diffuse_color); - ufbxi_update_factor(&material->fbx.specular_factor, &material->fbx.specular_color); - ufbxi_update_factor(&material->fbx.reflection_factor, &material->fbx.reflection_color); - ufbxi_update_factor(&material->fbx.transparency_factor, &material->fbx.transparency_color); - ufbxi_update_factor(&material->fbx.emission_factor, &material->fbx.emission_color); - ufbxi_update_factor(&material->fbx.ambient_factor, &material->fbx.ambient_color); - - ufbxi_update_factor(&material->pbr.base_factor, &material->pbr.base_color); - ufbxi_update_factor(&material->pbr.specular_factor, &material->pbr.specular_color); - ufbxi_update_factor(&material->pbr.emission_factor, &material->pbr.emission_color); - ufbxi_update_factor(&material->pbr.sheen_factor, &material->pbr.sheen_color); - ufbxi_update_factor(&material->pbr.thin_film_factor, &material->pbr.thin_film_thickness); - ufbxi_update_factor(&material->pbr.transmission_factor, &material->pbr.transmission_color); - - // Patch transmission roughness if only extra roughness is defined - if (!material->pbr.transmission_roughness.has_value && material->pbr.roughness.has_value && material->pbr.transmission_extra_roughness.has_value) { - material->pbr.transmission_roughness.value_real = material->pbr.roughness.value_real + material->pbr.transmission_extra_roughness.value_real; - } - - // Map roughness to glossiness and vice versa - ufbxi_for(const ufbxi_glossiness_remap, remap, ufbxi_glossiness_remaps, ufbxi_arraycount(ufbxi_glossiness_remaps)) { - ufbx_material_map *roughness = &material->pbr.maps[remap->roughness_map]; - ufbx_material_map *glossiness = &material->pbr.maps[remap->glossiness_map]; - if (material->features.features[remap->feature].enabled) { - *glossiness = *roughness; - memset(roughness, 0, sizeof(ufbx_material_map)); - if (glossiness->has_value) { - roughness->value_real = 1.0f - glossiness->value_real; - } - } else { - if (roughness->has_value) { - glossiness->value_real = 1.0f - roughness->value_real; - } - } - } -} - -typedef enum { - UFBXI_CONSTRAINT_PROP_NODE, - UFBXI_CONSTRAINT_PROP_IK_EFFECTOR, - UFBXI_CONSTRAINT_PROP_IK_END_NODE, - UFBXI_CONSTRAINT_PROP_AIM_UP, - UFBXI_CONSTRAINT_PROP_TARGET, -} ufbxi_constraint_prop_type; - -typedef struct { - ufbxi_constraint_prop_type type; - const char *name; -} ufbxi_constraint_prop; - -static const ufbxi_constraint_prop ufbxi_constraint_props[] = { - { UFBXI_CONSTRAINT_PROP_NODE, "Constrained Object" }, - { UFBXI_CONSTRAINT_PROP_NODE, "Constrained object (Child)" }, - { UFBXI_CONSTRAINT_PROP_NODE, "First Joint" }, - { UFBXI_CONSTRAINT_PROP_TARGET, "Source" }, - { UFBXI_CONSTRAINT_PROP_TARGET, "Source (Parent)" }, - { UFBXI_CONSTRAINT_PROP_TARGET, "Aim At Object" }, - { UFBXI_CONSTRAINT_PROP_TARGET, "Pole Vector Object" }, - { UFBXI_CONSTRAINT_PROP_IK_EFFECTOR, "Effector" }, - { UFBXI_CONSTRAINT_PROP_IK_END_NODE, "End Joint" }, - { UFBXI_CONSTRAINT_PROP_AIM_UP, "World Up Object" }, -}; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_constraint_prop(ufbxi_context *uc, ufbx_constraint *constraint, ufbx_node *node, const char *prop) -{ - ufbxi_for(const ufbxi_constraint_prop, cprop, ufbxi_constraint_props, ufbxi_arraycount(ufbxi_constraint_props)) { - if (strcmp(cprop->name, prop) != 0) continue; - switch (cprop->type) { - case UFBXI_CONSTRAINT_PROP_NODE: constraint->node = node; break; - case UFBXI_CONSTRAINT_PROP_IK_EFFECTOR: constraint->ik_effector = node; break; - case UFBXI_CONSTRAINT_PROP_IK_END_NODE: constraint->ik_end_node = node; break; - case UFBXI_CONSTRAINT_PROP_AIM_UP: constraint->aim_up_node = node; break; - case UFBXI_CONSTRAINT_PROP_TARGET: { - ufbx_constraint_target *target = ufbxi_push_zero(&uc->tmp_stack, ufbx_constraint_target, 1); - ufbxi_check(target); - target->node = node; - target->weight = 1.0f; - target->transform = ufbx_identity_transform; - } break; - default: - ufbxi_unreachable("Unexpected constraint prop"); - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_nurbs_basis(ufbxi_context *uc, ufbx_nurbs_basis *basis) -{ - if (basis->topology == UFBX_NURBS_TOPOLOGY_CLOSED) { - basis->num_wrap_control_points = 1; - } else if (basis->topology == UFBX_NURBS_TOPOLOGY_PERIODIC) { - basis->num_wrap_control_points = basis->order - 1; - } else { - basis->num_wrap_control_points = 0; - } - - if (basis->order > 1) { - size_t degree = basis->order - 1; - ufbx_real_list knots = basis->knot_vector; - if (knots.count >= 2*degree + 1) { - basis->t_min = knots.data[degree]; - basis->t_max = knots.data[knots.count - degree - 1]; - - size_t max_spans = knots.count - 2*degree; - ufbx_real *spans = ufbxi_push(&uc->result, ufbx_real, max_spans); - ufbxi_check(spans); - - ufbx_real prev = -UFBX_INFINITY; - size_t num_spans = 0; - for (size_t i = 0; i < max_spans; i++) { - ufbx_real t = knots.data[degree + i]; - if (t != prev) { - spans[num_spans++] = t; - prev = t; - } - } - - basis->spans.data = spans; - basis->spans.count = num_spans; - basis->valid = true; - for (size_t i = 1; i < knots.count; i++) { - if (knots.data[i - 1] > knots.data[i]) { - basis->valid = false; - break; - } - } - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_lod_group(ufbxi_context *uc, ufbx_lod_group *lod) -{ - size_t num_levels = 0; - for (size_t i = 0; i < lod->instances.count; i++) { - num_levels = ufbxi_max_sz(num_levels, lod->instances.data[0]->children.count); - } - - char prop_name[64]; - for (size_t i = 0; ; i++) { - int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i); - ufbx_prop *prop = ufbx_find_prop_len(&lod->props, prop_name, (size_t)len); - if (!prop) break; - num_levels = ufbxi_max_sz(num_levels, i + 1); - } - - ufbx_lod_level *levels = ufbxi_push_zero(&uc->result, ufbx_lod_level, num_levels); - ufbxi_check(levels); - - lod->relative_distances = ufbx_find_bool(&lod->props, "ThresholdsUsedAsPercentage", false); - lod->ignore_parent_transform = !ufbx_find_bool(&lod->props, "WorldSpace", true); - - lod->use_distance_limit = ufbx_find_bool(&lod->props, "MinMaxDistance", false); - lod->distance_limit_min = ufbx_find_real(&lod->props, "MinDistance", (ufbx_real)-100.0); - lod->distance_limit_max = ufbx_find_real(&lod->props, "MaxDistance", (ufbx_real)100.0); - - lod->lod_levels.data = levels; - lod->lod_levels.count = num_levels; - - for (size_t i = 0; i < num_levels; i++) { - ufbx_lod_level *level = &levels[i]; - - if (i > 0) { - int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i - 1); - level->distance = ufbx_find_real_len(&lod->props, prop_name, (size_t)len, 0.0f); - } else if (lod->relative_distances) { - level->distance = (ufbx_real)100.0; - } - - { - int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "DisplayLevels|Level%zu", i); - int64_t display = ufbx_find_int_len(&lod->props, prop_name, (size_t)len, 0); - if (display >= 0 && display <= 2) { - level->display = (ufbx_lod_display)display; - } - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_generate_normals(ufbxi_context *uc, ufbx_mesh *mesh) -{ - size_t num_indices = mesh->num_indices; - - mesh->generated_normals = true; - - ufbx_topo_edge *topo = ufbxi_push(&uc->tmp_stack, ufbx_topo_edge, num_indices); - ufbxi_check(topo); - - uint32_t *normal_indices = ufbxi_push(&uc->result, uint32_t, num_indices); - ufbxi_check(normal_indices); - - ufbx_compute_topology(mesh, topo, num_indices); - size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false); - - if (num_normals == mesh->num_vertices) { - mesh->vertex_normal.unique_per_vertex = true; - } - - ufbx_vec3 *normal_data = ufbxi_push(&uc->result, ufbx_vec3, num_normals + 1); - ufbxi_check(normal_data); - - normal_data[0] = ufbx_zero_vec3; - normal_data++; - - ufbx_compute_normals(mesh, &mesh->vertex_position, normal_indices, num_indices, normal_data, num_normals); - - mesh->vertex_normal.exists = true; - mesh->vertex_normal.values.data = normal_data; - mesh->vertex_normal.values.count = num_normals; - mesh->vertex_normal.indices.data = normal_indices; - mesh->vertex_normal.indices.count = num_indices; - mesh->vertex_normal.value_reals = 3; - - mesh->skinned_normal = mesh->vertex_normal; - - ufbxi_pop(&uc->tmp_stack, ufbx_topo_edge, num_indices, NULL); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_prop_prefix(ufbxi_context *uc, ufbx_string *dst, ufbx_string prefix) -{ - size_t stack_size = 0; - if (prefix.length > 0 && prefix.data[prefix.length - 1] != '|') { - stack_size = prefix.length + 1; - char *copy = ufbxi_push(&uc->tmp_stack, char, stack_size); - ufbxi_check(copy); - memcpy(copy, prefix.data, prefix.length); - copy[prefix.length] = '|'; - - prefix.data = copy; - prefix.length += 1; - } - - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prefix, false)); - *dst = prefix; - - if (stack_size > 0) { - ufbxi_pop(&uc->tmp_stack, char, stack_size, NULL); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_shader_texture_find_prefix(ufbxi_context *uc, ufbx_texture *texture, ufbx_shader_texture *shader) -{ - ufbx_string suffixes[3]; - size_t num_suffixes = 0; - - suffixes[num_suffixes++] = ufbxi_str_c(" Parameters/Connections"); - if (shader->shader_name.length > 0) { - suffixes[num_suffixes++] = shader->shader_name; - } - suffixes[num_suffixes++] = ufbxi_str_c("3dsMax|parameters"); - - ufbx_assert(num_suffixes <= ufbxi_arraycount(suffixes)); - - ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) { - ufbx_string suffix = *p_suffix; - - ufbxi_for_list(ufbx_prop, prop, texture->props.props) { - if (prop->type != UFBX_PROP_COMPOUND) continue; - if (ufbxi_ends_with(prop->name, suffix)) { - ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, prop->name)); - return 1; - } - } - } - - // Pre-7000 files don't have explicit Compound properties, so let's look for - // any property that has the suffix before the last `|` ... - ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) { - ufbx_string suffix = *p_suffix; - - ufbxi_for_list(ufbx_prop, prop, texture->props.props) { - ufbx_string name = prop->name; - while (name.length > 0) { - if (name.data[name.length - 1] == '|') { - break; - } - name.length--; - } - if (name.length <= 1) continue; - name.length--; - - if (ufbxi_ends_with(name, suffix)) { - ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, name)); - return 1; - } - } - } - - return 1; -} - -typedef struct { - uint64_t shader_id; - const char *shader_name; - const char *input_name; -} ufbxi_file_shader; - -// Known shaders that represent sampled images. -static const ufbxi_file_shader ufbxi_file_shaders[] = { - { UINT64_C(0x7e73161fad53b12a), "ai_image", "filename" }, - { 0, "OSLBitmap", ufbxi_Filename }, - { 0, "OSLBitmap2", ufbxi_Filename }, - { 0, "OSLBitmap3", ufbxi_Filename }, - { 0, "UberBitmap", ufbxi_Filename }, - { 0, "UberBitmap2", ufbxi_Filename }, -}; - -ufbxi_noinline static void ufbxi_update_shader_texture(ufbx_texture *texture, ufbx_shader_texture *shader) -{ - ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { - ufbx_prop *prop = input->prop; - if (prop) { - input->prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); - input->value_vec4 = prop->value_vec4; - input->value_int = prop->value_int; - input->value_str = prop->value_str; - input->value_blob = prop->value_blob; - input->texture = (ufbx_texture*)ufbx_get_prop_element(&texture->element, input->prop, UFBX_ELEMENT_TEXTURE); - } - - prop = input->texture_prop; - if (prop) { - input->texture_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); - ufbx_texture *tex = (ufbx_texture*)ufbx_get_prop_element(&texture->element, prop, UFBX_ELEMENT_TEXTURE); - if (tex) input->texture = tex; - } - - input->texture_enabled = input->texture != NULL; - prop = input->texture_enabled_prop; - if (prop) { - input->texture_enabled_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length); - input->texture_enabled = prop->value_int != 0; - } - } - - if (shader->type == UFBX_SHADER_TEXTURE_SELECT_OUTPUT) { - ufbx_shader_texture_input *map = ufbx_find_shader_texture_input(shader, "sourceMap"); - ufbx_shader_texture_input *index = ufbx_find_shader_texture_input(shader, "outputChannelIndex"); - if (index) { - shader->main_texture_output_index = index->value_int; - } - if (map) { - shader->main_texture = map->texture; - map->texture_output_index = shader->main_texture_output_index; - } - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_shader_texture(ufbxi_context *uc, ufbx_texture *texture) -{ - uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDa", 0); - uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDb", 0); - uint64_t classid = (uint64_t)classid_a << 32u | classid_b; - - ufbx_string max_texture = ufbx_find_string(&texture->props, "3dsMax|MaxTexture", ufbx_empty_string); - - // Check first if the texture looks like it could be a shader. - ufbx_shader_texture_type type = (ufbx_shader_texture_type)UFBX_SHADER_TEXTURE_TYPE_COUNT; - - if (!strcmp(max_texture.data, "MULTIOUTPUT_TO_OSLMap") || classid == UINT64_C(0x896ef2fc44bd743f)) { - type = UFBX_SHADER_TEXTURE_SELECT_OUTPUT; - } else if (!strcmp(max_texture.data, "OSLMap") || classid == UINT64_C(0x7f9a7b9d6fcdf00d)) { - type = UFBX_SHADER_TEXTURE_OSL; - } else if (texture->type == UFBX_TEXTURE_FILE && texture->relative_filename.length == 0 && texture->absolute_filename.length == 0 && !texture->video) { - type = UFBX_SHADER_TEXTURE_UNKNOWN; - } - - if ((uint32_t)type == UFBX_SHADER_TEXTURE_TYPE_COUNT) return 1; - - ufbx_shader_texture *shader = ufbxi_push_zero(&uc->result, ufbx_shader_texture, 1); - ufbxi_check(shader); - - shader->type = type; - - static const char *const name_props[] = { - "3dsMax|params|OSLShaderName", - }; - - static const char *const source_props[] = { - "3dsMax|params|OSLCode", - }; - - shader->shader_source.data = ufbxi_empty_char; - shader->shader_name.data = ufbxi_empty_char; - - ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(name_props); i++) { - ufbx_prop *prop = ufbx_find_prop(&texture->props, name_props[i]); - if (prop) { - shader->shader_name = prop->value_str; - break; - } - } - - ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(source_props); i++) { - ufbx_prop *prop = ufbx_find_prop(&texture->props, source_props[i]); - if (prop) { - shader->shader_source = prop->value_str; - shader->raw_shader_source = prop->value_blob; - break; - } - } - - ufbxi_check(ufbxi_shader_texture_find_prefix(uc, texture, shader)); - - if (shader->shader_name.length == 0) { - ufbx_string name = shader->prop_prefix; - if (ufbxi_remove_suffix_c(&name, " Parameters/Connections|")) { - size_t begin = name.length; - while (begin > 0 && name.data[begin - 1] != '|') { - begin--; - } - - shader->shader_name.data = name.data + begin; - shader->shader_name.length = name.length - begin; - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &shader->shader_name, false)); - } - } - - if (shader->shader_name.length == 0) { - if (max_texture.length > 0) { - shader->shader_name = max_texture; - } - } - - if (classid != 0) { - shader->shader_type_id = classid; - } - - if (shader->prop_prefix.length == 0) { - // If we not find any shader properties so we might have guessed wrong. - // We "leak" (freed with scene) the shader in this case but it's negligible. - return 1; - } - - ufbxi_for_list(ufbx_prop, prop, texture->props.props) { - - ufbx_string name = prop->name; - if (!ufbxi_remove_prefix_str(&name, shader->prop_prefix)) continue; - - // Check if this property is a modifier to an existing input. - ufbx_string base_name = name; - if (ufbxi_remove_suffix_c(&base_name, "_map") || ufbxi_remove_suffix_c(&base_name, ".shader")) { - ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length); - if (base) { - base->texture_prop = prop; - continue; - } - } else if (ufbxi_remove_suffix_c(&base_name, ".connected") || ufbxi_remove_suffix_c(&base_name, "Enabled")) { - ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length); - if (base) { - base->texture_enabled_prop = prop; - continue; - } - } - - // Use `uc->tmp_arr` to store the texture inputs so we can search them while we insert new ones. - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, - (shader->inputs.count + 1) * sizeof(ufbx_shader_texture_input))); - shader->inputs.data = (ufbx_shader_texture_input*)uc->tmp_arr; - - // Add a new property - ufbx_shader_texture_input *input = &shader->inputs.data[shader->inputs.count++]; - memset(input, 0, sizeof(ufbx_shader_texture_input)); - - // NOTE: This is a bit hackish, we are using a suffix of an interned string. It won't compare - // pointer equal to the same string but that shouldn't matter.. - input->name = name; - - // Connect the property only, values and textures etc are fetched in `ufbxi_update_shader_texture()`. - input->prop = prop; - } - - // Retain the shader inputs - shader->inputs.data = ufbxi_push_copy(&uc->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data); - ufbxi_check(shader->inputs.data); - - texture->shader = shader; - texture->type = UFBX_TEXTURE_SHADER; - uc->scene.metadata.num_shader_textures++; - - if (!uc->opts.disable_quirks) { - ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(ufbxi_file_shaders); i++) { - const ufbxi_file_shader *fs = &ufbxi_file_shaders[i]; - - if ((fs->shader_id && shader->shader_type_id == fs->shader_id) || !strcmp(shader->shader_name.data, fs->shader_name)) { - ufbx_shader_texture_input *input = ufbx_find_shader_texture_input(shader, fs->input_name); - if (input) { - // TODO: Support for specifying relative filename here if ever needed - ufbx_prop *prop = input->prop; - texture->absolute_filename = prop->value_str; - texture->raw_absolute_filename = prop->value_blob; - texture->type = UFBX_TEXTURE_FILE; - break; - } - } - } - } - - ufbxi_update_shader_texture(texture, shader); - - return 1; -} - -ufbxi_noinline static void ufbxi_propagate_main_textures(ufbx_scene *scene) -{ - // We need to do at least 2^(N-1) passes for N shader textures - size_t mask = scene->metadata.num_shader_textures; - while (mask) { - mask >>= 1; - - ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { - ufbx_texture *texture = *p_texture; - ufbx_shader_texture *shader = texture->shader; - if (!shader) continue; - - ufbx_texture *main_tex = shader->main_texture; - if (!main_tex || shader->main_texture_output_index != 0) continue; - - ufbx_shader_texture *main_shader = main_tex->shader; - if (!main_shader || !main_shader->main_texture) continue; - - shader->main_texture = main_shader->main_texture; - shader->main_texture_output_index = main_shader->main_texture_output_index; - } - } - - // Remove cyclic main textures - ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { - ufbx_texture *texture = *p_texture; - ufbx_shader_texture *shader = texture->shader; - if (!shader || !shader->main_texture || shader->main_texture_output_index != 0) continue; - ufbx_texture *main_tex = shader->main_texture; - if (main_tex && main_tex->shader && main_tex->shader->main_texture) { - // Should have been propagated to `texture` - shader->main_texture = NULL; - } - } - - ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { - ufbx_texture *texture = *p_texture; - ufbx_shader_texture *shader = texture->shader; - if (!shader) continue; - - ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { - if (!input->texture || !input->texture->shader) continue; - ufbx_shader_texture *input_shader = input->texture->shader; - if (input_shader->main_texture) { - input->texture = input_shader->main_texture; - input->texture_output_index = input_shader->main_texture_output_index; - } - } - } - - ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) { - ufbx_material *material = *p_material; - - ufbxi_for_list(ufbx_material_texture, tex, material->textures) { - ufbx_shader_texture *shader = tex->texture->shader; - if (shader && shader->main_texture && shader->main_texture_output_index == 0) { - tex->texture = shader->main_texture; - } - } - } -} - -#define ufbxi_patch_empty(m_dst, m_len, m_src) \ - do { if (!(m_dst).m_len) m_dst = m_src; } while (0) - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_texture_file(ufbxi_context *uc, ufbx_texture *texture) -{ - texture->file_index = UFBX_NO_INDEX; - - const char *key = NULL; - - // HACK: Even the raw entries have a null terminator so we can offset the - // pointer by one for relative filenames. This guarantees that an overlapping - // absolute and relative filenames will get separate textures. - if (texture->raw_absolute_filename.size > 0) { - key = (const char*)texture->raw_absolute_filename.data; - } else if (texture->raw_relative_filename.size > 0) { - key = (const char*)texture->raw_relative_filename.data + 1; - } - - if (key == NULL) return 1; - uint32_t hash = ufbxi_hash_ptr(key); - ufbxi_texture_file_entry *entry = ufbxi_map_find(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key); - if (!entry) { - entry = ufbxi_map_insert(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key); - ufbxi_check(entry); - - ufbx_texture_file *file = ufbxi_push_zero(&uc->tmp, ufbx_texture_file, 1); - ufbxi_check(file); - - file->index = uc->texture_file_map.size - 1; - - entry->key = key; - entry->file = file; - } - - ufbx_texture_file *file = entry->file; - texture->file_index = file->index; - texture->has_file = true; - ufbxi_patch_empty(file->filename, length, texture->filename); - ufbxi_patch_empty(file->relative_filename, length, texture->relative_filename); - ufbxi_patch_empty(file->absolute_filename, length, texture->absolute_filename); - ufbxi_patch_empty(file->raw_filename, size, texture->raw_filename); - ufbxi_patch_empty(file->raw_relative_filename, size, texture->raw_relative_filename); - ufbxi_patch_empty(file->raw_absolute_filename, size, texture->raw_absolute_filename); - ufbxi_patch_empty(file->content, size, texture->content); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_pop_texture_files(ufbxi_context *uc) -{ - uint32_t num_files = uc->texture_file_map.size; - ufbx_texture_file *files = ufbxi_push(&uc->result, ufbx_texture_file, num_files); - ufbxi_check(files); - - uc->scene.texture_files.data = files; - uc->scene.texture_files.count = num_files; - - ufbxi_texture_file_entry *entries = (ufbxi_texture_file_entry*)uc->texture_file_map.items; - for (size_t i = 0; i < num_files; i++) { - memcpy(&files[i], entries[i].file, sizeof(ufbx_texture_file)); - } - - return 1; -} - -typedef struct { - ufbx_texture *texture; - size_t order; -} ufbxi_ordered_texture; - -ufbxi_noinline static bool ufbxi_ordered_texture_less_texture(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb; - return a->texture < b->texture; -} - -ufbxi_noinline static bool ufbxi_ordered_texture_less_order(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb; - return a->order < b->order; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_deduplicate_textures(ufbxi_context *uc, ufbxi_buf *dst_buf, ufbxi_ordered_texture **p_dst, size_t *p_dst_count, size_t count) -{ - ufbxi_ordered_texture *textures = ufbxi_push_pop(dst_buf, &uc->tmp_stack, ufbxi_ordered_texture, count); - ufbxi_check(textures); - - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_ordered_texture))); - - ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, count, &ufbxi_ordered_texture_less_texture, NULL); - - // Remove adjacent duplicates - size_t dst_ix = 0; - for (size_t src_ix = 0; src_ix < count; src_ix++) { - if (src_ix > 0 && textures[src_ix - 1].texture == textures[src_ix].texture) { - continue; - } else { - if (src_ix != dst_ix) { - textures[dst_ix] = textures[src_ix]; - } - dst_ix++; - } - } - - size_t new_count = dst_ix; - ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, new_count, &ufbxi_ordered_texture_less_order, NULL); - - *p_dst_count = new_count; - *p_dst = textures; - - return 1; -} - -typedef enum { - UFBXI_FILE_TEXTURE_FETCH_INITIAL, - UFBXI_FILE_TEXTURE_FETCH_STARTED, - UFBXI_FILE_TEXTURE_FETCH_FINISHED, -} ufbxi_file_texture_fetch_state; - -// Populate `ufbx_texture.file_textures[]` arrays. -ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_file_textures(ufbxi_context *uc) -{ - // We keep pointers to `ufbx_texture` in `tmp_stack` as a working set, since we don't know - // how deep the shader graphs might be. - - // Start by pushing all the textures into the stack - size_t num_stack_textures = uc->scene.textures.count; - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, num_stack_textures, uc->scene.textures.data)); - - // Compressed `ufbxi_file_texture_fetch_state` - uint8_t *states = ufbxi_push_zero(&uc->tmp, uint8_t, uc->scene.textures.count); - ufbxi_check(states); - - while (num_stack_textures-- > 0) { - ufbx_texture *texture = NULL; - ufbxi_pop(&uc->tmp_stack, ufbx_texture*, 1, &texture); - - ufbxi_file_texture_fetch_state state = (ufbxi_file_texture_fetch_state)states[texture->typed_id]; - if (state == UFBXI_FILE_TEXTURE_FETCH_FINISHED) continue; - ufbx_shader_texture *shader = texture->shader; - - if (state == UFBXI_FILE_TEXTURE_FETCH_STARTED) { - states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED; - - // HACK: Reuse `tmp_parse` for storing intermediate information as we can clear it. - ufbxi_buf_clear(&uc->tmp_parse); - - // Now all non-cyclical dependents should be processed. - size_t num_deps = 0; - - if (texture->type == UFBX_TEXTURE_FILE) { - ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); - ufbxi_check(dst); - dst->texture = texture; - dst->order = num_deps++; - } - - ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) { - ufbx_texture *dep_tex = layer->texture; - if (dep_tex->file_textures.count > 0) { - ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); - ufbxi_check(dst); - dst->texture = dep_tex; - dst->order = num_deps++; - } - } - - if (shader) { - ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { - ufbx_texture *dep_tex = input->texture; - if (dep_tex && dep_tex->file_textures.count > 0) { - ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); - ufbxi_check(dst); - dst->texture = dep_tex; - dst->order = num_deps++; - } - } - } - - // Deduplicate the direct dependencies first - ufbxi_ordered_texture *deps; - ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &deps, &num_deps, num_deps)); - - if (num_deps == 1) { - // If we have only a single dependency (that is not the same one) we can just copy the pointer - texture->file_textures = deps[0].texture->file_textures; - } else { - // Now collect all the file textures and deduplicate them - size_t num_files = 0; - ufbxi_for(ufbxi_ordered_texture, dep, deps, num_deps) { - ufbxi_for_ptr_list(ufbx_texture, p_tex, dep->texture->file_textures) { - ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1); - ufbxi_check(dst); - dst->texture = *p_tex; - dst->order = num_files++; - } - } - - // Deduplicate the file textures - ufbxi_ordered_texture *files; - ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &files, &num_files, num_files)); - - texture->file_textures.count = num_files; - texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, num_files); - ufbxi_check(texture->file_textures.data); - - for (size_t i = 0; i < num_files; i++) { - texture->file_textures.data[i] = files[i].texture; - } - } - - } else { - if (texture->type == UFBX_TEXTURE_FILE) { - // Simple case: Just point to self - texture->file_textures.count = 1; - texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, 1); - ufbxi_check(texture->file_textures.data); - texture->file_textures.data[0] = texture; - - // In simple cases we can quit here, for more complex file textures queue - // the texture in case there are other file textures as inputs. - if (!texture->shader) { - states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED; - continue; - } - } - - // Complex: Process all dependencies first - states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_STARTED; - - // Push self first so we can return after processing dependencies - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &texture)); - num_stack_textures++; - - ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &layer->texture)); - num_stack_textures++; - } - - if (shader) { - ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) { - if (input->texture) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &input->texture)); - num_stack_textures++; - } - } - } - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static ufbx_node *ufbxi_get_geometry_transform_node(ufbx_element *element) -{ - if (element->instances.count == 1) { - ufbx_node *node = element->instances.data[0]; - if (node->has_geometry_transform) return node; - } - return NULL; -} - -ufbxi_noinline static void ufbxi_mirror_vec3_list(const void *v_list, ufbx_mirror_axis axis, size_t stride) -{ - const ufbx_void_list *list = (const ufbx_void_list*)v_list; - if (axis == UFBX_MIRROR_AXIS_NONE || !list || list->count == 0) return; - if (!stride) stride = sizeof(ufbx_vec3); - - void *ptr = (char*)list->data + (size_t)((int)axis - 1) * sizeof(ufbx_real); - void *end = (char*)ptr + list->count * stride; - while (ptr != end) { - ufbx_real *v = (ufbx_real*)ptr; - *v = -*v; - ptr = (char*)ptr + stride; - } -} - -ufbxi_noinline static void ufbxi_scale_vec3_list(const void *v_list, ufbx_real scale, size_t stride) -{ - const ufbx_void_list *list = (const ufbx_void_list*)v_list; - if (!list || list->count == 0) return; - if (!stride) stride = sizeof(ufbx_vec3); - - void *ptr = list->data, *end = (char*)ptr + list->count * stride; - while (ptr != end) { - ufbx_vec3 *v = (ufbx_vec3*)ptr; - v->x *= scale; - v->y *= scale; - v->z *= scale; - ptr = (char*)ptr + stride; - } -} - -ufbxi_noinline static void ufbxi_transform_vec3_list(const void *v_list, const ufbx_matrix *matrix, size_t stride) -{ - const ufbx_void_list *list = (const ufbx_void_list*)v_list; - if (!list || list->count == 0) return; - if (!stride) stride = sizeof(ufbx_vec3); - - void *ptr = list->data, *end = (char*)ptr + list->count * stride; - while (ptr != end) { - ufbx_vec3 *v = (ufbx_vec3*)ptr; - *v = ufbx_transform_position(matrix, *v); - ptr = (char*)ptr + stride; - } -} - -ufbxi_noinline static void ufbxi_normalize_vec3_list(const ufbx_vec3_list *list) -{ - ufbxi_nounroll ufbxi_for_list(ufbx_vec3, normal, *list) { - *normal = ufbxi_normalize3(*normal); - } -} - -// Forward declare as we're kind of preprocessing ata here that would usually happen later. -ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node); - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_attrib_winding(ufbxi_context *uc, ufbx_mesh *mesh, ufbx_uint32_list *indices, bool is_position) -{ - // All zero, no flipping needed - if (indices->data == uc->zero_indices || indices->count == 0) return 1; - - if (indices->data == mesh->vertex_position.indices.data && !is_position) { - // Sharing indices with vertex position, already flipped. - return 1; - } else if (indices->data == uc->consecutive_indices) { - // Need to duplicate consecutive indices, but we can cache the per mesh. - if (uc->tmp_mesh_consecutive_indices) { - indices->data = uc->tmp_mesh_consecutive_indices; - return 1; - } - indices->data = ufbxi_push_copy(&uc->result, uint32_t, indices->count, indices->data); - ufbxi_check(indices->data); - uc->tmp_mesh_consecutive_indices = indices->data; - } - - uint32_t *data = indices->data; - ufbxi_for_list(ufbx_face, face, mesh->faces) { - if (face->num_indices == 0) continue; - size_t begin = face->index_begin + 1; - size_t end = face->index_begin + face->num_indices - 1; - while (begin < end) { - uint32_t tmp = data[begin]; - data[begin] = data[end]; - data[end] = tmp; - begin++; - end--; - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_winding(ufbxi_context *uc, ufbx_mesh *mesh) -{ - uc->tmp_mesh_consecutive_indices = NULL; - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_position.indices, true)); - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_normal.indices, false)); - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_crease.indices, false)); - if (mesh->uv_sets.count > 0) { - ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_uv.indices, false)); - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_tangent.indices, false)); - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_bitangent.indices, false)); - } - mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv; - mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent; - mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent; - } - if (mesh->color_sets.count > 0) { - ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_color.indices, false)); - } - mesh->vertex_color = mesh->color_sets.data[0].vertex_color; - } - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_position.indices, false)); - if (mesh->skinned_normal.indices.data != mesh->vertex_normal.indices.data) { - ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_normal.indices, false)); - } - - ufbxi_update_vertex_first_index(mesh); - - // Mapping from old index values to flipped ones, reserve index -1 - // (aka `UFBX_NO_INDEX`) for itself. - if (mesh->edges.count > 0) { - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, (mesh->num_indices + 1) * sizeof(uint32_t))); - uint32_t *index_mapping = (uint32_t*)uc->tmp_arr + 1; - index_mapping[-1] = UFBX_NO_INDEX; - ufbxi_for_list(ufbx_face, face, mesh->faces) { - if (face->num_indices == 0) continue; - uint32_t begin = face->index_begin; - uint32_t count = face->num_indices - 1; - index_mapping[begin] = begin; - for (uint32_t i = 0; i < count; i++) { - index_mapping[begin + 1 + i] = begin + count - i; - } - } - - ufbxi_for_list(ufbx_edge, p_edge, mesh->edges) { - uint32_t a = index_mapping[(int32_t)p_edge->a]; - uint32_t b = index_mapping[(int32_t)p_edge->b]; - p_edge->a = b; - p_edge->b = a; - } - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_modify_geometry(ufbxi_context *uc) -{ - bool do_mirror = false; - bool do_winding = uc->opts.reverse_winding; - bool do_scale = false; - bool do_geometry_transforms = false; - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY - || uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) { - // Prefetch geometry transforms for processing, they will later be overwritten in `ufbxi_update_node()`. - ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { - ufbx_node *node = *p_node; - if (node->is_root) continue; - - node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node); - if (!ufbxi_is_transform_identity(&node->geometry_transform)) { - node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform); - node->has_geometry_transform = true; - } else { - node->geometry_to_node = ufbx_identity_matrix; - node->has_geometry_transform = false; - } - } - do_geometry_transforms = true; - } - if (uc->mirror_axis != 0) { - do_mirror = true; - } - if (uc->scene.metadata.geometry_scale != 1.0f) { - do_scale = true; - } - - ufbx_real geometry_scale = uc->scene.metadata.geometry_scale; - ufbx_mirror_axis mirror_axis = uc->mirror_axis; - - ufbxi_for_ptr_list(ufbx_blend_shape, p_shape, uc->scene.blend_shapes) { - ufbx_blend_shape *shape = *p_shape; - - if (do_scale) { - ufbxi_scale_vec3_list(&shape->position_offsets, geometry_scale, 0); - } - - if (do_mirror) { - ufbxi_mirror_vec3_list(&shape->position_offsets, mirror_axis, 0); - ufbxi_mirror_vec3_list(&shape->normal_offsets, mirror_axis, 0); - } - } - - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { - ufbx_mesh *mesh = *p_mesh; - - if (do_scale) { - ufbxi_scale_vec3_list(&mesh->vertex_position.values, geometry_scale, 0); - } - - bool do_flip_winding = do_winding; - if (do_mirror) { - ufbxi_mirror_vec3_list(&mesh->vertex_position.values, mirror_axis, 0); - ufbxi_mirror_vec3_list(&mesh->vertex_normal.values, mirror_axis, 0); - ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - ufbxi_mirror_vec3_list(&set->vertex_tangent.values, mirror_axis, 0); - ufbxi_mirror_vec3_list(&set->vertex_bitangent.values, mirror_axis, 0); - } - if (!uc->opts.handedness_conversion_retain_winding) { - do_flip_winding = !do_flip_winding; - } - } - - // Flip face winding retaining the first vertex - if (do_flip_winding) { - mesh->reversed_winding = true; - ufbxi_check(ufbxi_flip_winding(uc, mesh)); - } - - ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&mesh->element); - if (do_geometry_transforms && geo_node) { - ufbx_matrix tangent_matrix = geo_node->geometry_to_node; - tangent_matrix.m03 = 0.0f; - tangent_matrix.m13 = 0.0f; - tangent_matrix.m23 = 0.0f; - ufbx_matrix normal_matrix = ufbx_matrix_for_normals(&geo_node->geometry_to_node); - - ufbxi_transform_vec3_list(&mesh->vertex_position.values, &geo_node->geometry_to_node, 0); - ufbxi_transform_vec3_list(&mesh->vertex_normal.values, &normal_matrix, 0); - ufbxi_normalize_vec3_list(&mesh->vertex_normal.values); - - ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - ufbxi_transform_vec3_list(&set->vertex_tangent.values, &tangent_matrix, 0); - ufbxi_transform_vec3_list(&set->vertex_bitangent.values, &tangent_matrix, 0); - ufbxi_normalize_vec3_list(&set->vertex_tangent.values); - ufbxi_normalize_vec3_list(&set->vertex_bitangent.values); - } - } - } - - ufbxi_for_ptr_list(ufbx_line_curve, p_curve, uc->scene.line_curves) { - ufbx_line_curve *curve = *p_curve; - - if (do_scale) { - ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, 0); - } - - if (do_mirror) { - ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, 0); - } - - ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element); - if (do_geometry_transforms && geo_node) { - ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, 0); - } - } - - ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) { - ufbx_nurbs_curve *curve = *p_curve; - - if (do_scale) { - ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, sizeof(ufbx_vec4)); - } - - if (do_mirror) { - ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, sizeof(ufbx_vec4)); - } - - ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element); - if (do_geometry_transforms && geo_node) { - ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4)); - } - } - - ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) { - ufbx_nurbs_surface *surface = *p_surface; - - if (do_scale) { - ufbxi_scale_vec3_list(&surface->control_points, geometry_scale, sizeof(ufbx_vec4)); - } - - if (do_mirror) { - ufbxi_mirror_vec3_list(&surface->control_points, mirror_axis, sizeof(ufbx_vec4)); - } - - ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&surface->element); - if (do_geometry_transforms && geo_node) { - ufbxi_transform_vec3_list(&surface->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4)); - } - } - - if (uc->opts.geometry_transform_handling != UFBX_GEOMETRY_TRANSFORM_HANDLING_PRESERVE) { - // Reset all geometry transforms if we're not preserving them - ufbx_props *defaults = NULL; - ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { - ufbx_node *node = *p_node; - if (!defaults) defaults = node->props.defaults; - - if (node->has_geometry_transform) { - ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricTranslation, 0.0f); - ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricRotation, 0.0f); - ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricScaling, 1.0f); - } - } - - if (defaults) { - ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricTranslation, 0.0f); - ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricRotation, 0.0f); - ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricScaling, 1.0f); - } - } - - return 1; -} - -ufbxi_noinline static void ufbxi_postprocess_scene(ufbxi_context *uc) -{ - if (uc->opts.normalize_normals || uc->opts.normalize_tangents) { - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { - ufbx_mesh *mesh = *p_mesh; - if (uc->opts.normalize_normals) { - ufbxi_normalize_vec3_list(&mesh->vertex_normal.values); - } - if (uc->opts.normalize_tangents) { - ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - ufbxi_normalize_vec3_list(&mesh->vertex_tangent.values); - ufbxi_normalize_vec3_list(&mesh->vertex_bitangent.values); - } - } - } - } - - if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { - uc->scene.metadata.ortho_size_unit = 1.0f / uc->scene.metadata.geometry_scale; - } else { - uc->scene.metadata.ortho_size_unit = 30.0f; - } -} - -ufbxi_noinline static size_t ufbxi_next_path_segment(const char *data, size_t begin, size_t length) -{ - for (size_t i = begin; i < length; i++) { - if (data[i] == '/' || data[i] == '\\') { - return i; - } - } - return length; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_absolute_to_relative_path(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_rel, const ufbxi_strblob *p_src, bool raw) -{ - const char *rel = ufbxi_strblob_data(p_rel, raw); - const char *src = ufbxi_strblob_data(p_src, raw); - size_t rel_length = ufbxi_strblob_length(p_rel, raw); - size_t src_length = ufbxi_strblob_length(p_src, raw); - - if (rel_length == 0 || src_length == 0) return 1; - - // Absolute paths must start with the same character (either drive or '/') - if (rel[0] != src[0]) return 1; - - // Find the last directory of the path we want to be relative to - while (rel_length > 0 && (rel[rel_length - 1] != '/' && rel[rel_length - 1] != '\\')) { - rel_length--; - } - - if (rel_length == 0) return 1; - char separator = rel[rel_length - 1]; - - size_t max_length = rel_length * 2 + src_length; - - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, max_length)); - char *tmp = uc->tmp_arr; - size_t tmp_length = 0; - - size_t rel_begin = 0; - size_t src_begin = 0; - while (rel_begin < rel_length && src_begin < src_length) { - size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length); - size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length); - if (rel_end != src_end || memcmp(rel + rel_begin, src + src_begin, src_end - src_begin) != 0) break; - - rel_begin = rel_end + 1; - src_begin = src_end + 1; - } - - while (rel_begin < rel_length) { - size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length); - tmp[tmp_length++] = '.'; - tmp[tmp_length++] = '.'; - tmp[tmp_length++] = separator; - rel_begin = rel_end + 1; - } - - while (src_begin < src_length) { - size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length); - size_t len = src_end - src_begin; - - memcpy(tmp + tmp_length, src + src_begin, len); - tmp_length += len; - - if (src_end < src_length) { - tmp[tmp_length++] = separator; - } - - src_begin = src_end + 1; - } - - ufbx_assert(tmp_length <= max_length); - - const char *dst = ufbxi_push_string(&uc->string_pool, tmp, tmp_length, NULL, true); - ufbxi_check(dst); - - ufbxi_strblob_set(p_dst, dst, tmp_length, raw); - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_filenames(ufbxi_context *uc, ufbxi_strblob *filename, ufbxi_strblob *absolute_filename, ufbxi_strblob *relative_filename, bool raw) -{ - if (ufbxi_strblob_length(relative_filename, raw) == 0) { - const ufbxi_strblob *original_file_path = raw - ? (const ufbxi_strblob*)&uc->scene.metadata.raw_original_file_path - : (const ufbxi_strblob*)&uc->scene.metadata.original_file_path; - - ufbxi_check(ufbxi_absolute_to_relative_path(uc, relative_filename, original_file_path, absolute_filename, raw)); - } - - ufbxi_check(ufbxi_resolve_relative_filename(uc, filename, relative_filename, raw)); - - return 1; -} - -ufbxi_noinline static bool ufbxi_file_content_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_file_content *a = (const ufbxi_file_content*)va, *b = (const ufbxi_file_content*)vb; - return ufbxi_str_less(a->absolute_filename, b->absolute_filename); -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_file_contents(ufbxi_context *uc, ufbxi_file_content *content, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_file_content))); - ufbxi_stable_sort(sizeof(ufbxi_file_content), 32, content, uc->tmp_arr, count, &ufbxi_file_content_less, NULL); - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data) -{ - if (p_data->size == 0 || p_filename->length == 0) return 1; - ufbxi_file_content *content = ufbxi_push(&uc->tmp_stack, ufbxi_file_content, 1); - ufbxi_check(content); - - content->absolute_filename = *p_filename; - content->content = *p_data; - return 1; -} - -ufbxi_noinline static void ufbxi_fetch_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data) -{ - if (p_data->size > 0) return; - ufbx_string filename = *p_filename; - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbxi_file_content, 8, &index, uc->file_content, 0, uc->num_file_content, - ( ufbxi_str_less(a->absolute_filename, filename) ), - ( a->absolute_filename.data == filename.data )); - if (index != SIZE_MAX) { - *p_data = uc->file_content[index].content; - } -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_file_content(ufbxi_context *uc) -{ - size_t initial_stack = uc->tmp_stack.num_items; - - ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) { - ufbx_video *video = *p_video; - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->filename, (ufbxi_strblob*)&video->absolute_filename, (ufbxi_strblob*)&video->relative_filename, false)); - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->raw_filename, (ufbxi_strblob*)&video->raw_absolute_filename, (ufbxi_strblob*)&video->raw_relative_filename, true)); - ufbxi_check(ufbxi_push_file_content(uc, &video->absolute_filename, &video->content)); - } - - ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) { - ufbx_audio_clip *clip = *p_clip; - clip->absolute_filename = ufbx_find_string(&clip->props, "Path", ufbx_empty_string); - clip->relative_filename = ufbx_find_string(&clip->props, "RelPath", ufbx_empty_string); - clip->raw_absolute_filename = ufbx_find_blob(&clip->props, "Path", ufbx_empty_blob); - clip->raw_relative_filename = ufbx_find_blob(&clip->props, "RelPath", ufbx_empty_blob); - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->filename, (ufbxi_strblob*)&clip->absolute_filename, (ufbxi_strblob*)&clip->relative_filename, false)); - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->raw_filename, (ufbxi_strblob*)&clip->raw_absolute_filename, (ufbxi_strblob*)&clip->raw_relative_filename, true)); - ufbxi_check(ufbxi_push_file_content(uc, &clip->absolute_filename, &clip->content)); - } - - uc->num_file_content = uc->tmp_stack.num_items - initial_stack; - uc->file_content = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_file_content, uc->num_file_content); - ufbxi_check(uc->file_content); - ufbxi_check(ufbxi_sort_file_contents(uc, uc->file_content, uc->num_file_content)); - - ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) { - ufbx_video *video = *p_video; - ufbxi_fetch_file_content(uc, &video->absolute_filename, &video->content); - } - - ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) { - ufbx_audio_clip *clip = *p_clip; - ufbxi_fetch_file_content(uc, &clip->absolute_filename, &clip->content); - } - - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_validate_indices(ufbxi_context *uc, ufbx_uint32_list *indices, size_t max_index) -{ - if (max_index == 0 && uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_CLAMP) { - indices->data = NULL; - indices->count = 0; - return 1; - } - - ufbxi_nounroll ufbxi_for_list(uint32_t, p_ix, *indices) { - uint32_t ix = *p_ix; - if (ix >= max_index) { - ufbxi_check(ufbxi_fix_index(uc, p_ix, ix, max_index)); - } - } - - return 1; -} - -static bool ufbxi_material_part_usage_less(void *user, const void *va, const void *vb) -{ - ufbx_mesh_part *parts = (ufbx_mesh_part*)user; - uint32_t a = *(const uint32_t*)va, b = *(const uint32_t*)vb; - ufbx_mesh_part *pa = &parts[a]; - ufbx_mesh_part *pb = &parts[b]; - if (pa->face_indices.count == 0 || pb->face_indices.count == 0) { - if (pa->face_indices.count == pb->face_indices.count) return a < b; - return pa->face_indices.count > pb->face_indices.count; - } - return pa->face_indices.data[0] < pb->face_indices.data[0]; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh_material(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh) -{ - size_t num_materials = mesh->materials.count; - size_t num_parts = mesh->material_parts.count; - size_t num_faces = mesh->faces.count; - - ufbx_mesh_part *parts = mesh->material_parts.data; - ufbx_assert(!parts || (mesh->material_parts.count == num_materials) || (mesh->material_parts.count == 1 && num_materials == 0)); - - uint32_t *face_material = mesh->face_material.data; - - // Count the number of faces and triangles per material - ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { - ufbx_face face = mesh->faces.data[i]; - uint32_t mat_ix = 0; - - if (face_material) { - mat_ix = face_material[i]; - if (mat_ix >= num_materials) { - face_material[i] = 0; - mat_ix = 0; - } - } - - if (parts) { - ufbxi_mesh_part_add_face(&parts[mat_ix], face.num_indices); - } - } - - if (parts) { - // Allocate per-material buffers (clear `num_faces` to 0 to re-use it as - // an index when fetching the face indices). - uint32_t part_index = 0; - ufbxi_for(ufbx_mesh_part, part, parts, num_parts) { - part->index = part_index++; - part->face_indices.count = part->num_faces; - part->face_indices.data = ufbxi_push(buf, uint32_t, part->num_faces); - ufbxi_check_err(error, part->face_indices.data); - part->num_faces = 0; - } - - // Fetch the per-material face indices - ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) { - uint32_t mat_ix = face_material ? face_material[i] : 0; - if (mat_ix < num_parts) { - ufbx_mesh_part *part = &parts[mat_ix]; - part->face_indices.data[part->num_faces++] = (uint32_t)i; - } - } - - mesh->material_part_usage_order.count = num_parts; - mesh->material_part_usage_order.data = ufbxi_push(buf, uint32_t, num_parts); - ufbxi_check_err(error, mesh->material_part_usage_order.data); - for (size_t i = 0; i < num_parts; i++) { - mesh->material_part_usage_order.data[i] = (uint32_t)i; - } - ufbxi_unstable_sort(mesh->material_part_usage_order.data, num_parts, sizeof(uint32_t), &ufbxi_material_part_usage_less, parts); - } - - return 1; -} - -typedef struct { - ufbxi_refcount refcount; - ufbx_anim anim; - uint32_t magic; -} ufbxi_anim_imp; - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_anim(ufbxi_context *uc, ufbx_anim **p_anim, ufbx_anim_layer **layers, size_t num_layers) -{ - ufbx_anim *anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1); - ufbxi_check(anim); - - anim->layers.data = layers; - anim->layers.count = num_layers; - - *p_anim = anim; - return 1; -} - -ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc) -{ - size_t num_elements = uc->num_elements; - - uc->scene.elements.count = num_elements; - uc->scene.elements.data = ufbxi_push(&uc->result, ufbx_element*, num_elements); - ufbxi_check(uc->scene.elements.data); - - uc->scene.metadata.element_buffer_size = uc->tmp_element_byte_offset; - char *element_data = (char*)ufbxi_push_pop(&uc->result, &uc->tmp_elements, uint64_t, uc->tmp_element_byte_offset/8); - ufbxi_check(element_data); - - size_t *element_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_offsets, size_t, uc->tmp_element_offsets.num_items); - ufbxi_buf_free(&uc->tmp_element_offsets); - ufbxi_check(element_offsets); - for (size_t i = 0; i < num_elements; i++) { - ufbx_element *element = (ufbx_element*)(element_data + element_offsets[i]); - - if (element->type == UFBX_ELEMENT_NODE) { - ufbx_node *node = (ufbx_node*)element; - if (node->scale_helper) { - ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id); - ufbx_assert(extra); - node->scale_helper = (ufbx_node*)(element_data + element_offsets[extra->scale_helper_id]); - } - } - - uc->scene.elements.data[i] = element; - } - - uc->scene.elements.count = num_elements; - ufbxi_buf_free(&uc->tmp_element_offsets); - ufbxi_buf_free(&uc->tmp_elements); - - uc->tmp_element_flag = ufbxi_push_zero(&uc->tmp, uint8_t, num_elements); - ufbxi_check(uc->tmp_element_flag); - - uc->scene.metadata.original_file_path = ufbx_find_string(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_string); - uc->scene.metadata.raw_original_file_path = ufbx_find_blob(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_blob); - - // Resolve and add the connections to elements - ufbxi_check(ufbxi_resolve_connections(uc)); - ufbxi_check(ufbxi_add_connections_to_elements(uc)); - ufbxi_check(ufbxi_linearize_nodes(uc)); - - for (size_t type = 0; type < UFBX_ELEMENT_TYPE_COUNT; type++) { - size_t num_typed = uc->tmp_typed_element_offsets[type].num_items; - size_t *typed_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_typed_element_offsets[type], size_t, num_typed); - ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]); - ufbxi_check(typed_offsets); - - ufbx_element_list *typed_elems = &uc->scene.elements_by_type[type]; - typed_elems->count = num_typed; - typed_elems->data = ufbxi_push(&uc->result, ufbx_element*, num_typed); - ufbxi_check(typed_elems->data); - - for (size_t i = 0; i < num_typed; i++) { - typed_elems->data[i] = (ufbx_element*)(element_data + typed_offsets[i]); - } - - ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]); - } - - // Create named elements - uc->scene.elements_by_name.count = num_elements; - uc->scene.elements_by_name.data = ufbxi_push(&uc->result, ufbx_name_element, num_elements); - ufbxi_check(uc->scene.elements_by_name.data); - - for (size_t i = 0; i < num_elements; i++) { - - ufbx_element *elem = uc->scene.elements.data[i]; - ufbx_name_element *name_elem = &uc->scene.elements_by_name.data[i]; - - name_elem->name = elem->name; - name_elem->type = elem->type; - name_elem->_internal_key = ufbxi_get_name_key(elem->name.data, elem->name.length); - name_elem->element = elem; - } - - ufbxi_check(ufbxi_sort_name_elements(uc, uc->scene.elements_by_name.data, num_elements)); - - // Setup node children arrays and attribute pointers/lists - ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { - ufbx_node *node = *p_node, *parent = node->parent; - if (parent) { - parent->children.count++; - if (parent->children.data == NULL) { - parent->children.data = p_node; - } - - if (node->is_geometry_transform_helper) { - parent->geometry_transform_helper = node; - } - - // Force top-level nodes to have `UFBX_INHERIT_MODE_NORMAL` to make unit scaling work. - if (parent->is_root && uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) { - node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL; - node->inherit_mode = UFBX_INHERIT_MODE_NORMAL; - } - - // RrSs nodes inherit scale from their parent, Rrs ignore the scale of - // their _immediate_ parent, potentially multiple if chained. - if (node->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) { - node->inherit_scale_node = parent; - } else if (node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { - node->inherit_scale_node = parent->inherit_scale_node; - } - } - - ufbx_connection_list conns = ufbxi_find_dst_connections(&node->element, NULL); - - ufbxi_for_list(ufbx_connection, conn, conns) { - ufbx_element *elem = conn->src; - ufbx_element_type type = elem->type; - if (!(type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB)) continue; - - size_t index = node->all_attribs.count++; - if (index == 0) { - node->attrib = elem; - node->attrib_type = type; - } else { - if (index == 1) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &node->attrib)); - } - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &elem)); - } - - switch (elem->type) { - case UFBX_ELEMENT_MESH: node->mesh = (ufbx_mesh*)elem; break; - case UFBX_ELEMENT_LIGHT: node->light = (ufbx_light*)elem; break; - case UFBX_ELEMENT_CAMERA: node->camera = (ufbx_camera*)elem; break; - case UFBX_ELEMENT_BONE: node->bone = (ufbx_bone*)elem; break; - default: /* No shorthand */ break; - } - } - - if (node->all_attribs.count > 1) { - node->all_attribs.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, node->all_attribs.count); - ufbxi_check(node->all_attribs.data); - } else if (node->all_attribs.count == 1) { - node->all_attribs.data = &node->attrib; - } - - ufbxi_check(ufbxi_fetch_dst_elements(uc, &node->materials, &node->element, false, false, NULL, UFBX_ELEMENT_MATERIAL)); - } - - // Resolve bind pose bones that don't use the normal connection system - ufbxi_for_ptr_list(ufbx_pose, p_pose, uc->scene.poses) { - ufbx_pose *pose = *p_pose; - - // HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer - size_t num_bones = pose->bone_poses.count; - ufbxi_tmp_bone_pose *tmp_poses = (ufbxi_tmp_bone_pose*)pose->bone_poses.data; - pose->bone_poses.data = ufbxi_push(&uc->result, ufbx_bone_pose, num_bones); - ufbxi_check(pose->bone_poses.data); - - // Filter only found bones - pose->bone_poses.count = 0; - for (size_t i = 0; i < num_bones; i++) { - ufbx_element *elem = ufbxi_find_element_by_fbx_id(uc, tmp_poses[i].bone_fbx_id); - if (!elem || elem->type != UFBX_ELEMENT_NODE) continue; - - ufbx_node *node = (ufbx_node*)elem; - ufbx_bone_pose *bone = &pose->bone_poses.data[pose->bone_poses.count++]; - bone->bone_node = node; - bone->bone_to_world = tmp_poses[i].bone_to_world; - - if (pose->is_bind_pose) { - if (node->bind_pose == NULL) { - node->bind_pose = pose; - } - - ufbx_connection_list node_conns = ufbxi_find_src_connections(elem, NULL); - ufbxi_for_list(ufbx_connection, conn, node_conns) { - if (conn->dst->type != UFBX_ELEMENT_SKIN_CLUSTER) continue; - ufbx_skin_cluster *cluster = (ufbx_skin_cluster*)conn->dst; - if (ufbxi_matrix_all_zero(&cluster->bind_to_world)) { - cluster->bind_to_world = bone->bone_to_world; - } - } - } - } - ufbxi_check(ufbxi_sort_bone_poses(uc, pose)); - } - - // Fetch pointers that may break elements - - // Setup node attribute instances - for (int type = UFBX_ELEMENT_TYPE_FIRST_ATTRIB; type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB; type++) { - ufbxi_for_ptr_list(ufbx_element, p_elem, uc->scene.elements_by_type[type]) { - ufbx_element *elem = *p_elem; - ufbxi_check(ufbxi_fetch_src_elements(uc, &elem->instances, elem, false, true, NULL, UFBX_ELEMENT_NODE)); - } - } - - bool search_node = uc->version < 7000; - - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, uc->scene.skin_clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - cluster->bone_node = (ufbx_node*)ufbxi_fetch_dst_element(&cluster->element, false, NULL, UFBX_ELEMENT_NODE); - } - - ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, uc->scene.skin_deformers) { - ufbx_skin_deformer *skin = *p_skin; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &skin->clusters, &skin->element, false, true, NULL, UFBX_ELEMENT_SKIN_CLUSTER)); - - // Remove clusters without a valid `bone` - if (!uc->opts.connect_broken_elements) { - size_t num_broken = 0; - for (size_t i = 0; i < skin->clusters.count; i++) { - if (!skin->clusters.data[i]->bone_node) { - num_broken++; - } else if (num_broken > 0) { - skin->clusters.data[i - num_broken] = skin->clusters.data[i]; - } - } - skin->clusters.count -= num_broken; - } - - size_t total_weights = 0; - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - ufbxi_check(SIZE_MAX - total_weights > cluster->num_weights); - total_weights += cluster->num_weights; - } - - size_t num_vertices = 0; - - // Iterate through meshes so we can pad the vertices to the largest one - { - ufbx_connection_list conns = ufbxi_find_src_connections(&skin->element, NULL); - ufbxi_for_list(ufbx_connection, conn, conns) { - ufbx_mesh *mesh = NULL; - if (conn->dst_prop.length > 0) continue; - if (conn->dst->type == UFBX_ELEMENT_MESH) { - mesh = (ufbx_mesh*)conn->dst; - } else if (conn->dst->type == UFBX_ELEMENT_NODE) { - ufbx_node *node = (ufbx_node*)conn->dst; - if (node->geometry_transform_helper) node = node->geometry_transform_helper; - mesh = node->mesh; - } - if (!mesh) continue; - num_vertices = ufbxi_max_sz(num_vertices, mesh->num_vertices); - } - } - - if (!uc->opts.skip_skin_vertices) { - skin->vertices.count = num_vertices; - skin->vertices.data = ufbxi_push_zero(&uc->result, ufbx_skin_vertex, num_vertices); - ufbxi_check(skin->vertices.data); - - skin->weights.count = total_weights; - skin->weights.data = ufbxi_push_zero(&uc->result, ufbx_skin_weight, total_weights); - ufbxi_check(skin->weights.data); - - bool retain_all = !uc->opts.clean_skin_weights; - - // Count the number of weights per vertex - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - for (size_t i = 0; i < cluster->num_weights; i++) { - uint32_t vertex = cluster->vertices.data[i]; - if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) { - skin->vertices.data[vertex].num_weights++; - } - } - } - - ufbx_real default_dq = skin->skinning_method == UFBX_SKINNING_METHOD_DUAL_QUATERNION ? 1.0f : 0.0f; - - // Prefix sum to assign the vertex weight offsets and set up default DQ values - uint32_t offset = 0; - uint32_t max_weights = 0; - for (size_t i = 0; i < num_vertices; i++) { - skin->vertices.data[i].weight_begin = offset; - skin->vertices.data[i].dq_weight = default_dq; - uint32_t num_weights = skin->vertices.data[i].num_weights; - offset += num_weights; - skin->vertices.data[i].num_weights = 0; - - if (num_weights > max_weights) max_weights = num_weights; - } - ufbx_assert(offset <= total_weights); - skin->max_weights_per_vertex = max_weights; - - // Copy the DQ weights to vertices - for (size_t i = 0; i < skin->num_dq_weights; i++) { - uint32_t vertex = skin->dq_vertices.data[i]; - if (vertex < num_vertices) { - skin->vertices.data[vertex].dq_weight = skin->dq_weights.data[i]; - } - } - - // Copy the weights to vertices - uint32_t cluster_index = 0; - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - for (size_t i = 0; i < cluster->num_weights; i++) { - uint32_t vertex = cluster->vertices.data[i]; - if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) { - uint32_t local_index = skin->vertices.data[vertex].num_weights++; - uint32_t index = skin->vertices.data[vertex].weight_begin + local_index; - skin->weights.data[index].cluster_index = cluster_index; - skin->weights.data[index].weight = cluster->weights.data[i]; - } - } - cluster_index++; - } - - // Sort the vertex weights by descending weight value - ufbxi_check(ufbxi_sort_skin_weights(uc, skin)); - } - } - - ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, uc->scene.blend_deformers) { - ufbx_blend_deformer *blend = *p_blend; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &blend->channels, &blend->element, false, true, NULL, UFBX_ELEMENT_BLEND_CHANNEL)); - } - - ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) { - ufbx_cache_deformer *deformer = *p_deformer; - deformer->channel = ufbx_find_string(&deformer->props, "ChannelName", ufbx_empty_string); - deformer->file = (ufbx_cache_file*)ufbxi_fetch_dst_element(&deformer->element, false, NULL, UFBX_ELEMENT_CACHE_FILE); - } - - ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { - ufbx_cache_file *cache = *p_cache; - - cache->absolute_filename = ufbx_find_string(&cache->props, "CacheAbsoluteFileName", ufbx_empty_string); - cache->relative_filename = ufbx_find_string(&cache->props, "CacheFileName", ufbx_empty_string); - - cache->raw_absolute_filename = ufbx_find_blob(&cache->props, "CacheAbsoluteFileName", ufbx_empty_blob); - cache->raw_relative_filename = ufbx_find_blob(&cache->props, "CacheFileName", ufbx_empty_blob); - - int64_t type = ufbx_find_int(&cache->props, "CacheFileType", 0); - if (type >= 0 && type <= UFBX_CACHE_FILE_FORMAT_MC) { - cache->format = (ufbx_cache_file_format)type; - } - - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->filename, (ufbxi_strblob*)&cache->absolute_filename, (ufbxi_strblob*)&cache->relative_filename, false)); - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->raw_filename, (ufbxi_strblob*)&cache->raw_absolute_filename, (ufbxi_strblob*)&cache->raw_relative_filename, true)); - } - - ufbx_assert(uc->tmp_full_weights.num_items == uc->scene.blend_channels.count); - ufbx_real_list *full_weights = ufbxi_push_pop(&uc->tmp, &uc->tmp_full_weights, ufbx_real_list, uc->tmp_full_weights.num_items); - ufbxi_buf_free(&uc->tmp_full_weights); - ufbxi_check(full_weights); - - ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, uc->scene.blend_channels) { - ufbx_blend_channel *channel = *p_channel; - - ufbxi_check(ufbxi_fetch_blend_keyframes(uc, &channel->keyframes, &channel->element)); - - for (size_t i = 0; i < channel->keyframes.count; i++) { - ufbx_blend_keyframe *key = &channel->keyframes.data[i]; - key->target_weight = 1.0f; - if (i < full_weights->count) { - if (!uc->blender_full_weights) { - key->target_weight = full_weights->data[i] / (ufbx_real)100.0; - } else if (full_weights->count == key->shape->num_offsets) { - if (i == 0) { - // Duplicate `index_data` for modification if we retain DOM - if (uc->opts.retain_dom) { - full_weights->data = ufbxi_push_copy(&uc->result, ufbx_real, full_weights->count, full_weights->data); - ufbxi_check(full_weights->data); - } - ufbxi_for_list(ufbx_real, p_weight, *full_weights) { - *p_weight /= (ufbx_real)100.0; - } - } - key->shape->offset_weights = *full_weights; - } - } - } - - ufbxi_check(ufbxi_sort_blend_keyframes(uc, channel->keyframes.data, channel->keyframes.count)); - full_weights++; - - if (channel->keyframes.count > 0) { - channel->target_shape = channel->keyframes.data[channel->keyframes.count - 1].shape; - } - } - - { - // Generate and patch procedural index buffers - uint32_t *zero_indices = ufbxi_push(&uc->result, uint32_t, uc->max_zero_indices); - uint32_t *consecutive_indices = ufbxi_push(&uc->result, uint32_t, uc->max_consecutive_indices); - ufbxi_check(zero_indices && consecutive_indices); - - memset(zero_indices, 0, sizeof(uint32_t) * uc->max_zero_indices); - for (size_t i = 0; i < uc->max_consecutive_indices; i++) { - consecutive_indices[i] = (uint32_t)i; - } - - uc->zero_indices = zero_indices; - uc->consecutive_indices = consecutive_indices; - - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { - ufbx_mesh *mesh = *p_mesh; - - ufbxi_patch_index_pointer(uc, &mesh->vertex_position.indices.data); - ufbxi_patch_index_pointer(uc, &mesh->vertex_normal.indices.data); - ufbxi_patch_index_pointer(uc, &mesh->vertex_color.indices.data); - ufbxi_patch_index_pointer(uc, &mesh->vertex_crease.indices.data); - ufbxi_patch_index_pointer(uc, &mesh->face_material.data); - ufbxi_patch_index_pointer(uc, &mesh->face_group.data); - - ufbxi_patch_index_pointer(uc, &mesh->skinned_position.indices.data); - ufbxi_patch_index_pointer(uc, &mesh->skinned_normal.indices.data); - - ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) { - ufbxi_patch_index_pointer(uc, &set->vertex_uv.indices.data); - ufbxi_patch_index_pointer(uc, &set->vertex_bitangent.indices.data); - ufbxi_patch_index_pointer(uc, &set->vertex_tangent.indices.data); - } - - ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) { - ufbxi_patch_index_pointer(uc, &set->vertex_color.indices.data); - } - - // Generate normals if necessary - if (!mesh->vertex_normal.exists && uc->opts.generate_missing_normals) { - ufbxi_check(ufbxi_generate_normals(uc, mesh)); - } - - // Assign first UV and color sets as the "canonical" ones - if (mesh->uv_sets.count > 0) { - mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv; - mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent; - mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent; - } - if (mesh->color_sets.count > 0) { - mesh->vertex_color = mesh->color_sets.data[0].vertex_color; - } - - if (mesh->face_group_parts.count == 1) { - ufbxi_patch_index_pointer(uc, &mesh->face_group_parts.data[0].face_indices.data); - } - - ufbxi_check(ufbxi_fetch_mesh_materials(uc, &mesh->materials, &mesh->element, true)); - - // Patch materials to instances if necessary - if (mesh->materials.count > 0) { - ufbxi_for_ptr_list(ufbx_node, p_node, mesh->instances) { - ufbx_node *node = *p_node; - if (node->materials.count < mesh->materials.count && mesh->materials.data[0] != NULL) { - ufbx_material **materials = ufbxi_push(&uc->result, ufbx_material*, mesh->materials.count); - ufbxi_check(materials); - ufbxi_nounroll for (size_t i = 0; i < node->materials.count; i++) { - materials[i] = node->materials.data[i]; - } - ufbxi_nounroll for (size_t i = node->materials.count; i < mesh->materials.count; i++) { - materials[i] = mesh->materials.data[i]; - } - node->materials.data = materials; - node->materials.count = mesh->materials.count; - } - } - } - - if (uc->retain_mesh_parts) { - size_t num_parts = ufbxi_max_sz(mesh->materials.count, 1); - mesh->material_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, num_parts); - ufbxi_check(mesh->material_parts.data); - mesh->material_parts.count = num_parts; - } - - if (mesh->materials.count <= 1) { - // Use the shared consecutive index buffer for mesh faces if there's only one material - // See HACK(consecutive-faces) in `ufbxi_read_mesh()`. - if (mesh->material_parts.count > 0) { - ufbx_mesh_part *part = &mesh->material_parts.data[0]; - part->num_faces = mesh->num_faces; - part->num_triangles = mesh->num_triangles; - part->num_empty_faces = mesh->num_empty_faces; - part->num_point_faces = mesh->num_point_faces; - part->num_line_faces = mesh->num_line_faces; - part->face_indices.data = uc->consecutive_indices; - part->face_indices.count = mesh->num_faces; - mesh->material_part_usage_order.data = uc->zero_indices; - mesh->material_part_usage_order.count = 1; - } - - if (mesh->materials.count == 1) { - mesh->face_material.data = uc->zero_indices; - mesh->face_material.count = mesh->num_faces; - } else { - mesh->face_material.data = NULL; - mesh->face_material.count = 0; - } - } else if (mesh->materials.count > 0) { - ufbxi_check(ufbxi_finalize_mesh_material(&uc->result, &uc->error, mesh)); - } - - // Fetch deformers - ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->skin_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_SKIN_DEFORMER)); - ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->blend_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_BLEND_DEFORMER)); - ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->cache_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_CACHE_DEFORMER)); - ufbxi_check(ufbxi_fetch_deformers(uc, &mesh->all_deformers, &mesh->element, search_node)); - - // Vertex position must always exist if not explicitly allowed to be missing - if (!mesh->vertex_position.exists && !uc->opts.allow_missing_vertex_position) { - ufbxi_check(mesh->num_indices == 0); - mesh->vertex_position.exists = true; - mesh->vertex_position.unique_per_vertex = true; - mesh->skinned_position.exists = true; - mesh->skinned_position.unique_per_vertex = true; - } - - // Update metadata - if (mesh->max_face_triangles > uc->scene.metadata.max_face_triangles) { - uc->scene.metadata.max_face_triangles = mesh->max_face_triangles; - } - } - } - - ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, uc->scene.stereo_cameras) { - ufbx_stereo_camera *stereo = *p_stereo; - stereo->left = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_LeftCamera, UFBX_ELEMENT_CAMERA); - stereo->right = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_RightCamera, UFBX_ELEMENT_CAMERA); - } - - ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) { - ufbx_nurbs_curve *curve = *p_curve; - ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &curve->basis)); - } - - ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) { - ufbx_nurbs_surface *surface = *p_surface; - ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_u)); - ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_v)); - - surface->material = (ufbx_material*)ufbxi_fetch_dst_element(&surface->element, true, NULL, UFBX_ELEMENT_MATERIAL); - } - - ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, uc->scene.anim_stacks) { - ufbx_anim_stack *stack = *p_stack; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &stack->layers, &stack->element, false, true, NULL, UFBX_ELEMENT_ANIM_LAYER)); - - ufbxi_check(ufbxi_push_anim(uc, &stack->anim, stack->layers.data, stack->layers.count)); - } - - ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, uc->scene.anim_layers) { - ufbx_anim_layer *layer = *p_layer; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->anim_values, &layer->element, false, true, NULL, UFBX_ELEMENT_ANIM_VALUE)); - - ufbxi_check(ufbxi_push_anim(uc, &layer->anim, p_layer, 1)); - - uint32_t min_id = UINT32_MAX, max_id = 0; - - // Combine the animated properties with elements (potentially duplicates!) - size_t num_anim_props = 0; - ufbxi_for_ptr_list(ufbx_anim_value, p_value, layer->anim_values) { - ufbx_anim_value *value = *p_value; - ufbxi_for_list(ufbx_connection, ac, value->element.connections_src) { - if (ac->src_prop.length == 0 && ac->dst_prop.length > 0) { - ufbx_anim_prop *aprop = ufbxi_push(&uc->tmp_stack, ufbx_anim_prop, 1); - uint32_t id = ac->dst->element_id; - min_id = ufbxi_min32(min_id, id); - max_id = ufbxi_max32(max_id, id); - uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1; - layer->_element_id_bitmask[(id >> 5) & id_mask] |= 1u << (id & 31); - ufbxi_check(aprop); - aprop->anim_value = value; - aprop->element = ac->dst; - aprop->_internal_key = ufbxi_get_name_key(ac->dst_prop.data, ac->dst_prop.length); - aprop->prop_name = ac->dst_prop; - num_anim_props++; - } - } - } - - if (min_id != UINT32_MAX) { - layer->_min_element_id = min_id; - layer->_max_element_id = max_id; - } - - switch (ufbxi_find_int(&layer->props, ufbxi_BlendMode, 0)) { - case 0: // Additive - layer->blended = true; - layer->additive = true; - break; - case 1: // Override - layer->blended = false; - layer->additive = false; - break; - case 2: // Override Passthrough - layer->blended = true; - layer->additive = false; - break; - default: // Unknown - layer->blended = false; - layer->additive = false; - break; - } - - ufbx_prop *weight_prop = ufbxi_find_prop(&layer->props, ufbxi_Weight); - if (weight_prop) { - layer->weight = weight_prop->value_real / (ufbx_real)100.0; - if (layer->weight < 0.0f) layer->weight = 0.0f; - if (layer->weight > 0.99999f) layer->weight = 1.0f; - layer->weight_is_animated = (weight_prop->flags & UFBX_PROP_FLAG_ANIMATED) != 0; - } else { - layer->weight = 1.0f; - layer->weight_is_animated = false; - } - layer->compose_rotation = ufbxi_find_int(&layer->props, ufbxi_RotationAccumulationMode, 0) == 0; - layer->compose_scale = ufbxi_find_int(&layer->props, ufbxi_ScaleAccumulationMode, 0) == 0; - - // Add a dummy NULL element animated prop at the end so we can iterate - // animated props without worrying about boundary conditions.. - { - ufbx_anim_prop *aprop = ufbxi_push_zero(&uc->tmp_stack, ufbx_anim_prop, 1); - ufbxi_check(aprop); - } - - layer->anim_props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_anim_prop, num_anim_props + 1); - ufbxi_check(layer->anim_props.data); - layer->anim_props.count = num_anim_props; - ufbxi_check(ufbxi_sort_anim_props(uc, layer->anim_props.data, layer->anim_props.count)); - } - - ufbxi_for_ptr_list(ufbx_anim_value, p_value, uc->scene.anim_values) { - ufbx_anim_value *value = *p_value; - - // TODO: Search for things like d|Visibility with a constructed name - value->default_value.x = ufbxi_find_real(&value->props, ufbxi_X, value->default_value.x); - value->default_value.x = ufbxi_find_real(&value->props, ufbxi_d_X, value->default_value.x); - value->default_value.y = ufbxi_find_real(&value->props, ufbxi_Y, value->default_value.y); - value->default_value.y = ufbxi_find_real(&value->props, ufbxi_d_Y, value->default_value.y); - value->default_value.z = ufbxi_find_real(&value->props, ufbxi_Z, value->default_value.z); - value->default_value.z = ufbxi_find_real(&value->props, ufbxi_d_Z, value->default_value.z); - - ufbxi_for_list(ufbx_connection, conn, value->element.connections_dst) { - if (conn->src->type == UFBX_ELEMENT_ANIM_CURVE && conn->src_prop.length == 0) { - ufbx_anim_curve *curve = (ufbx_anim_curve*)conn->src; - - uint32_t index = 0; - const char *name = conn->dst_prop.data; - if (name == ufbxi_Y || name == ufbxi_d_Y) index = 1; - if (name == ufbxi_Z || name == ufbxi_d_Z) index = 2; - - ufbx_prop *prop = ufbx_find_prop_len(&value->props, conn->dst_prop.data, conn->dst_prop.length); - if (prop) { - value->default_value.v[index] = prop->value_real; - } - value->curves[index] = curve; - } - } - } - - ufbxi_for_ptr_list(ufbx_anim_curve, p_curve, uc->scene.anim_curves) { - ufbx_anim_curve *curve = *p_curve; - if (curve->keyframes.count > 0) { - curve->min_time = curve->keyframes.data[0].time; - curve->max_time = curve->keyframes.data[curve->keyframes.count - 1].time; - } - } - - ufbxi_for_ptr_list(ufbx_shader, p_shader, uc->scene.shaders) { - ufbx_shader *shader = *p_shader; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &shader->bindings, &shader->element, false, false, NULL, UFBX_ELEMENT_SHADER_BINDING)); - - ufbx_prop *api = ufbx_find_prop(&shader->props, "RenderAPI"); - if (api) { - if (!strcmp(api->value_str.data, "ARNOLD_SHADER_ID")) { - shader->type = UFBX_SHADER_ARNOLD_STANDARD_SURFACE; - } else if (!strcmp(api->value_str.data, "OSL")) { - shader->type = UFBX_SHADER_OSL_STANDARD_SURFACE; - } else if (!strcmp(api->value_str.data, "SFX_PBS_SHADER")) { - shader->type = UFBX_SHADER_SHADERFX_GRAPH; - } - } - } - - ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) { - ufbx_material *material = *p_material; - material->shader = (ufbx_shader*)ufbxi_fetch_src_element(&material->element, false, NULL, UFBX_ELEMENT_SHADER); - - if (!strcmp(material->shading_model_name.data, "lambert") || !strcmp(material->shading_model_name.data, "Lambert")) { - material->shader_type = UFBX_SHADER_FBX_LAMBERT; - } else if (!strcmp(material->shading_model_name.data, "phong") || !strcmp(material->shading_model_name.data, "Phong")) { - material->shader_type = UFBX_SHADER_FBX_PHONG; - } - - if (material->shader) { - material->shader_type = material->shader->type; - } else { - if (uc->opts.use_blender_pbr_material && uc->exporter == UFBX_EXPORTER_BLENDER_BINARY && uc->exporter_version >= ufbx_pack_version(4,12,0)) { - material->shader_type = UFBX_SHADER_BLENDER_PHONG; - } - - // TODO: Is this too strict? - if (material->shader_type == UFBX_SHADER_UNKNOWN) { - uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDa", 0); - uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDb", 0); - if (classid_a == 0x3d6b1cecu && classid_b == 0xdeadc001u) { - material->shader_type = UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL; - material->shader_prop_prefix.data = "3dsMax|Parameters|"; - material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); - } else if (classid_a == 0xf1551e33u && classid_b == 0x37fb1337u) { - material->shader_type = UFBX_SHADER_OPENPBR_MATERIAL; - material->shader_prop_prefix.data = "3dsMax|Parameters|"; - material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); - } else if (classid_a == 0x38420192u && classid_b == 0x45fe4e1bu) { - material->shader_type = UFBX_SHADER_GLTF_MATERIAL; - material->shader_prop_prefix.data = "3dsMax|"; - material->shader_prop_prefix.length = strlen("3dsMax|"); - } else if (classid_a == 0xd00f1e00u && classid_b == 0xbe77e500u) { - material->shader_type = UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH; - material->shader_prop_prefix.data = "3dsMax|main|"; - material->shader_prop_prefix.length = strlen("3dsMax|main|"); - } else if (classid_a == 0xd00f1e00u && classid_b == 0x01dbad33u) { - material->shader_type = UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS; - material->shader_prop_prefix.data = "3dsMax|main|"; - material->shader_prop_prefix.length = strlen("3dsMax|main|"); - } - } - } - - ufbxi_check(ufbxi_fetch_textures(uc, &material->textures, &material->element, false)); - } - - // Ugh.. Patch the textures from meshes for legacy LayerElement-style textures - { - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) { - ufbx_mesh *mesh = *p_mesh; - size_t num_materials = mesh->materials.count; - - ufbxi_mesh_extra *extra = (ufbxi_mesh_extra*)ufbxi_get_element_extra(uc, mesh->element.element_id); - if (!extra) continue; - if (num_materials == 0) continue; - - // TODO: This leaks currently to result, probably doesn't matter.. - ufbx_texture_list textures; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &textures, &mesh->element, true, false, NULL, UFBX_ELEMENT_TEXTURE)); - - size_t num_material_textures = 0; - ufbxi_for(ufbxi_tmp_mesh_texture, tex, extra->texture_arr, extra->texture_count) { - if (tex->all_same) { - int32_t texture_id = tex->num_faces > 0 ? (int32_t)tex->face_texture[0] : 0; - if (texture_id >= 0 && (size_t)texture_id < textures.count) { - ufbxi_tmp_material_texture *mat_texs = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, num_materials); - ufbxi_check(mat_texs); - num_material_textures += num_materials; - for (size_t i = 0; i < num_materials; i++) { - mat_texs[i].material_id = (int32_t)i; - mat_texs[i].texture_id = texture_id; - mat_texs[i].prop_name = tex->prop_name; - } - } - } else if (mesh->face_material.count) { - size_t num_faces = ufbxi_min_sz(tex->num_faces, mesh->num_faces); - int32_t prev_material = -1; - int32_t prev_texture = -1; - for (size_t i = 0; i < num_faces; i++) { - int32_t texture_id = (int32_t)tex->face_texture[i]; - int32_t material_id = (int32_t)mesh->face_material.data[i]; - if (texture_id < 0 || (size_t)texture_id >= textures.count) continue; - if (material_id < 0 || (size_t)material_id >= num_materials) continue; - if (material_id == prev_material && texture_id == prev_texture) continue; - prev_material = material_id; - prev_texture = texture_id; - - ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1); - ufbxi_check(mat_tex); - mat_tex->material_id = material_id; - mat_tex->texture_id = texture_id; - mat_tex->prop_name = tex->prop_name; - num_material_textures++; - } - } - } - - // Push a sentinel material texture to the end so we don't need to - // duplicate the material texture flushing code twice. - { - ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1); - ufbxi_check(mat_tex); - mat_tex->material_id = -1; - mat_tex->texture_id = -1; - mat_tex->prop_name = ufbx_empty_string; - } - - ufbxi_tmp_material_texture *mat_texs = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_material_texture, num_material_textures + 1); - ufbxi_check(mat_texs); - ufbxi_check(ufbxi_sort_tmp_material_textures(uc, mat_texs, num_material_textures)); - - int32_t prev_material = -2; - int32_t prev_texture = -2; - const char *prev_prop = NULL; - size_t num_textures_in_material = 0; - for (size_t i = 0; i < num_material_textures + 1; i++) { - ufbxi_tmp_material_texture mat_tex = mat_texs[i]; - if (mat_tex.material_id != prev_material) { - if (prev_material >= 0 && num_textures_in_material > 0) { - ufbx_material *mat = mesh->materials.data[prev_material]; - if (mat && mat->textures.count == 0) { - ufbx_material_texture *texs = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures_in_material); - ufbxi_check(texs); - mat->textures.data = texs; - mat->textures.count = num_textures_in_material; - } else { - ufbxi_pop(&uc->tmp_stack, ufbx_material_texture, num_textures_in_material, NULL); - } - } - - if (mat_tex.material_id < 0) break; - prev_material = mat_tex.material_id; - prev_texture = -1; - prev_prop = NULL; - num_textures_in_material = 0; - } - if (mat_tex.texture_id == prev_texture && mat_tex.prop_name.data == prev_prop) continue; - prev_texture = mat_tex.texture_id; - prev_prop = mat_tex.prop_name.data; - - ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1); - ufbxi_check(tex); - ufbx_assert(prev_texture >= 0 && (size_t)prev_texture < textures.count); - tex->texture = textures.data[prev_texture]; - tex->shader_prop = tex->material_prop = mat_tex.prop_name; - num_textures_in_material++; - } - } - } - - ufbxi_check(ufbxi_resolve_file_content(uc)); - - ufbxi_for_ptr_list(ufbx_texture, p_texture, uc->scene.textures) { - ufbx_texture *texture = *p_texture; - ufbxi_texture_extra *extra = (ufbxi_texture_extra*)ufbxi_get_element_extra(uc, texture->element.element_id); - - ufbx_prop *uv_set = ufbxi_find_prop(&texture->props, ufbxi_UVSet); - if (uv_set) { - texture->uv_set = uv_set->value_str; - } else { - texture->uv_set = ufbx_empty_string; - } - - texture->video = (ufbx_video*)ufbxi_fetch_dst_element(&texture->element, false, NULL, UFBX_ELEMENT_VIDEO); - if (texture->video) { - texture->content = texture->video->content; - } - - ufbxi_check(ufbxi_finalize_shader_texture(uc, texture)); - - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->filename, (ufbxi_strblob*)&texture->absolute_filename, (ufbxi_strblob*)&texture->relative_filename, false)); - ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->raw_filename, (ufbxi_strblob*)&texture->raw_absolute_filename, (ufbxi_strblob*)&texture->raw_relative_filename, true)); - - // Fetch layered texture layers and patch alphas/blend modes - if (texture->type == UFBX_TEXTURE_LAYERED) { - ufbxi_check(ufbxi_fetch_texture_layers(uc, &texture->layers, &texture->element)); - if (extra) { - for (size_t i = 0, num = ufbxi_min_sz(extra->num_alphas, texture->layers.count); i < num; i++) { - texture->layers.data[i].alpha = extra->alphas[i]; - } - for (size_t i = 0, num = ufbxi_min_sz(extra->num_blend_modes, texture->layers.count); i < num; i++) { - int32_t mode = extra->blend_modes[i]; - if (mode >= 0 && mode < UFBX_BLEND_OVERLAY) { - texture->layers.data[i].blend_mode = (ufbx_blend_mode)mode; - } - } - } - } - - ufbxi_check(ufbxi_insert_texture_file(uc, texture)); - } - - ufbxi_propagate_main_textures(&uc->scene); - ufbxi_check(ufbxi_pop_texture_files(uc)); - - // Second pass to fetch material maps - ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) { - ufbx_material *material = *p_material; - - ufbxi_check(ufbxi_sort_material_textures(uc, material->textures.data, material->textures.count)); - ufbxi_fetch_maps(&uc->scene, material); - - // Fetch `ufbx_material_texture.shader_prop` names - if (material->shader) { - ufbxi_for_ptr_list(ufbx_shader_binding, p_binding, material->shader->bindings) { - ufbx_shader_binding *binding = *p_binding; - - ufbxi_for_list(ufbx_shader_prop_binding, prop, binding->prop_bindings) { - ufbx_string name = prop->material_prop; - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_material_texture, 4, &index, material->textures.data, 0, material->textures.count, - ( ufbxi_str_less(a->material_prop, name) ), ( a->material_prop.data == name.data )); - for (; index < material->textures.count && material->textures.data[index].shader_prop.data == name.data; index++) { - material->textures.data[index].shader_prop = prop->shader_prop; - } - } - } - } - } - - ufbxi_for_ptr_list(ufbx_display_layer, p_layer, uc->scene.display_layers) { - ufbx_display_layer *layer = *p_layer; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->nodes, &layer->element, false, true, NULL, UFBX_ELEMENT_NODE)); - } - - ufbxi_for_ptr_list(ufbx_selection_set, p_set, uc->scene.selection_sets) { - ufbx_selection_set *set = *p_set; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &set->nodes, &set->element, false, true, NULL, UFBX_ELEMENT_SELECTION_NODE)); - } - - ufbxi_for_ptr_list(ufbx_selection_node, p_node, uc->scene.selection_nodes) { - ufbx_selection_node *node = *p_node; - node->target_node = (ufbx_node*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_NODE); - node->target_mesh = (ufbx_mesh*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_MESH); - if (!node->target_mesh && node->target_node) { - node->target_mesh = node->target_node->mesh; - } else if (!node->target_node && node->target_mesh && node->target_mesh->instances.count > 0) { - node->target_node = node->target_mesh->instances.data[0]; - } - - ufbx_mesh *mesh = node->target_mesh; - if (mesh) { - ufbxi_check(ufbxi_validate_indices(uc, &node->vertices, mesh->num_vertices)); - ufbxi_check(ufbxi_validate_indices(uc, &node->edges, mesh->num_edges)); - ufbxi_check(ufbxi_validate_indices(uc, &node->faces, mesh->num_faces)); - } - } - - ufbxi_for_ptr_list(ufbx_constraint, p_constraint, uc->scene.constraints) { - ufbx_constraint *constraint = *p_constraint; - - size_t tmp_base = uc->tmp_stack.num_items; - - // Find property connections in _both_ src and dst connections as they are inconsistent - // in pre-7000 files. For example "Constrained Object" is a "PO" connection in 6100. - ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_src) { - if (conn->src_prop.length == 0 || conn->dst->type != UFBX_ELEMENT_NODE) continue; - ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->dst, conn->src_prop.data)); - } - ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_dst) { - if (conn->dst_prop.length == 0 || conn->src->type != UFBX_ELEMENT_NODE) continue; - ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->src, conn->dst_prop.data)); - } - - size_t num_targets = uc->tmp_stack.num_items - tmp_base; - constraint->targets.count = num_targets; - constraint->targets.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_constraint_target, num_targets); - ufbxi_check(constraint->targets.data); - } - - ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, uc->scene.audio_layers) { - ufbx_audio_layer *layer = *p_layer; - ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->clips, &layer->element, false, true, NULL, UFBX_ELEMENT_AUDIO_CLIP)); - } - - ufbxi_for_ptr_list(ufbx_lod_group, p_lod, uc->scene.lod_groups) { - ufbxi_check(ufbxi_finalize_lod_group(uc, *p_lod)); - } - - ufbxi_check(ufbxi_fetch_file_textures(uc)); - - // NOTE: This will be patched over in `ufbxi_update_scene()` if there are `anim_layers` - if (uc->scene.anim_layers.count == 0) { - ufbxi_check(ufbxi_push_anim(uc, &uc->scene.anim, NULL, 0)); - } - - uc->scene.metadata.ktime_second = uc->ktime_sec; - - // Maya seems to use scale of 100/3, Blender binary uses exactly 33, ASCII has always value of 1.0 - if (uc->version < 6000) { - uc->scene.metadata.bone_prop_size_unit = 1.0f; - } else if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) { - uc->scene.metadata.bone_prop_size_unit = 33.0f; - } else if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { - uc->scene.metadata.bone_prop_size_unit = 1.0f; - } else { - uc->scene.metadata.bone_prop_size_unit = (ufbx_real)(100.0/3.0); - } - if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) { - uc->scene.metadata.bone_prop_limb_length_relative = false; - } else { - uc->scene.metadata.bone_prop_limb_length_relative = true; - } - - return 1; -} - -// -- Interpret the read scene - -static ufbxi_forceinline void ufbxi_add_translate(ufbx_transform *t, ufbx_vec3 v) -{ - t->translation.x += v.x; - t->translation.y += v.y; - t->translation.z += v.z; -} - -static ufbxi_forceinline void ufbxi_sub_translate(ufbx_transform *t, ufbx_vec3 v) -{ - t->translation.x -= v.x; - t->translation.y -= v.y; - t->translation.z -= v.z; -} - -static ufbxi_forceinline void ufbxi_mul_scale(ufbx_transform *t, ufbx_vec3 v) -{ - t->translation.x *= v.x; - t->translation.y *= v.y; - t->translation.z *= v.z; - t->scale.x *= v.x; - t->scale.y *= v.y; - t->scale.z *= v.z; -} - -static ufbxi_forceinline void ufbxi_mul_scale_real(ufbx_transform *t, ufbx_real v) -{ - t->translation.x *= v; - t->translation.y *= v; - t->translation.z *= v; - t->scale.x *= v; - t->scale.y *= v; - t->scale.z *= v; -} - -static ufbxi_noinline ufbx_quat ufbxi_mul_quat(ufbx_quat a, ufbx_quat b) -{ - ufbx_quat r; - r.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; - r.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; - r.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; - r.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; - return r; -} - -static ufbxi_forceinline void ufbxi_add_weighted_vec3(ufbx_vec3 *r, ufbx_vec3 b, ufbx_real w) -{ - r->x += b.x * w; - r->y += b.y * w; - r->z += b.z * w; -} - -static ufbxi_forceinline void ufbxi_add_weighted_quat(ufbx_quat *r, ufbx_quat b, ufbx_real w) -{ - r->x += b.x * w; - r->y += b.y * w; - r->z += b.z * w; - r->w += b.w * w; -} - -static ufbxi_noinline void ufbxi_add_weighted_mat(ufbx_matrix *r, const ufbx_matrix *b, ufbx_real w) -{ - ufbxi_add_weighted_vec3(&r->cols[0], b->cols[0], w); - ufbxi_add_weighted_vec3(&r->cols[1], b->cols[1], w); - ufbxi_add_weighted_vec3(&r->cols[2], b->cols[2], w); - ufbxi_add_weighted_vec3(&r->cols[3], b->cols[3], w); -} - -static void ufbxi_mul_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order) -{ - if (ufbxi_is_vec3_zero(v)) return; - - ufbx_quat q = ufbx_euler_to_quat(v, order); - if (t->rotation.w != 1.0) { - t->rotation = ufbxi_mul_quat(q, t->rotation); - } else { - t->rotation = q; - } - - if (!ufbxi_is_vec3_zero(t->translation)) { - t->translation = ufbx_quat_rotate_vec3(q, t->translation); - } -} - -static void ufbxi_mul_rotate_quat(ufbx_transform *t, ufbx_quat q) -{ - if (ufbxi_is_quat_identity(q)) return; - - if (t->rotation.w != 1.0) { - t->rotation = ufbxi_mul_quat(q, t->rotation); - } else { - t->rotation = q; - } - - if (!ufbxi_is_vec3_zero(t->translation)) { - t->translation = ufbx_quat_rotate_vec3(q, t->translation); - } -} - -static void ufbxi_mul_inv_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order) -{ - if (ufbxi_is_vec3_zero(v)) return; - - ufbx_quat q = ufbx_euler_to_quat(v, order); - q.x = -q.x; q.y = -q.y; q.z = -q.z; - if (t->rotation.w != 1.0) { - t->rotation = ufbxi_mul_quat(q, t->rotation); - } else { - t->rotation = q; - } - - if (!ufbxi_is_vec3_zero(t->translation)) { - t->translation = ufbx_quat_rotate_vec3(q, t->translation); - } -} - -// -- Updating state from properties - -ufbxi_forceinline static void ufbxi_mirror_translation(ufbx_vec3 *p_vec, ufbx_mirror_axis axis) -{ - ufbxi_dev_assert(axis); - p_vec->v[axis - 1] = -p_vec->v[axis - 1]; -} - -ufbxi_forceinline static void ufbxi_mirror_rotation(ufbx_quat *p_quat, ufbx_mirror_axis axis) -{ - ufbxi_dev_assert(axis); - p_quat->v[axis % 3] = -p_quat->v[axis % 3]; - p_quat->v[(axis + 1) % 3] = -p_quat->v[(axis + 1) % 3]; -} - -ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node) -{ - ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - // WorldTransform = ParentWorldTransform * T * R * S * (OT * OR * OS) - - ufbxi_mul_scale(&t, scaling); - ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_add_translate(&t, translation); - - if (node->has_adjust_transform) { - t.translation.x *= node->adjust_translation_scale; - t.translation.y *= node->adjust_translation_scale; - t.translation.z *= node->adjust_translation_scale; - } - - if (node->adjust_mirror_axis) { - ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis); - ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); - } - - return t; -} - -ufbxi_noinline static ufbx_transform ufbxi_get_transform(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node, const ufbx_vec3 *translation_scale) -{ - ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scale_offset = ufbxi_find_vec3(props, ufbxi_ScalingOffset, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rot_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f); - - ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Lcl_Translation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); - - ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - // WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1 - // NOTE: Rpost is inverted (!) after converting from PostRotation Euler angles - - if (translation_scale) { - translation.x *= translation_scale->x; - translation.y *= translation_scale->y; - translation.z *= translation_scale->z; - } - - if (node->has_adjust_transform) { - ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation); - ufbxi_mul_scale_real(&t, node->adjust_post_scale); - } - - ufbxi_sub_translate(&t, scale_pivot); - ufbxi_mul_scale(&t, scaling); - ufbxi_add_translate(&t, scale_pivot); - - ufbxi_add_translate(&t, scale_offset); - - ufbxi_sub_translate(&t, rot_pivot); - ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_mul_rotate(&t, rotation, order); - ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_add_translate(&t, rot_pivot); - - ufbxi_add_translate(&t, rot_offset); - - ufbxi_add_translate(&t, translation); - - if (node->has_adjust_transform) { - ufbxi_add_translate(&t, node->adjust_pre_translation); - ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation); - ufbxi_mul_scale_real(&t, node->adjust_pre_scale); - t.translation.x *= node->adjust_translation_scale; - t.translation.y *= node->adjust_translation_scale; - t.translation.z *= node->adjust_translation_scale; - } - - if (node->adjust_mirror_axis) { - ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis); - ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); - } - - return t; -} - -ufbxi_noinline static ufbx_quat ufbxi_get_rotation(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node) -{ - ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - if (node->has_adjust_transform) { - ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation); - } - - ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_mul_rotate(&t, rotation, order); - ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ); - - if (node->has_adjust_transform) { - ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation); - } - - if (node->adjust_mirror_axis) { - ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis); - } - - return t.rotation; -} - -ufbxi_noinline static ufbx_vec3 ufbxi_get_scale(const ufbx_props *props, const ufbx_node *node) -{ - ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - if (node->has_adjust_transform) { - ufbxi_mul_scale_real(&t, node->adjust_post_scale); - } - - ufbxi_mul_scale(&t, scaling); - - if (node->has_adjust_transform) { - ufbxi_mul_scale_real(&t, node->adjust_pre_scale); - } - - return t.scale; -} - -ufbxi_noinline static ufbx_transform ufbxi_get_texture_transform(const ufbx_props *props) -{ - ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_TextureScalingPivot, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_TextureRotationPivot, 0.0f, 0.0f, 0.0f); - - ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - ufbxi_sub_translate(&t, scale_pivot); - ufbxi_mul_scale(&t, scaling); - ufbxi_add_translate(&t, scale_pivot); - - ufbxi_sub_translate(&t, rot_pivot); - ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_add_translate(&t, rot_pivot); - - ufbxi_add_translate(&t, translation); - - if (ufbxi_find_int(props, ufbxi_UVSwap, 0) != 0) { - const ufbx_vec3 swap_scale = { -1.0f, 0.0f, 0.0f }; - const ufbx_vec3 swap_rotate = { 0.0f, 0.0f, -90.0f }; - ufbxi_mul_scale(&t, swap_scale); - ufbxi_mul_rotate(&t, swap_rotate, UFBX_ROTATION_ORDER_XYZ); - } - - return t; -} - -ufbxi_noinline static ufbx_transform ufbxi_get_constraint_transform(const ufbx_props *props) -{ - ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f); - ufbx_vec3 rotation_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f); - ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f); - - ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }}; - - ufbxi_mul_scale(&t, scaling); - ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ); - ufbxi_mul_rotate(&t, rotation_offset, UFBX_ROTATION_ORDER_XYZ); - ufbxi_add_translate(&t, translation); - - return t; -} - -ufbxi_noinline static void ufbxi_update_node(ufbx_node *node, const ufbx_transform_override *overrides, size_t num_overrides) -{ - node->rotation_order = (ufbx_rotation_order)ufbxi_find_enum(&node->props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); - node->euler_rotation = ufbxi_find_vec3(&node->props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f); - - if (!node->is_root) { - const ufbx_vec3 *transform_scale = NULL; - if (node->parent && node->parent->scale_helper) { - transform_scale = &node->parent->scale_helper->local_transform.scale; - } - node->local_transform = ufbxi_get_transform(&node->props, node->rotation_order, node, transform_scale); - if (node->is_scale_helper && node->parent && node->parent->inherit_scale_node) { - ufbx_node *scale_parent = node->parent->inherit_scale_node; - if (scale_parent->scale_helper) { - ufbx_vec3 inherit_scale = scale_parent->scale_helper->local_transform.scale; - node->local_transform.scale.x *= inherit_scale.x; - node->local_transform.scale.y *= inherit_scale.y; - node->local_transform.scale.z *= inherit_scale.z; - } - } - - if (num_overrides > 0) { - uint32_t typed_id = node->typed_id; - size_t override_ix = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_transform_override, 16, &override_ix, overrides, 0, num_overrides, - ( a->node_id < typed_id ), ( a->node_id == typed_id )); - if (override_ix != SIZE_MAX) { - node->local_transform = overrides[override_ix].transform; - } - } - node->node_to_parent = ufbx_transform_to_matrix(&node->local_transform); - node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node); - } else { - node->geometry_transform = ufbx_identity_transform; - } - - ufbx_matrix unscaled_node_to_parent = ufbxi_unscaled_transform_to_matrix(&node->local_transform); - - node->inherit_scale = node->local_transform.scale; - - ufbx_node *parent = node->parent; - if (parent) { - if (node->inherit_mode == UFBX_INHERIT_MODE_NORMAL) { - node->node_to_world = ufbx_matrix_mul(&parent->node_to_world, &node->node_to_parent); - node->unscaled_node_to_world = ufbx_matrix_mul(&parent->node_to_world, &unscaled_node_to_parent); - } else { - ufbx_transform transform = node->local_transform; - - ufbx_vec3 parent_scale = ufbxi_one_vec3; - if (node->inherit_scale_node) { - parent_scale = node->inherit_scale_node->inherit_scale; - } - - transform.scale.x *= parent_scale.x; - transform.scale.y *= parent_scale.y; - transform.scale.z *= parent_scale.z; - transform.translation.x *= parent->inherit_scale.x; - transform.translation.y *= parent->inherit_scale.y; - transform.translation.z *= parent->inherit_scale.z; - - ufbx_matrix node_to_unscaled_parent = ufbx_transform_to_matrix(&transform); - ufbx_matrix unscaled_node_to_unscaled_parent = ufbxi_unscaled_transform_to_matrix(&transform); - - node->inherit_scale = transform.scale; - node->node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &node_to_unscaled_parent); - node->unscaled_node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &unscaled_node_to_unscaled_parent); - } - } else { - node->node_to_world = node->node_to_parent; - node->unscaled_node_to_world = unscaled_node_to_parent; - } - - if (!ufbxi_is_transform_identity(&node->geometry_transform)) { - node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform); - node->geometry_to_world = ufbx_matrix_mul(&node->node_to_world, &node->geometry_to_node); - node->has_geometry_transform = true; - } else { - node->geometry_to_node = ufbx_identity_matrix; - node->geometry_to_world = node->node_to_world; - node->has_geometry_transform = false; - } - - node->visible = ufbxi_find_int(&node->props, ufbxi_Visibility, 1) != 0; -} - -ufbxi_noinline static void ufbxi_update_light(ufbx_light *light) -{ - // NOTE: FBX seems to store intensities 100x of what's specified in at least - // Maya and Blender, should there be a quirks mode to not do this for specific - // exporters. Does the FBX SDK do this transparently as well? - light->intensity = ufbxi_find_real(&light->props, ufbxi_Intensity, (ufbx_real)100.0) / (ufbx_real)100.0; - - light->color = ufbxi_find_vec3(&light->props, ufbxi_Color, 1.0f, 1.0f, 1.0f); - light->type = (ufbx_light_type)ufbxi_find_enum(&light->props, ufbxi_LightType, 0, UFBX_LIGHT_VOLUME); - light->decay = (ufbx_light_decay)ufbxi_find_enum(&light->props, ufbxi_DecayType, UFBX_LIGHT_DECAY_NONE, UFBX_LIGHT_DECAY_CUBIC); - light->area_shape = (ufbx_light_area_shape)ufbxi_find_enum(&light->props, ufbxi_AreaLightShape, 0, UFBX_LIGHT_AREA_SHAPE_SPHERE); - light->inner_angle = ufbxi_find_real(&light->props, ufbxi_HotSpot, 0.0f); - light->inner_angle = ufbxi_find_real(&light->props, ufbxi_InnerAngle, light->inner_angle); - light->outer_angle = ufbxi_find_real(&light->props, ufbxi_Cone_angle, 0.0f); - light->outer_angle = ufbxi_find_real(&light->props, ufbxi_ConeAngle, light->outer_angle); - light->outer_angle = ufbxi_find_real(&light->props, ufbxi_OuterAngle, light->outer_angle); - light->cast_light = ufbxi_find_int(&light->props, ufbxi_CastLight, 1) != 0; - light->cast_shadows = ufbxi_find_int(&light->props, ufbxi_CastShadows, 0) != 0; -} - -typedef struct { - // 1/1000 decimal fixed point for size - uint16_t film_size_x, film_size_y; -} ufbxi_aperture_format; - -static const ufbxi_aperture_format ufbxi_aperture_formats[] = { - { 1000, 1000, }, // UFBX_APERTURE_FORMAT_CUSTOM - { 404, 295, }, // UFBX_APERTURE_FORMAT_16MM_THEATRICAL - { 493, 292, }, // UFBX_APERTURE_FORMAT_SUPER_16MM - { 864, 630, }, // UFBX_APERTURE_FORMAT_35MM_ACADEMY - { 816, 612, }, // UFBX_APERTURE_FORMAT_35MM_TV_PROJECTION - { 980, 735, }, // UFBX_APERTURE_FORMAT_35MM_FULL_APERTURE - { 825, 446, }, // UFBX_APERTURE_FORMAT_35MM_185_PROJECTION - { 864, 732, }, // UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC - { 2066, 906, }, // UFBX_APERTURE_FORMAT_70MM_PROJECTION - { 1485, 991, }, // UFBX_APERTURE_FORMAT_VISTAVISION - { 2080, 1480, }, // UFBX_APERTURE_FORMAT_DYNAVISION - { 2772, 2072, }, // UFBX_APERTURE_FORMAT_IMAX -}; - -ufbxi_noinline static void ufbxi_update_camera(ufbx_scene *scene, ufbx_camera *camera) -{ - camera->projection_mode = (ufbx_projection_mode)ufbxi_find_enum(&camera->props, ufbxi_CameraProjectionType, 0, UFBX_PROJECTION_MODE_ORTHOGRAPHIC); - camera->aspect_mode = (ufbx_aspect_mode)ufbxi_find_enum(&camera->props, ufbxi_AspectRatioMode, 0, UFBX_ASPECT_MODE_FIXED_HEIGHT); - camera->aperture_mode = (ufbx_aperture_mode)ufbxi_find_enum(&camera->props, ufbxi_ApertureMode, UFBX_APERTURE_MODE_VERTICAL, UFBX_APERTURE_MODE_FOCAL_LENGTH); - camera->aperture_format = (ufbx_aperture_format)ufbxi_find_enum(&camera->props, ufbxi_ApertureFormat, UFBX_APERTURE_FORMAT_CUSTOM, UFBX_APERTURE_FORMAT_IMAX); - camera->gate_fit = (ufbx_gate_fit)ufbxi_find_enum(&camera->props, ufbxi_GateFit, 0, UFBX_GATE_FIT_STRETCH); - - camera->near_plane = ufbxi_find_real(&camera->props, ufbxi_NearPlane, 0.0f); - camera->far_plane = ufbxi_find_real(&camera->props, ufbxi_FarPlane, 0.0f); - - // Search both W/H and Width/Height but prefer the latter - ufbx_real aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectW, 0.0f); - ufbx_real aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectH, 0.0f); - aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectWidth, aspect_x); - aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectHeight, aspect_y); - - ufbx_real fov = ufbxi_find_real(&camera->props, ufbxi_FieldOfView, 0.0f); - ufbx_real fov_x = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewX, 0.0f); - ufbx_real fov_y = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewY, 0.0f); - - ufbx_real focal_length = ufbxi_find_real(&camera->props, ufbxi_FocalLength, 0.0f); - ufbx_real ortho_extent = scene->metadata.ortho_size_unit * ufbxi_find_real(&camera->props, ufbxi_OrthoZoom, 1.0f); - - ufbxi_aperture_format format = ufbxi_aperture_formats[camera->aperture_format]; - ufbx_vec2 film_size = { (ufbx_real)format.film_size_x * (ufbx_real)0.001, (ufbx_real)format.film_size_y * (ufbx_real)0.001 }; - ufbx_real squeeze_ratio = camera->aperture_format == UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC ? 2.0f : 1.0f; - - film_size.x = ufbxi_find_real(&camera->props, ufbxi_FilmWidth, film_size.x); - film_size.y = ufbxi_find_real(&camera->props, ufbxi_FilmHeight, film_size.y); - squeeze_ratio = ufbxi_find_real(&camera->props, ufbxi_FilmSqueezeRatio, squeeze_ratio); - - if (aspect_x <= 0.0f && aspect_y <= 0.0f) { - aspect_x = film_size.x > 0.0f ? film_size.x : 1.0f; - aspect_y = film_size.y > 0.0f ? film_size.y : 1.0f; - } else if (aspect_x <= 0.0f) { - if (film_size.x > 0.0f && film_size.y > 0.0f) { - aspect_x = aspect_y / film_size.y * film_size.x; - } else { - aspect_x = aspect_y; - } - } else if (aspect_y <= 0.0f) { - if (film_size.x > 0.0f && film_size.y > 0.0f) { - aspect_y = aspect_x / film_size.x * film_size.y; - } else { - aspect_y = aspect_x; - } - } - - film_size.y *= squeeze_ratio; - - // TODO: Should this be done always? - ortho_extent *= scene->metadata.geometry_scale; - camera->near_plane *= scene->metadata.geometry_scale; - camera->far_plane *= scene->metadata.geometry_scale; - - camera->focal_length_mm = focal_length; - camera->film_size_inch = film_size; - camera->squeeze_ratio = squeeze_ratio; - camera->orthographic_extent = ortho_extent; - - switch (camera->aspect_mode) { - case UFBX_ASPECT_MODE_WINDOW_SIZE: - case UFBX_ASPECT_MODE_FIXED_RATIO: - camera->resolution_is_pixels = false; - camera->resolution.x = aspect_x; - camera->resolution.y = aspect_y; - break; - case UFBX_ASPECT_MODE_FIXED_RESOLUTION: - camera->resolution_is_pixels = true; - camera->resolution.x = aspect_x; - camera->resolution.y = aspect_y; - break; - case UFBX_ASPECT_MODE_FIXED_WIDTH: - camera->resolution_is_pixels = true; - camera->resolution.x = aspect_x; - camera->resolution.y = aspect_x * aspect_y; - break; - case UFBX_ASPECT_MODE_FIXED_HEIGHT: - camera->resolution_is_pixels = true; - camera->resolution.x = aspect_y * aspect_x; - camera->resolution.y = aspect_y; - break; - default: - ufbxi_unreachable("Unexpected aspect mode"); - } - - ufbx_real aspect_ratio = camera->resolution.x / camera->resolution.y; - ufbx_real film_ratio = film_size.x / film_size.y; - - camera->aspect_ratio = aspect_ratio; - - ufbx_gate_fit effective_fit = camera->gate_fit; - if (effective_fit == UFBX_GATE_FIT_FILL) { - effective_fit = aspect_ratio > film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL; - } else if (effective_fit == UFBX_GATE_FIT_OVERSCAN) { - effective_fit = aspect_ratio < film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL; - } - - switch (effective_fit) { - case UFBX_GATE_FIT_NONE: - camera->aperture_size_inch = camera->film_size_inch; - camera->orthographic_size.x = ortho_extent; - camera->orthographic_size.y = ortho_extent; - break; - case UFBX_GATE_FIT_VERTICAL: - camera->aperture_size_inch.x = camera->film_size_inch.y * aspect_ratio; - camera->aperture_size_inch.y = camera->film_size_inch.y; - camera->orthographic_size.x = ortho_extent * aspect_ratio; - camera->orthographic_size.y = ortho_extent; - break; - case UFBX_GATE_FIT_HORIZONTAL: - camera->aperture_size_inch.x = camera->film_size_inch.x; - camera->aperture_size_inch.y = camera->film_size_inch.x / aspect_ratio; - camera->orthographic_size.x = ortho_extent; - camera->orthographic_size.y = ortho_extent / aspect_ratio; - break; - case UFBX_GATE_FIT_FILL: - case UFBX_GATE_FIT_OVERSCAN: - camera->aperture_size_inch = camera->film_size_inch; - camera->orthographic_size.x = ortho_extent; - camera->orthographic_size.y = ortho_extent; - ufbxi_unreachable("Unreachable, set to vertical/horizontal above"); - break; - case UFBX_GATE_FIT_STRETCH: - camera->aperture_size_inch = camera->film_size_inch; - camera->orthographic_size.x = ortho_extent; - camera->orthographic_size.y = ortho_extent; - // TODO: Not sure what to do here... - break; - default: - ufbxi_unreachable("Unexpected gate fit"); - } - - switch (camera->aperture_mode) { - case UFBX_APERTURE_MODE_HORIZONTAL_AND_VERTICAL: - camera->field_of_view_deg.x = fov_x; - camera->field_of_view_deg.y = fov_y; - camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov_x * (UFBXI_DEG_TO_RAD * 0.5f))); - camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov_y * (UFBXI_DEG_TO_RAD * 0.5f))); - break; - case UFBX_APERTURE_MODE_HORIZONTAL: - camera->field_of_view_deg.x = fov; - camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f))); - camera->field_of_view_tan.y = camera->field_of_view_tan.x / aspect_ratio; - camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f; - break; - case UFBX_APERTURE_MODE_VERTICAL: - camera->field_of_view_deg.y = fov; - camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f))); - camera->field_of_view_tan.x = camera->field_of_view_tan.y * aspect_ratio; - camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f; - break; - case UFBX_APERTURE_MODE_FOCAL_LENGTH: - camera->field_of_view_tan.x = camera->aperture_size_inch.x / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f; - camera->field_of_view_tan.y = camera->aperture_size_inch.y / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f; - camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f; - camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f; - break; - default: - ufbxi_unreachable("Unexpected aperture mode"); - } - - if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) { - camera->projection_plane = camera->field_of_view_tan; - } else { - camera->projection_plane = camera->orthographic_size; - } -} - -ufbxi_noinline static void ufbxi_update_bone(ufbx_scene *scene, ufbx_bone *bone) -{ - ufbx_real unit = scene->metadata.bone_prop_size_unit; - - bone->radius = ufbxi_find_real(&bone->props, ufbxi_Size, unit) / unit; - if (scene->metadata.bone_prop_limb_length_relative) { - bone->relative_length = ufbxi_find_real(&bone->props, ufbxi_LimbLength, 1.0f); - } else { - bone->relative_length = 1.0f; - } -} - -ufbxi_noinline static void ufbxi_update_line_curve(ufbx_line_curve *line) -{ - line->color = ufbxi_find_vec3(&line->props, ufbxi_Color, 1.0f, 1.0f, 1.0f); -} - -ufbxi_noinline static void ufbxi_update_pose(ufbx_pose *pose) -{ - ufbxi_for_list(ufbx_bone_pose, bone, pose->bone_poses) { - ufbx_node *node = bone->bone_node; - - const ufbx_matrix *parent_to_world = &ufbx_identity_matrix; - ufbx_bone_pose *bone_pose = ufbx_get_bone_pose(pose, node->parent); - if (bone_pose) { - parent_to_world = &bone_pose->bone_to_world; - } else if (node->parent) { - parent_to_world = &node->parent->node_to_world; - } - - ufbx_matrix world_to_parent = ufbx_matrix_invert(parent_to_world); - bone->bone_to_parent = ufbx_matrix_mul(&world_to_parent, &bone->bone_to_world); - } -} - -ufbxi_noinline static void ufbxi_update_skin_cluster(ufbx_skin_cluster *cluster) -{ - if (cluster->bone_node) { - cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bone_node->node_to_world, &cluster->geometry_to_bone); - } else { - cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bind_to_world, &cluster->geometry_to_bone); - } - cluster->geometry_to_world_transform = ufbx_matrix_to_transform(&cluster->geometry_to_world); -} - -ufbxi_noinline static void ufbxi_update_blend_channel(ufbx_blend_channel *channel) -{ - ufbx_real weight = ufbxi_find_real(&channel->props, ufbxi_DeformPercent, 0.0f) * (ufbx_real)0.01; - channel->weight = weight; - - ptrdiff_t num_keys = (ptrdiff_t)channel->keyframes.count; - if (num_keys > 0) { - ufbx_blend_keyframe *keys = channel->keyframes.data; - - // Reset the effective weights to zero and find the split around zero - ptrdiff_t last_negative = -1; - for (ptrdiff_t i = 0; i < num_keys; i++) { - keys[i].effective_weight = (ufbx_real)0.0; - if (keys[i].target_weight < 0.0) last_negative = i; - } - - // Find either the next or last keyframe away from zero - ufbx_blend_keyframe zero_key = { NULL }; - ufbx_blend_keyframe *prev = &zero_key, *next = &zero_key; - if (weight > 0.0) { - if (last_negative >= 0) prev = &keys[last_negative]; - for (ptrdiff_t i = last_negative + 1; i < num_keys; i++) { - prev = next; - next = &keys[i]; - if (next->target_weight > weight) break; - } - } else { - if (last_negative + 1 < num_keys) prev = &keys[last_negative + 1]; - for (ptrdiff_t i = last_negative; i >= 0; i--) { - prev = next; - next = &keys[i]; - if (next->target_weight < weight) break; - } - } - - // Linearly interpolate between the endpoints with the weight - ufbx_real delta = next->target_weight - prev->target_weight; - if (delta != 0.0) { - ufbx_real t = (weight - prev->target_weight) / delta; - prev->effective_weight = 1.0f - t; - next->effective_weight = t; - } - } -} - -ufbxi_noinline static void ufbxi_update_material(ufbx_scene *scene, ufbx_material *material) -{ - if (material->props.num_animated > 0) { - ufbxi_fetch_maps(scene, material); - } -} - -ufbxi_noinline static void ufbxi_update_texture(ufbx_texture *texture) -{ - texture->uv_transform = ufbxi_get_texture_transform(&texture->props); - if (!ufbxi_is_transform_identity(&texture->uv_transform)) { - texture->has_uv_transform = true; - texture->texture_to_uv = ufbx_transform_to_matrix(&texture->uv_transform); - texture->uv_to_texture = ufbx_matrix_invert(&texture->texture_to_uv); - } else { - texture->has_uv_transform = false; - texture->texture_to_uv = ufbx_identity_matrix; - texture->uv_to_texture = ufbx_identity_matrix; - } - texture->wrap_u = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeU, 0, UFBX_WRAP_CLAMP); - texture->wrap_v = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeV, 0, UFBX_WRAP_CLAMP); - - if (texture->shader) { - ufbxi_update_shader_texture(texture, texture->shader); - } -} - -ufbxi_noinline static void ufbxi_update_anim_stack(ufbx_scene *scene, ufbx_anim_stack *stack) -{ - ufbx_prop *begin, *end; - begin = ufbxi_find_prop(&stack->props, ufbxi_LocalStart); - end = ufbxi_find_prop(&stack->props, ufbxi_LocalStop); - if (!begin || !end) { - begin = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStart); - end = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStop); - } - - if (begin && end) { - stack->time_begin = (double)begin->value_int / (double)scene->metadata.ktime_second; - stack->time_end = (double)end->value_int / (double)scene->metadata.ktime_second; - } - - stack->anim->time_begin = stack->time_begin; - stack->anim->time_end = stack->time_end; -} - -ufbxi_noinline static void ufbxi_update_display_layer(ufbx_display_layer *layer) -{ - layer->visible = ufbxi_find_int(&layer->props, ufbxi_Show, 1) != 0; - layer->frozen = ufbxi_find_int(&layer->props, ufbxi_Freeze, 1) != 0; - layer->ui_color = ufbxi_find_vec3(&layer->props, ufbxi_Color, 0.8f, 0.8f, 0.8f); -} - -ufbxi_noinline static void ufbxi_find_bool3(bool *dst, ufbx_props *props, const char *name, bool default_value) -{ - size_t name_len = strlen(name); - char local[64]; - ufbx_assert(name_len < sizeof(local) - 2); - memcpy(local, name, name_len); - - size_t local_len = name_len + 1; - local[local_len] = '\0'; - - int64_t def = default_value ? 1 : 0; - local[name_len] = 'X'; - dst[0] = ufbx_find_int_len(props, local, local_len, def) != 0; - local[name_len] = 'Y'; - dst[1] = ufbx_find_int_len(props, local, local_len, def) != 0; - local[name_len] = 'Z'; - dst[2] = ufbx_find_int_len(props, local, local_len, def) != 0; -} - -ufbxi_noinline static void ufbxi_update_constraint(ufbx_constraint *constraint) -{ - ufbx_props *props = &constraint->props; - ufbx_constraint_type constraint_type = constraint->type; - - constraint->transform_offset = ufbxi_get_constraint_transform(props); - - constraint->weight = ufbxi_find_real(props, ufbxi_Weight, (ufbx_real)100.0) / (ufbx_real)100.0; - - ufbxi_for_list(ufbx_constraint_target, target, constraint->targets) { - ufbx_node *node = target->node; - - ufbx_real weight_scale = (ufbx_real)100.0; - if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) { - // IK weights seem to be not scaled 100x? - weight_scale = (ufbx_real)1.0; - } - - ufbx_prop *prop; // ufbxi_uninit - ufbx_string parts[2]; // ufbxi_uninit - parts[0] = node->name; - parts[1] = ufbxi_str_c(".Weight"); - prop = ufbx_find_prop_concat(props, parts, 2); - target->weight = (prop ? prop->value_real : weight_scale) / weight_scale; - - if (constraint_type == UFBX_CONSTRAINT_PARENT) { - parts[1] = ufbxi_str_c(".Offset T"); - prop = ufbx_find_prop_concat(props, parts, 2); - ufbx_vec3 t = prop ? prop->value_vec3 : ufbx_zero_vec3; - parts[1] = ufbxi_str_c(".Offset R"); - prop = ufbx_find_prop_concat(props, parts, 2); - ufbx_vec3 r = prop ? prop->value_vec3 : ufbx_zero_vec3; - parts[1] = ufbxi_str_c(".Offset S"); - prop = ufbx_find_prop_concat(props, parts, 2); - ufbx_vec3 s = prop ? prop->value_vec3 : ufbxi_one_vec3; - - target->transform.translation = t; - target->transform.rotation = ufbx_euler_to_quat(r, UFBX_ROTATION_ORDER_XYZ); - target->transform.scale = s; - } - } - - constraint->active = ufbx_find_int(props, "Active", 1) != 0; - if (constraint_type == UFBX_CONSTRAINT_AIM) { - ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1); - - const ufbx_vec3 default_aim = { 1.0f, 0.0f, 0.0f }; - const ufbx_vec3 default_up = { 0.0f, 1.0f, 0.0f }; - - int64_t up_type = ufbx_find_int(props, "WorldUpType", 0); - if (up_type >= 0 && up_type < UFBX_CONSTRAINT_AIM_UP_NONE) { - constraint->aim_up_type = (ufbx_constraint_aim_up_type)up_type; - } - constraint->aim_vector = ufbx_find_vec3(props, "AimVector", default_aim); - constraint->aim_up_vector = ufbx_find_vec3(props, "UpVector", default_up); - - } else if (constraint_type == UFBX_CONSTRAINT_PARENT) { - ufbxi_find_bool3(constraint->constrain_translation, props, "AffectTranslation", 1); - ufbxi_find_bool3(constraint->constrain_rotation, props, "AffectRotation", 1); - ufbxi_find_bool3(constraint->constrain_scale, props, "AffectScale", 0); - } else if (constraint_type == UFBX_CONSTRAINT_POSITION) { - ufbxi_find_bool3(constraint->constrain_translation, props, "Affect", 1); - } else if (constraint_type == UFBX_CONSTRAINT_ROTATION) { - ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1); - } else if (constraint_type == UFBX_CONSTRAINT_SCALE) { - ufbxi_find_bool3(constraint->constrain_scale, props, "Affect", 1); - } else if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) { - constraint->constrain_rotation[0] = true; - constraint->constrain_rotation[1] = true; - constraint->constrain_rotation[2] = true; - constraint->ik_pole_vector = ufbx_find_vec3(props, "PoleVectorType", ufbx_zero_vec3); - } -} - -ufbxi_noinline static void ufbxi_update_anim(ufbx_scene *scene) -{ - if (scene->anim_stacks.count > 0) { - scene->anim = scene->anim_stacks.data[0]->anim; - } -} - -static ufbxi_forceinline void ufbxi_mirror_matrix_dst(ufbx_matrix *m, ufbx_mirror_axis axis) -{ - if (axis == 0) return; - int32_t ax = (int32_t)axis - 1; - m->cols[0].v[ax] = -m->cols[0].v[ax]; - m->cols[1].v[ax] = -m->cols[1].v[ax]; - m->cols[2].v[ax] = -m->cols[2].v[ax]; - m->cols[3].v[ax] = -m->cols[3].v[ax]; -} - -static ufbxi_forceinline void ufbxi_mirror_matrix_src(ufbx_matrix *m, ufbx_mirror_axis axis) -{ - if (axis == 0) return; - int32_t ax = (int32_t)axis - 1; - m->cols[ax].x = -m->cols[ax].x; - m->cols[ax].y = -m->cols[ax].y; - m->cols[ax].z = -m->cols[ax].z; -} - -static ufbxi_noinline void ufbxi_mirror_matrix(ufbx_matrix *m, ufbx_mirror_axis axis) -{ - if (axis == 0) return; - ufbxi_mirror_matrix_src(m, axis); - ufbxi_mirror_matrix_dst(m, axis); -} - -ufbxi_noinline static void ufbxi_update_initial_clusters(ufbx_scene *scene) -{ - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - cluster->geometry_to_bone = cluster->mesh_node_to_bone; - } - - ufbx_mirror_axis mirror_axis = scene->metadata.mirror_axis; - ufbx_real geometry_scale = scene->metadata.geometry_scale; - - // Space conversion for bind matrices - { - ufbx_matrix world_to_units; - ufbx_real translation_scale = 1.0f; - - if (scene->metadata.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && scene->metadata.mirror_axis == UFBX_MIRROR_AXIS_NONE) { - world_to_units = scene->root_node->node_to_parent; - } else { - ufbx_transform root_transform; - root_transform.translation = ufbx_zero_vec3; - root_transform.rotation = scene->metadata.root_rotation; - root_transform.scale.x = scene->metadata.root_scale; - root_transform.scale.y = scene->metadata.root_scale; - root_transform.scale.z = scene->metadata.root_scale; - world_to_units = ufbx_transform_to_matrix(&root_transform); - translation_scale = scene->metadata.geometry_scale; - } - - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - cluster->bind_to_world = ufbx_matrix_mul(&world_to_units, &cluster->bind_to_world); - cluster->bind_to_world.cols[3].x *= translation_scale; - cluster->bind_to_world.cols[3].y *= translation_scale; - cluster->bind_to_world.cols[3].z *= translation_scale; - ufbxi_mirror_matrix(&cluster->bind_to_world, mirror_axis); - } - - ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) { - ufbxi_for_list(ufbx_bone_pose, pose, (*p_pose)->bone_poses) { - pose->bone_to_world = ufbx_matrix_mul(&world_to_units, &pose->bone_to_world); - pose->bone_to_world.cols[3].x *= translation_scale; - pose->bone_to_world.cols[3].y *= translation_scale; - pose->bone_to_world.cols[3].z *= translation_scale; - ufbxi_mirror_matrix(&pose->bone_to_world, mirror_axis); - } - } - } - - // Patch initial `mesh_node_to_bone` - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - - ufbx_skin_deformer *skin = (ufbx_skin_deformer*)ufbxi_fetch_src_element(&cluster->element, false, NULL, UFBX_ELEMENT_SKIN_DEFORMER); - if (!skin) continue; - - ufbx_node *node = (ufbx_node*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_NODE); - if (!node) { - ufbx_mesh *mesh = (ufbx_mesh*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_MESH); - if (mesh && mesh->instances.count > 0) { - node = mesh->instances.data[0]; - } - } - if (!node) continue; - - // Normalize to the non-helper node - if (node->is_geometry_transform_helper) { - node = node->parent; - } - - if (ufbxi_matrix_all_zero(&cluster->mesh_node_to_bone)) { - // If `mesh_node_to_bone` is not explicitly specified compute it from bind pose. - ufbx_matrix world_to_bind = ufbx_matrix_invert(&cluster->bind_to_world); - cluster->mesh_node_to_bone = ufbx_matrix_mul(&world_to_bind, &node->node_to_world); - } else { - // If `mesh_node_to_bone` is explicit, we may need to modify it for space conversion. - ufbxi_mirror_matrix(&cluster->mesh_node_to_bone, mirror_axis); - if (geometry_scale != 1.0f) { - cluster->mesh_node_to_bone.cols[3].x *= geometry_scale; - cluster->mesh_node_to_bone.cols[3].y *= geometry_scale; - cluster->mesh_node_to_bone.cols[3].z *= geometry_scale; - } - } - - // HACK: Account for geometry transforms by looking at the transform of the - // helper node if one is present. I don't think this is exactly how the skinning - // matrices are formed. - // TODO: Add a test with moving the skinned mesh root around. - if (node->geometry_transform_helper) { - ufbx_node *geo_node = node->geometry_transform_helper; - cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &geo_node->node_to_parent); - } else if (node->has_geometry_transform) { - cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &node->geometry_to_node); - } else { - cluster->geometry_to_bone = cluster->mesh_node_to_bone; - } - } -} - -ufbxi_noinline static ufbx_coordinate_axis ufbxi_find_axis(const ufbx_props *props, const char *axis_name, const char *sign_name) -{ - int64_t axis = ufbxi_find_int(props, axis_name, 3); - int64_t sign = ufbxi_find_int(props, sign_name, 2); - - switch (axis) { - case 0: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_X : UFBX_COORDINATE_AXIS_NEGATIVE_X; - case 1: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Y : UFBX_COORDINATE_AXIS_NEGATIVE_Y; - case 2: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Z : UFBX_COORDINATE_AXIS_NEGATIVE_Z; - default: return UFBX_COORDINATE_AXIS_UNKNOWN; - } -} - -static const ufbx_real ufbxi_time_mode_fps[] = { - 30.0f, // UFBX_TIME_MODE_DEFAULT - 120.0f, // UFBX_TIME_MODE_120_FPS - 100.0f, // UFBX_TIME_MODE_100_FPS - 60.0f, // UFBX_TIME_MODE_60_FPS - 50.0f, // UFBX_TIME_MODE_50_FPS - 48.0f, // UFBX_TIME_MODE_48_FPS - 30.0f, // UFBX_TIME_MODE_30_FPS - 30.0f, // UFBX_TIME_MODE_30_FPS_DROP - 29.97f, // UFBX_TIME_MODE_NTSC_DROP_FRAME - 29.97f, // UFBX_TIME_MODE_NTSC_FULL_FRAME - 25.0f, // UFBX_TIME_MODE_PAL - 24.0f, // UFBX_TIME_MODE_24_FPS - 1000.0f, // UFBX_TIME_MODE_1000_FPS - 23.976f, // UFBX_TIME_MODE_FILM_FULL_FRAME - 24.0f, // UFBX_TIME_MODE_CUSTOM - 96.0f, // UFBX_TIME_MODE_96_FPS - 72.0f, // UFBX_TIME_MODE_72_FPS - 59.94f, // UFBX_TIME_MODE_59_94_FPS -}; - -// Returns whether a non-identity matrix was needed -static ufbxi_noinline bool ufbxi_axis_matrix(ufbx_matrix *mat, ufbx_coordinate_axes src, ufbx_coordinate_axes dst) -{ - uint32_t src_x = (uint32_t)src.right; - uint32_t dst_x = (uint32_t)dst.right; - uint32_t src_y = (uint32_t)src.up; - uint32_t dst_y = (uint32_t)dst.up; - uint32_t src_z = (uint32_t)src.front; - uint32_t dst_z = (uint32_t)dst.front; - - if (src_x == dst_x && src_y == dst_y && src_z == dst_z) return false; - - // Remap axes (axis enum divided by 2) potentially flipping if the signs (enum parity) doesn't match - memset(mat, 0, sizeof(ufbx_matrix)); - mat->cols[src_x >> 1].v[dst_x >> 1] = ((src_x ^ dst_x) & 1) == 0 ? 1.0f : -1.0f; - mat->cols[src_y >> 1].v[dst_y >> 1] = ((src_y ^ dst_y) & 1) == 0 ? 1.0f : -1.0f; - mat->cols[src_z >> 1].v[dst_z >> 1] = ((src_z ^ dst_z) & 1) == 0 ? 1.0f : -1.0f; - - return true; -} - -ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufbx_scene *scene) -{ - ufbx_transform root_transform = ufbx_identity_transform; - if (!ufbxi_matrix_all_zero(&uc->axis_matrix)) { - root_transform = ufbx_matrix_to_transform(&uc->axis_matrix); - } - root_transform.scale.x *= uc->unit_scale; - root_transform.scale.y *= uc->unit_scale; - root_transform.scale.z *= uc->unit_scale; - - ufbx_space_conversion conversion = uc->opts.space_conversion; - - ufbx_quat light_post_rotation = ufbx_identity_quat; - ufbx_quat camera_post_rotation = ufbx_identity_quat; - ufbx_vec3 light_direction = { 0.0f, -1.0f, 0.0f }; - bool has_light_transform = false; - bool has_camera_transform = false; - - if (ufbx_coordinate_axes_valid(uc->opts.target_light_axes)) { - ufbx_matrix mat; // ufbxi_uninit - ufbx_coordinate_axes light_axes = { - UFBX_COORDINATE_AXIS_POSITIVE_X, - UFBX_COORDINATE_AXIS_NEGATIVE_Z, - UFBX_COORDINATE_AXIS_POSITIVE_Y, - }; - if (ufbxi_axis_matrix(&mat, uc->opts.target_light_axes, light_axes)) { - light_post_rotation = ufbx_matrix_to_transform(&mat).rotation; - - ufbx_matrix inv = ufbx_matrix_invert(&mat); - light_direction = ufbx_transform_direction(&inv, light_direction); - has_light_transform = true; - } - } - - if (ufbx_coordinate_axes_valid(uc->opts.target_camera_axes)) { - ufbx_matrix mat; // ufbxi_uninit - ufbx_coordinate_axes camera_axes = { - UFBX_COORDINATE_AXIS_POSITIVE_Z, - UFBX_COORDINATE_AXIS_POSITIVE_Y, - UFBX_COORDINATE_AXIS_NEGATIVE_X, - }; - if (ufbxi_axis_matrix(&mat, uc->opts.target_camera_axes, camera_axes)) { - camera_post_rotation = ufbx_matrix_to_transform(&mat).rotation; - has_camera_transform = true; - } - } - - ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) { - ufbx_light *light = *p_light; - light->local_direction.x = 0.0f; - light->local_direction.y = -1.0f; - light->local_direction.z = 0.0f; - } - - scene->metadata.space_conversion = conversion; - scene->metadata.geometry_transform_handling = uc->opts.geometry_transform_handling; - scene->metadata.inherit_mode_handling = uc->opts.inherit_mode_handling; - scene->metadata.pivot_handling = uc->opts.pivot_handling; - scene->metadata.handedness_conversion_axis = uc->opts.handedness_conversion_axis; - - ufbx_real root_scale = ufbxi_min3(root_transform.scale); - if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) { - scene->metadata.geometry_scale = root_scale; - scene->metadata.root_scale = 1.0f; - } else { - scene->metadata.geometry_scale = 1.0f; - scene->metadata.root_scale = root_scale; - } - scene->metadata.root_rotation = root_transform.rotation; - - ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) { - ufbx_node *node = *p_node; - - node->adjust_post_rotation = ufbx_identity_quat; - node->adjust_pre_rotation = ufbx_identity_quat; - node->adjust_pre_scale = 1.0f; - node->adjust_post_scale = 1.0f; - node->adjust_translation_scale = 1.0f; - - if (conversion == UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS) { - if (node->node_depth <= 1 && !node->is_root) { - node->adjust_pre_rotation = root_transform.rotation; - node->adjust_pre_scale = root_scale; - node->has_adjust_transform = true; - node->has_root_adjust_transform = true; - } - } else if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) { - if (!node->is_root) { - if (node->node_depth <= 1) { - node->adjust_pre_rotation = root_transform.rotation; - } - node->adjust_translation_scale = root_scale; - node->has_adjust_transform = true; - } - } - - if (node->parent) { - // We are not inheriting local scale, so propagate root scale manually and - // apply scale compensation if necessary. - ufbx_node *parent = node->parent; - if (parent->has_root_adjust_transform && node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { - node->adjust_post_scale *= root_scale; - node->has_adjust_transform = true; - node->has_root_adjust_transform = true; - } - if (parent->is_scale_compensate_parent && node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { - ufbx_vec3 scale = ufbxi_find_vec3(&parent->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f); - ufbx_real size = scale.x; - if (ufbx_fabs(scale.y - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.y; - if (ufbx_fabs(scale.z - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.z; - node->adjust_post_scale *= 1.0f / size; - node->has_adjust_transform = true; - } - } - - if (node->all_attribs.count == 1) { - if (has_light_transform && node->light) { - node->adjust_post_rotation = light_post_rotation; - node->light->local_direction = light_direction; - node->has_adjust_transform = true; - } - if (has_camera_transform && node->camera) { - node->adjust_post_rotation = camera_post_rotation; - node->camera->projection_axes = uc->opts.target_camera_axes; - node->has_adjust_transform = true; - } - } - } -} - -ufbxi_noinline static void ufbxi_update_scene(ufbx_scene *scene, bool initial, const ufbx_transform_override *transform_overrides, size_t num_transform_overrides) -{ - ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) { - ufbxi_update_node(*p_node, transform_overrides, num_transform_overrides); - } - - ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) { - ufbxi_update_light(*p_light); - } - - ufbxi_for_ptr_list(ufbx_camera, p_camera, scene->cameras) { - ufbxi_update_camera(scene, *p_camera); - } - - ufbxi_for_ptr_list(ufbx_bone, p_bone, scene->bones) { - ufbxi_update_bone(scene, *p_bone); - } - - ufbxi_for_ptr_list(ufbx_line_curve, p_line, scene->line_curves) { - ufbxi_update_line_curve(*p_line); - } - - if (initial) { - ufbxi_update_initial_clusters(scene); - - ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) { - ufbxi_update_pose(*p_pose); - } - } - - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { - ufbxi_update_skin_cluster(*p_cluster); - } - - ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, scene->blend_channels) { - ufbxi_update_blend_channel(*p_channel); - } - - ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) { - ufbxi_update_texture(*p_texture); - } - - ufbxi_propagate_main_textures(scene); - - ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) { - ufbxi_update_material(scene, *p_material); - } - - ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, scene->anim_stacks) { - ufbxi_update_anim_stack(scene, *p_stack); - } - - ufbxi_for_ptr_list(ufbx_display_layer, p_layer, scene->display_layers) { - ufbxi_update_display_layer(*p_layer); - } - - ufbxi_for_ptr_list(ufbx_constraint, p_constraint, scene->constraints) { - ufbxi_update_constraint(*p_constraint); - } - - ufbxi_update_anim(scene); -} - -static ufbxi_noinline void ufbxi_update_scene_metadata(ufbx_metadata *metadata) -{ - ufbx_props *props = &metadata->scene_props; - metadata->original_application.vendor = ufbx_find_string(props, "Original|ApplicationVendor", ufbx_empty_string); - metadata->original_application.name = ufbx_find_string(props, "Original|ApplicationName", ufbx_empty_string); - metadata->original_application.version = ufbx_find_string(props, "Original|ApplicationVersion", ufbx_empty_string); - metadata->latest_application.vendor = ufbx_find_string(props, "LastSaved|ApplicationVendor", ufbx_empty_string); - metadata->latest_application.name = ufbx_find_string(props, "LastSaved|ApplicationName", ufbx_empty_string); - metadata->latest_application.version = ufbx_find_string(props, "LastSaved|ApplicationVersion", ufbx_empty_string); -} - -static const ufbx_real ufbxi_pow10_targets[] = { - 0.0f, - (ufbx_real)1e-8, (ufbx_real)1e-7, (ufbx_real)1e-6, (ufbx_real)1e-5, - (ufbx_real)1e-4, (ufbx_real)1e-3, (ufbx_real)1e-2, (ufbx_real)1e-1, - (ufbx_real)1e+0, (ufbx_real)1e+1, (ufbx_real)1e+2, (ufbx_real)1e+3, - (ufbx_real)1e+4, (ufbx_real)1e+5, (ufbx_real)1e+6, (ufbx_real)1e+7, - (ufbx_real)1e+8, (ufbx_real)1e+9, -}; - -static ufbxi_noinline ufbx_real ufbxi_round_if_near(const ufbx_real *targets, size_t num_targets, ufbx_real value) -{ - for (size_t i = 0; i < num_targets; i++) { - double target = targets[i]; - double error = target * 9.5367431640625e-7; - if (error < 0.0) error = -error; - if (error < 7.52316384526264005e-37) error = 7.52316384526264005e-37; - if (value >= target - error && value <= target + error) { - return (ufbx_real)target; - } - } - return value; -} - -static ufbxi_noinline void ufbxi_update_scene_settings(ufbx_scene_settings *settings) -{ - ufbx_real unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_UnitScaleFactor, 1.0f); - ufbx_real original_unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_OriginalUnitScaleFactor, unit_scale_factor); - - settings->axes.up = ufbxi_find_axis(&settings->props, ufbxi_UpAxis, ufbxi_UpAxisSign); - settings->axes.front = ufbxi_find_axis(&settings->props, ufbxi_FrontAxis, ufbxi_FrontAxisSign); - settings->axes.right = ufbxi_find_axis(&settings->props, ufbxi_CoordAxis, ufbxi_CoordAxisSign); - settings->unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), unit_scale_factor * (ufbx_real)0.01); - settings->original_unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), original_unit_scale_factor * (ufbx_real)0.01); - settings->frames_per_second = ufbxi_find_real(&settings->props, ufbxi_CustomFrameRate, 24.0f); - settings->ambient_color = ufbxi_find_vec3(&settings->props, ufbxi_AmbientColor, 0.0f, 0.0f, 0.0f); - settings->original_axis_up = ufbxi_find_axis(&settings->props, ufbxi_OriginalUpAxis, ufbxi_OriginalUpAxisSign); - - ufbx_prop *default_camera = ufbxi_find_prop(&settings->props, ufbxi_DefaultCamera); - if (default_camera) { - settings->default_camera = default_camera->value_str; - } else { - settings->default_camera = ufbx_empty_string; - } - - settings->time_mode = (ufbx_time_mode)ufbxi_find_enum(&settings->props, ufbxi_TimeMode, UFBX_TIME_MODE_24_FPS, UFBX_TIME_MODE_59_94_FPS); - settings->time_protocol = (ufbx_time_protocol)ufbxi_find_enum(&settings->props, ufbxi_TimeProtocol, UFBX_TIME_PROTOCOL_DEFAULT, UFBX_TIME_PROTOCOL_DEFAULT); - settings->snap_mode = (ufbx_snap_mode)ufbxi_find_enum(&settings->props, ufbxi_SnapOnFrameMode, UFBX_SNAP_MODE_NONE, UFBX_SNAP_MODE_SNAP_AND_PLAY); - - if (settings->time_mode != UFBX_TIME_MODE_CUSTOM) { - settings->frames_per_second = ufbxi_time_mode_fps[settings->time_mode]; - } -} - -static ufbxi_noinline void ufbxi_update_scene_settings_obj(ufbxi_context *uc) -{ - ufbx_scene_settings *settings = &uc->scene.settings; - settings->original_unit_meters = settings->unit_meters = uc->opts.obj_unit_meters; - if (ufbx_coordinate_axes_valid(uc->opts.obj_axes)) { - settings->axes = uc->opts.obj_axes; - } else { - settings->axes.right = UFBX_COORDINATE_AXIS_UNKNOWN; - settings->axes.up = UFBX_COORDINATE_AXIS_UNKNOWN; - settings->axes.front = UFBX_COORDINATE_AXIS_UNKNOWN; - } -} - -// -- Geometry caches - -#if UFBXI_FEATURE_GEOMETRY_CACHE - -typedef struct { - ufbxi_refcount refcount; - ufbx_geometry_cache cache; - uint32_t magic; - bool owned_by_scene; - - ufbxi_buf string_buf; -} ufbxi_geometry_cache_imp; - -ufbx_static_assert(geometry_cache_imp_offset, offsetof(ufbxi_geometry_cache_imp, cache) == sizeof(ufbxi_refcount)); - -typedef struct { - ufbx_string name; - ufbx_string interpretation; - uint32_t sample_rate; - uint32_t start_time; - uint32_t end_time; - uint32_t current_time; - uint32_t consecutive_fails; - bool try_load; -} ufbxi_cache_tmp_channel; - -typedef enum { - UFBXI_CACHE_XML_TYPE_NONE, - UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME, - UFBXI_CACHE_XML_TYPE_SINGLE_FILE, -} ufbxi_cache_xml_type; - -typedef enum { - UFBXI_CACHE_XML_FORMAT_NONE, - UFBXI_CACHE_XML_FORMAT_MCC, - UFBXI_CACHE_XML_FORMAT_MCX, -} ufbxi_cache_xml_format; - -typedef struct { - ufbx_error error; - ufbx_string filename; - bool owned_by_scene; - bool ignore_if_not_found; - - ufbx_geometry_cache_opts opts; - - ufbxi_allocator *ator_tmp; - ufbxi_allocator ator_result; - - ufbxi_buf result; - ufbxi_buf tmp; - ufbxi_buf tmp_stack; - - ufbxi_cache_tmp_channel *channels; - size_t num_channels; - - // Temporary array - char *tmp_arr; - size_t tmp_arr_size; - - ufbxi_string_pool string_pool; - - ufbx_open_file_cb open_file_cb; - - double frames_per_second; - - ufbx_string stream_filename; - ufbx_stream stream; - - bool mc_for8; - - ufbx_string xml_filename; - uint32_t xml_ticks_per_frame; - ufbxi_cache_xml_type xml_type; - ufbxi_cache_xml_format xml_format; - - ufbx_string channel_name; - - char *name_buf; - size_t name_cap; - - uint64_t file_offset; - const char *pos, *pos_end; - - ufbx_geometry_cache cache; - ufbxi_geometry_cache_imp *imp; - - char buffer[128]; -} ufbxi_cache_context; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_read(ufbxi_cache_context *cc, void *dst, size_t size, bool allow_eof) -{ - size_t buffered = ufbxi_min_sz(ufbxi_to_size(cc->pos_end - cc->pos), size); - memcpy(dst, cc->pos, buffered); - cc->pos += buffered; - size -= buffered; - cc->file_offset += buffered; - if (size == 0) return 1; - dst = (char*)dst + buffered; - - if (size >= sizeof(cc->buffer)) { - size_t num_read = cc->stream.read_fn(cc->stream.user, dst, size); - ufbxi_check_err_msg(&cc->error, num_read <= size, "IO error"); - if (!allow_eof) { - ufbxi_check_err_msg(&cc->error, num_read == size, "Truncated file"); - } - cc->file_offset += num_read; - size -= num_read; - dst = (char*)dst + num_read; - } else { - size_t num_read = cc->stream.read_fn(cc->stream.user, cc->buffer, sizeof(cc->buffer)); - ufbxi_check_err_msg(&cc->error, num_read <= sizeof(cc->buffer), "IO error"); - if (!allow_eof) { - ufbxi_check_err_msg(&cc->error, num_read >= size, "Truncated file"); - } - cc->pos = cc->buffer; - cc->pos_end = cc->buffer + sizeof(cc->buffer); - - memcpy(dst, cc->pos, size); - cc->pos += size; - cc->file_offset += size; - - size_t num_written = ufbxi_min_sz(size, num_read); - size -= num_written; - dst = (char*)dst + num_written; - } - - if (size > 0) { - memset(dst, 0, size); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context *cc, uint64_t size) -{ - cc->file_offset += size; - - uint64_t buffered = ufbxi_min64((uint64_t)(cc->pos_end - cc->pos), size); - cc->pos += buffered; - size -= buffered; - - if (cc->stream.skip_fn) { - while (size >= UFBXI_MAX_SKIP_SIZE) { - size -= UFBXI_MAX_SKIP_SIZE; - ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file"); - - // Check that we can read at least one byte in case the file is broken - // and causes us to seek indefinitely forwards as `fseek()` does not - // report if we hit EOF... - char single_byte[1]; // ufbxi_uninit - size_t num_read = cc->stream.read_fn(cc->stream.user, single_byte, 1); - ufbxi_check_err_msg(&cc->error, num_read <= 1, "IO error"); - ufbxi_check_err_msg(&cc->error, num_read == 1, "Truncated file"); - } - - if (size > 0) { - ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, (size_t)size), "Truncated file"); - } - - } else { - char skip_buf[2048]; // ufbxi_uninit - while (size > 0) { - size_t to_skip = (size_t)ufbxi_min64(size, sizeof(skip_buf)); - size -= to_skip; - ufbxi_check_err_msg(&cc->error, cc->stream.read_fn(cc->stream.user, skip_buf, to_skip), "Truncated file"); - } - } - - return 1; -} - -#define ufbxi_cache_mc_tag(a,b,c,d) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u | (uint32_t)(d)) - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_tag(ufbxi_cache_context *cc, uint32_t *p_tag) -{ - char buf[4]; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, true)); - *p_tag = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; - if (*p_tag == ufbxi_cache_mc_tag('F','O','R','8')) { - cc->mc_for8 = true; - } - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u32(ufbxi_cache_context *cc, uint32_t *p_value) -{ - char buf[4]; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false)); - *p_value = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; - if (cc->mc_for8) { - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false)); - } - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u64(ufbxi_cache_context *cc, uint64_t *p_value) -{ - if (!cc->mc_for8) { - uint32_t v32; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &v32)); - *p_value = v32; - } else { - char buf[8]; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 8, false)); - uint32_t hi = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; - uint32_t lo = (uint32_t)(uint8_t)buf[4]<<24u | (uint32_t)(uint8_t)buf[5]<<16 | (uint32_t)(uint8_t)buf[6]<<8u | (uint32_t)(uint8_t)buf[7]; - *p_value = (uint64_t)hi << 32u | (uint64_t)lo; - } - return 1; -} - -static const uint8_t ufbxi_cache_data_format_size[] = { - 0, 4, 12, 8, 24, -}; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_mc(ufbxi_cache_context *cc) -{ - uint32_t version = 0, time_start = 0, time_end = 0; - uint32_t count = 0, time = 0; - char skip_buf[8]; // ufbxi_uninit - - for (;;) { - uint32_t tag; // ufbxi_uninit - uint64_t size; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_tag(cc, &tag)); - if (tag == 0) break; - - if (tag == ufbxi_cache_mc_tag('C','A','C','H') || tag == ufbxi_cache_mc_tag('M','Y','C','H')) { - continue; - } - if (cc->mc_for8) { - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, skip_buf, 4, false)); - } - - ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u64(cc, &size)); - uint64_t begin = cc->file_offset; - - size_t alignment = cc->mc_for8 ? 8 : 4; - - ufbx_cache_data_format format = UFBX_CACHE_DATA_FORMAT_UNKNOWN; - switch (tag) { - case ufbxi_cache_mc_tag('F','O','R','4'): cc->mc_for8 = false; break; - case ufbxi_cache_mc_tag('F','O','R','8'): cc->mc_for8 = true; break; - case ufbxi_cache_mc_tag('V','R','S','N'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &version)); break; - case ufbxi_cache_mc_tag('S','T','I','M'): - ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_start)); - time = time_start; - break; - case ufbxi_cache_mc_tag('E','T','I','M'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_end)); break; - case ufbxi_cache_mc_tag('T','I','M','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time)); break; - case ufbxi_cache_mc_tag('C','H','N','M'): { - ufbxi_check_err(&cc->error, size > 0 && size < SIZE_MAX); - size_t length = (size_t)size - 1; - size_t padded_length = ((size_t)size + alignment - 1) & ~(alignment - 1); - ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->name_buf, &cc->name_cap, padded_length)); - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, cc->name_buf, padded_length, false)); - cc->channel_name.data = cc->name_buf; - cc->channel_name.length = length; - ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->channel_name, false)); - } break; - case ufbxi_cache_mc_tag('S','I','Z','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &count)); break; - case ufbxi_cache_mc_tag('F','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT; break; - case ufbxi_cache_mc_tag('D','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE; break; - case ufbxi_cache_mc_tag('F','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_FLOAT; break; - case ufbxi_cache_mc_tag('D','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break; - case ufbxi_cache_mc_tag('D','B','L','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break; - default: ufbxi_fail_err(&cc->error, "Unknown tag"); - } - - if (format != UFBX_CACHE_DATA_FORMAT_UNKNOWN) { - ufbx_cache_frame *frame = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, 1); - ufbxi_check_err(&cc->error, frame); - - uint32_t elem_size = ufbxi_cache_data_format_size[format]; - uint64_t total_size = (uint64_t)elem_size * (uint64_t)count; - ufbxi_check_err(&cc->error, size >= elem_size * count); - - frame->channel = cc->channel_name; - frame->time = (double)time * (1.0/6000.0); - frame->filename = cc->stream_filename; - frame->data_format = format; - frame->data_encoding = UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN; - frame->data_offset = cc->file_offset; - frame->data_count = count; - frame->data_element_bytes = elem_size; - frame->data_total_bytes = total_size; - frame->file_format = UFBX_CACHE_FILE_FORMAT_MC; - - uint64_t end = begin + ((size + alignment - 1) & ~(uint64_t)(alignment - 1)); - ufbxi_check_err(&cc->error, end >= cc->file_offset); - uint64_t left = end - cc->file_offset; - ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, left)); - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_pc2(ufbxi_cache_context *cc) -{ - char header[32]; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, header, sizeof(header), false)); - - uint32_t version = ufbxi_read_u32(header + 12); - uint32_t num_points = ufbxi_read_u32(header + 16); - double start_frame = ufbxi_read_f32(header + 20); - double frames_per_sample = ufbxi_read_f32(header + 24); - uint32_t num_samples = ufbxi_read_u32(header + 28); - - (void)version; - - ufbx_cache_frame *frames = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, num_samples); - ufbxi_check_err(&cc->error, frames); - - uint64_t total_points = (uint64_t)num_points * (uint64_t)num_samples; - ufbxi_check_err(&cc->error, total_points < UINT64_MAX / 12); - - uint64_t offset = cc->file_offset; - - // Skip almost to the end of the data and try to read one byte as there's - // nothing after the data so we can't detect EOF.. - if (total_points > 0) { - char last_byte[1]; // ufbxi_uninit - ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, total_points * 12 - 1)); - ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, last_byte, 1, false)); - } - - for (uint32_t i = 0; i < num_samples; i++) { - ufbx_cache_frame *frame = &frames[i]; - - double sample_frame = start_frame + (double)i * frames_per_sample; - frame->channel = cc->channel_name; - frame->time = sample_frame / cc->frames_per_second; - frame->filename = cc->stream_filename; - frame->data_format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT; - frame->data_encoding = UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN; - frame->data_offset = offset; - frame->data_count = num_points; - frame->data_element_bytes = 12; - frame->data_total_bytes = num_points * 12; - frame->file_format = UFBX_CACHE_FILE_FORMAT_PC2; - offset += num_points * 12; - } - - return 1; -} - -static ufbxi_noinline bool ufbxi_tmp_channel_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_cache_tmp_channel *a = (const ufbxi_cache_tmp_channel *)va, *b = (const ufbxi_cache_tmp_channel *)vb; - return ufbxi_str_less(a->name, b->name); -} - -static ufbxi_noinline int ufbxi_cache_sort_tmp_channels(ufbxi_cache_context *cc, ufbxi_cache_tmp_channel *channels, size_t count) -{ - ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbxi_cache_tmp_channel))); - ufbxi_stable_sort(sizeof(ufbxi_cache_tmp_channel), 16, channels, cc->tmp_arr, count, &ufbxi_tmp_channel_less, NULL); - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml_imp(ufbxi_cache_context *cc, ufbxi_xml_document *doc) -{ - cc->xml_ticks_per_frame = 250; - cc->xml_filename = cc->stream_filename; - - ufbxi_xml_tag *tag_root = ufbxi_xml_find_child(doc->root, "Autodesk_Cache_File"); - if (tag_root) { - ufbxi_xml_tag *tag_type = ufbxi_xml_find_child(tag_root, "cacheType"); - ufbxi_xml_tag *tag_fps = ufbxi_xml_find_child(tag_root, "cacheTimePerFrame"); - ufbxi_xml_tag *tag_channels = ufbxi_xml_find_child(tag_root, "Channels"); - - size_t num_extra = 0; - ufbxi_for(ufbxi_xml_tag, tag, tag_root->children, tag_root->num_children) { - if (tag->num_children != 1) continue; - if (strcmp(tag->name.data, "extra") != 0) continue; - ufbx_string *extra = ufbxi_push(&cc->tmp_stack, ufbx_string, 1); - ufbxi_check_err(&cc->error, extra); - *extra = tag->children[0].text; - ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, extra, false)); - num_extra++; - } - cc->cache.extra_info.count = num_extra; - cc->cache.extra_info.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_string, num_extra); - ufbxi_check_err(&cc->error, cc->cache.extra_info.data); - - if (tag_type) { - ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag_type, "Type"); - ufbxi_xml_attrib *format = ufbxi_xml_find_attrib(tag_type, "Format"); - if (type) { - if (!strcmp(type->value.data, "OneFilePerFrame")) { - cc->xml_type = UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME; - } else if (!strcmp(type->value.data, "OneFile")) { - cc->xml_type = UFBXI_CACHE_XML_TYPE_SINGLE_FILE; - } - } - if (format) { - if (!strcmp(format->value.data, "mcc")) { - cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCC; - } else if (!strcmp(format->value.data, "mcx")) { - cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCX; - } - } - } - - if (tag_fps) { - ufbxi_xml_attrib *fps = ufbxi_xml_find_attrib(tag_fps, "TimePerFrame"); - if (fps) { - uint32_t value = ufbxi_parse_uint32_radix(fps->value.data, 10); - if (value > 0) { - cc->xml_ticks_per_frame = value; - } - } - } - - if (tag_channels) { - cc->channels = ufbxi_push_zero(&cc->tmp, ufbxi_cache_tmp_channel, tag_channels->num_children); - ufbxi_check_err(&cc->error, cc->channels); - - ufbxi_for(ufbxi_xml_tag, tag, tag_channels->children, tag_channels->num_children) { - ufbxi_xml_attrib *name = ufbxi_xml_find_attrib(tag, "ChannelName"); - ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag, "ChannelType"); - ufbxi_xml_attrib *interpretation = ufbxi_xml_find_attrib(tag, "ChannelInterpretation"); - if (!(name && type && interpretation)) continue; - - ufbxi_cache_tmp_channel *channel = &cc->channels[cc->num_channels++]; - channel->name = name->value; - channel->interpretation = interpretation->value; - ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->name, false)); - ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->interpretation, false)); - - ufbxi_xml_attrib *sampling_rate = ufbxi_xml_find_attrib(tag, "SamplingRate"); - ufbxi_xml_attrib *start_time = ufbxi_xml_find_attrib(tag, "StartTime"); - ufbxi_xml_attrib *end_time = ufbxi_xml_find_attrib(tag, "EndTime"); - if (sampling_rate && start_time && end_time) { - channel->sample_rate = ufbxi_parse_uint32_radix(sampling_rate->value.data, 10); - channel->start_time = ufbxi_parse_uint32_radix(start_time->value.data, 10); - channel->end_time = ufbxi_parse_uint32_radix(end_time->value.data, 10); - channel->current_time = channel->start_time; - channel->try_load = true; - } - } - } - } - - ufbxi_check_err(&cc->error, ufbxi_cache_sort_tmp_channels(cc, cc->channels, cc->num_channels)); - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml(ufbxi_cache_context *cc) -{ - ufbxi_xml_load_opts opts = { 0 }; - opts.ator = cc->ator_tmp; - opts.read_fn = cc->stream.read_fn; - opts.read_user = cc->stream.user; - opts.prefix = cc->pos; - opts.prefix_length = ufbxi_to_size(cc->pos_end - cc->pos); - ufbxi_xml_document *doc = ufbxi_load_xml(&opts, &cc->error); - ufbxi_check_err(&cc->error, doc); - - int xml_ok = ufbxi_cache_load_xml_imp(cc, doc); - ufbxi_free_xml(doc); - ufbxi_check_err(&cc->error, xml_ok); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_file(ufbxi_cache_context *cc, ufbx_string filename) -{ - cc->stream_filename = filename; - ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->stream_filename, false)); - - // Assume all files have at least 16 bytes of header - size_t magic_len = cc->stream.read_fn(cc->stream.user, cc->buffer, 16); - ufbxi_check_err_msg(&cc->error, magic_len <= 16, "IO error"); - ufbxi_check_err_msg(&cc->error, magic_len == 16, "Truncated file"); - cc->pos = cc->buffer; - cc->pos_end = cc->buffer + 16; - - cc->file_offset = 0; - - if (!memcmp(cc->buffer, "POINTCACHE2", 11)) { - ufbxi_check_err(&cc->error, ufbxi_cache_load_pc2(cc)); - } else if (!memcmp(cc->buffer, "FOR4", 4) || !memcmp(cc->buffer, "FOR8", 4)) { - ufbxi_check_err(&cc->error, ufbxi_cache_load_mc(cc)); - } else { - ufbxi_check_err(&cc->error, ufbxi_cache_load_xml(cc)); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_try_open_file(ufbxi_cache_context *cc, ufbx_string filename, const ufbx_blob *original_filename, bool *p_found) -{ - memset(&cc->stream, 0, sizeof(cc->stream)); - ufbxi_regression_assert(strlen(filename.data) == filename.length); - if (!ufbxi_open_file(&cc->open_file_cb, &cc->stream, filename.data, filename.length, original_filename, cc->ator_tmp, UFBX_OPEN_FILE_GEOMETRY_CACHE)) { - return 1; - } - - int ok = ufbxi_cache_load_file(cc, filename); - *p_found = true; - - if (cc->stream.close_fn) { - cc->stream.close_fn(cc->stream.user); - } - - return ok; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_frame_files(ufbxi_cache_context *cc) -{ - if (cc->xml_filename.length == 0) return 1; - - const char *extension = NULL; - switch (cc->xml_format) { - case UFBXI_CACHE_XML_FORMAT_MCC: extension = "mc"; break; - case UFBXI_CACHE_XML_FORMAT_MCX: extension = "mcx"; break; - default: return 1; - } - - // Ensure worst case space for `path/filenameFrame123Tick456.mcx` - size_t name_buf_len = cc->xml_filename.length + 64; - char *name_buf = ufbxi_push(&cc->tmp, char, name_buf_len); - ufbxi_check_err(&cc->error, name_buf); - - // Find the prefix before `.xml` - size_t prefix_len = cc->xml_filename.length; - for (size_t i = prefix_len; i > 0; --i) { - if (cc->xml_filename.data[i - 1] == '.') { - prefix_len = i - 1; - break; - } - } - memcpy(name_buf, cc->xml_filename.data, prefix_len); - - char *suffix_data = name_buf + prefix_len; - size_t suffix_len = name_buf_len - prefix_len; - - ufbx_string filename; - filename.data = name_buf; - - if (cc->xml_type == UFBXI_CACHE_XML_TYPE_SINGLE_FILE) { - filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, ".%s", extension); - bool found = false; - ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found)); - } else if (cc->xml_type == UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME) { - uint32_t lowest_time = 0; - for (;;) { - // Find the first `time >= lowest_time` value that has data in some channel - uint32_t time = UINT32_MAX; - ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) { - if (!chan->try_load || chan->consecutive_fails > 10) continue; - uint32_t sample_rate = chan->sample_rate ? chan->sample_rate : cc->xml_ticks_per_frame; - if (chan->current_time < lowest_time) { - uint32_t delta = (lowest_time - chan->current_time - 1) / sample_rate; - chan->current_time += delta * sample_rate; - if (UINT32_MAX - chan->current_time >= sample_rate) { - chan->current_time += sample_rate; - } else { - chan->try_load = false; - continue; - } - } - if (chan->current_time <= chan->end_time) { - time = ufbxi_min32(time, chan->current_time); - } - } - if (time == UINT32_MAX) break; - - // Try to load a file at the specified frame/tick - uint32_t frame = time / cc->xml_ticks_per_frame; - uint32_t tick = time % cc->xml_ticks_per_frame; - if (tick == 0) { - filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%u.%s", frame, extension); - } else { - filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%uTick%u.%s", frame, tick, extension); - } - bool found = false; - ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found)); - - // Update channel status - ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) { - if (chan->current_time == time) { - chan->consecutive_fails = found ? 0 : chan->consecutive_fails + 1; - } - } - - lowest_time = time + 1; - } - } - - return 1; -} - -static ufbxi_noinline bool ufbxi_cmp_cache_frame_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_cache_frame *a = (const ufbx_cache_frame *)va, *b = (const ufbx_cache_frame *)vb; - if (a->channel.data != b->channel.data) { - // Channel names should be interned - ufbxi_regression_assert(!ufbxi_str_equal(a->channel, b->channel)); - return ufbxi_str_less(a->channel, b->channel); - } - return a->time < b->time; -} - -static ufbxi_noinline int ufbxi_cache_sort_frames(ufbxi_cache_context *cc, ufbx_cache_frame *frames, size_t count) -{ - ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbx_cache_frame))); - ufbxi_stable_sort(sizeof(ufbx_cache_frame), 16, frames, cc->tmp_arr, count, &ufbxi_cmp_cache_frame_less, NULL); - return 1; -} - -typedef struct { - ufbx_cache_interpretation interpretation; - const char *pattern; -} ufbxi_cache_interpretation_name; - -static const ufbxi_cache_interpretation_name ufbxi_cache_interpretation_names[] = { - { UFBX_CACHE_INTERPRETATION_POINTS, "\\cpoints?" }, - { UFBX_CACHE_INTERPRETATION_VERTEX_POSITION, "\\cpositions?" }, - { UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL, "\\cnormals?" }, -}; - -static ufbxi_noinline int ufbxi_cache_setup_channels(ufbxi_cache_context *cc) -{ - ufbxi_cache_tmp_channel *tmp_chan = cc->channels, *tmp_end = ufbxi_add_ptr(tmp_chan, cc->num_channels); - - size_t begin = 0, num_channels = 0; - while (begin < cc->cache.frames.count) { - ufbx_cache_frame *frame = &cc->cache.frames.data[begin]; - size_t end = begin + 1; - while (end < cc->cache.frames.count && cc->cache.frames.data[end].channel.data == frame->channel.data) { - end++; - } - - ufbx_cache_channel *chan = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_channel, 1); - ufbxi_check_err(&cc->error, chan); - - chan->name = frame->channel; - chan->interpretation_name = ufbx_empty_string; - chan->frames.data = frame; - chan->frames.count = end - begin; - - while (tmp_chan < tmp_end && ufbxi_str_less(tmp_chan->name, chan->name)) { - tmp_chan++; - } - if (tmp_chan < tmp_end && ufbxi_str_equal(tmp_chan->name, chan->name)) { - chan->interpretation_name = tmp_chan->interpretation; - } - - if (frame->file_format == UFBX_CACHE_FILE_FORMAT_PC2) { - chan->interpretation = UFBX_CACHE_INTERPRETATION_VERTEX_POSITION; - } else { - ufbxi_for(const ufbxi_cache_interpretation_name, name, ufbxi_cache_interpretation_names, ufbxi_arraycount(ufbxi_cache_interpretation_names)) { - if (ufbxi_match(&chan->interpretation_name, name->pattern)) { - chan->interpretation = name->interpretation; - break; - } - } - } - - ufbx_mirror_axis mirror_axis = UFBX_MIRROR_AXIS_NONE; - ufbx_real scale_factor = 1.0f; - if (chan->interpretation != UFBX_CACHE_INTERPRETATION_UNKNOWN) { - mirror_axis = cc->opts.mirror_axis; - if (cc->opts.use_scale_factor) { - scale_factor = cc->opts.scale_factor; - } - } - chan->mirror_axis = mirror_axis; - chan->scale_factor = scale_factor; - ufbxi_for_list(ufbx_cache_frame, f, chan->frames) { - f->mirror_axis = mirror_axis; - f->scale_factor = scale_factor; - } - - num_channels++; - begin = end; - } - - cc->cache.channels.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_channel, num_channels); - ufbxi_check_err(&cc->error, cc->cache.channels.data); - cc->cache.channels.count = num_channels; - - return 1; -} - - -static ufbxi_noinline int ufbxi_cache_load_imp(ufbxi_cache_context *cc, ufbx_string filename) -{ - cc->tmp.ator = cc->ator_tmp; - cc->tmp_stack.ator = cc->ator_tmp; - - cc->channel_name.data = ufbxi_empty_char; - - if (!cc->open_file_cb.fn) { - cc->open_file_cb.fn = ufbx_default_open_file; - } - - // Make sure the filename we pass to `open_file_fn()` is NULL-terminated - char *filename_data = ufbxi_push(&cc->tmp, char, filename.length + 1); - ufbxi_check_err(&cc->error, filename_data); - memcpy(filename_data, filename.data, filename.length); - filename_data[filename.length] = '\0'; - ufbx_string filename_copy = { filename_data, filename.length }; - - // TODO: NULL termination! - bool found = false; - ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename_copy, NULL, &found)); - if (!found) { - ufbxi_set_err_info(&cc->error, filename.data, filename.length); - ufbxi_fail_err_msg(&cc->error, "open_file_fn()", "File not found"); - } - - cc->cache.root_filename = cc->stream_filename; - - ufbxi_check_err(&cc->error, ufbxi_cache_load_frame_files(cc)); - - size_t num_frames = cc->tmp_stack.num_items; - cc->cache.frames.count = num_frames; - cc->cache.frames.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_frame, num_frames); - ufbxi_check_err(&cc->error, cc->cache.frames.data); - - ufbxi_check_err(&cc->error, ufbxi_cache_sort_frames(cc, cc->cache.frames.data, cc->cache.frames.count)); - ufbxi_check_err(&cc->error, ufbxi_cache_setup_channels(cc)); - - // Must be last allocation! - cc->imp = ufbxi_push(&cc->result, ufbxi_geometry_cache_imp, 1); - ufbxi_check_err(&cc->error, cc->imp); - - ufbxi_init_ref(&cc->imp->refcount, UFBXI_CACHE_IMP_MAGIC, NULL); - - cc->imp->cache = cc->cache; - cc->imp->magic = UFBXI_CACHE_IMP_MAGIC; - cc->imp->owned_by_scene = cc->owned_by_scene; - cc->imp->refcount.ator = cc->ator_result; - cc->imp->refcount.buf = cc->result; - cc->imp->refcount.buf.ator = &cc->imp->refcount.ator; - cc->imp->string_buf = cc->string_pool.buf; - cc->imp->string_buf.ator = &cc->imp->refcount.ator; - - return 1; -} - -ufbxi_noinline static ufbx_geometry_cache *ufbxi_cache_load(ufbxi_cache_context *cc, ufbx_string filename) -{ - int ok = ufbxi_cache_load_imp(cc, filename); - - ufbxi_buf_free(&cc->tmp); - ufbxi_buf_free(&cc->tmp_stack); - ufbxi_free(cc->ator_tmp, char, cc->name_buf, cc->name_cap); - ufbxi_free(cc->ator_tmp, char, cc->tmp_arr, cc->tmp_arr_size); - if (!cc->owned_by_scene) { - ufbxi_string_pool_temp_free(&cc->string_pool); - ufbxi_free_ator(cc->ator_tmp); - } - - if (ok) { - return &cc->imp->cache; - } else { - ufbxi_fix_error_type(&cc->error, "Failed to load geometry cache", NULL); - if (!cc->owned_by_scene) { - ufbxi_buf_free(&cc->string_pool.buf); - ufbxi_free_ator(&cc->ator_result); - } - return NULL; - } -} - -ufbxi_noinline static ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error) -{ - ufbx_geometry_cache_opts opts; // ufbxi_uninit - if (user_opts) { - opts = *user_opts; - } else { - memset(&opts, 0, sizeof(opts)); - } - - ufbxi_cache_context cc = { UFBX_ERROR_NONE }; - ufbxi_allocator ator_tmp = { 0 }; - ufbxi_init_ator(&cc.error, &ator_tmp, &opts.temp_allocator, "temp"); - ufbxi_init_ator(&cc.error, &cc.ator_result, &opts.result_allocator, "result"); - cc.ator_tmp = &ator_tmp; - - cc.opts = opts; - - cc.open_file_cb = opts.open_file_cb; - - cc.string_pool.error = &cc.error; - ufbxi_map_init(&cc.string_pool.map, cc.ator_tmp, &ufbxi_map_cmp_string, NULL); - cc.string_pool.buf.ator = &cc.ator_result; - cc.string_pool.buf.unordered = true; - cc.string_pool.initial_size = 64; - cc.result.ator = &cc.ator_result; - - cc.frames_per_second = opts.frames_per_second > 0.0 ? opts.frames_per_second : 30.0; - - ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, filename); - if (p_error) { - if (cache) { - ufbxi_clear_error(p_error); - } else { - *p_error = cc.error; - } - } - return cache; -} - -static ufbxi_noinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp) -{ - ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); - ufbxi_buf_free(&imp->string_buf); -} - -#else - -typedef struct { - ufbxi_refcount refcount; - uint32_t magic; - bool owned_by_scene; -} ufbxi_geometry_cache_imp; - -static ufbxi_noinline ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error) -{ - if (p_error) { - memset(p_error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(p_error, "UFBX_ENABLE_GEOMETRY_CACHE"); - ufbxi_report_err_msg(p_error, "UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled"); - } - return NULL; -} - -static ufbxi_forceinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp) -{ -} - -#endif - -// -- External files - -typedef enum { - UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE, -} ufbxi_external_file_type; - -typedef struct { - ufbxi_external_file_type type; - ufbx_string filename; - ufbx_string absolute_filename; - size_t index; - void *data; - size_t data_size; -} ufbxi_external_file; - -static bool ufbxi_less_external_file(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_external_file *a = (const ufbxi_external_file*)va, *b = (const ufbxi_external_file*)vb; - if (a->type != b->type) return a->type < b->type; - int cmp = ufbxi_str_cmp(a->filename, b->filename); - if (cmp != 0) return cmp < 0; - if (a->index != b->index) return a->index < b->index; - return false; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_cache(ufbxi_context *uc, ufbxi_external_file *file) -{ -#if UFBXI_FEATURE_GEOMETRY_CACHE - ufbxi_cache_context cc = { UFBX_ERROR_NONE }; - cc.owned_by_scene = true; - - cc.open_file_cb = uc->opts.open_file_cb; - cc.frames_per_second = uc->scene.settings.frames_per_second; - - // Temporarily "borrow" allocators for the geometry cache - cc.ator_tmp = &uc->ator_tmp; - cc.string_pool = uc->string_pool; - cc.result = uc->result; - - cc.opts.mirror_axis = uc->mirror_axis; - cc.opts.use_scale_factor = true; - cc.opts.scale_factor = uc->scene.metadata.geometry_scale; - - ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, file->filename); - if (!cache) { - if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) { - memset(&cc.error, 0, sizeof(cc.error)); - cache = ufbxi_cache_load(&cc, file->absolute_filename); - } - } - - // Return the "borrowed" allocators - uc->string_pool = cc.string_pool; - uc->result = cc.result; - - if (!cache) { - if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) { - if (uc->opts.ignore_missing_external_files) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Failed to open geometry cache: %s", file->filename.data)); - return 1; - } else { - cc.error.type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND; - cc.error.description.data = "External file not found"; - cc.error.description.length = strlen("External file not found"); - } - } - - uc->error = cc.error; - return 0; - } - - file->data = cache; - return 1; -#else - if (uc->opts.ignore_missing_external_files) return 1; - - ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_GEOMETRY_CACHE"); - ufbxi_fail_msg("UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled"); -#endif -} - -static ufbxi_noinline ufbxi_external_file *ufbxi_find_external_file(ufbxi_external_file *files, size_t num_files, ufbxi_external_file_type type, const char *name) -{ - size_t ix = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbxi_external_file, 32, &ix, files, 0, num_files, - ( type != a->type ? type < a->type : strcmp(a->filename.data, name) < 0 ), - ( a->type == type && a->filename.data == name )); - return ix != SIZE_MAX ? &files[ix] : NULL; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_files(ufbxi_context *uc) -{ - size_t num_files = 0; - - // Gather external files to deduplicate them - ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { - ufbx_cache_file *cache = *p_cache; - if (cache->filename.length > 0) { - ufbxi_external_file *file = ufbxi_push_zero(&uc->tmp_stack, ufbxi_external_file, 1); - ufbxi_check(file); - file->index = num_files++; - file->type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE; - file->filename = cache->filename; - file->absolute_filename = cache->absolute_filename; - } - } - - // Sort and load the external files - ufbxi_external_file *files = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_external_file, num_files); - ufbxi_check(files); - ufbxi_unstable_sort(files, num_files, sizeof(ufbxi_external_file), &ufbxi_less_external_file, NULL); - - ufbxi_external_file_type prev_type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE; - const char *prev_name = NULL; - ufbxi_for(ufbxi_external_file, file, files, num_files) { - if (file->filename.data == prev_name && file->type == prev_type) continue; - if (file->type == UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE) { - ufbxi_check(ufbxi_load_external_cache(uc, file)); - } - prev_name = file->filename.data; - prev_type = file->type; - } - - // Patch the loaded files - ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) { - ufbx_cache_file *cache = *p_cache; - ufbxi_external_file *file = ufbxi_find_external_file(files, num_files, - UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE, cache->filename.data); - if (file && file->data) { - cache->external_cache = (ufbx_geometry_cache*)file->data; - } - } - - // Patch the geometry deformers - ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) { - ufbx_cache_deformer *deformer = *p_deformer; - if (!deformer->file || !deformer->file->external_cache) continue; - ufbx_geometry_cache *cache = deformer->file->external_cache; - deformer->external_cache = cache; - - // HACK: It seems like channels may be connected even if the name is wrong - // and they work when exporting from Marvelous to Maya... - if (cache->channels.count == 1) { - deformer->external_channel = &cache->channels.data[0]; - } else { - ufbx_string channel = deformer->channel; - size_t ix = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_cache_channel, 16, &ix, cache->channels.data, 0, cache->channels.count, - ( ufbxi_str_less(a->name, channel) ), ( a->name.data == channel.data )); - if (ix != SIZE_MAX) { - deformer->external_channel = &cache->channels.data[ix]; - } - } - } - - return 1; -} - -static ufbxi_noinline void ufbxi_transform_to_axes(ufbxi_context *uc, ufbx_coordinate_axes dst_axes) -{ - if (!ufbx_coordinate_axes_valid(uc->scene.settings.axes)) return; - if (!ufbxi_axis_matrix(&uc->axis_matrix, uc->scene.settings.axes, dst_axes)) return; - - if (ufbx_matrix_determinant(&uc->axis_matrix) < 0.0f) { - if (uc->opts.handedness_conversion_axis != UFBX_MIRROR_AXIS_NONE) { - ufbx_mirror_axis mirror_axis = uc->opts.handedness_conversion_axis; - uc->mirror_axis = mirror_axis; - uc->scene.metadata.mirror_axis = uc->mirror_axis; - - ufbxi_mirror_matrix_dst(&uc->axis_matrix, uc->mirror_axis); - ufbxi_dev_assert(ufbx_matrix_determinant(&uc->axis_matrix) >= 0.0f); - - ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) { - ufbx_node *node = *p_node; - if (!node->is_root) { - node->adjust_mirror_axis = mirror_axis; - } - } - } - } - - if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) { - ufbx_matrix axis_mat = uc->axis_matrix; - if (!ufbxi_is_transform_identity(&uc->scene.root_node->local_transform)) { - ufbx_matrix root_mat = ufbx_transform_to_matrix(&uc->scene.root_node->local_transform); - axis_mat = ufbx_matrix_mul(&root_mat, &axis_mat); - } - - ufbxi_mirror_matrix(&axis_mat, uc->mirror_axis); - - uc->scene.root_node->local_transform = ufbx_matrix_to_transform(&axis_mat); - uc->scene.root_node->node_to_parent = axis_mat; - } -} - -static ufbxi_noinline int ufbxi_scale_units(ufbxi_context *uc, ufbx_real target_meters) -{ - if (uc->scene.settings.unit_meters <= 0.0f) return 1; - target_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), target_meters); - - ufbx_real ratio = uc->scene.settings.unit_meters / target_meters; - ratio = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), ratio); - if (ratio == 1.0f) return 1; - - uc->unit_scale = ratio; - - if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) { - uc->scene.root_node->local_transform.scale.x *= ratio; - uc->scene.root_node->local_transform.scale.y *= ratio; - uc->scene.root_node->local_transform.scale.z *= ratio; - uc->scene.root_node->node_to_parent.m00 *= ratio; - uc->scene.root_node->node_to_parent.m01 *= ratio; - uc->scene.root_node->node_to_parent.m02 *= ratio; - uc->scene.root_node->node_to_parent.m10 *= ratio; - uc->scene.root_node->node_to_parent.m11 *= ratio; - uc->scene.root_node->node_to_parent.m12 *= ratio; - uc->scene.root_node->node_to_parent.m20 *= ratio; - uc->scene.root_node->node_to_parent.m21 *= ratio; - uc->scene.root_node->node_to_parent.m22 *= ratio; - } - - return 1; -} - -// -- Curve evaluation - -static ufbxi_forceinline double ufbxi_find_cubic_bezier_t(double p1, double p2, double x0) -{ - double p1_3 = p1 * 3.0, p2_3 = p2 * 3.0; - double a = p1_3 - p2_3 + 1.0; - double b = p2_3 - p1_3 - p1_3; - double c = p1_3; - - double a_3 = 3.0*a, b_2 = 2.0*b; - double t = x0; - double x1, t2, t3; - - // Manually unroll three iterations of Newton-Rhapson, this is enough - // for most tangents - t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; - t -= x1 / (a_3*t2 + b_2*t + c); - - t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; - t -= x1 / (a_3*t2 + b_2*t + c); - - t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; - t -= x1 / (a_3*t2 + b_2*t + c); - - // 4 ULP from 1.0 - const double eps = 8.881784197001252e-16; - if (ufbx_fabs(x1) <= eps) return t; - - // Perform more iterations until we reach desired accuracy - for (size_t i = 0; i < 4; i++) { - - t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; - t -= x1 / (a_3*t2 + b_2*t + c); - - t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0; - t -= x1 / (a_3*t2 + b_2*t + c); - - if (ufbx_fabs(x1) <= eps) return t; - } - - return t; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_skinning(ufbx_scene *scene, ufbx_error *error, ufbxi_buf *buf_result, ufbxi_buf *buf_tmp, - double time, bool load_caches, ufbx_geometry_cache_data_opts *cache_opts) -{ -#if UFBXI_FEATURE_SKINNING_EVALUATION - size_t max_skinned_indices = 0; - - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) { - ufbx_mesh *mesh = *p_mesh; - if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue; - max_skinned_indices = ufbxi_max_sz(max_skinned_indices, mesh->num_indices); - } - - ufbx_topo_edge *topo = ufbxi_push(buf_tmp, ufbx_topo_edge, max_skinned_indices); - ufbxi_check_err(error, topo); - - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) { - ufbx_mesh *mesh = *p_mesh; - if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue; - if (mesh->num_vertices == 0) continue; - - size_t num_vertices = mesh->num_vertices; - ufbx_vec3 *result_pos = ufbxi_push(buf_result, ufbx_vec3, num_vertices + 1); - ufbxi_check_err(error, result_pos); - - result_pos[0] = ufbx_zero_vec3; - result_pos++; - - bool cached_position = false, cached_normals = false; - if (load_caches && mesh->cache_deformers.count > 0) { - ufbxi_for_ptr_list(ufbx_cache_deformer, p_cache, mesh->cache_deformers) { - ufbx_cache_channel *channel = (*p_cache)->external_channel; - if (!channel) continue; - - if ((channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_POSITION || channel->interpretation == UFBX_CACHE_INTERPRETATION_POINTS) && !cached_position) { - size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, result_pos, num_vertices, cache_opts); - if (num_read == num_vertices) { - mesh->skinned_is_local = true; - cached_position = true; - } - } else if (channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL && !cached_normals) { - // TODO: Is this right at all? - size_t num_normals = mesh->skinned_normal.values.count; - ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1); - ufbxi_check_err(error, normal_data); - normal_data[0] = ufbx_zero_vec3; - normal_data++; - - size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, normal_data, num_normals, cache_opts); - if (num_read == num_normals) { - cached_normals = true; - mesh->skinned_normal.values.data = normal_data; - } - } - } - } - - if (!cached_position) { - memcpy(result_pos, mesh->vertices.data, num_vertices * sizeof(ufbx_vec3)); - - ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, mesh->blend_deformers) { - ufbx_add_blend_vertex_offsets(*p_blend, result_pos, num_vertices, 1.0f); - } - - // TODO: What should we do about multiple skins?? - if (mesh->skin_deformers.count > 0) { - ufbx_matrix *fallback = mesh->instances.count > 0 ? &mesh->instances.data[0]->geometry_to_world : NULL; - ufbx_skin_deformer *skin = mesh->skin_deformers.data[0]; - for (size_t i = 0; i < num_vertices; i++) { - ufbx_matrix mat = ufbx_get_skin_vertex_matrix(skin, i, fallback); - result_pos[i] = ufbx_transform_position(&mat, result_pos[i]); - } - - mesh->skinned_is_local = false; - } - } - - mesh->skinned_position.values.data = result_pos; - - if (!cached_normals) { - size_t num_indices = mesh->num_indices; - uint32_t *normal_indices = ufbxi_push(buf_result, uint32_t, num_indices); - ufbxi_check_err(error, normal_indices); - - ufbx_compute_topology(mesh, topo, num_indices); - size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false); - - if (num_normals == mesh->num_vertices) { - mesh->skinned_normal.unique_per_vertex = true; - } - - ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1); - ufbxi_check_err(error, normal_data); - - normal_data[0] = ufbx_zero_vec3; - normal_data++; - - ufbx_compute_normals(mesh, &mesh->skinned_position, normal_indices, num_indices, normal_data, num_normals); - - mesh->generated_normals = true; - mesh->skinned_normal.exists = true; - mesh->skinned_normal.values.data = normal_data; - mesh->skinned_normal.values.count = num_normals; - mesh->skinned_normal.indices.data = normal_indices; - mesh->skinned_normal.indices.count = num_indices; - mesh->skinned_normal.value_reals = 3; - } - } - - return 1; -#else - ufbxi_fmt_err_info(error, "UFBX_ENABLE_SKINNING_EVALUATION"); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_SKINNING_EVALUATION", "Feature disabled"); - return 0; -#endif -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_fixup_opts_string(ufbxi_context *uc, ufbx_string *str, bool push) -{ - if (str->length > 0) { - if (str->length == SIZE_MAX) { - str->length = str->data ? strlen(str->data) : 0; - } - if (push) { - ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, str, false)); - } - } else { - str->data = ufbxi_empty_char; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_resolve_warning_elements(ufbxi_context *uc) -{ - size_t num_elements = uc->tmp_element_id.num_items; - uint32_t *element_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_id, uint32_t, num_elements); - ufbxi_check(element_ids); - - ufbxi_for_list(ufbx_warning, warning, uc->scene.metadata.warnings) { - uint32_t element_id = warning->element_id; - // Decode `element_id`, see HACK(warning-element) in `ufbxi_vwarnf_imp()` for the encoding. - if ((element_id & 0x80000000u) != 0 && element_id != ~0u) { - warning->element_id = element_ids[element_id & ~0x80000000u]; - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) -{ - // Check for deferred failure - if (uc->deferred_failure) return 0; - if (uc->deferred_load) { - ufbx_stream stream = { 0 }; - ufbx_open_file_opts opts = { 0 }; - const char *filename = uc->load_filename; - size_t filename_len = uc->load_filename_len; - bool ok = false; - if (filename_len == SIZE_MAX) { - opts.filename_null_terminated = true; - filename_len = strlen(filename); - } - if (uc->opts.filename.length == 0 || uc->opts.filename.data == NULL) { - uc->opts.filename.data = filename; - uc->opts.filename.length = filename_len; - } - ufbx_error error; - error.type = UFBX_ERROR_NONE; - if (uc->opts.open_main_file_with_default || uc->opts.open_file_cb.fn == &ufbx_default_open_file) { - ufbx_open_file_context ctx = (ufbx_open_file_context)&uc->ator_tmp; - ok = ufbx_open_file_ctx(&stream, ctx, filename, filename_len, &opts, &error); - } else { - ok = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->load_filename, filename_len, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_MAIN_MODEL); - } - if (!ok) { - if (error.type != UFBX_ERROR_NONE) { - // cppcheck-suppress uninitStructMember - uc->error = error; - } else { - ufbxi_set_err_info(&uc->error, filename, filename_len); - } - ufbxi_fail_msg("open_file_fn()", "File not found"); - } - uc->read_fn = stream.read_fn; - uc->skip_fn = stream.skip_fn; - uc->size_fn = stream.size_fn; - uc->close_fn = stream.close_fn; - uc->read_user = stream.user; - } - - if (uc->opts.progress_cb.fn && uc->progress_bytes_total == 0 && uc->size_fn) { - uint64_t total = uc->size_fn(uc->read_user); - ufbxi_check(total != UINT64_MAX); - uc->progress_bytes_total = total; - } - - ufbxi_check(uc->opts.path_separator >= 0x20 && uc->opts.path_separator <= 0x7e); - - ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.filename, false)); - ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.obj_mtl_path, true)); - ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.geometry_transform_helper_name, true)); - ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.scale_helper_name, true)); - - ufbxi_check(ufbxi_thread_pool_init(&uc->thread_pool, &uc->error, &uc->ator_tmp, &uc->opts.thread_opts)); - - if (!uc->opts.allow_unsafe) { - ufbxi_check_msg(uc->opts.index_error_handling != UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options"); - ufbxi_check_msg(uc->opts.unicode_error_handling != UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options"); - } else { - uc->scene.metadata.is_unsafe = true; - } - - if (uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_NO_INDEX) { - uc->scene.metadata.may_contain_no_index = true; - } - - uc->retain_mesh_parts = !uc->opts.ignore_geometry && !uc->opts.skip_mesh_parts; - uc->scene.metadata.may_contain_missing_vertex_position = uc->opts.allow_missing_vertex_position; - uc->scene.metadata.may_contain_broken_elements = uc->opts.connect_broken_elements; - - uc->scene.metadata.creator.data = ufbxi_empty_char; - - uc->unit_scale = 1.0f; - if (uc->data == NULL) { - ufbxi_dev_assert(uc->data_begin == NULL); - uc->data_begin = uc->data = ufbxi_zero_size_buffer; - } - - uc->retain_vertex_w = (uc->opts.retain_dom || uc->opts.retain_vertex_attrib_w) && !uc->opts.ignore_geometry; - - ufbxi_check(ufbxi_load_strings(uc)); - ufbxi_check(ufbxi_load_maps(uc)); - ufbxi_check(ufbxi_determine_format(uc)); - - ufbx_file_format format = uc->scene.metadata.file_format; - - if (format == UFBX_FILE_FORMAT_FBX) { - ufbxi_check(ufbxi_begin_parse(uc)); - if (uc->version < 6000) { - ufbxi_check(ufbxi_read_legacy_root(uc)); - } else { - ufbxi_check(ufbxi_read_root(uc)); - } - if (!ufbxi_supports_version(uc->version)) { - ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNSUPPORTED_VERSION, "Unsupported FBX version (%u)", uc->version)); - } - ufbxi_update_scene_metadata(&uc->scene.metadata); - ufbxi_check(ufbxi_init_file_paths(uc)); - } else if (format == UFBX_FILE_FORMAT_OBJ) { - ufbxi_check(ufbxi_obj_load(uc)); - ufbxi_update_scene_metadata(&uc->scene.metadata); - } else if (format == UFBX_FILE_FORMAT_MTL) { - ufbxi_check(ufbxi_mtl_load(uc)); - ufbxi_update_scene_metadata(&uc->scene.metadata); - } - - // Fake DOM root if necessary - if (uc->opts.retain_dom && !uc->scene.dom_root) { - ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1); - ufbxi_check(dom_root); - dom_root->name.data = ufbxi_empty_char; - uc->scene.dom_root = dom_root; - } - - ufbxi_check(ufbxi_pre_finalize_scene(uc)); - - // We can free `tmp_parse` already here as all parsing is done by now. - ufbxi_buf_free(&uc->tmp_parse); - - ufbxi_check(ufbxi_finalize_scene(uc)); - - ufbxi_update_scene_settings(&uc->scene.settings); - if (uc->scene.metadata.file_format == UFBX_FILE_FORMAT_OBJ) { - ufbxi_update_scene_settings_obj(uc); - } - - // Axis conversion - if (ufbx_coordinate_axes_valid(uc->opts.target_axes)) { - ufbxi_transform_to_axes(uc, uc->opts.target_axes); - } - - // Unit conversion - if (uc->opts.target_unit_meters > 0.0f) { - ufbxi_check(ufbxi_scale_units(uc, uc->opts.target_unit_meters)); - } - - // TODO: This could be done in evaluate as well with refactoring - ufbxi_update_adjust_transforms(uc, &uc->scene); - - ufbxi_check(ufbxi_modify_geometry(uc)); - ufbxi_postprocess_scene(uc); - - ufbxi_update_scene(&uc->scene, true, NULL, 0); - - // Force a non-NULL anim pointer - if (!uc->scene.anim) { - uc->scene.anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1); - } - - if (uc->opts.load_external_files) { - ufbxi_check(ufbxi_load_external_files(uc)); - } - - // Evaluate skinning if requested - if (uc->opts.evaluate_skinning) { - ufbx_geometry_cache_data_opts cache_opts = { 0 }; - cache_opts.open_file_cb = uc->opts.open_file_cb; - ufbxi_check(ufbxi_evaluate_skinning(&uc->scene, &uc->error, &uc->result, &uc->tmp, - 0.0, uc->opts.load_external_files && uc->opts.evaluate_caches, &cache_opts)); - } - - // Pop warnings to metadata - ufbxi_check(ufbxi_pop_warnings(&uc->warnings, &uc->scene.metadata.warnings, uc->scene.metadata.has_warning)); - ufbxi_check(ufbxi_resolve_warning_elements(uc)); - - // Copy local data to the scene - uc->scene.metadata.version = uc->version; - uc->scene.metadata.ascii = uc->from_ascii; - uc->scene.metadata.big_endian = uc->file_big_endian; - uc->scene.metadata.geometry_ignored = uc->opts.ignore_geometry; - uc->scene.metadata.animation_ignored = uc->opts.ignore_animation; - uc->scene.metadata.embedded_ignored = uc->opts.ignore_embedded; - - // Retain the scene, this must be the final allocation as we copy - // `ator_result` to `ufbx_scene_imp`. - ufbxi_scene_imp *imp = ufbxi_push(&uc->result, ufbxi_scene_imp, 1); - ufbxi_check(imp); - - ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, NULL); - - imp->magic = UFBXI_SCENE_IMP_MAGIC; - imp->scene = uc->scene; - imp->refcount.ator = uc->ator_result; - imp->refcount.ator.error = NULL; - - // Copy retained buffers and translate the allocator struct to the one - // contained within `ufbxi_scene_imp` - imp->refcount.buf = uc->result; - imp->refcount.buf.ator = &imp->refcount.ator; - imp->string_buf = uc->string_pool.buf; - imp->string_buf.ator = &imp->refcount.ator; - - imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size; - imp->scene.metadata.temp_memory_used = uc->ator_tmp.current_size; - imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs; - imp->scene.metadata.temp_allocs = uc->ator_tmp.num_allocs; - - ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) { - (*p_elem)->scene = &imp->scene; - } - - uc->scene_imp = imp; - - return 1; -} - -static ufbxi_noinline void ufbxi_free_temp(ufbxi_context *uc) -{ - ufbxi_thread_pool_free(&uc->thread_pool); - - ufbxi_string_pool_temp_free(&uc->string_pool); - ufbxi_buf_free(&uc->warnings.tmp_stack); - - ufbxi_map_free(&uc->prop_type_map); - ufbxi_map_free(&uc->fbx_id_map); - ufbxi_map_free(&uc->ptr_fbx_id_map); - ufbxi_map_free(&uc->texture_file_map); - ufbxi_map_free(&uc->anim_stack_map); - ufbxi_map_free(&uc->fbx_attr_map); - ufbxi_map_free(&uc->node_prop_set); - ufbxi_map_free(&uc->dom_node_map); - - ufbxi_buf_free(&uc->tmp); - ufbxi_buf_free(&uc->tmp_parse); - for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { - ufbxi_buf_free(&uc->tmp_thread_parse[i]); - } - ufbxi_buf_free(&uc->tmp_stack); - ufbxi_buf_free(&uc->tmp_connections); - ufbxi_buf_free(&uc->tmp_node_ids); - ufbxi_buf_free(&uc->tmp_elements); - ufbxi_buf_free(&uc->tmp_element_offsets); - ufbxi_buf_free(&uc->tmp_element_fbx_ids); - ufbxi_buf_free(&uc->tmp_element_ptrs); - for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { - ufbxi_buf_free(&uc->tmp_typed_element_offsets[i]); - } - ufbxi_buf_free(&uc->tmp_mesh_textures); - ufbxi_buf_free(&uc->tmp_full_weights); - ufbxi_buf_free(&uc->tmp_dom_nodes); - ufbxi_buf_free(&uc->tmp_element_id); - ufbxi_buf_free(&uc->tmp_ascii_spans); - - ufbxi_free(&uc->ator_tmp, ufbxi_node, uc->top_nodes, uc->top_nodes_cap); - ufbxi_free(&uc->ator_tmp, void*, uc->element_extra_arr, uc->element_extra_cap); - - ufbxi_free(&uc->ator_tmp, char, uc->ascii.token.str_data, uc->ascii.token.str_cap); - ufbxi_free(&uc->ator_tmp, char, uc->ascii.prev_token.str_data, uc->ascii.prev_token.str_cap); - - ufbxi_free(&uc->ator_tmp, char, uc->read_buffer, uc->read_buffer_size); - ufbxi_free(&uc->ator_tmp, char, uc->tmp_arr, uc->tmp_arr_size); - ufbxi_free(&uc->ator_tmp, char, uc->swap_arr, uc->swap_arr_size); - - ufbxi_obj_free(uc); - - ufbxi_free_ator(&uc->ator_tmp); -} - -static ufbxi_noinline void ufbxi_free_result(ufbxi_context *uc) -{ - ufbxi_buf_free(&uc->result); - ufbxi_buf_free(&uc->string_pool.buf); - - ufbxi_free_ator(&uc->ator_result); -} - -static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_opts *user_opts, ufbx_error *p_error) -{ - // Test endianness - { - uint8_t buf[2]; - uint16_t val = 0xbbaa; - memcpy(buf, &val, 2); - uc->local_big_endian = buf[0] == 0xbb; - } - - uc->double_parse_flags = ufbxi_parse_double_init_flags(); - - if (user_opts) { - uc->opts = *user_opts; - } else { - memset(&uc->opts, 0, sizeof(uc->opts)); - } - - if (uc->opts.file_size_estimate) { - uc->progress_bytes_total = uc->opts.file_size_estimate; - } - - if (uc->opts.ignore_all_content) { - uc->opts.ignore_geometry = true; - uc->opts.ignore_animation = true; - uc->opts.ignore_embedded = true; - } - - ufbx_inflate_retain inflate_retain; - inflate_retain.initialized = false; - - ufbxi_init_ator(&uc->error, &uc->ator_tmp, &uc->opts.temp_allocator, "temp"); - ufbxi_init_ator(&uc->error, &uc->ator_result, &uc->opts.result_allocator, "result"); - - if (uc->opts.read_buffer_size == 0) { - uc->opts.read_buffer_size = 0x4000; - } - if (uc->opts.read_buffer_size <= 32) { - uc->opts.read_buffer_size = 32; - } - - if (uc->opts.file_format_lookahead == 0) { - uc->opts.file_format_lookahead = 0x4000; - } else if (uc->opts.file_format_lookahead < UFBXI_MIN_FILE_FORMAT_LOOKAHEAD) { - uc->opts.file_format_lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD; - } - - if (!uc->opts.path_separator) { - uc->opts.path_separator = UFBX_PATH_SEPARATOR; - } - - if (!uc->opts.progress_cb.fn || uc->opts.progress_interval_hint >= SIZE_MAX) { - uc->progress_interval = SIZE_MAX; - } else if (uc->opts.progress_interval_hint > 0) { - uc->progress_interval = (size_t)uc->opts.progress_interval_hint; - } else { - uc->progress_interval = 0x4000; - } - - if (!uc->opts.open_file_cb.fn) { - uc->opts.open_file_cb.fn = &ufbx_default_open_file; - } - - if (!uc->opts.thread_opts.memory_limit) { - uc->opts.thread_opts.memory_limit = 32*1024*1024; - } - - uc->synthetic_id_counter = UFBXI_SYNTHETIC_ID_START; - - uc->string_pool.error = &uc->error; - ufbxi_map_init(&uc->string_pool.map, &uc->ator_tmp, &ufbxi_map_cmp_string, NULL); - uc->string_pool.buf.ator = &uc->ator_result; - uc->string_pool.buf.unordered = true; - uc->string_pool.initial_size = 1024; - uc->string_pool.error_handling = uc->opts.unicode_error_handling; - - ufbxi_map_init(&uc->prop_type_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); - ufbxi_map_init(&uc->fbx_id_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL); - ufbxi_map_init(&uc->ptr_fbx_id_map, &uc->ator_tmp, &ufbxi_map_cmp_ptr_id, NULL); - ufbxi_map_init(&uc->texture_file_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); - ufbxi_map_init(&uc->anim_stack_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); - ufbxi_map_init(&uc->fbx_attr_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL); - ufbxi_map_init(&uc->node_prop_set, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL); - ufbxi_map_init(&uc->dom_node_map, &uc->ator_tmp, &ufbxi_map_cmp_uintptr, NULL); - - uc->tmp.ator = &uc->ator_tmp; - uc->tmp_parse.ator = &uc->ator_tmp; - uc->tmp_stack.ator = &uc->ator_tmp; - uc->tmp_connections.ator = &uc->ator_tmp; - uc->tmp_node_ids.ator = &uc->ator_tmp; - uc->tmp_elements.ator = &uc->ator_tmp; - uc->tmp_element_offsets.ator = &uc->ator_tmp; - uc->tmp_element_fbx_ids.ator = &uc->ator_tmp; - uc->tmp_element_ptrs.ator = &uc->ator_tmp; - for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { - uc->tmp_typed_element_offsets[i].ator = &uc->ator_tmp; - } - uc->tmp_mesh_textures.ator = &uc->ator_tmp; - uc->tmp_full_weights.ator = &uc->ator_tmp; - uc->tmp_dom_nodes.ator = &uc->ator_tmp; - uc->tmp_element_id.ator = &uc->ator_tmp; - uc->tmp_ascii_spans.ator = &uc->ator_tmp; - - for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) { - uc->tmp_thread_parse[i].ator = &uc->ator_tmp; - uc->tmp_thread_parse[i].unordered = true; - uc->tmp_thread_parse[i].clearable = true; - } - - uc->result.ator = &uc->ator_result; - - uc->tmp.unordered = true; - uc->tmp_parse.unordered = true; - uc->tmp_parse.clearable = true; - uc->result.unordered = true; - - uc->warnings.error = &uc->error; - uc->warnings.result = &uc->result; - uc->warnings.tmp_stack.ator = &uc->ator_tmp; - uc->string_pool.warnings = &uc->warnings; - - // Set zero size `swap_arr` to a non-NULL buffer so we can tell the difference between empty - // array and an allocation failure. - uc->swap_arr = (char*)ufbxi_zero_size_buffer; - - // NOTE: Though `inflate_retain` leaks out of the scope we don't use it outside this function. - // cppcheck-suppress autoVariables - uc->inflate_retain = &inflate_retain; - - int ok = ufbxi_load_imp(uc); - - if (uc->close_fn) { - uc->close_fn(uc->read_user); - } - - ufbxi_free_temp(uc); - - if (ok) { - if (p_error) { - ufbxi_clear_error(p_error); - } - return &uc->scene_imp->scene; - } else { - ufbxi_fix_error_type(&uc->error, "Failed to load", p_error); - if (p_error && p_error->type == UFBX_ERROR_UNKNOWN && uc->scene.metadata.file_format == UFBX_FILE_FORMAT_FBX && !ufbxi_supports_version(uc->version)) { - p_error->description.data = "Unsupported version"; - p_error->description.length = strlen("Unsupported version"); - p_error->type = UFBX_ERROR_UNSUPPORTED_VERSION; - ufbxi_fmt_err_info(p_error, "%u", uc->version); - } - ufbxi_free_result(uc); - return NULL; - } -} - -// -- Animation evaluation - -static ufbxi_forceinline bool ufbxi_override_less_than_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) -{ - if (over->element_id != element_id) return over->element_id < element_id; - if (over->_internal_key != prop->_internal_key) return over->_internal_key < prop->_internal_key; - return strcmp(over->prop_name.data, prop->name.data); -} - -static ufbxi_forceinline bool ufbxi_override_equals_to_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) -{ - if (over->element_id != element_id) return false; - if (over->_internal_key != prop->_internal_key) return false; - return !strcmp(over->prop_name.data, prop->name.data); -} - -static ufbxi_noinline bool ufbxi_find_prop_override(const ufbx_prop_override_list *overrides, uint32_t element_id, ufbx_prop *prop) -{ - size_t ix = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_prop_override, 16, &ix, overrides->data, 0, overrides->count, - ( ufbxi_override_less_than_prop(a, element_id, prop) ), - ( ufbxi_override_equals_to_prop(a, element_id, prop) )); - - if (ix != SIZE_MAX) { - const ufbx_prop_override *over = &overrides->data[ix]; - const uint32_t clear_flags = UFBX_PROP_FLAG_NO_VALUE | UFBX_PROP_FLAG_NOT_FOUND; - prop->flags = (ufbx_prop_flags)(((uint32_t)prop->flags & ~clear_flags) | UFBX_PROP_FLAG_OVERRIDDEN); - prop->value_vec4 = over->value; - prop->value_real_arr[3] = 0.0f; - prop->value_int = over->value_int; - prop->value_str = over->value_str; - prop->value_blob.data = prop->value_str.data; - prop->value_blob.size = prop->value_str.length; - return true; - } else { - return false; - } -} - -static ufbxi_noinline ufbx_prop_override_list ufbxi_find_element_prop_overrides(const ufbx_prop_override_list *overrides, uint32_t element_id) -{ - size_t begin = overrides->count, end = begin; - - ufbxi_macro_lower_bound_eq(ufbx_prop_override, 32, &begin, overrides->data, 0, overrides->count, - (a->element_id < element_id), - (a->element_id == element_id)); - - ufbxi_macro_upper_bound_eq(ufbx_prop_override, 32, &end, overrides->data, begin, overrides->count, - (a->element_id == element_id)); - - ufbx_prop_override_list result = { overrides->data + begin, end - begin }; - return result; -} - -typedef struct ufbxi_anim_layer_combine_ctx { - const ufbx_anim *anim; - const ufbx_element *element; - double time; - ufbx_rotation_order rotation_order; - bool has_rotation_order; -} ufbxi_anim_layer_combine_ctx; - -static ufbxi_noinline double ufbxi_pow_abs(double v, double e) -{ - if (e <= 0.0) return 1.0; - if (e >= 1.0) return v; - double sign = v < 0.0 ? -1.0 : 1.0; - return sign * ufbx_pow(v * sign, e); -} - -// Recursion is limited by the fact that we recurse only when the property name is "Lcl Rotation" -// and when recursing we always evaluate the property "RotationOrder" -static ufbxi_noinline void ufbxi_combine_anim_layer(ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value) - ufbxi_recursive_function_void(ufbxi_combine_anim_layer, (ctx, layer, weight, prop_name, result, value), 2, - (ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value)) -{ - if (layer->compose_rotation && layer->blended && prop_name == ufbxi_Lcl_Rotation && !ctx->has_rotation_order) { - ufbx_prop rp = ufbx_evaluate_prop_len(ctx->anim, ctx->element, ufbxi_RotationOrder, sizeof(ufbxi_RotationOrder) - 1, ctx->time); - // NOTE: Defaults to 0 (UFBX_ROTATION_XYZ) gracefully if property is not found - if (rp.value_int >= 0 && rp.value_int <= UFBX_ROTATION_ORDER_SPHERIC) { - ctx->rotation_order = (ufbx_rotation_order)rp.value_int; - } else { - ctx->rotation_order = UFBX_ROTATION_ORDER_XYZ; - } - ctx->has_rotation_order = true; - } - - if (layer->additive) { - if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) { - result->x *= (ufbx_real)ufbxi_pow_abs(value->x, weight); - result->y *= (ufbx_real)ufbxi_pow_abs(value->y, weight); - result->z *= (ufbx_real)ufbxi_pow_abs(value->z, weight); - } else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) { - ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order); - ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order); - b = ufbx_quat_slerp(ufbx_identity_quat, b, weight); - ufbx_quat res = ufbxi_mul_quat(a, b); - *result = ufbx_quat_to_euler(res, ctx->rotation_order); - } else { - result->x += value->x * weight; - result->y += value->y * weight; - result->z += value->z * weight; - } - } else if (layer->blended) { - ufbx_real res_weight = 1.0f - weight; - if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) { - result->x = (ufbx_real)(ufbxi_pow_abs(result->x, res_weight) * ufbxi_pow_abs(value->x, weight)); - result->y = (ufbx_real)(ufbxi_pow_abs(result->y, res_weight) * ufbxi_pow_abs(value->y, weight)); - result->z = (ufbx_real)(ufbxi_pow_abs(result->z, res_weight) * ufbxi_pow_abs(value->z, weight)); - } else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) { - ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order); - ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order); - ufbx_quat res = ufbx_quat_slerp(a, b, weight); - *result = ufbx_quat_to_euler(res, ctx->rotation_order); - } else { - result->x = result->x * res_weight + value->x * weight; - result->y = result->y * res_weight + value->y * weight; - result->z = result->z * res_weight + value->z * weight; - } - } else { - *result = *value; - } -} - -static ufbxi_forceinline bool ufbxi_anim_layer_might_contain_id(const ufbx_anim_layer *layer, uint32_t id) -{ - uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1; - bool ok = id - layer->_min_element_id <= (layer->_max_element_id - layer->_min_element_id); - ok &= (layer->_element_id_bitmask[(id >> 5) & id_mask] & (1u << (id & 31))) != 0; - return ok; -} - -static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, size_t num_props, uint32_t flags) -{ - ufbxi_anim_layer_combine_ctx combine_ctx = { anim, element, time }; - - uint32_t element_id = element->element_id; - size_t num_layers = anim->layers.count; - for (size_t layer_ix = 0; layer_ix < num_layers; layer_ix++) { - ufbx_anim_layer *layer = anim->layers.data[layer_ix]; - if (!ufbxi_anim_layer_might_contain_id(layer, element_id)) continue; - - // Find the weight for the current layer - // TODO: Should this be searched from multiple layers? - ufbx_real weight = layer_ix < anim->override_layer_weights.count ? anim->override_layer_weights.data[layer_ix] : layer->weight; - if (layer->weight_is_animated && layer->blended) { - ufbx_anim_prop *weight_aprop = ufbxi_find_anim_prop_start(layer, &layer->element); - if (weight_aprop) { - weight = ufbx_evaluate_anim_value_real_flags(weight_aprop->anim_value, time, flags) / (ufbx_real)100.0; - if (weight < 0.0f) weight = 0.0f; - if (weight > 0.99999f) weight = 1.0f; - } - } - - ufbx_anim_prop *aprop = ufbxi_find_anim_prop_start(layer, element); - if (!aprop) continue; - - for (size_t i = 0; i < num_props; i++) { - ufbx_prop *prop = &props[i]; - - // Don't evaluate on top of overridden properties - if ((prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0) continue; - - // Connections override animation by default - if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) continue; - - // Skip until we reach `aprop >= prop` - // NOTE: No need to check for end as `anim_props` is terminated with a NULL sentinel. - while (aprop->element == element && aprop->_internal_key < prop->_internal_key) aprop++; - if (aprop->prop_name.data != prop->name.data) { - while (aprop->element == element && strcmp(aprop->prop_name.data, prop->name.data) < 0) aprop++; - } - - // TODO: Should we skip the blending for the first layer _per property_ - // This could be done by having `UFBX_PROP_FLAG_ANIMATION_EVALUATED` - // that gets set for the first layer of animation that is applied. - if (aprop->prop_name.data == prop->name.data) { - ufbx_vec3 v = ufbx_evaluate_anim_value_vec3_flags(aprop->anim_value, time, flags); - if (layer_ix == 0) { - prop->value_vec3 = v; - } else { - ufbxi_combine_anim_layer(&combine_ctx, layer, weight, prop->name.data, &prop->value_vec3, &v); - } - } - } - } - - ufbxi_for(ufbx_prop, prop, props, num_props) { - if (prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) continue; - prop->value_int = ufbxi_f64_to_i64(prop->value_real); - } -} - -// Recursion limited by not calling `ufbx_evaluate_prop_len()` with a connected property, -// meaning it will never call `ufbxi_evaluate_connected_prop()` again indirectly. -static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) - ufbxi_recursive_function_void(ufbxi_evaluate_connected_prop, (prop, anim, element, name, time, flags), 3, - (ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags)) -{ - ufbx_connection *conn = ufbxi_find_prop_connection(element, name); - - for (size_t i = 0; i < 1000 && conn; i++) { - ufbx_connection *next_conn = ufbxi_find_prop_connection(conn->src, conn->src_prop.data); - if (!next_conn) break; - conn = next_conn; - } - - // Found a non-cyclic connection - if (conn && !ufbxi_find_prop_connection(conn->src, conn->src_prop.data)) { - ufbx_prop ep = ufbx_evaluate_prop_len_flags(anim, conn->src, conn->src_prop.data, conn->src_prop.length, time, flags); - prop->value_vec4 = ep.value_vec4; - prop->value_int = ep.value_int; - prop->value_str = ep.value_str; - prop->value_blob = ep.value_blob; - } else { - // Connection not found, maybe it's animated? - prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags & ~(uint32_t)UFBX_PROP_FLAG_CONNECTED); - } -} - -typedef struct { - const ufbx_prop *prop, *prop_end; - const ufbx_prop_override *over, *over_end; - ufbx_prop tmp; -} ufbxi_prop_iter; - -static ufbxi_noinline void ufbxi_init_prop_iter_slow(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element) -{ - iter->prop = element->props.props.data; - iter->prop_end = element->props.props.data + element->props.props.count; - - ufbx_prop_override_list over = ufbxi_find_element_prop_overrides(&anim->prop_overrides, element->element_id); - iter->over = over.data; - iter->over_end = over.data + over.count; - if (over.count > 0) { - memset(&iter->tmp, 0, sizeof(ufbx_prop)); - } -} - -static ufbxi_forceinline void ufbxi_init_prop_iter(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element) -{ - iter->prop = element->props.props.data; - iter->prop_end = ufbxi_add_ptr(element->props.props.data, element->props.props.count); - iter->over = iter->over_end = NULL; - if (anim->prop_overrides.count > 0) { - ufbxi_init_prop_iter_slow(iter, anim, element); - } -} - -static ufbxi_noinline const ufbx_prop *ufbxi_next_prop_slow(ufbxi_prop_iter *iter) -{ - const ufbx_prop *prop = iter->prop; - const ufbx_prop_override *over = iter->over; - if (prop == iter->prop_end && over == iter->over_end) return NULL; - - // We can use `UINT32_MAX` as a terminating key (aka prefix) as prop names must - // be valid UTF-8 and the byte sequence "\xff\xff\xff\xff" is not valid. - uint32_t prop_key = prop != iter->prop_end ? prop->_internal_key : UINT32_MAX; - uint32_t over_key = over != iter->over_end ? over->_internal_key : UINT32_MAX; - - int cmp = 0; - if (prop_key != over_key) { - cmp = prop_key < over_key ? -1 : 1; - } else { - cmp = strcmp(prop->name.data, over->prop_name.data); - } - - if (cmp >= 0) { - ufbx_prop *dst = &iter->tmp; - dst->name = over->prop_name; - dst->_internal_key = over->_internal_key; - dst->type = UFBX_PROP_UNKNOWN; - dst->flags = UFBX_PROP_FLAG_OVERRIDDEN; - dst->value_str = over->value_str; - dst->value_blob.data = dst->value_str.data; - dst->value_blob.size = dst->value_str.length; - dst->value_int = over->value_int; - dst->value_vec4 = over->value; - iter->over = over + 1; - if (cmp == 0) { - iter->prop = prop + 1; - } - return dst; - } else { - iter->prop = prop + 1; - return prop; - } -} - -static ufbxi_forceinline const ufbx_prop *ufbxi_next_prop(ufbxi_prop_iter *iter) -{ - if (iter->over == iter->over_end) { - if (iter->prop == iter->prop_end) return NULL; - return iter->prop++; - } else { - return ufbxi_next_prop_slow(iter); - } -} - -static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, const char *const *prop_names, size_t max_props, uint32_t flags) -{ - const char *name = prop_names[0]; - uint32_t key = ufbxi_get_name_key_c(name); - size_t num_props = 0; - -#if defined(UFBX_REGRESSION) - for (size_t i = 1; i < max_props; i++) { - ufbx_assert(strcmp(prop_names[i - 1], prop_names[i]) < 0); - } -#endif - - size_t name_ix = 0; - - ufbxi_prop_iter iter; // ufbxi_uninit - ufbxi_init_prop_iter(&iter, anim, element); - const ufbx_prop *prop = NULL; - while ((prop = ufbxi_next_prop(&iter)) != NULL) { - while (name_ix < max_props) { - if (key > prop->_internal_key) break; - if (name == prop->name.data) { - if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { - ufbx_prop *dst = &props[num_props++]; - *dst = *prop; - ufbxi_evaluate_connected_prop(dst, anim, element, name, time, flags); - } else if ((prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN)) != 0) { - props[num_props++] = *prop; - } - break; - } else if (strcmp(name, prop->name.data) < 0) { - name_ix++; - if (name_ix < max_props) { - name = prop_names[name_ix]; - key = ufbxi_get_name_key_c(name); - } - } else { - break; - } - } - } - - ufbxi_evaluate_props(anim, element, time, props, num_props, flags); - - ufbx_props prop_list; - prop_list.props.data = props; - prop_list.props.count = prop_list.num_animated = num_props; - prop_list.defaults = (ufbx_props*)&element->props; - return prop_list; -} - -// Recursion limited by not calling `ufbx_evaluate_curve()` with `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. -static ufbxi_noinline ufbx_real ufbxi_extrapolate_curve(const ufbx_anim_curve *curve, double real_time, uint32_t flags) - ufbxi_recursive_function(ufbx_real, ufbxi_extrapolate_curve, (curve, real_time, flags), 3, - (const ufbx_anim_curve *curve, double real_time, uint32_t flags)) -{ - bool pre = real_time < curve->min_time; - const ufbx_keyframe *key; - ufbx_extrapolation ext; - if (pre) { - key = &curve->keyframes.data[0]; - ext = curve->pre_extrapolation; - } else { - key = &curve->keyframes.data[curve->keyframes.count - 1]; - ext = curve->post_extrapolation; - } - - if (ext.mode == UFBX_EXTRAPOLATION_CONSTANT) { - return key->value; - } else if (ext.mode == UFBX_EXTRAPOLATION_SLOPE) { - ufbx_tangent tangent = *(pre ? &key->right : &key->left); - return key->value + (ufbx_real)(tangent.dy * ((real_time - key->time) / tangent.dx)); - } else if (ext.repeat_count == 0) { - return key->value; - } - - // Perform all operations in KTime ticks to be frame perfect - double scale = (double)curve->element.scene->metadata.ktime_second; - double min_time = ufbx_rint(curve->min_time * scale); - double max_time = ufbx_rint(curve->max_time * scale); - double time = real_time * scale; - - double delta = pre ? min_time - time : time - max_time; - double duration = max_time - min_time; - - // Require at least one KTime unit - if (!(duration >= 1.0)) return key->value; - - double rep = delta / duration; - double rep_n = ufbx_floor(rep); - double rep_d = delta - rep_n * duration; - - if (ext.repeat_count > 0 && rep_n >= (double)ext.repeat_count) { - // Clamp to the repeat count to handle mirroring - rep_n = (double)(ext.repeat_count - 1); - rep_d = duration; - } - - if (ext.mode == UFBX_EXTRAPOLATION_MIRROR) { - double rep_parity = rep_n*0.5 - ufbx_floor(rep_n*0.5); - if (rep_parity <= 0.25) { - rep_d = duration - rep_d; - } - } - - if (pre) rep_d = duration - rep_d; - double new_time = (min_time + rep_d) / scale; - - ufbx_real value = ufbx_evaluate_curve_flags(curve, new_time, key->value, flags | UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION); - - if (ext.mode == UFBX_EXTRAPOLATION_REPEAT_RELATIVE) { - ufbx_real val_delta = curve->keyframes.data[curve->keyframes.count - 1].value - curve->keyframes.data[0].value; - if (pre) val_delta = -val_delta; - value += val_delta * (ufbx_real)(rep_n + 1.0); - } - - return value; -} - -#if UFBXI_FEATURE_SCENE_EVALUATION - -typedef struct { - char *src_element; - char *dst_element; - - ufbxi_scene_imp *src_imp; - ufbx_scene src_scene; - ufbx_evaluate_opts opts; - ufbx_anim *anim; - double time; - - ufbx_error error; - - // Allocators - ufbxi_allocator ator_result; - ufbxi_allocator ator_tmp; - - ufbxi_buf result; - ufbxi_buf tmp; - - ufbx_scene scene; - - ufbxi_scene_imp *scene_imp; -} ufbxi_eval_context; - -static ufbxi_forceinline ufbx_element *ufbxi_translate_element(ufbxi_eval_context *ec, void *elem) -{ - return elem ? (ufbx_element*)(ec->dst_element + ((char*)elem - ec->src_element)) : NULL; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_element_list(ufbxi_eval_context *ec, void *p_list) -{ - ufbx_element_list *list = (ufbx_element_list*)p_list; - size_t count = list->count; - ufbx_element **src = list->data; - ufbx_element **dst = ufbxi_push(&ec->result, ufbx_element*, count); - ufbxi_check_err(&ec->error, dst); - list->data = dst; - for (size_t i = 0; i < count; i++) { - dst[i] = ufbxi_translate_element(ec, src[i]); - } - return 1; -} - -static ufbxi_noinline void ufbxi_translate_maps(ufbxi_eval_context *ec, ufbx_material_map *maps, size_t count) -{ - ufbxi_nounroll ufbxi_for(ufbx_material_map, map, maps, count) { - map->texture = (ufbx_texture*)ufbxi_translate_element(ec, map->texture); - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_anim(ufbxi_eval_context *ec, ufbx_anim **p_anim) -{ - ufbx_anim *anim = ufbxi_push_copy(&ec->result, ufbx_anim, 1, *p_anim); - ufbxi_check_err(&ec->error, anim); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &anim->layers)); - *p_anim = anim; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context *ec) -{ - ec->scene = ec->src_scene; - size_t num_elements = ec->scene.elements.count; - - char *element_data = (char*)ufbxi_push(&ec->result, uint64_t, ec->scene.metadata.element_buffer_size/8); - ufbxi_check_err(&ec->error, element_data); - - ec->scene.elements.data = ufbxi_push(&ec->result, ufbx_element*, num_elements); - ufbxi_check_err(&ec->error, ec->scene.elements.data); - - ec->src_element = (char*)ec->src_scene.elements.data[0]; - ec->dst_element = element_data; - - for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { - ec->scene.elements_by_type[i].data = ufbxi_push(&ec->result, ufbx_element*, ec->scene.elements_by_type[i].count); - ufbxi_check_err(&ec->error, ec->scene.elements_by_type[i].data); - } - - size_t num_connections = ec->scene.connections_dst.count; - ec->scene.connections_src.data = ufbxi_push(&ec->result, ufbx_connection, num_connections); - ec->scene.connections_dst.data = ufbxi_push(&ec->result, ufbx_connection, num_connections); - ufbxi_check_err(&ec->error, ec->scene.connections_src.data); - ufbxi_check_err(&ec->error, ec->scene.connections_dst.data); - for (size_t i = 0; i < num_connections; i++) { - ufbx_connection *src = &ec->scene.connections_src.data[i]; - ufbx_connection *dst = &ec->scene.connections_dst.data[i]; - *src = ec->src_scene.connections_src.data[i]; - *dst = ec->src_scene.connections_dst.data[i]; - src->src = ufbxi_translate_element(ec, src->src); - src->dst = ufbxi_translate_element(ec, src->dst); - dst->src = ufbxi_translate_element(ec, dst->src); - dst->dst = ufbxi_translate_element(ec, dst->dst); - } - - ec->scene.elements_by_name.data = ufbxi_push(&ec->result, ufbx_name_element, num_elements); - ufbxi_check_err(&ec->error, ec->scene.elements_by_name.data); - - ec->scene.root_node = (ufbx_node*)ufbxi_translate_element(ec, ec->scene.root_node); - ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->scene.anim)); - - for (size_t i = 0; i < num_elements; i++) { - ufbx_element *src = ec->src_scene.elements.data[i]; - ufbx_element *dst = ufbxi_translate_element(ec, src); - size_t size = ufbx_element_type_size[src->type]; - ufbx_assert(size > 0); - memcpy(dst, src, size); - - ec->scene.elements.data[i] = dst; - ec->scene.elements_by_type[src->type].data[src->typed_id] = dst; - - dst->connections_src.data = ec->scene.connections_src.data + (dst->connections_src.data - ec->src_scene.connections_src.data); - dst->connections_dst.data = ec->scene.connections_dst.data + (dst->connections_dst.data - ec->src_scene.connections_dst.data); - if (dst->instances.count > 0) { - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &dst->instances)); - } - - ufbx_name_element named = ec->src_scene.elements_by_name.data[i]; - named.element = ufbxi_translate_element(ec, named.element); - ec->scene.elements_by_name.data[i] = named; - } - - ufbxi_for_ptr_list(ufbx_node, p_node, ec->scene.nodes) { - ufbx_node *node = *p_node; - node->parent = (ufbx_node*)ufbxi_translate_element(ec, node->parent); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->children)); - - node->attrib = ufbxi_translate_element(ec, node->attrib); - node->mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->mesh); - node->light = (ufbx_light*)ufbxi_translate_element(ec, node->light); - node->camera = (ufbx_camera*)ufbxi_translate_element(ec, node->camera); - node->bone = (ufbx_bone*)ufbxi_translate_element(ec, node->bone); - node->inherit_scale_node = (ufbx_node*)ufbxi_translate_element(ec, node->inherit_scale_node); - node->scale_helper = (ufbx_node*)ufbxi_translate_element(ec, node->scale_helper); - node->bind_pose = (ufbx_pose*)ufbxi_translate_element(ec, node->bind_pose); - - if (node->all_attribs.count > 1) { - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->all_attribs)); - } else if (node->all_attribs.count == 1) { - node->all_attribs.data = &node->attrib; - } - - node->geometry_transform_helper = (ufbx_node*)ufbxi_translate_element(ec, node->geometry_transform_helper); - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->materials)); - } - - ufbxi_for_ptr_list(ufbx_mesh, p_mesh, ec->scene.meshes) { - ufbx_mesh *mesh = *p_mesh; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->materials)); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->skin_deformers)); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->blend_deformers)); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->cache_deformers)); - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->all_deformers)); - } - - ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, ec->scene.stereo_cameras) { - ufbx_stereo_camera *stereo = *p_stereo; - stereo->left = (ufbx_camera*)ufbxi_translate_element(ec, stereo->left); - stereo->right = (ufbx_camera*)ufbxi_translate_element(ec, stereo->right); - } - - ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, ec->scene.skin_deformers) { - ufbx_skin_deformer *skin = *p_skin; - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &skin->clusters)); - } - - ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, ec->scene.skin_clusters) { - ufbx_skin_cluster *cluster = *p_cluster; - cluster->bone_node = (ufbx_node*)ufbxi_translate_element(ec, cluster->bone_node); - } - - ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, ec->scene.blend_deformers) { - ufbx_blend_deformer *blend = *p_blend; - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &blend->channels)); - } - - ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, ec->scene.blend_channels) { - ufbx_blend_channel *chan = *p_chan; - - ufbx_blend_keyframe *keys = ufbxi_push(&ec->result, ufbx_blend_keyframe, chan->keyframes.count); - ufbxi_check_err(&ec->error, keys); - for (size_t i = 0; i < chan->keyframes.count; i++) { - keys[i] = chan->keyframes.data[i]; - keys[i].shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, keys[i].shape); - } - chan->keyframes.data = keys; - chan->target_shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, chan->target_shape); - } - - ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, ec->scene.cache_deformers) { - ufbx_cache_deformer *deformer = *p_deformer; - deformer->file = (ufbx_cache_file*)ufbxi_translate_element(ec, deformer->file); - } - - ufbxi_for_ptr_list(ufbx_material, p_material, ec->scene.materials) { - ufbx_material *material = *p_material; - - material->shader = (ufbx_shader*)ufbxi_translate_element(ec, material->shader); - ufbxi_translate_maps(ec, material->fbx.maps, UFBX_MATERIAL_FBX_MAP_COUNT); - ufbxi_translate_maps(ec, material->pbr.maps, UFBX_MATERIAL_PBR_MAP_COUNT); - - ufbx_material_texture *textures = ufbxi_push(&ec->result, ufbx_material_texture, material->textures.count); - ufbxi_check_err(&ec->error, textures); - for (size_t i = 0; i < material->textures.count; i++) { - textures[i] = material->textures.data[i]; - textures[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, textures[i].texture); - } - material->textures.data = textures; - } - - ufbxi_for_ptr_list(ufbx_texture, p_texture, ec->scene.textures) { - ufbx_texture *texture = *p_texture; - texture->video = (ufbx_video*)ufbxi_translate_element(ec, texture->video); - - ufbx_texture_layer *layers = ufbxi_push(&ec->result, ufbx_texture_layer, texture->layers.count); - ufbxi_check_err(&ec->error, layers); - for (size_t i = 0; i < texture->layers.count; i++) { - layers[i] = texture->layers.data[i]; - layers[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, layers[i].texture); - } - texture->layers.data = layers; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &texture->file_textures)); - - if (texture->shader) { - ufbx_shader_texture *shader = texture->shader; - shader = ufbxi_push_copy(&ec->result, ufbx_shader_texture, 1, shader); - ufbxi_check_err(&ec->error, shader); - texture->shader = shader; - - ufbx_shader_texture_input *inputs = ufbxi_push_copy(&ec->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data); - ufbxi_check_err(&ec->error, inputs); - shader->inputs.data = inputs; - } - } - - ufbxi_for_ptr_list(ufbx_shader, p_shader, ec->scene.shaders) { - ufbx_shader *shader = *p_shader; - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &shader->bindings)); - } - - ufbxi_for_ptr_list(ufbx_display_layer, p_layer, ec->scene.display_layers) { - ufbx_display_layer *layer = *p_layer; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->nodes)); - } - - ufbxi_for_ptr_list(ufbx_selection_set, p_set, ec->scene.selection_sets) { - ufbx_selection_set *set = *p_set; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &set->nodes)); - } - - ufbxi_for_ptr_list(ufbx_selection_node, p_node, ec->scene.selection_nodes) { - ufbx_selection_node *node = *p_node; - - node->target_node = (ufbx_node*)ufbxi_translate_element(ec, node->target_node); - node->target_mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->target_mesh); - } - - ufbxi_for_ptr_list(ufbx_constraint, p_constraint, ec->scene.constraints) { - ufbx_constraint *constraint = *p_constraint; - - constraint->node = (ufbx_node*)ufbxi_translate_element(ec, constraint->node); - constraint->aim_up_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->aim_up_node); - constraint->ik_effector = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_effector); - constraint->ik_end_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_end_node); - - ufbx_constraint_target *targets = ufbxi_push(&ec->result, ufbx_constraint_target, constraint->targets.count); - ufbxi_check_err(&ec->error, targets); - for (size_t i = 0; i < constraint->targets.count; i++) { - targets[i] = constraint->targets.data[i]; - targets[i].node = (ufbx_node*)ufbxi_translate_element(ec, targets[i].node); - } - constraint->targets.data = targets; - } - - ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, ec->scene.audio_layers) { - ufbx_audio_layer *layer = *p_layer; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->clips)); - } - - ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, ec->scene.anim_stacks) { - ufbx_anim_stack *stack = *p_stack; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &stack->layers)); - ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &stack->anim)); - } - - ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, ec->scene.anim_layers) { - ufbx_anim_layer *layer = *p_layer; - - ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->anim_values)); - ufbx_anim_prop *props = ufbxi_push(&ec->result, ufbx_anim_prop, layer->anim_props.count + 1); - ufbxi_check_err(&ec->error, props); - for (size_t i = 0; i < layer->anim_props.count; i++) { - props[i] = layer->anim_props.data[i]; - props[i].element = ufbxi_translate_element(ec, props[i].element); - props[i].anim_value = (ufbx_anim_value*)ufbxi_translate_element(ec, props[i].anim_value); - } - // Maintain NULL sentinel - memset(props + layer->anim_props.count, 0, sizeof(ufbx_anim_prop)); - layer->anim_props.data = props; - } - - ufbxi_for_ptr_list(ufbx_pose, p_pose, ec->scene.poses) { - ufbx_pose *pose = *p_pose; - - ufbx_bone_pose *bones = ufbxi_push(&ec->result, ufbx_bone_pose, pose->bone_poses.count); - ufbxi_check_err(&ec->error, bones); - for (size_t i = 0; i < pose->bone_poses.count; i++) { - bones[i] = pose->bone_poses.data[i]; - bones[i].bone_node = (ufbx_node*)ufbxi_translate_element(ec, bones[i].bone_node); - } - pose->bone_poses.data = bones; - } - - ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->anim)); - - ufbxi_for_ptr_list(ufbx_anim_value, p_value, ec->scene.anim_values) { - ufbx_anim_value *value = *p_value; - value->curves[0] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[0]); - value->curves[1] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[1]); - value->curves[2] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[2]); - } - - ufbx_anim anim = *ec->anim; - ufbx_prop_override *over = anim.prop_overrides.data, *over_end = ufbxi_add_ptr(over, anim.prop_overrides.count); - - // Evaluate the properties - ufbxi_for_ptr_list(ufbx_element, p_elem, ec->scene.elements) { - ufbx_element *elem = *p_elem; - size_t num_animated = elem->props.num_animated; - size_t num_override = 0; - - // Setup the overrides for this element if found - while (over != over_end && over->element_id == elem->element_id) { - num_override++; - over++; - } - - num_animated += num_override; - if (num_animated == 0) continue; - - anim.prop_overrides.data = ufbxi_sub_ptr(over, num_override); - anim.prop_overrides.count = num_override; - - ufbx_prop *props = ufbxi_push(&ec->result, ufbx_prop, num_animated); - ufbxi_check_err(&ec->error, props); - - elem->props = ufbx_evaluate_props_flags(&anim, elem, ec->time, props, num_animated, ec->opts.evaluate_flags); - elem->props.defaults = &ec->src_scene.elements.data[elem->element_id]->props; - } - - // Update all derived values - ufbxi_update_scene(&ec->scene, false, anim.transform_overrides.data, anim.transform_overrides.count); - - // Evaluate skinning if requested - if (ec->opts.evaluate_skinning) { - ufbx_geometry_cache_data_opts cache_opts = { 0 }; - cache_opts.open_file_cb = ec->opts.open_file_cb; - ufbxi_check_err(&ec->error, ufbxi_evaluate_skinning(&ec->scene, &ec->error, &ec->result, &ec->tmp, - ec->time, ec->opts.load_external_files && ec->opts.evaluate_caches, &cache_opts)); - } - - // Retain the scene, this must be the final allocation as we copy - // `ator_result` to `ufbx_scene_imp`. - ufbxi_scene_imp *imp = ufbxi_push_zero(&ec->result, ufbxi_scene_imp, 1); - ufbxi_check_err(&ec->error, imp); - - ufbx_assert(ec->src_imp->magic == UFBXI_SCENE_IMP_MAGIC); - ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, &ec->src_imp->refcount); - - imp->magic = UFBXI_SCENE_IMP_MAGIC; - imp->scene = ec->scene; - imp->refcount.ator = ec->ator_result; - imp->refcount.ator.error = NULL; - - // Copy retained buffers and translate the allocator struct to the one - // contained within `ufbxi_scene_imp` - imp->refcount.buf = ec->result; - imp->refcount.buf.ator = &imp->refcount.ator; - - imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size; - imp->scene.metadata.temp_memory_used = ec->ator_tmp.current_size; - imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs; - imp->scene.metadata.temp_allocs = ec->ator_tmp.num_allocs; - - ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) { - (*p_elem)->scene = &imp->scene; - } - - ec->scene_imp = imp; - ec->result.ator = &ec->ator_result; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline ufbx_scene *ufbxi_evaluate_scene(ufbxi_eval_context *ec, ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *user_opts, ufbx_error *p_error) -{ - if (user_opts) { - ec->opts = *user_opts; - } else { - memset(&ec->opts, 0, sizeof(ec->opts)); - } - - ec->src_imp = ufbxi_get_imp(ufbxi_scene_imp, scene); - ec->src_scene = *scene; - ec->anim = anim ? (ufbx_anim*)anim : scene->anim; - ec->time = time; - - ufbxi_init_ator(&ec->error, &ec->ator_tmp, &ec->opts.temp_allocator, "temp"); - ufbxi_init_ator(&ec->error, &ec->ator_result, &ec->opts.result_allocator, "result"); - - ec->result.ator = &ec->ator_result; - ec->tmp.ator = &ec->ator_tmp; - - ec->result.unordered = true; - ec->tmp.unordered = true; - - if (ufbxi_evaluate_imp(ec)) { - ufbxi_buf_free(&ec->tmp); - ufbxi_free_ator(&ec->ator_tmp); - if (p_error) { - ufbxi_clear_error(p_error); - } - return &ec->scene_imp->scene; - } else { - ufbxi_fix_error_type(&ec->error, "Failed to evaluate", p_error); - ufbxi_buf_free(&ec->tmp); - ufbxi_buf_free(&ec->result); - ufbxi_free_ator(&ec->ator_tmp); - ufbxi_free_ator(&ec->ator_result); - return NULL; - } -} - -#endif - -typedef struct { - ufbx_error error; - ufbxi_allocator ator_result; - ufbxi_buf result; - const ufbx_scene *scene; - ufbx_anim_opts opts; - - ufbx_anim anim; - ufbxi_anim_imp *imp; -} ufbxi_create_anim_context; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_check_string(ufbx_error *error, ufbx_string *dst, const ufbx_string *src) -{ - size_t length = src->length != SIZE_MAX ? src->length : strlen(src->data); - const char *data = length != 0 ? src->data : ufbxi_empty_char; - if (length > 0) { - size_t valid_length = ufbxi_utf8_valid_length(data, length); - ufbxi_check_err_msg(error, valid_length == length, "Invalid UTF-8"); - } - - dst->data = data; - dst->length = length; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_anim_string(ufbxi_create_anim_context *ac, ufbx_string *str) -{ - size_t length = str->length; - if (length > 0) { - char *copy = ufbxi_push(&ac->result, char, length + 1); - ufbxi_check_err(&ac->error, copy); - memcpy(copy, str->data, length); - copy[str->length] = '\0'; - str->data = copy; - } else { - ufbx_assert(str->data == ufbxi_empty_char); - } - - return 1; -} - -static bool ufbxi_prop_override_prop_name_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb; - if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; - return ufbxi_str_less(a->prop_name, b->prop_name); -} - -static bool ufbxi_prop_override_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb; - if (a->element_id != b->element_id) return a->element_id < b->element_id; - if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key; - return strcmp(a->prop_name.data, b->prop_name.data) < 0; -} - -static bool ufbxi_transform_override_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_transform_override *a = (const ufbx_transform_override*)va, *b = (const ufbx_transform_override*)vb; - return a->node_id < b->node_id; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_anim_context *ac) -{ - const ufbx_scene *scene = ac->scene; - ufbx_anim *anim = &ac->anim; - - ufbxi_init_ator(&ac->error, &ac->ator_result, &ac->opts.result_allocator, "result"); - ac->result.unordered = true; - ac->result.ator = &ac->ator_result; - - anim->ignore_connections = ac->opts.ignore_connections; - anim->custom = true; - - size_t num_layers = ac->opts.layer_ids.count; - anim->layers.count = num_layers; - anim->layers.data = ufbxi_push_zero(&ac->result, ufbx_anim_layer*, num_layers); - ufbxi_check_err(&ac->error, anim->layers.data); - - if (ac->opts.override_layer_weights.count > 0) { - ufbxi_check_err_msg(&ac->error, ac->opts.override_layer_weights.count == num_layers, "override_layer_weights[] count must match layer_ids[] count"); - anim->override_layer_weights.data = ufbxi_push_copy(&ac->result, ufbx_real, num_layers, ac->opts.override_layer_weights.data); - ufbxi_check_err(&ac->error, anim->override_layer_weights.data); - anim->override_layer_weights.count = num_layers; - } - - for (size_t i = 0; i < num_layers; i++) { - uint32_t index = ac->opts.layer_ids.data[i]; - ufbxi_check_err_msg(&ac->error, index < scene->anim_layers.count, "layer_ids out of bounds"); - anim->layers.data[i] = ac->scene->anim_layers.data[index]; - } - - ufbx_const_prop_override_desc_list prop_overrides = ac->opts.prop_overrides; - if (prop_overrides.count > 0) { - anim->prop_overrides.count = prop_overrides.count; - anim->prop_overrides.data = ufbxi_push_zero(&ac->result, ufbx_prop_override, prop_overrides.count); - ufbxi_check_err(&ac->error, anim->prop_overrides.data); - - for (size_t i = 0; i < prop_overrides.count; i++) { - const ufbx_prop_override_desc *src = &prop_overrides.data[i]; - ufbx_prop_override *dst = &anim->prop_overrides.data[i]; - - dst->element_id = src->element_id; - dst->value = src->value; - dst->value_int = src->value_int; - - if (dst->value.x != 0.0f && dst->value_int == 0) { - dst->value_int = (int64_t)dst->value.x; - } else if (dst->value_int != 0 && dst->value.x == 0.0f) { - dst->value.x = (ufbx_real)dst->value_int; - } - - ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->prop_name, &src->prop_name)); - ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->value_str, &src->value_str)); - - dst->_internal_key = ufbxi_get_name_key(dst->prop_name.data, dst->prop_name.length); - } - - // Sort `anim->prop_overrides` first by `prop_name` only so we can deduplicate and - // convert them to global strings in `ufbxi_strings[]` if possible. - ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_prop_name_less, NULL); - - const ufbx_string *global_str = ufbxi_strings, *global_end = global_str + ufbxi_arraycount(ufbxi_strings); - ufbx_string prev_name = { ufbxi_empty_char }; - ufbxi_for_list(ufbx_prop_override, over, anim->prop_overrides) { - if (over->value_str.length > 0) { - ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->value_str)); - } - - if (ufbxi_str_equal(over->prop_name, prev_name)) { - over->prop_name = prev_name; - continue; - } - - while (global_str != global_end && ufbxi_str_less(*global_str, over->prop_name)) { - ++global_str; - } - - if (global_str != global_end && ufbxi_str_equal(*global_str, over->prop_name)) { - over->prop_name = *global_str; - } else { - ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->prop_name)); - } - - prev_name = over->prop_name; - } - - // Sort `anim->prop_overrides` to the actual order expected by evaluation. - ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_less, NULL); - - for (size_t i = 1; i < prop_overrides.count; i++) { - const ufbx_prop_override *prev = &anim->prop_overrides.data[i - 1]; - const ufbx_prop_override *next = &anim->prop_overrides.data[i]; - if (prev->element_id == next->element_id && prev->prop_name.data == next->prop_name.data) { - ufbxi_fmt_err_info(&ac->error, "element %u prop \"%s\"", prev->element_id, prev->prop_name.data); - ufbxi_fail_err_msg(&ac->error, "Duplicate override", "Duplicate override"); - } - } - } - - if (ac->opts.transform_overrides.count > 0) { - anim->transform_overrides.count = ac->opts.transform_overrides.count; - anim->transform_overrides.data = ufbxi_push_copy(&ac->result, ufbx_transform_override, anim->transform_overrides.count, ac->opts.transform_overrides.data); - ufbxi_check_err(&ac->error, anim->transform_overrides.data); - ufbxi_unstable_sort(anim->transform_overrides.data, anim->transform_overrides.count, sizeof(ufbx_transform_override), &ufbxi_transform_override_less, NULL); - } - - ac->imp = ufbxi_push(&ac->result, ufbxi_anim_imp, 1); - ufbxi_check_err(&ac->error, ac->imp); - - ufbxi_init_ref(&ac->imp->refcount, UFBXI_ANIM_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, scene))->refcount); - - ac->imp->magic = UFBXI_ANIM_IMP_MAGIC; - ac->imp->anim = ac->anim; - ac->imp->refcount.ator = ac->ator_result; - ac->imp->refcount.buf = ac->result; - - return 1; -} - -// -- Animation baking - -typedef struct { - ufbxi_refcount refcount; - ufbx_baked_anim bake; - uint32_t magic; -} ufbxi_baked_anim_imp; - -#if UFBXI_FEATURE_ANIMATION_BAKING - -typedef struct { - double time; - uint32_t flags; -} ufbxi_bake_time; - -UFBX_LIST_TYPE(ufbxi_bake_time_list, ufbxi_bake_time); - -typedef struct { - ufbx_error error; - ufbxi_allocator ator_tmp; - ufbxi_allocator ator_result; - - ufbxi_buf result; - ufbxi_buf tmp; - ufbxi_buf tmp_prop; - ufbxi_buf tmp_times; - ufbxi_buf tmp_bake_props; - ufbxi_buf tmp_nodes; - ufbxi_buf tmp_elements; - ufbxi_buf tmp_props; - ufbxi_buf tmp_bake_stack; - - ufbxi_bake_time_list layer_weight_times; - - ufbx_baked_node **baked_nodes; - bool *nodes_to_bake; - - char *tmp_arr; - size_t tmp_arr_size; - - const ufbx_scene *scene; - const ufbx_anim *anim; - ufbx_bake_opts opts; - - double ktime_offset; - - double time_begin; - double time_end; - double time_min; - double time_max; - - ufbx_baked_anim bake; - ufbxi_baked_anim_imp *imp; -} ufbxi_bake_context; - -typedef struct { - uint32_t sort_id; - uint32_t element_id; - const char *prop_name; - ufbx_anim_value *anim_value; -} ufbxi_bake_prop; - -static bool ufbxi_bake_prop_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbxi_bake_prop *a = (const ufbxi_bake_prop*)va; - const ufbxi_bake_prop *b = (const ufbxi_bake_prop*)vb; - if (a->sort_id != b->sort_id) return a->sort_id < b->sort_id; - if (a->element_id != b->element_id) return a->element_id < b->element_id; - if (a->prop_name != b->prop_name) return strcmp(a->prop_name, b->prop_name) < 0; - return false; -} - -ufbx_static_assert(bake_step_left, UFBX_BAKED_KEY_STEP_LEFT == 0x1); -ufbx_static_assert(bake_step_right, UFBX_BAKED_KEY_STEP_RIGHT == 0x2); -ufbx_static_assert(bake_step_key, UFBX_BAKED_KEY_STEP_KEY == 0x4); -static ufbxi_forceinline int ufbxi_cmp_bake_time(ufbxi_bake_time a, ufbxi_bake_time b) -{ - if (a.time != b.time) return a.time < b.time ? -1 : 1; - // Bit twiddling for a fast sorting of `0x1 (LEFT) < 0x0 < 0x2 (RIGHT)` - // by `step ^ 1`: `0x0 (LEFT) < 0x1 < 0x3 (RIGHT)` - uint32_t a_step = a.flags & 0x3, b_step = b.flags & 0x3; - if (a_step != b_step) return (a_step ^ 0x1) < (b_step ^ 0x1) ? -1 : 1; - return 0; -} - -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_bake_push_time(ufbxi_bake_context *bc, double time, uint32_t flags) -{ - ufbxi_bake_time *p_key = ufbxi_push_fast(&bc->tmp_times, ufbxi_bake_time, 1); - if (!p_key) return 0; - p_key->time = time; - p_key->flags = flags; - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_times(ufbxi_bake_context *bc, const ufbx_anim_value *anim_value, bool resample_linear, uint32_t key_flag) -{ - double sample_rate = bc->opts.resample_rate; - double min_duration = bc->opts.minimum_sample_rate > 0.0 ? 1.0 / bc->opts.minimum_sample_rate : 0.0; - - for (size_t curve_ix = 0; curve_ix < 3; curve_ix++) { - ufbx_anim_curve *curve = anim_value->curves[curve_ix]; - if (!curve) continue; - - const ufbx_keyframe *keys = curve->keyframes.data; - size_t num_keys = curve->keyframes.count; - for (size_t key_ix = 0; key_ix < num_keys; key_ix++) { - ufbx_keyframe a = keys[key_ix]; - double a_time = a.time; - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, key_flag)); - if (key_ix + 1 >= num_keys) break; - ufbx_keyframe b = keys[key_ix + 1]; - double b_time = b.time; - - // Skip fully flat sections - if (a.value == b.value && a.right.dy == 0.0f && b.left.dy == 0.0f) continue; - - if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_PREV) { - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, b_time, UFBX_BAKED_KEY_STEP_LEFT)); - } else if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT) { - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, UFBX_BAKED_KEY_STEP_RIGHT)); - } else if ((resample_linear || a.interpolation == UFBX_INTERPOLATION_CUBIC) && sample_rate > 0.0) { - double duration = b_time - a_time; - if (duration <= min_duration) continue; - - double factor = 1.0; - while (duration * sample_rate / factor >= (double)bc->opts.max_keyframe_segments) { - factor *= 2.0; - } - - double padding = 0.5 / sample_rate; - double start = ufbx_ceil((a_time + padding) * sample_rate / factor) * factor; - double stop = b_time - padding; - for (size_t i = 0; i < bc->opts.max_keyframe_segments; i++) { - double time = (start + (double)i * factor) / sample_rate; - if (time >= stop) break; - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time, 0)); - } - } - } - } - - return 1; -} - -static const char *const ufbxi_transform_props[] = { - ufbxi_Lcl_Translation, ufbxi_Lcl_Rotation, ufbxi_Lcl_Scaling, ufbxi_PreRotation, ufbxi_PostRotation, - ufbxi_RotationOffset, ufbxi_ScalingOffset, ufbxi_RotationPivot, ufbxi_ScalingPivot, ufbxi_RotationOrder, -}; - -static const char *const ufbxi_complex_translation_props[] = { - ufbxi_ScalingPivot, ufbxi_RotationPivot, ufbxi_RotationOffset, ufbxi_ScalingOffset, -}; - -static const char *const ufbxi_complex_rotation_props[] = { - ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder, -}; - -static const char *const ufbxi_complex_rotation_sources[] = { - ufbxi_Lcl_Rotation, ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder, -}; - -ufbxi_nodiscard static ufbxi_noinline bool ufbxi_in_list(const char *const *items, size_t count, const char *item) -{ - for (size_t i = 0; i < count; i++) { - if (items[i] == item) return true; - } - return false; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_sort_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time *times, size_t count) -{ - ufbxi_check_err(&bc->error, ufbxi_grow_array(&bc->ator_tmp, &bc->tmp_arr, &bc->tmp_arr_size, count * sizeof(ufbxi_bake_time))); - ufbxi_macro_stable_sort(ufbxi_bake_time, 32, times, bc->tmp_arr, count, ( ufbxi_cmp_bake_time(*a, *b) < 0 )); - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time_list *p_dst) -{ - if (bc->layer_weight_times.count > 0) { - ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_times, ufbxi_bake_time, bc->layer_weight_times.count, bc->layer_weight_times.data)); - } - - if (bc->tmp_times.num_items == 0) { - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_begin, 0)); - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_end, 0)); - } - - size_t num_times = bc->tmp_times.num_items; - ufbxi_bake_time *times = ufbxi_push_pop(&bc->tmp_prop, &bc->tmp_times, ufbxi_bake_time, num_times); - ufbxi_check_err(&bc->error, times); - - ufbxi_check_err(&bc->error, ufbxi_sort_bake_times(bc, times, num_times)); - - // Deduplicate times - if (num_times > 0) { - size_t dst = 0; - ufbxi_bake_time prev = times[0]; - for (size_t src = 1; src < num_times; src++) { - ufbxi_bake_time next = times[src]; - // Merge keys with the same time and step flags `(0x1, 0x2)` - if (next.time == prev.time) { - if (((next.flags ^ prev.flags) & 0x3) == 0) { - prev.flags |= next.flags; - continue; - } else if (prev.flags & UFBX_BAKED_KEY_STEP_LEFT) { - next.flags |= UFBX_BAKED_KEY_STEP_KEY; - } else if (next.flags & UFBX_BAKED_KEY_STEP_RIGHT) { - prev.flags |= UFBX_BAKED_KEY_STEP_KEY; - } - } - - times[dst++] = prev; - prev = next; - } - times[dst++] = prev; - num_times = dst; - } - - // Cull too close resampled keys, these may arise during merging multiple times - if (num_times > 0) { - double min_dist = 0.25 / bc->opts.resample_rate; - uint32_t keep_flags = UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT|UFBX_BAKED_KEY_STEP_KEY|UFBX_BAKED_KEY_KEYFRAME; - - size_t dst = 0; - for (size_t src = 0; src < num_times; src++) { - ufbxi_bake_time cur = times[src]; - double delta = UFBX_INFINITY; - - bool keep = true; - if ((cur.flags & keep_flags) == 0) { - if (dst > 0) delta = cur.time - times[dst - 1].time; - if (src + 1 < num_times) delta = ufbx_fmin(delta, times[src + 1].time - cur.time); - if (delta < min_dist) keep = false; - } - if (keep) { - times[dst++] = cur; - } - } - num_times = dst; - } - - // Enforce maximum sample rate - if (bc->opts.maximum_sample_rate > 0.0) { - const double epsilon = 0.0078125 / bc->opts.maximum_sample_rate; - double sample_rate = bc->opts.maximum_sample_rate; - double max_interval = 1.0 / bc->opts.maximum_sample_rate; - double min_interval = 1.0 / bc->opts.maximum_sample_rate - epsilon; - size_t dst = 0, src = 0; - - // Pre-expand constant keyframes - for (size_t i = 0; i < num_times; i++) { - if ((times[i].flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { - double sign = (times[i].flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -1.0 : 1.0; - double time = times[i].time + sign * max_interval; - if (i > 0) time = ufbx_fmax(time, times[i - 1].time); - if (i + 1 < num_times) time = ufbx_fmin(time, times[i + 1].time); - times[i].time = time; - times[i].flags = UFBX_BAKED_KEY_REDUCED; - } - } - - ufbxi_bake_time prev_time = { -UFBX_INFINITY }; - while (src < num_times) { - ufbxi_bake_time src_time = times[src]; - src++; - - size_t start_src = src; - ufbxi_bake_time next_time; - next_time.time = ufbx_ceil(src_time.time * sample_rate - epsilon) / sample_rate; - next_time.flags = UFBX_BAKED_KEY_REDUCED; - while (src < num_times && times[src].time <= next_time.time + epsilon) { - src++; - } - - if (src != start_src || src_time.time - prev_time.time <= min_interval) { - prev_time = next_time; - } else { - prev_time = src_time; - } - - if (dst == 0 || prev_time.time > times[dst - 1].time) { - times[dst++] = prev_time; - } - } - - num_times = dst; - } - - if (num_times > 0) { - if (times[0].time < bc->time_min) bc->time_min = times[0].time; - if (times[num_times - 1].time > bc->time_max) bc->time_max = times[num_times - 1].time; - } - - p_dst->data = times; - p_dst->count = num_times; - - return 1; -} - -#define ufbxi_add_epsilon(a, epsilon) ((a)>0 ? (a)*(epsilon) : (a)/(epsilon)) -#define ufbxi_sub_epsilon(a, epsilon) ((a)>0 ? (a)/(epsilon) : (a)*(epsilon)) - -static ufbxi_noinline bool ufbxi_postprocess_step(ufbxi_bake_context *bc, double prev_time, double next_time, double *p_time, ufbx_baked_key_flags flags) -{ - ufbxi_dev_assert((flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0); - bool left = (flags & UFBX_BAKED_KEY_STEP_LEFT) != 0; - - double step = 0.001; - double epsilon = 1.0 + UFBX_FLT_EPSILON * 4.0f; - - double time = *p_time; - switch (bc->opts.step_handling) { - case UFBX_BAKE_STEP_HANDLING_DEFAULT: - break; - case UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION: - step = bc->opts.step_custom_duration; - epsilon = 1.0 + bc->opts.step_custom_epsilon; - break; - case UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME: - return true; - case UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE: - if (left) { - *p_time = time = ufbx_nextafter(time, -UFBX_INFINITY); - return time > prev_time; - } else { - *p_time = time = ufbx_nextafter(time, UFBX_INFINITY); - return time < next_time; - } - case UFBX_BAKE_STEP_HANDLING_IGNORE: - return false; - default: - ufbxi_unreachable("Unhandled bake step handling"); - return false; - } - - if (left) { - double min_time = ufbx_fmax(prev_time + step, ufbxi_add_epsilon(prev_time, epsilon)); - *p_time = time = ufbx_fmin(time - step, ufbxi_sub_epsilon(time, epsilon)); - return time > min_time; - } else { - double max_time = ufbx_fmin(next_time - step, ufbxi_sub_epsilon(next_time, epsilon)); - *p_time = time = ufbx_fmax(time + step, ufbxi_add_epsilon(time, epsilon)); - return time < max_time; - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_vec3(ufbxi_bake_context *bc, ufbx_baked_vec3_list *p_dst, bool *p_constant, ufbx_baked_vec3_list src) -{ - if (src.count == 0) return 1; - - // Offset times - if (bc->ktime_offset != 0.0) { - double scale = (double)bc->scene->metadata.ktime_second; - double offset = bc->ktime_offset; - for (size_t i = 0; i < src.count; i++) { - src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale; - } - } - - // Postprocess stepped tangents - { - size_t dst = 0; - double prev_time = src.data[0].time; - for (size_t i = 0; i < src.count; i++) { - ufbx_baked_vec3 cur = src.data[i]; - double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY; - bool keep = true; - if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { - keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags); - } - if (keep) { - src.data[dst] = cur; - dst++; - prev_time = cur.time; - } - } - src.count = dst; - } - - if (bc->opts.key_reduction_enabled) { - double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold; - for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) { - size_t dst = 1; - for (size_t i = 1; i < src.count; i++) { - ufbx_baked_vec3 prev = src.data[i - 1]; - ufbx_baked_vec3 cur = src.data[i]; - if (i + 1 < src.count) { - ufbx_baked_vec3 next = src.data[i + 1]; - double delta = (cur.time - prev.time) / (next.time - prev.time); - ufbx_vec3 tmp = ufbxi_lerp3(prev.value, next.value, (ufbx_real)delta); - double error = 0.0; - error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x); - error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y); - error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z); - if (error <= threshold) { - src.data[dst] = src.data[i + 1]; - i += 1; - dst += 1; - continue; - } - } - - src.data[dst] = src.data[i]; - dst += 1; - } - if (dst == src.count) break; - src.count = dst; - } - } - - bool constant = true; - ufbx_vec3 ref = src.data[0].value; - for (size_t i = 1; i < src.count; i++) { - ufbx_vec3 v = src.data[i].value; - if (v.x != ref.x || v.y != ref.y || v.z != ref.z) { - constant = false; - break; - } - } - *p_constant = constant; - - p_dst->count = src.count; - p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_vec3, src.count, src.data); - ufbxi_check_err(&bc->error, p_dst->data); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_quat(ufbxi_bake_context *bc, ufbx_baked_quat_list *p_dst, bool *p_constant, ufbx_baked_quat_list src) -{ - if (src.count == 0) return 1; - - // Offset times - if (bc->ktime_offset != 0.0) { - double scale = (double)bc->scene->metadata.ktime_second; - double offset = bc->ktime_offset; - for (size_t i = 0; i < src.count; i++) { - src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale; - } - } - - // Postprocess stepped tangents - { - size_t dst = 0; - double prev_time = src.data[0].time; - for (size_t i = 0; i < src.count; i++) { - ufbx_baked_quat cur = src.data[i]; - double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY; - bool keep = true; - if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { - keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags); - } - if (keep) { - prev_time = cur.time; - src.data[dst] = cur; - dst++; - } - } - src.count = dst; - } - - // Fix quaternion antipodality - for (size_t i = 1; i < src.count; i++) { - src.data[i].value = ufbx_quat_fix_antipodal(src.data[i].value, src.data[i - 1].value); - } - - if (bc->opts.key_reduction_enabled) { - double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold; - for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) { - size_t dst = 1; - for (size_t i = 1; i < src.count; i++) { - ufbx_baked_quat prev = src.data[i - 1]; - ufbx_baked_quat cur = src.data[i]; - if (i + 1 < src.count) { - ufbx_baked_quat next = src.data[i + 1]; - double delta = (cur.time - prev.time) / (next.time - prev.time); - double error = 0.0; - - if (bc->opts.key_reduction_rotation) { - ufbx_quat tmp = ufbx_quat_slerp(prev.value, next.value, (ufbx_real)delta); - error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x); - error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y); - error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z); - error += ((double)tmp.w - (double)cur.value.w) * ((double)tmp.w - (double)cur.value.w); - } else { - error += ((double)prev.value.x - (double)cur.value.x) * ((double)prev.value.x - (double)cur.value.x); - error += ((double)prev.value.y - (double)cur.value.y) * ((double)prev.value.y - (double)cur.value.y); - error += ((double)prev.value.z - (double)cur.value.z) * ((double)prev.value.z - (double)cur.value.z); - error += ((double)prev.value.w - (double)cur.value.w) * ((double)prev.value.w - (double)cur.value.w); - error += ((double)next.value.x - (double)cur.value.x) * ((double)next.value.x - (double)cur.value.x); - error += ((double)next.value.y - (double)cur.value.y) * ((double)next.value.y - (double)cur.value.y); - error += ((double)next.value.z - (double)cur.value.z) * ((double)next.value.z - (double)cur.value.z); - error += ((double)next.value.w - (double)cur.value.w) * ((double)next.value.w - (double)cur.value.w); - error *= 0.5; - } - - if (error <= threshold) { - src.data[dst] = src.data[i + 1]; - i += 1; - dst += 1; - continue; - } - } - - src.data[dst] = src.data[i]; - dst += 1; - } - if (dst == src.count) break; - src.count = dst; - } - } - - bool constant = true; - ufbx_quat ref = src.data[0].value; - for (size_t i = 1; i < src.count; i++) { - ufbx_quat v = src.data[i].value; - if (v.x != ref.x || v.y != ref.y || v.z != ref.z || v.y != ref.w) { - constant = false; - break; - } - } - *p_constant = constant; - - p_dst->count = src.count; - p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_quat, src.count, src.data); - ufbxi_check_err(&bc->error, p_dst->data); - - return 1; -} - -static ufbxi_forceinline double ufbxi_bake_time_sample_time(ufbxi_bake_time time) -{ - // Move an infinitesimal step for stepped tangents - if ((time.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) { - double dir = (time.flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -UFBX_INFINITY : UFBX_INFINITY; - return ufbx_nextafter(time.time, dir); - } else { - return time.time; - } -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_resampled_times(ufbxi_bake_context *bc, const ufbx_baked_vec3_list *p_keys) -{ - ufbx_baked_vec3_list keys = *p_keys; - - ufbxi_bake_time *times = ufbxi_push(&bc->tmp_times, ufbxi_bake_time, keys.count); - ufbxi_check_err(&bc->error, times); - for (size_t i = 0; i < keys.count; i++) { - ufbx_baked_key_flags flags = keys.data[i].flags; - double time = keys.data[i].time; - if ((flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 && i + 1 < keys.count && (keys.data[i + 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) { - time = keys.data[i + 1].time; - } else if ((flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && i > 0 && (keys.data[i - 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) { - time = keys.data[i - 1].time; - } - times[i].time = time; - times[i].flags = flags & 0x7; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) -{ - ufbx_assert(bc->baked_nodes && bc->nodes_to_bake); - - ufbx_node *node = (ufbx_node*)bc->scene->elements.data[element_id]; - ufbxi_dev_assert(node->element.type == UFBX_ELEMENT_NODE); - - bool complex_translation = false; - bool complex_rotation = false; - - for (size_t i = 0; i < ufbxi_arraycount(ufbxi_complex_translation_props); i++) { - const char *name = ufbxi_complex_translation_props[i]; - ufbx_prop *prop = ufbxi_find_prop(&node->props, name); - if (prop && !ufbxi_is_vec3_zero(prop->value_vec3)) { - complex_translation = true; - } - ufbxi_for(ufbxi_bake_prop, bprop, props, count) { - if (bprop->prop_name == name) { - complex_translation = true; - } - } - } - - for (size_t i = 0; i < ufbxi_arraycount(ufbxi_complex_rotation_props); i++) { - const char *name = ufbxi_complex_rotation_props[i]; - ufbxi_for(ufbxi_bake_prop, bprop, props, count) { - if (bprop->prop_name == name) { - complex_rotation = true; - } - } - } - - ufbxi_bake_time_list times_t, times_r, times_s; - - // Translation - bool resample_translation = false; - - // Account for the _resampled_ scale helper scale animation to keep the - // translation scale consistent with the parent scaling. - ufbx_baked_node *scale_helper_t = NULL; - ufbx_vec3 constant_scale_t = { 1.0f, 1.0f, 1.0f }; - if (!node->is_scale_helper && node->parent && node->parent->scale_helper) { - scale_helper_t = bc->baked_nodes[node->parent->scale_helper->typed_id]; - if (scale_helper_t) { - if (!scale_helper_t->constant_scale) { - resample_translation = true; - } - ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_t->scale_keys)); - } else { - constant_scale_t = node->parent->scale_helper->inherit_scale; - } - } - - if (complex_translation) { - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - // Literally any transform related property can affect complex translation - if (ufbxi_in_list(ufbxi_transform_props, ufbxi_arraycount(ufbxi_transform_props), prop->prop_name)) { - bool resample_linear = resample_translation || prop->prop_name != ufbxi_Lcl_Translation; - uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Translation ? UFBX_BAKED_KEY_KEYFRAME : 0; - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); - } - } - } else { - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - if (prop->prop_name == ufbxi_Lcl_Translation) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_translation, UFBX_BAKED_KEY_KEYFRAME)); - } - } - } - - ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_t)); - - // Rotation - if (complex_rotation) { - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - if (ufbxi_in_list(ufbxi_complex_rotation_sources, ufbxi_arraycount(ufbxi_complex_rotation_sources), prop->prop_name)) { - bool resample_linear = !bc->opts.no_resample_rotation || prop->prop_name != ufbxi_Lcl_Rotation; - uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Rotation ? UFBX_BAKED_KEY_KEYFRAME : 0; - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); - } - } - } else { - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - if (prop->prop_name == ufbxi_Lcl_Rotation) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, !bc->opts.no_resample_rotation, UFBX_BAKED_KEY_KEYFRAME)); - } - } - } - ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_r)); - - // Scaling - bool resample_scale = false; - - // Account for the resampled scale - ufbx_baked_node *scale_helper_s = NULL; - ufbx_vec3 constant_scale_s = { 1.0f, 1.0f, 1.0f }; - if (node->is_scale_helper && node->parent && node->parent->inherit_scale_node && node->parent->inherit_scale_node->scale_helper) { - ufbx_node *inherit_helper = node->parent->inherit_scale_node->scale_helper; - scale_helper_s = bc->baked_nodes[inherit_helper->typed_id]; - if (scale_helper_s) { - if (!scale_helper_s->constant_scale) { - resample_scale = true; - } - ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_s->scale_keys)); - } else { - constant_scale_s = inherit_helper->local_transform.scale; - } - } - - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - if (prop->prop_name == ufbxi_Lcl_Scaling) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_scale, UFBX_BAKED_KEY_KEYFRAME)); - } - } - ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_s)); - - ufbx_baked_vec3_list keys_t; - ufbx_baked_quat_list keys_r; - ufbx_baked_vec3_list keys_s; - - keys_t.count = times_t.count; - keys_t.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys_t.count); - ufbxi_check_err(&bc->error, keys_t.data); - - keys_r.count = times_r.count; - keys_r.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_quat, keys_r.count); - ufbxi_check_err(&bc->error, keys_r.data); - - keys_s.count = times_s.count; - keys_s.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys_s.count); - ufbxi_check_err(&bc->error, keys_s.data); - - size_t ix_t = 0, ix_r = 0, ix_s = 0; - while (ix_t < times_t.count || ix_r < times_r.count || ix_s < times_s.count) { - ufbxi_bake_time bake_time = { UFBX_INFINITY }; - uint32_t flags_r = 0, flags_t = 0, flags_s = 0; - - uint32_t flags = 0; - if (ix_r < times_r.count) { - bake_time = times_r.data[ix_r]; - flags_r = bake_time.flags; - bake_time.flags &= 0x7; - flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION; - } - if (ix_t < times_t.count) { - ufbxi_bake_time t = times_t.data[ix_t]; - int cmp = ufbxi_cmp_bake_time(t, bake_time); - if (cmp <= 0) { - if (cmp < 0) { - bake_time = t; - flags = 0; - } - bake_time.flags |= t.flags & 0x7; - flags_t = t.flags; - flags |= UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; - } - } - if (ix_s < times_s.count) { - ufbxi_bake_time t = times_s.data[ix_s]; - int cmp = ufbxi_cmp_bake_time(t, bake_time); - if (cmp <= 0) { - if (cmp < 0) { - bake_time = t; - flags = 0; - } - bake_time.flags |= t.flags & 0x7; - flags_s = t.flags; - flags |= UFBX_TRANSFORM_FLAG_INCLUDE_SCALE; - } - } - - flags |= UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER|UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE|UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES; - if (bc->opts.evaluate_flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) { - flags |= UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION; - } - - double eval_time = ufbxi_bake_time_sample_time(bake_time); - ufbx_transform transform = ufbx_evaluate_transform_flags(bc->anim, node, eval_time, flags); - - if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) { - if (scale_helper_t) { - ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_t->scale_keys, eval_time); - transform.translation.x *= scale.x; - transform.translation.y *= scale.y; - transform.translation.z *= scale.z; - } - - transform.translation.x *= constant_scale_t.x; - transform.translation.y *= constant_scale_t.y; - transform.translation.z *= constant_scale_t.z; - - keys_t.data[ix_t].time = bake_time.time; - keys_t.data[ix_t].value = transform.translation; - keys_t.data[ix_t].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_t); - ix_t++; - } - if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) { - keys_r.data[ix_r].time = bake_time.time; - keys_r.data[ix_r].value = transform.rotation; - keys_r.data[ix_r].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_r); - ix_r++; - } - if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) { - if (scale_helper_s) { - ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_s->scale_keys, eval_time); - transform.scale.x *= scale.x; - transform.scale.y *= scale.y; - transform.scale.z *= scale.z; - } - - transform.scale.x *= constant_scale_s.x; - transform.scale.y *= constant_scale_s.y; - transform.scale.z *= constant_scale_s.z; - - keys_s.data[ix_s].time = bake_time.time; - keys_s.data[ix_s].value = transform.scale; - keys_s.data[ix_s].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_s); - ix_s++; - } - } - - ufbx_baked_node *baked_node = ufbxi_push_zero(&bc->tmp_nodes, ufbx_baked_node, 1); - ufbxi_check_err(&bc->error, baked_node); - - baked_node->element_id = node->element_id; - baked_node->typed_id = node->typed_id; - ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->translation_keys, &baked_node->constant_translation, keys_t)); - ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_quat(bc, &baked_node->rotation_keys, &baked_node->constant_rotation, keys_r)); - ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->scale_keys, &baked_node->constant_scale, keys_s)); - - bc->baked_nodes[node->typed_id] = baked_node; - - ufbxi_buf_clear(&bc->tmp_prop); - - // If this node is a scale helper, make sure to bake its siblings and - // potentially their scale helpers if they are not a part of the animation. - if (node->is_scale_helper) { - ufbx_assert(node->parent); - ufbxi_for_ptr_list(ufbx_node, p_child, node->parent->children) { - ufbx_node *child = *p_child; - if (child == node) continue; - if (!bc->nodes_to_bake[child->typed_id]) { - bc->nodes_to_bake[child->typed_id] = true; - ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_bake_stack, uint32_t, 1, &child->element_id)); - } - if (child->inherit_scale_node && child->inherit_scale_node->scale_helper && child->scale_helper - && bc->nodes_to_bake[child->inherit_scale_node->scale_helper->typed_id]) { - ufbx_assert(bc->baked_nodes[child->inherit_scale_node->scale_helper->typed_id]); - if (!bc->nodes_to_bake[child->scale_helper->typed_id]) { - bc->nodes_to_bake[child->scale_helper->typed_id] = true; - ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_bake_stack, uint32_t, 1, &child->scale_helper->element_id)); - } - } - } - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) -{ - ufbxi_check_err(&bc->error, ufbxi_bake_node_imp(bc, element_id, props, count)); - - // Baking a node may cause further nodes to be baked, so keep going - // until all dependencies are baked. - while (bc->tmp_bake_stack.num_items > 0) { - uint32_t child_id = 0; - ufbxi_pop(&bc->tmp_bake_stack, uint32_t, 1, &child_id); - ufbxi_check_err(&bc->error, ufbxi_bake_node_imp(bc, child_id, NULL, 0)); - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_context *bc, ufbx_element *element, const char *prop_name, ufbxi_bake_prop *props, size_t count) -{ - ufbxi_for(ufbxi_bake_prop, prop, props, count) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, false, UFBX_BAKED_KEY_KEYFRAME)); - } - - ufbxi_bake_time_list times; - ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×)); - - ufbx_baked_vec3_list keys; - keys.count = times.count; - keys.data = ufbxi_push(&bc->tmp_prop, ufbx_baked_vec3, keys.count); - ufbxi_check_err(&bc->error, keys.data); - - ufbx_string name; - name.data = prop_name; - name.length = strlen(prop_name); - - for (size_t i = 0; i < times.count; i++) { - ufbxi_bake_time bake_time = times.data[i]; - double eval_time = ufbxi_bake_time_sample_time(bake_time); - ufbx_prop prop = ufbx_evaluate_prop_len_flags(bc->anim, element, name.data, name.length, eval_time, bc->opts.evaluate_flags); - keys.data[i].time = bake_time.time; - keys.data[i].value = prop.value_vec3; - keys.data[i].flags = (ufbx_baked_key_flags)bake_time.flags; - } - - ufbx_baked_prop *baked_prop = ufbxi_push_zero(&bc->tmp_props, ufbx_baked_prop, 1); - ufbxi_check_err(&bc->error, baked_prop); - - baked_prop->name.length = strlen(prop_name); - baked_prop->name.data = ufbxi_push_copy(&bc->result, char, baked_prop->name.length + 1, prop_name); - ufbxi_check_err(&bc->error, baked_prop->name.data); - - ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_prop->keys, &baked_prop->constant_value, keys)); - - ufbxi_buf_clear(&bc->tmp_prop); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_element(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_prop *props, size_t count) -{ - ufbx_element *element = bc->scene->elements.data[element_id]; - if (element->type == UFBX_ELEMENT_NODE && !bc->opts.skip_node_transforms) { - ufbxi_check_err(&bc->error, ufbxi_bake_node(bc, element_id, props, count)); - } - - size_t begin = 0; - while (begin < count) { - const char *prop_name = props[begin].prop_name; - size_t end = begin + 1; - while (end < count && props[end].prop_name == prop_name) { - end++; - } - - // Don't bake transform related props for nodes unless specifically requested - if (element->type == UFBX_ELEMENT_NODE && !bc->opts.bake_transform_props && ufbxi_in_list(ufbxi_transform_props, ufbxi_arraycount(ufbxi_transform_props), prop_name)) { - begin = end; - continue; - } - - ufbxi_check_err(&bc->error, ufbxi_bake_anim_prop(bc, element, prop_name, props + begin, end - begin)); - begin = end; - } - - size_t num_props = bc->tmp_props.num_items; - if (num_props > 0) { - ufbx_baked_element *baked_elem = ufbxi_push_zero(&bc->tmp_elements, ufbx_baked_element, 1); - ufbxi_check_err(&bc->error, baked_elem); - - baked_elem->element_id = element->element_id; - baked_elem->props.count = num_props; - baked_elem->props.data = ufbxi_push_pop(&bc->result, &bc->tmp_props, ufbx_baked_prop, num_props); - ufbxi_check_err(&bc->error, baked_elem->props.data); - } - - return 1; -} - -static ufbxi_noinline bool ufbxi_baked_node_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_baked_node *a = (const ufbx_baked_node*)va, *b = (const ufbx_baked_node*)vb; - return a->typed_id < b->typed_id; -} - -static ufbxi_noinline bool ufbxi_baked_element_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_baked_element *a = (const ufbx_baked_element*)va, *b = (const ufbx_baked_element*)vb; - return a->element_id < b->element_id; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc) -{ - const ufbx_anim *anim = bc->anim; - const ufbx_scene *scene = bc->scene; - - if (!bc->opts.skip_node_transforms) { - bc->baked_nodes = ufbxi_push_zero(&bc->result, ufbx_baked_node*, scene->nodes.count); - ufbxi_check_err(&bc->error, bc->baked_nodes); - bc->nodes_to_bake = ufbxi_push_zero(&bc->result, bool, scene->nodes.count); - ufbxi_check_err(&bc->error, bc->nodes_to_bake); - } - - ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, anim->layers) { - ufbx_anim_layer *layer = *p_layer; - - ufbxi_for_list(ufbx_anim_prop, anim_prop, layer->anim_props) { - ufbxi_bake_prop *prop = ufbxi_push(&bc->tmp_bake_props, ufbxi_bake_prop, 1); - ufbxi_check_err(&bc->error, prop); - - ufbx_element *element = anim_prop->element; - - // Sort nodes by `typed_id` to make sure we process them in order. - if (element->type == UFBX_ELEMENT_NODE) { - if (bc->nodes_to_bake) { - bc->nodes_to_bake[element->typed_id] = true; - } - prop->sort_id = element->typed_id; - } else { - prop->sort_id = UINT32_MAX; - } - - prop->element_id = element->element_id; - prop->prop_name = anim_prop->prop_name.data; - prop->anim_value = anim_prop->anim_value; - } - } - - size_t num_props = bc->tmp_bake_props.num_items; - ufbxi_bake_prop *props = ufbxi_push_pop(&bc->tmp, &bc->tmp_bake_props, ufbxi_bake_prop, num_props); - ufbxi_check_err(&bc->error, props); - - ufbxi_unstable_sort(props, num_props, sizeof(ufbxi_bake_prop), &ufbxi_bake_prop_less, NULL); - - // Pre-bake layer weight times - if (!bc->opts.ignore_layer_weight_animation) { - bool has_weight_times = false; - ufbxi_for(ufbxi_bake_prop, prop, props, num_props) { - if (prop->prop_name != ufbxi_Weight) continue; - ufbx_element *element = scene->elements.data[prop->element_id]; - if (element->type == UFBX_ELEMENT_ANIM_LAYER) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, true, 0)); - has_weight_times = true; - } - } - - if (has_weight_times) { - ufbxi_bake_time_list weight_times = { 0 }; - ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, &weight_times)); - - bc->layer_weight_times.count = weight_times.count; - bc->layer_weight_times.data = ufbxi_push_copy(&bc->tmp, ufbxi_bake_time, weight_times.count, weight_times.data); - ufbxi_check_err(&bc->error, bc->layer_weight_times.data); - - ufbxi_buf_clear(&bc->tmp_prop); - } - } - - size_t begin = 0; - while (begin < num_props) { - uint32_t element_id = props[begin].element_id; - size_t end = begin + 1; - while (end < num_props && props[end].element_id == element_id) { - end++; - } - ufbxi_check_err(&bc->error, ufbxi_bake_element(bc, element_id, props + begin, end - begin)); - begin = end; - } - - size_t num_nodes = bc->tmp_nodes.num_items; - size_t num_elements = bc->tmp_elements.num_items; - - bc->bake.nodes.count = num_nodes; - bc->bake.nodes.data = ufbxi_push_pop(&bc->result, &bc->tmp_nodes, ufbx_baked_node, num_nodes); - ufbxi_check_err(&bc->error, bc->bake.nodes.data); - - bc->bake.elements.count = num_elements; - bc->bake.elements.data = ufbxi_push_pop(&bc->result, &bc->tmp_elements, ufbx_baked_element, num_elements); - ufbxi_check_err(&bc->error, bc->bake.elements.data); - - ufbxi_unstable_sort(bc->bake.nodes.data, bc->bake.nodes.count, sizeof(ufbx_baked_node), &ufbxi_baked_node_less, NULL); - ufbxi_unstable_sort(bc->bake.elements.data, bc->bake.elements.count, sizeof(ufbx_baked_element), &ufbxi_baked_element_less, NULL); - - if (bc->time_min < bc->time_max) { - bc->bake.key_time_min = bc->time_min; - bc->bake.key_time_max = bc->time_max; - } - - if (bc->time_begin < bc->time_end) { - bc->bake.playback_time_begin = bc->time_begin; - bc->bake.playback_time_end = bc->time_end; - bc->bake.playback_duration = bc->time_end - bc->time_begin; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_imp(ufbxi_bake_context *bc, const ufbx_anim *anim) -{ - if (bc->opts.resample_rate <= 0.0) bc->opts.resample_rate = 30.0; - if (bc->opts.minimum_sample_rate <= 0.0) bc->opts.minimum_sample_rate = 19.5; - if (bc->opts.max_keyframe_segments == 0) bc->opts.max_keyframe_segments = 32; - if (bc->opts.key_reduction_threshold == 0) bc->opts.key_reduction_threshold = 0.000001; - if (bc->opts.key_reduction_passes == 0) bc->opts.key_reduction_passes = 4; - - if (bc->opts.trim_start_time && anim->time_begin > 0.0) { - bc->ktime_offset = -anim->time_begin * (double)bc->scene->metadata.ktime_second; - } - - ufbxi_init_ator(&bc->error, &bc->ator_tmp, &bc->opts.temp_allocator, "temp"); - ufbxi_init_ator(&bc->error, &bc->ator_result, &bc->opts.result_allocator, "result"); - - bc->result.unordered = true; - bc->result.ator = &bc->ator_result; - - bc->tmp.unordered = true; - bc->tmp.ator = &bc->ator_tmp; - - bc->tmp_prop.ator = &bc->ator_tmp; - bc->tmp_prop.unordered = true; - bc->tmp_prop.clearable = true; - - bc->tmp_times.ator = &bc->ator_tmp; - bc->tmp_bake_props.ator = &bc->ator_tmp; - bc->tmp_nodes.ator = &bc->ator_tmp; - bc->tmp_elements.ator = &bc->ator_tmp; - bc->tmp_props.ator = &bc->ator_tmp; - bc->tmp_bake_stack.ator = &bc->ator_tmp; - - bc->anim = anim; - if (anim->time_begin < anim->time_end) { - bc->time_begin = anim->time_begin; - bc->time_end = anim->time_end; - } - bc->time_min = UFBX_INFINITY; - bc->time_max = -UFBX_INFINITY; - - bc->imp = ufbxi_push(&bc->result, ufbxi_baked_anim_imp, 1); - ufbxi_check_err(&bc->error, bc->imp); - - ufbxi_check_err(&bc->error, ufbxi_bake_anim(bc)); - - ufbxi_init_ref(&bc->imp->refcount, UFBXI_BAKED_ANIM_IMP_MAGIC, NULL); - - bc->bake.metadata.result_memory_used = bc->ator_result.current_size; - bc->bake.metadata.temp_memory_used = bc->ator_tmp.current_size; - bc->bake.metadata.result_allocs = bc->ator_result.num_allocs; - bc->bake.metadata.temp_allocs = bc->ator_tmp.num_allocs; - - bc->imp->magic = UFBXI_BAKED_ANIM_IMP_MAGIC; - bc->imp->bake = bc->bake; - bc->imp->refcount.ator = bc->ator_result; - bc->imp->refcount.buf = bc->result; - - return 1; -} - -#endif - -// -- NURBS - -static ufbxi_forceinline ufbx_real ufbxi_nurbs_weight(const ufbx_real_list *knots, size_t knot, size_t degree, ufbx_real u) -{ - if (knot >= knots->count) return 0.0f; - if (knots->count - knot < degree) return 0.0f; - ufbx_real prev_u = knots->data[knot], next_u = knots->data[knot + degree]; - if (prev_u >= next_u) return 0.0f; - if (u <= prev_u) return 0.0f; - if (u >= next_u) return 1.0f; - return (u - prev_u) / (next_u - prev_u); -} - -static ufbxi_forceinline ufbx_real ufbxi_nurbs_deriv(const ufbx_real_list *knots, size_t knot, size_t degree) -{ - if (knot >= knots->count) return 0.0f; - if (knots->count - knot < degree) return 0.0f; - ufbx_real prev_u = knots->data[knot], next_u = knots->data[knot + degree]; - if (prev_u >= next_u) return 0.0f; - return (ufbx_real)degree / (next_u - prev_u); -} - -typedef struct { - ufbxi_refcount refcount; - ufbx_line_curve curve; - uint32_t magic; -} ufbxi_line_curve_imp; - -ufbx_static_assert(line_curve_imp_offset, offsetof(ufbxi_line_curve_imp, curve) == sizeof(ufbxi_refcount)); - -#if UFBXI_FEATURE_TESSELLATION - -typedef struct { - ufbx_error error; - - ufbx_tessellate_curve_opts opts; - - const ufbx_nurbs_curve *curve; - - ufbxi_allocator ator_tmp; - ufbxi_allocator ator_result; - - ufbxi_buf result; - - ufbx_line_curve line; - - ufbxi_line_curve_imp *imp; - -} ufbxi_tessellate_curve_context; - -typedef struct { - ufbx_error error; - - ufbx_tessellate_surface_opts opts; - - const ufbx_nurbs_surface *surface; - - ufbxi_allocator ator_tmp; - ufbxi_allocator ator_result; - - ufbxi_buf tmp; - ufbxi_buf result; - - ufbxi_map position_map; - - ufbx_mesh mesh; - - ufbxi_mesh_imp *imp; - -} ufbxi_tessellate_surface_context; - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_curve_imp(ufbxi_tessellate_curve_context *tc) -{ - if (tc->opts.span_subdivision <= 0) { - tc->opts.span_subdivision = 4; - } - size_t num_sub = tc->opts.span_subdivision; - - const ufbx_nurbs_curve *curve = tc->curve; - ufbx_line_curve *line = &tc->line; - ufbxi_check_err_msg(&tc->error, curve->basis.valid && curve->control_points.count > 0, "Bad NURBS geometry"); - - ufbxi_init_ator(&tc->error, &tc->ator_tmp, &tc->opts.temp_allocator, "temp"); - ufbxi_init_ator(&tc->error, &tc->ator_result, &tc->opts.result_allocator, "result"); - - tc->result.unordered = true; - tc->result.ator = &tc->ator_result; - - size_t num_spans = curve->basis.spans.count; - - // Check conservatively that we don't overflow anything - { - size_t over_spans = num_spans * 2 * sizeof(ufbx_real); - size_t over = over_spans * num_sub; - ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over, over_spans, num_sub)); - } - - bool is_open = curve->basis.topology == UFBX_NURBS_TOPOLOGY_OPEN; - - size_t num_indices = num_spans + (num_spans - 1) * (num_sub - 1); - size_t num_vertices = num_indices - (is_open ? 0u : 1u); - ufbxi_check_err(&tc->error, num_indices <= INT32_MAX); - - uint32_t *indices = ufbxi_push(&tc->result, uint32_t, num_indices); - ufbx_vec3 *vertices = ufbxi_push(&tc->result, ufbx_vec3, num_vertices); - ufbx_line_segment *segments = ufbxi_push(&tc->result, ufbx_line_segment, 1); - ufbxi_check_err(&tc->error, indices && vertices && segments); - - for (size_t span_ix = 0; span_ix < num_spans; span_ix++) { - size_t num_splits = span_ix + 1 == num_spans ? 1 : num_sub; - - for (size_t sub_ix = 0; sub_ix < num_splits; sub_ix++) { - size_t ix = span_ix * num_sub + sub_ix; - - if (ix < num_vertices) { - ufbx_real u = curve->basis.spans.data[span_ix]; - if (sub_ix > 0) { - ufbx_real t = (ufbx_real)sub_ix / (ufbx_real)num_sub; - u = u * (1.0f - t) + t * curve->basis.spans.data[span_ix + 1]; - } - - ufbx_curve_point point = ufbx_evaluate_nurbs_curve(curve, u); - vertices[ix] = point.position; - indices[ix] = (uint32_t)ix; - } else { - indices[ix] = 0; - } - } - } - - segments[0].index_begin = 0; - segments[0].num_indices = (uint32_t)num_indices; - - line->element.name.data = ufbxi_empty_char; - line->element.type = UFBX_ELEMENT_LINE_CURVE; - line->element.typed_id = UINT32_MAX; - line->element.element_id = UINT32_MAX; - - line->color.x = 1.0f; - line->color.y = 1.0f; - line->color.z = 1.0f; - - line->control_points.data = vertices; - line->control_points.count = num_vertices; - line->point_indices.data = indices; - line->point_indices.count = num_indices; - line->segments.data = segments; - line->segments.count = 1; - - line->from_tessellated_nurbs = true; - - tc->imp = ufbxi_push(&tc->result, ufbxi_line_curve_imp, 1); - ufbxi_check_err(&tc->error, tc->imp); - - ufbxi_init_ref(&tc->imp->refcount, UFBXI_LINE_CURVE_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, curve->element.scene))->refcount); - - tc->imp->magic = UFBXI_LINE_CURVE_IMP_MAGIC; - tc->imp->curve = tc->line; - tc->imp->refcount.ator = tc->ator_result; - tc->imp->refcount.buf = tc->result; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_surface_imp(ufbxi_tessellate_surface_context *tc) -{ - if (tc->opts.span_subdivision_u <= 0) { - tc->opts.span_subdivision_u = 4; - } - if (tc->opts.span_subdivision_v <= 0) { - tc->opts.span_subdivision_v = 4; - } - - size_t sub_u = tc->opts.span_subdivision_u; - size_t sub_v = tc->opts.span_subdivision_v; - - const ufbx_nurbs_surface *surface = tc->surface; - ufbx_mesh *mesh = &tc->mesh; - ufbxi_check_err_msg(&tc->error, surface->basis_u.valid && surface->basis_v.valid - && surface->num_control_points_u > 0 && surface->num_control_points_v > 0, "Bad NURBS geometry"); - - ufbxi_init_ator(&tc->error, &tc->ator_tmp, &tc->opts.temp_allocator, "temp"); - ufbxi_init_ator(&tc->error, &tc->ator_result, &tc->opts.result_allocator, "result"); - - tc->result.unordered = true; - tc->tmp.unordered = true; - - tc->result.ator = &tc->ator_result; - tc->tmp.ator = &tc->ator_tmp; - - bool open_u = surface->basis_u.topology == UFBX_NURBS_TOPOLOGY_OPEN; - bool open_v = surface->basis_v.topology == UFBX_NURBS_TOPOLOGY_OPEN; - - size_t spans_u = surface->basis_u.spans.count; - size_t spans_v = surface->basis_v.spans.count; - - // Check conservatively that we don't overflow anything - { - size_t over_spans_u = spans_u * 2 * sizeof(ufbx_real); - size_t over_spans_v = spans_v * 2 * sizeof(ufbx_real); - size_t over_u = over_spans_u * sub_u; - size_t over_v = over_spans_v * sub_v; - size_t over_uv = over_u * over_v; - ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_u, over_spans_u, sub_u)); - ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_v, over_spans_v, sub_v)); - ufbxi_check_err(&tc->error, !ufbxi_does_overflow(over_uv, over_u, over_v)); - } - - size_t faces_u = (spans_u - 1) * sub_u; - size_t faces_v = (spans_v - 1) * sub_v; - - size_t indices_u = spans_u + (spans_u - 1) * (sub_u - 1); - size_t indices_v = spans_v + (spans_v - 1) * (sub_v - 1); - - size_t num_faces = faces_u * faces_v; - size_t num_indices = indices_u * indices_v; - ufbxi_check_err(&tc->error, num_indices <= INT32_MAX); - - uint32_t *position_ix = ufbxi_push(&tc->tmp, uint32_t, num_indices); - ufbx_vec3 *positions = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); - ufbx_vec3 *normals = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); - ufbx_vec2 *uvs = ufbxi_push(&tc->result, ufbx_vec2, num_indices + 1); - ufbx_vec3 *tangents = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); - ufbx_vec3 *bitangents = ufbxi_push(&tc->result, ufbx_vec3, num_indices + 1); - ufbxi_check_err(&tc->error, position_ix && uvs && tangents && bitangents); - - *positions++ = ufbx_zero_vec3; - *normals++ = ufbx_zero_vec3; - *uvs++ = ufbx_zero_vec2; - *tangents++ = ufbx_zero_vec3; - *bitangents++ = ufbx_zero_vec3; - - uint32_t num_positions = 0; - - for (size_t span_v = 0; span_v < spans_v; span_v++) { - size_t splits_v = span_v + 1 == spans_v ? 1 : sub_v; - - for (size_t split_v = 0; split_v < splits_v; split_v++) { - size_t ix_v = span_v * sub_v + split_v; - ufbx_assert(ix_v < indices_v); - - ufbx_real v = surface->basis_v.spans.data[span_v]; - if (split_v > 0) { - ufbx_real t = (ufbx_real)split_v / (ufbx_real)splits_v; - v = v * (1.0f - t) + t * surface->basis_v.spans.data[span_v + 1]; - } - ufbx_real original_v = v; - if (span_v + 1 == spans_v && !open_v) { - v = surface->basis_v.spans.data[0]; - } - - for (size_t span_u = 0; span_u < spans_u; span_u++) { - size_t splits_u = span_u + 1 == spans_u ? 1 : sub_u; - for (size_t split_u = 0; split_u < splits_u; split_u++) { - size_t ix_u = span_u * sub_u + split_u; - ufbx_assert(ix_u < indices_u); - - ufbx_real u = surface->basis_u.spans.data[span_u]; - if (split_u > 0) { - ufbx_real t = (ufbx_real)split_u / (ufbx_real)splits_u; - u = u * (1.0f - t) + t * surface->basis_u.spans.data[span_u + 1]; - } - ufbx_real original_u = u; - if (span_u + 1 == spans_u && !open_u) { - u = surface->basis_u.spans.data[0]; - } - - ufbx_surface_point point = ufbx_evaluate_nurbs_surface(surface, u, v); - ufbx_vec3 pos = point.position; - - ufbx_vec3 tangent_u = ufbxi_slow_normalize3(&point.derivative_u); - ufbx_vec3 tangent_v = ufbxi_slow_normalize3(&point.derivative_v); - - // Check if there's any wrapped positions that we could match - size_t neighbors[5]; // ufbxi_uninit - size_t num_neighbors = 0; - - if ((span_v == 0 && (span_u > 0 || split_u > 0)) || (span_u == 0 && (span_v > 0 || split_v > 0))) { - // Top/left - neighbors[num_neighbors++] = 0; - } - if (span_v + 1 == spans_v) { - // Bottom - neighbors[num_neighbors++] = ix_u; - if (span_u > 0 || split_u > 0) { - neighbors[num_neighbors++] = ix_v * indices_u; - } - } - if (span_u + 1 == spans_u) { - // Right - neighbors[num_neighbors++] = ix_v * indices_u; - if (span_v > 0 || split_v > 0) { - neighbors[num_neighbors++] = indices_u - 1; - } - } - - size_t ix = ix_v * indices_u + ix_u; - - uint32_t pos_ix = num_positions; - for (size_t i = 0; i < num_neighbors; i++) { - size_t nb_ix = neighbors[i]; - ufbx_assert(nb_ix < ix); - uint32_t nb_pos_ix = position_ix[nb_ix]; - ufbx_vec3 nb_pos = positions[nb_pos_ix]; - ufbx_real dx = nb_pos.x - pos.x; - ufbx_real dy = nb_pos.y - pos.y; - ufbx_real dz = nb_pos.z - pos.z; - ufbx_real delta = dx*dx + dy*dy + dz*dz; - if (delta < 0.0000001f) { // TODO: Configurable / something more rigorous - pos_ix = nb_pos_ix; - break; - } - } - - position_ix[ix] = pos_ix; - if (pos_ix == num_positions) { - positions[pos_ix] = pos; - num_positions = pos_ix + 1; - } - uvs[ix].x = original_u; - uvs[ix].y = original_v; - tangents[ix] = tangent_u; - bitangents[ix] = tangent_v; - } - } - } - } - - ufbx_face *faces = ufbxi_push(&tc->result, ufbx_face, num_faces); - uint32_t *vertex_ix = ufbxi_push(&tc->result, uint32_t, num_faces * 4); - uint32_t *attrib_ix = ufbxi_push(&tc->result, uint32_t, num_faces * 4); - ufbxi_check_err(&tc->error, faces && vertex_ix && attrib_ix); - - size_t face_ix = 0; - size_t dst_index = 0; - - size_t num_triangles = 0; - - for (size_t face_v = 0; face_v < faces_v; face_v++) { - for (size_t face_u = 0; face_u < faces_u; face_u++) { - - attrib_ix[dst_index + 0] = (uint32_t)((face_v + 0) * indices_u + (face_u + 0)); - attrib_ix[dst_index + 1] = (uint32_t)((face_v + 0) * indices_u + (face_u + 1)); - attrib_ix[dst_index + 2] = (uint32_t)((face_v + 1) * indices_u + (face_u + 1)); - attrib_ix[dst_index + 3] = (uint32_t)((face_v + 1) * indices_u + (face_u + 0)); - - vertex_ix[dst_index + 0] = position_ix[attrib_ix[dst_index + 0]]; - vertex_ix[dst_index + 1] = position_ix[attrib_ix[dst_index + 1]]; - vertex_ix[dst_index + 2] = position_ix[attrib_ix[dst_index + 2]]; - vertex_ix[dst_index + 3] = position_ix[attrib_ix[dst_index + 3]]; - - bool is_triangle = false; - for (size_t prev_ix = 0; prev_ix < 4; prev_ix++) { - size_t next_ix = (prev_ix + 1) % 4; - if (vertex_ix[dst_index + prev_ix] == vertex_ix[dst_index + next_ix]) { - for (size_t i = next_ix; i < 3; i++) { - attrib_ix[dst_index + i] = attrib_ix[dst_index + i + 1]; - vertex_ix[dst_index + i] = vertex_ix[dst_index + i + 1]; - } - is_triangle = true; - break; - } - } - - faces[face_ix].index_begin = (uint32_t)dst_index; - faces[face_ix].num_indices = is_triangle ? 3 : 4; - dst_index += is_triangle ? 3 : 4; - num_triangles += is_triangle ? 1 : 2; - face_ix++; - } - } - - ufbxi_check_err(&tc->error, positions && normals); - - mesh->element.name.data = ufbxi_empty_char; - mesh->element.type = UFBX_ELEMENT_MESH; - mesh->element.typed_id = UINT32_MAX; - mesh->element.element_id = UINT32_MAX; - - mesh->vertices.data = positions; - mesh->vertices.count = num_positions; - mesh->num_vertices = num_positions; - mesh->vertex_indices.data = vertex_ix; - mesh->vertex_indices.count = dst_index; - - mesh->faces.data = faces; - mesh->faces.count = num_faces; - - mesh->vertex_position.exists = true; - mesh->vertex_position.values.data = positions; - mesh->vertex_position.values.count = num_positions; - mesh->vertex_position.indices.data = vertex_ix; - mesh->vertex_position.indices.count = dst_index; - mesh->vertex_position.unique_per_vertex = true; - - mesh->vertex_uv.exists = true; - mesh->vertex_uv.values.data = uvs; - mesh->vertex_uv.values.count = dst_index; - mesh->vertex_uv.indices.data = attrib_ix; - mesh->vertex_uv.indices.count = dst_index; - - mesh->vertex_normal.exists = true; - mesh->vertex_normal.values.data = normals; - mesh->vertex_normal.values.count = num_positions; - mesh->vertex_normal.indices.data = vertex_ix; - mesh->vertex_normal.indices.count = dst_index; - - mesh->vertex_tangent.exists = true; - mesh->vertex_tangent.values.data = tangents; - mesh->vertex_tangent.values.count = dst_index; - mesh->vertex_tangent.indices.data = attrib_ix; - mesh->vertex_tangent.indices.count = dst_index; - - mesh->vertex_bitangent.exists = true; - mesh->vertex_bitangent.values.data = bitangents; - mesh->vertex_bitangent.values.count = dst_index; - mesh->vertex_bitangent.indices.data = attrib_ix; - mesh->vertex_bitangent.indices.count = dst_index; - - mesh->num_faces = num_faces; - mesh->num_triangles = num_triangles; - mesh->num_indices = dst_index; - mesh->max_face_triangles = 2; - - if (surface->material) { - mesh->face_material.data = ufbxi_push_zero(&tc->result, uint32_t, num_faces); - ufbxi_check_err(&tc->error, mesh->face_material.data); - - ufbx_material **mat = ufbxi_push_zero(&tc->result, ufbx_material*, 1); - ufbxi_check_err(&tc->error, mat); - - *mat = surface->material; - mesh->materials.data = mat; - mesh->materials.count = 1; - } - - if (!tc->opts.skip_mesh_parts) { - mesh->material_parts.count = 1; - mesh->material_parts.data = ufbxi_push_zero(&tc->result, ufbx_mesh_part, 1); - ufbxi_check_err(&tc->error, mesh->material_parts.data); - } - - ufbxi_check_err(&tc->error, ufbxi_finalize_mesh_material(&tc->result, &tc->error, mesh)); - ufbxi_check_err(&tc->error, ufbxi_finalize_mesh(&tc->result, &tc->error, mesh)); - - mesh->generated_normals = true; - ufbx_compute_normals(mesh, &mesh->vertex_position, - mesh->vertex_normal.indices.data, mesh->vertex_normal.indices.count, - mesh->vertex_normal.values.data, mesh->vertex_normal.values.count); - - if (surface->flip_normals) { - ufbxi_nounroll ufbxi_for_list(ufbx_vec3, normal, mesh->vertex_normal.values) { - normal->x *= -1.0f; - normal->y *= -1.0f; - normal->z *= -1.0f; - } - } - - tc->imp = ufbxi_push(&tc->result, ufbxi_mesh_imp, 1); - ufbxi_check_err(&tc->error, tc->imp); - - ufbxi_init_ref(&tc->imp->refcount, UFBXI_MESH_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, surface->element.scene))->refcount); - - tc->imp->magic = UFBXI_MESH_IMP_MAGIC; - tc->imp->mesh = tc->mesh; - tc->imp->refcount.ator = tc->ator_result; - tc->imp->refcount.buf = tc->result; - tc->imp->mesh.subdivision_evaluated = true; - - return 1; -} - -#endif - -// -- Topology - -#if UFBXI_FEATURE_KD - -typedef struct { - ufbx_real split; - uint32_t index_plus_one; // 0 for empty - uint32_t slow_left; - uint32_t slow_right; - uint32_t slow_end; -} ufbxi_kd_node; - -typedef struct { - ufbx_face face; - ufbx_vertex_vec3 positions; - ufbx_vec3 axes[3]; - ufbxi_kd_node kd_nodes[1 << (UFBXI_KD_FAST_DEPTH + 1)]; - uint32_t *kd_indices; - - // Temporary - ufbx_vec3 cur_axis_dir; - ufbx_face cur_face; -} ufbxi_ngon_context; - -typedef struct { - ufbx_real min_t[2]; - ufbx_real max_t[2]; - ufbx_vec2 points[3]; - uint32_t indices[3]; -} ufbxi_kd_triangle; - -ufbxi_noinline static ufbx_vec2 ufbxi_ngon_project(ufbxi_ngon_context *nc, uint32_t index) -{ - ufbx_vec3 point = nc->positions.values.data[nc->positions.indices.data[nc->face.index_begin + index]]; - - ufbx_vec2 p; - p.x = ufbxi_dot3(nc->axes[0], point); - p.y = ufbxi_dot3(nc->axes[1], point); - return p; -} - -ufbxi_forceinline static ufbx_real ufbxi_orient2d(ufbx_vec2 a, ufbx_vec2 b, ufbx_vec2 c) -{ - return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); -} - -ufbxi_noinline static bool ufbxi_kd_check_point(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t index) -{ - if (index == tri->indices[0] || index == tri->indices[1] || index == tri->indices[2]) return false; - ufbx_vec2 p = ufbxi_ngon_project(nc, index); - - ufbx_real u = ufbxi_orient2d(p, tri->points[0], tri->points[1]); - ufbx_real v = ufbxi_orient2d(p, tri->points[1], tri->points[2]); - ufbx_real w = ufbxi_orient2d(p, tri->points[2], tri->points[0]); - - if (u <= 0.0f && v <= 0.0f && w <= 0.0f) return true; - if (u >= 0.0f && v >= 0.0f && w >= 0.0f) return true; - return false; -} - -// Recursion limited by 32-bit indices in input, minus halvings from `ufbxi_kd_check_fast()` -ufbxi_noinline static bool ufbxi_kd_check_slow(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t begin, uint32_t count, uint32_t axis) - ufbxi_recursive_function(bool, ufbxi_kd_check_slow, (nc, tri, begin, count, axis), 32 - UFBXI_KD_FAST_DEPTH, - (ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t begin, uint32_t count, uint32_t axis)) -{ - ufbx_vertex_vec3 pos = nc->positions; - uint32_t *kd_indices = nc->kd_indices; - - while (count > 0) { - uint32_t num_left = count / 2; - uint32_t begin_right = begin + num_left + 1; - uint32_t num_right = count - (num_left + 1); - - uint32_t index = kd_indices[begin + num_left]; - ufbx_vec3 point = pos.values.data[pos.indices.data[nc->face.index_begin + index]]; - ufbx_real split = ufbxi_dot3(point, nc->axes[axis]); - bool hit_left = tri->min_t[axis] <= split; - bool hit_right = tri->max_t[axis] >= split; - - if (hit_left && hit_right) { - if (ufbxi_kd_check_point(nc, tri, index)) { - return true; - } - - if (ufbxi_kd_check_slow(nc, tri, begin_right, num_right, axis ^ 1)) { - return true; - } - } - - axis ^= 1; - if (hit_left) { - count = num_left; - } else { - begin = begin_right; - count = num_right; - } - } - - return false; -} - -// Recursion limited by `UFBXI_KD_FAST_DEPTH` -ufbxi_noinline static bool ufbxi_kd_check_fast(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t kd_index, uint32_t axis, uint32_t depth) - ufbxi_recursive_function(bool, ufbxi_kd_check_fast, (nc, tri, kd_index, axis, depth), UFBXI_KD_FAST_DEPTH, - (ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t kd_index, uint32_t axis, uint32_t depth)) -{ - for (;;) { - ufbxi_kd_node node = nc->kd_nodes[kd_index]; - if (node.index_plus_one == 0) return false; - - bool hit_left = tri->min_t[axis] <= node.split; - bool hit_right = tri->max_t[axis] >= node.split; - - uint32_t side = hit_left ? 0 : 1; - uint32_t child_kd_index = kd_index * 2 + 1 + side; - if (hit_left && hit_right) { - - // Check for the point on the split plane - uint32_t index = node.index_plus_one - 1; - if (ufbxi_kd_check_point(nc, tri, index)) { - return true; - } - - // Recurse always to the right if we hit both sides - if (depth + 1 == UFBXI_KD_FAST_DEPTH) { - if (ufbxi_kd_check_slow(nc, tri, node.slow_right, node.slow_end - node.slow_right, axis ^ 1)) { - return true; - } - } else { - if (ufbxi_kd_check_fast(nc, tri, child_kd_index + 1, axis ^ 1, depth + 1)) { - return true; - } - } - } - - depth++; - axis ^= 1; - kd_index = child_kd_index; - - if (depth == UFBXI_KD_FAST_DEPTH) { - if (hit_left) { - return ufbxi_kd_check_slow(nc, tri, node.slow_left, node.slow_right - node.slow_left, axis); - } else { - return ufbxi_kd_check_slow(nc, tri, node.slow_right, node.slow_end - node.slow_right, axis); - } - } - } -} - -ufbxi_noinline static bool ufbxi_kd_check(ufbxi_ngon_context *nc, const ufbx_vec2 *points, const uint32_t *indices) -{ - ufbxi_kd_triangle tri; // ufbxi_uninit - tri.points[0] = points[0]; - tri.points[1] = points[1]; - tri.points[2] = points[2]; - tri.indices[0] = indices[0]; - tri.indices[1] = indices[1]; - tri.indices[2] = indices[2]; - tri.min_t[0] = ufbxi_min_real(ufbxi_min_real(points[0].x, points[1].x), points[2].x); - tri.min_t[1] = ufbxi_min_real(ufbxi_min_real(points[0].y, points[1].y), points[2].y); - tri.max_t[0] = ufbxi_max_real(ufbxi_max_real(points[0].x, points[1].x), points[2].x); - tri.max_t[1] = ufbxi_max_real(ufbxi_max_real(points[0].y, points[1].y), points[2].y); - return ufbxi_kd_check_fast(nc, &tri, 0, 0, 0); -} - -ufbxi_noinline static bool ufbxi_kd_index_less(void *user, const void *va, const void *vb) -{ - ufbxi_ngon_context *nc = (ufbxi_ngon_context*)user; - ufbx_vertex_vec3 *pos = &nc->positions; - const uint32_t a = *(const uint32_t*)va, b = *(const uint32_t*)vb; - ufbx_real da = ufbxi_dot3(nc->cur_axis_dir, pos->values.data[pos->indices.data[nc->cur_face.index_begin + a]]); - ufbx_real db = ufbxi_dot3(nc->cur_axis_dir, pos->values.data[pos->indices.data[nc->cur_face.index_begin + b]]); - return da < db; -} - -// Recursion limited by 32-bit indices in input -ufbxi_noinline static void ufbxi_kd_build(ufbxi_ngon_context *nc, uint32_t *indices, uint32_t *tmp, uint32_t num, uint32_t axis, uint32_t fast_index, uint32_t depth) - ufbxi_recursive_function_void(ufbxi_kd_build, (nc, indices, tmp, num, axis, fast_index, depth), 32, - (ufbxi_ngon_context *nc, uint32_t *indices, uint32_t *tmp, uint32_t num, uint32_t axis, uint32_t fast_index, uint32_t depth)) -{ - if (num == 0) return; - - ufbx_vertex_vec3 pos = nc->positions; - ufbx_vec3 axis_dir = nc->axes[axis]; - ufbx_face face = nc->face; - - nc->cur_axis_dir = axis_dir; - nc->cur_face = face; - - // Sort the remaining indices based on the axis - ufbxi_stable_sort(sizeof(uint32_t), 16, indices, tmp, num, &ufbxi_kd_index_less, nc); - - uint32_t num_left = num / 2; - uint32_t begin_right = num_left + 1; - uint32_t num_right = num - begin_right; - uint32_t dst_right = num_left + 1; - if (depth < UFBXI_KD_FAST_DEPTH) { - uint32_t skip_left = 1u << (UFBXI_KD_FAST_DEPTH - depth - 1); - dst_right = dst_right > skip_left ? dst_right - skip_left : 0; - - uint32_t index = indices[num_left]; - ufbxi_kd_node *kd = &nc->kd_nodes[fast_index]; - - kd->split = ufbxi_dot3(axis_dir, pos.values.data[pos.indices.data[face.index_begin + index]]); - kd->index_plus_one = index + 1; - - if (depth + 1 == UFBXI_KD_FAST_DEPTH) { - kd->slow_left = (uint32_t)(indices - nc->kd_indices); - kd->slow_right = kd->slow_left + num_left; - kd->slow_end = kd->slow_right + num_right; - } else { - kd->slow_left = UINT32_MAX; - kd->slow_right = UINT32_MAX; - kd->slow_end = UINT32_MAX; - } - } - - uint32_t child_fast = fast_index * 2 + 1; - ufbxi_kd_build(nc, indices, tmp, num_left, axis ^ 1, child_fast + 0, depth + 1); - - if (dst_right != begin_right) { - memmove(indices + dst_right, indices + begin_right, num_right * sizeof(uint32_t)); - } - - ufbxi_kd_build(nc, indices + dst_right, tmp, num_right, axis ^ 1, child_fast + 1, depth + 1); -} - -#endif - -#if UFBXI_FEATURE_TRIANGULATION - -ufbxi_noinline static ufbx_real ufbxi_ngon_tri_weight(const ufbx_vec2 *points) -{ - ufbx_vec2 p0 = points[0], p1 = points[1], p2 = points[2]; - ufbx_real orient = ufbxi_orient2d(p0, p1, p2); - if (orient <= 0.0f) return -1.0f; - - ufbx_real a = ufbxi_distsq2(p0, p1); - ufbx_real b = ufbxi_distsq2(p1, p2); - ufbx_real c = ufbxi_distsq2(p2, p0); - ufbx_real ab = (a + b - c) / (ufbx_real)ufbx_sqrt(4.0f * a * b); - ufbx_real bc = (b + c - a) / (ufbx_real)ufbx_sqrt(4.0f * b * c); - ufbx_real ca = (c + a - b) / (ufbx_real)ufbx_sqrt(4.0f * c * a); - return (ufbx_real)ufbx_fmax(UFBX_EPSILON, 2.0f - ufbx_fmax(ufbx_fmax(ab, bc), ca)); -} - -ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, uint32_t *indices, uint32_t num_indices) -{ - ufbx_face face = nc->face; - ufbx_assert(face.num_indices > 4); - - // Form an orthonormal basis to project the polygon into a 2D plane - ufbx_vec3 normal = ufbx_get_weighted_face_normal(&nc->positions, face); - ufbx_real len = ufbxi_length3(normal); - if (len > UFBX_EPSILON) { - normal = ufbxi_mul3(normal, 1.0f / len); - } else { - normal.x = 1.0f; - normal.y = 0.0f; - normal.z = 0.0f; - } - - ufbx_vec3 axis; // ufbxi_uninit - if (normal.x*normal.x < 0.5f) { - axis.x = 1.0f; - axis.y = 0.0f; - axis.z = 0.0f; - } else { - axis.x = 0.0f; - axis.y = 1.0f; - axis.z = 0.0f; - } - nc->axes[0] = ufbxi_slow_normalized_cross3(&axis, &normal); - nc->axes[1] = ufbxi_slow_normalized_cross3(&normal, &nc->axes[0]); - nc->axes[2] = normal; - - uint32_t *kd_indices = indices; - nc->kd_indices = kd_indices; - - uint32_t *kd_tmp = indices + face.num_indices; - - // Collect all the reflex corners for intersection testing. - uint32_t num_kd_indices = 0; - { - ufbx_vec2 a = ufbxi_ngon_project(nc, face.num_indices - 1); - ufbx_vec2 b = ufbxi_ngon_project(nc, 0); - for (uint32_t i = 0; i < face.num_indices; i++) { - uint32_t next = i + 1 < face.num_indices ? i + 1 : 0; - ufbx_vec2 c = ufbxi_ngon_project(nc, next); - - if (ufbxi_orient2d(a, b, c) <= 0.0f) { - kd_indices[num_kd_indices++] = i; - } - - a = b; - b = c; - } - } - - // Build a KD-tree of the vertices. - uint32_t num_skip_indices = (1u << (UFBXI_KD_FAST_DEPTH + 1)) - 1; - uint32_t kd_slow_indices = num_kd_indices > num_skip_indices ? num_kd_indices - num_skip_indices : 0; - ufbxi_ignore(kd_slow_indices); - ufbx_assert(kd_slow_indices + face.num_indices * 2 <= num_indices); - ufbxi_kd_build(nc, kd_indices, kd_tmp, num_kd_indices, 0, 0, 0); - - uint32_t *edges = indices + num_indices - face.num_indices * 2; - - // Initialize `edges` to be a connectivity structure where: - // `edges[2*i + 0]` is the previous vertex of `i` - // `edges[2*i + 1]` is the next vertex of `i` - // When clipped we mark indices with the high bit (0x80000000) - for (uint32_t i = 0; i < face.num_indices; i++) { - edges[i*2 + 0] = i > 0 ? i - 1 : face.num_indices - 1; - edges[i*2 + 1] = i + 1 < face.num_indices ? i + 1 : 0; - } - - // Core of the ear clipping algorithm. - // Iterate through the polygon corners looking for potential ears satisfying: - // - Angle must be less than 180deg - // - The triangle formed by the two edges must be contained within the polygon - // As these properties change only locally between modifications we only need - // to iterate the polygon once if we move backwards one step every time we clip an ear. - uint32_t indices_left = face.num_indices; - { - uint32_t point_indices[4] = { 0, 1, 2, 3 }; - ufbx_real weights[2]; // ufbxi_uninit - ufbx_vec2 points[4]; // ufbxi_uninit - - uint32_t num_steps = 0; - while (indices_left > 3) { - points[0] = ufbxi_ngon_project(nc, point_indices[0]); - points[1] = ufbxi_ngon_project(nc, point_indices[1]); - points[2] = ufbxi_ngon_project(nc, point_indices[2]); - points[3] = ufbxi_ngon_project(nc, point_indices[3]); - - weights[0] = ufbxi_ngon_tri_weight(points + 0); - weights[1] = ufbxi_ngon_tri_weight(points + 1); - - uint32_t first_side = weights[1] > weights[0] ? 1 : 0; - bool clipped = false; - ufbxi_nounroll for (uint32_t side_ix = 0; side_ix < 2; side_ix++) { - uint32_t side = side_ix ^ first_side; - if (!(weights[side] >= 0.0f)) break; - - // If there is no reflex angle contained within the triangle formed - // by `{ a, b, c }` connect the vertices `a - c` (prev, next) directly. - if (!ufbxi_kd_check(nc, points + side, point_indices + side)) { - uint32_t ia = point_indices[side + 0]; - uint32_t ib = point_indices[side + 1]; - uint32_t ic = point_indices[side + 2]; - - // Mark as clipped - edges[ib*2 + 0] |= 0x80000000; - edges[ib*2 + 1] |= 0x80000000; - - edges[ic*2 + 0] = ia; - edges[ia*2 + 1] = ic; - - indices_left -= 1; - - // TODO: This may cause O(n^2) behavior! - num_steps = 0; - - if (side == 1) { - point_indices[2] = point_indices[3]; - point_indices[3] = edges[point_indices[3]*2 + 1]; - } else { - point_indices[1] = point_indices[0]; - point_indices[0] = edges[point_indices[0]*2 + 0]; - } - - clipped = true; - break; - } - } - if (clipped) continue; - - // Continue forward - point_indices[0] = point_indices[1]; - point_indices[1] = point_indices[2]; - point_indices[2] = point_indices[3]; - point_indices[3] = edges[point_indices[3]*2 + 1]; - num_steps++; - - // If we have walked around the entire polygon it is irregular and - // ear cutting won't find any more triangles. - // TODO: This could be stricter? - if (num_steps >= face.num_indices*2) break; - } - - // Fallback: Cut non-ears until the polygon is completed. - // TODO: Could do something better here.. - uint32_t ix = point_indices[1]; - while (indices_left > 3) { - uint32_t prev = edges[ix*2 + 0]; - uint32_t next = edges[ix*2 + 1]; - - // Mark as clipped - edges[ix*2 + 0] |= 0x80000000; - edges[ix*2 + 1] |= 0x80000000; - - edges[prev*2 + 1] = next; - edges[next*2 + 0] = prev; - - indices_left -= 1; - ix = next; - } - - // Now we have a single triangle left at `ix`. - edges[ix*2 + 0] |= 0x80000000; - edges[ix*2 + 1] |= 0x80000000; - } - - // Expand the adjacency information `edges` into proper triangles. - // Care needs to be taken here as both refer to the same memory area: - // The last 4 triangles may overlap in source and destination so we write - // them to a stack buffer and copy them over in the end. - uint32_t max_triangles = face.num_indices - 2; - uint32_t num_triangles = 0, num_last_triangles = 0; - uint32_t last_triangles[4*3]; // ufbxi_uninit - - uint32_t index_begin = face.index_begin; - for (uint32_t ix = 0; ix < face.num_indices; ix++) { - uint32_t prev = edges[ix*2 + 0]; - uint32_t next = edges[ix*2 + 1]; - if (!(prev & 0x80000000)) continue; - - uint32_t *dst = indices + num_triangles * 3; - if (num_triangles + 4 >= max_triangles) { - dst = last_triangles + num_last_triangles * 3; - num_last_triangles++; - } - - dst[0] = index_begin + (prev & 0x7fffffff); - dst[1] = index_begin + ix; - dst[2] = index_begin + (next & 0x7fffffff); - num_triangles++; - } - - // Copy over the last triangles - ufbx_assert(num_triangles == max_triangles); - memcpy(indices + (max_triangles - num_last_triangles) * 3, last_triangles, num_last_triangles * 3 * sizeof(uint32_t)); - - return num_triangles; -} - -#endif - -static bool ufbxi_topo_less_index_prev_next(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; - if ((int32_t)a->prev != (int32_t)b->prev) return (int32_t)a->prev < (int32_t)b->prev; - return (int32_t)a->next < (int32_t)b->next; -} - -static bool ufbxi_topo_less_index_index(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_topo_edge *a = (const ufbx_topo_edge*)va, *b = (const ufbx_topo_edge*)vb; - return (int32_t)a->index < (int32_t)b->index; -} - -ufbxi_noinline static void ufbxi_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo) -{ - size_t num_indices = mesh->num_indices; - - // Temporarily use `prev` and `next` for vertices - for (uint32_t fi = 0; fi < mesh->num_faces; fi++) { - ufbx_face face = mesh->faces.data[fi]; - for (uint32_t pi = 0; pi < face.num_indices; pi++) { - ufbx_topo_edge *te = &topo[face.index_begin + pi]; - uint32_t ni = (pi + 1) % face.num_indices; - uint32_t va = mesh->vertex_indices.data[face.index_begin + pi]; - uint32_t vb = mesh->vertex_indices.data[face.index_begin + ni]; - - if (vb < va) { - uint32_t vt = va; va = vb; vb = vt; - } - te->index = face.index_begin + pi; - te->twin = UFBX_NO_INDEX; - te->edge = UFBX_NO_INDEX; - te->prev = va; - te->next = vb; - te->face = fi; - te->flags = (ufbx_topo_flags)0; - } - } - - ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_prev_next, NULL); - - if (mesh->edges.data) { - for (uint32_t ei = 0; ei < mesh->num_edges; ei++) { - ufbx_edge edge = mesh->edges.data[ei]; - uint32_t va = mesh->vertex_indices.data[edge.a]; - uint32_t vb = mesh->vertex_indices.data[edge.b]; - if (vb < va) { - uint32_t vt = va; va = vb; vb = vt; - } - - size_t ix = num_indices; - ufbxi_macro_lower_bound_eq(ufbx_topo_edge, 32, &ix, topo, 0, num_indices, - (a->prev == va ? a->next < vb : a->prev < va), (a->prev == va && a->next == vb)); - - for (; ix < num_indices && topo[ix].prev == va && topo[ix].next == vb; ix++) { - topo[ix].edge = ei; - } - } - } - - // Connect paired edges - for (size_t i0 = 0; i0 < num_indices; ) { - size_t i1 = i0; - - uint32_t a = topo[i0].prev, b = topo[i0].next; - while (i1 + 1 < num_indices && topo[i1 + 1].prev == a && topo[i1 + 1].next == b) i1++; - - if (i1 == i0 + 1) { - topo[i0].twin = topo[i1].index; - topo[i1].twin = topo[i0].index; - } else if (i1 > i0 + 1) { - for (size_t i = i0; i <= i1; i++) { - topo[i].flags = (ufbx_topo_flags)(topo[i].flags | UFBX_TOPO_NON_MANIFOLD); - } - } - - i0 = i1 + 1; - } - - ufbxi_unstable_sort(topo, num_indices, sizeof(ufbx_topo_edge), &ufbxi_topo_less_index_index, NULL); - - // Fix `prev` and `next` to the actual index values - for (uint32_t fi = 0; fi < mesh->num_faces; fi++) { - ufbx_face face = mesh->faces.data[fi]; - for (uint32_t i = 0; i < face.num_indices; i++) { - ufbx_topo_edge *to = &topo[face.index_begin + i]; - to->prev = (uint32_t)(face.index_begin + (i + face.num_indices - 1) % face.num_indices); - to->next = (uint32_t)(face.index_begin + (i + 1) % face.num_indices); - } - } -} - -static bool ufbxi_is_edge_smooth(const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index, bool assume_smooth) -{ - ufbxi_ignore(num_topo); - ufbx_assert((size_t)index < num_topo); - if (mesh->edge_smoothing.data) { - uint32_t edge = topo[index].edge; - if (edge != UFBX_NO_INDEX && mesh->edge_smoothing.data[edge]) return true; - } - - if (mesh->face_smoothing.data) { - if (mesh->face_smoothing.data[topo[index].face]) return true; - uint32_t twin = topo[index].twin; - if (twin != UFBX_NO_INDEX) { - if (mesh->face_smoothing.data[topo[twin].face]) return true; - } - } - - if (!mesh->edge_smoothing.data && !mesh->face_smoothing.data && mesh->vertex_normal.exists) { - uint32_t twin = topo[index].twin; - if (twin != UFBX_NO_INDEX && mesh->vertex_normal.exists) { - ufbx_assert((size_t)twin < num_topo); - ufbx_vec3 a0 = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); - ufbx_vec3 a1 = ufbx_get_vertex_vec3(&mesh->vertex_normal, topo[index].next); - ufbx_vec3 b0 = ufbx_get_vertex_vec3(&mesh->vertex_normal, topo[twin].next); - ufbx_vec3 b1 = ufbx_get_vertex_vec3(&mesh->vertex_normal, twin); - if (a0.x == b0.x && a0.y == b0.y && a0.z == b0.z) return true; - if (a1.x == b1.x && a1.y == b1.y && a1.z == b1.z) return true; - } - } else if (assume_smooth) { - return true; - } - - return false; -} - -// -- Subdivision - -#if UFBXI_FEATURE_SUBDIVISION - -typedef struct { - const void *data; - ufbx_real weight; -} ufbxi_subdivide_input; - -typedef int ufbxi_subdivide_sum_fn(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs); - -typedef struct { - ufbxi_subdivide_sum_fn *sum_fn; - void *sum_user; - - const void *values; - size_t stride; - - const uint32_t *indices; - - bool check_split_data; - bool ignore_indices; - - ufbx_subdivision_boundary boundary; - -} ufbxi_subdivide_layer_input; - -typedef struct { - void *values; - size_t num_values; - uint32_t *indices; - size_t num_indices; - bool unique_per_vertex; -} ufbxi_subdivide_layer_output; - -typedef struct { - ufbx_subdivision_weight *weights; - size_t num_weights; -} ufbxi_subdivision_vertex_weights; - -typedef struct { - ufbxi_mesh_imp *imp; - - ufbx_error error; - - ufbx_mesh *src_mesh_ptr; - ufbx_mesh src_mesh; - ufbx_mesh dst_mesh; - ufbx_topo_edge *topo; - size_t num_topo; - - ufbx_subdivide_opts opts; - - ufbxi_allocator ator_result; - ufbxi_allocator ator_tmp; - - ufbxi_buf result; - ufbxi_buf tmp; - ufbxi_buf source; - - ufbxi_subdivide_input *inputs; - size_t inputs_cap; - - ufbx_real *tmp_vertex_weights; - ufbx_subdivision_weight *tmp_weights; - size_t total_weights; - size_t max_vertex_weights; - -} ufbxi_subdivide_context; - -static int ufbxi_subdivide_sum_vec2(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) -{ - (void)user; - ufbx_vec2 dst = { 0 }; - ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { - const ufbx_vec2 *src = (const ufbx_vec2*)inputs[i].data; - ufbx_real weight = inputs[i].weight; - dst.x += src->x * weight; - dst.y += src->y * weight; - } - *(ufbx_vec2*)output = dst; - - return 1; -} - -static int ufbxi_subdivide_sum_vec3(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) -{ - (void)user; - ufbx_vec3 dst = { 0 }; - ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { - const ufbx_vec3 *src = (const ufbx_vec3*)inputs[i].data; - ufbx_real weight = inputs[i].weight; - dst.x += src->x * weight; - dst.y += src->y * weight; - dst.z += src->z * weight; - } - *(ufbx_vec3*)output = dst; - - return 1; -} - -static int ufbxi_subdivide_sum_vec4(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) -{ - (void)user; - ufbx_vec4 dst = { 0 }; - ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { - const ufbx_vec4 *src = (const ufbx_vec4*)inputs[i].data; - ufbx_real weight = inputs[i].weight; - dst.x += src->x * weight; - dst.y += src->y * weight; - dst.z += src->z * weight; - dst.w += src->w * weight; - } - *(ufbx_vec4*)output = dst; - - return 1; -} - -static ufbxi_noinline bool ufbxi_subdivision_weight_less(void *user, const void *va, const void *vb) -{ - (void)user; - ufbx_subdivision_weight a = *(const ufbx_subdivision_weight*)va, b = *(const ufbx_subdivision_weight*)vb; - ufbxi_dev_assert(a.index != b.index); - if (a.weight != b.weight) return a.weight > b.weight; - return a.index < b.index; -} - -static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) -{ - ufbxi_subdivide_context *sc = (ufbxi_subdivide_context*)user; - - ufbx_real *vertex_weights = sc->tmp_vertex_weights; - ufbx_subdivision_weight *tmp_weights = sc->tmp_weights; - size_t num_weights = 0; - - ufbxi_nounroll for (size_t input_ix = 0; input_ix != num_inputs; input_ix++) { - ufbxi_subdivision_vertex_weights src = *(const ufbxi_subdivision_vertex_weights*)inputs[input_ix].data; - ufbx_real input_weight = inputs[input_ix].weight; - - for (size_t weight_ix = 0; weight_ix < src.num_weights; weight_ix++) { - ufbx_real weight = input_weight * src.weights[weight_ix].weight; - if (weight < 1.175494351e-38f) continue; - - uint32_t vx = src.weights[weight_ix].index; - ufbxi_dev_assert(vx < sc->src_mesh.num_vertices); - - ufbx_real prev = vertex_weights[vx]; - vertex_weights[vx] = prev + weight; - if (prev == 0.0f) { - tmp_weights[num_weights++].index = vx; - } - } - } - - ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { - uint32_t vx = tmp_weights[i].index; - tmp_weights[i].weight = vertex_weights[vx]; - vertex_weights[vx] = 0.0f; - } - - ufbxi_unstable_sort(tmp_weights, num_weights, sizeof(ufbx_subdivision_weight), ufbxi_subdivision_weight_less, NULL); - - if (sc->max_vertex_weights != SIZE_MAX) { - num_weights = ufbxi_min_sz(sc->max_vertex_weights, num_weights); - - // Normalize weights - ufbx_real prefix_weight = 0.0f; - ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { - prefix_weight += tmp_weights[i].weight; - } - ufbxi_nounroll for (size_t i = 0; i != num_weights; i++) { - tmp_weights[i].weight /= prefix_weight; - } - } - - sc->total_weights += num_weights; - ufbx_subdivision_weight *weights = ufbxi_push_copy(&sc->tmp, ufbx_subdivision_weight, num_weights, tmp_weights); - ufbxi_check_err(&sc->error, weights); - - ufbxi_subdivision_vertex_weights *dst = (ufbxi_subdivision_vertex_weights*)output; - dst->weights = weights; - dst->num_weights = num_weights; - - return 1; -} - -static ufbxi_subdivide_sum_fn *const ufbxi_real_sum_fns[] = { - NULL, - &ufbxi_subdivide_sum_vec2, - &ufbxi_subdivide_sum_vec3, - &ufbxi_subdivide_sum_vec4, -}; - -ufbxi_noinline static bool ufbxi_is_edge_split(const ufbxi_subdivide_layer_input *input, const ufbx_topo_edge *topo, uint32_t index) -{ - uint32_t twin = topo[index].twin; - if (twin != UFBX_NO_INDEX) { - uint32_t a0 = input->indices[index]; - uint32_t a1 = input->indices[topo[index].next]; - uint32_t b0 = input->indices[topo[twin].next]; - uint32_t b1 = input->indices[twin]; - if (a0 == b0 && a1 == b1) return false; - if (!input->check_split_data) return true; - size_t stride = input->stride; - char *da0 = (char*)input->values + a0 * stride; - char *da1 = (char*)input->values + a1 * stride; - char *db0 = (char*)input->values + b0 * stride; - char *db1 = (char*)input->values + b1 * stride; - if (!memcmp(da0, db0, stride) && !memcmp(da1, db1, stride)) return false; - return true; - } - - return false; -} - -static ufbx_real ufbxi_edge_crease(const ufbx_mesh *mesh, bool split, const ufbx_topo_edge *topo, uint32_t index) -{ - if (topo[index].twin == UFBX_NO_INDEX) return 1.0f; - if (split) return 1.0f; - if (mesh->edge_crease.data && topo[index].edge != UFBX_NO_INDEX) return mesh->edge_crease.data[topo[index].edge] * (ufbx_real)10.0; - return 0.0f; -} - -static ufbxi_noinline int ufbxi_subdivide_layer(ufbxi_subdivide_context *sc, ufbxi_subdivide_layer_output *output, const ufbxi_subdivide_layer_input *input) -{ - ufbx_subdivision_boundary boundary = input->boundary; - - const ufbx_mesh *mesh = &sc->src_mesh; - const ufbx_topo_edge *topo = sc->topo; - size_t num_topo = sc->num_topo; - - uint32_t *edge_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); - ufbxi_check_err(&sc->error, edge_indices); - - size_t num_edge_values = 0; - for (uint32_t ix = 0; ix < (uint32_t)mesh->num_indices; ix++) { - uint32_t twin = topo[ix].twin; - if (twin < ix && !ufbxi_is_edge_split(input, topo, ix)) { - edge_indices[ix] = edge_indices[twin]; - } else { - edge_indices[ix] = (uint32_t)num_edge_values++; - } - } - - size_t stride = input->stride; - size_t num_initial_values = (num_edge_values + mesh->num_faces + mesh->num_indices); - char *values = (char*)ufbxi_push_size(&sc->tmp, stride, num_initial_values); - ufbxi_check_err(&sc->error, values); - - char *face_values = values; - char *edge_values = face_values + mesh->num_faces * stride; - char *vertex_values = edge_values + num_edge_values * stride; - - size_t num_vertex_values = 0; - - uint32_t *vertex_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); - ufbxi_check_err(&sc->error, vertex_indices); - - size_t min_inputs = ufbxi_max_sz(32, mesh->max_face_triangles + 2); - ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, min_inputs)); - ufbxi_subdivide_input *inputs = sc->inputs; - - // Assume initially unique per vertex, remove if not the case - output->unique_per_vertex = true; - - bool sharp_corners = false; - bool sharp_splits = false; - bool sharp_all = false; - - switch (boundary) { - case UFBX_SUBDIVISION_BOUNDARY_DEFAULT: - case UFBX_SUBDIVISION_BOUNDARY_SHARP_NONE: - case UFBX_SUBDIVISION_BOUNDARY_LEGACY: - // All smooth - break; - case UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS: - sharp_corners = true; - break; - case UFBX_SUBDIVISION_BOUNDARY_SHARP_BOUNDARY: - sharp_corners = true; - sharp_splits = true; - break; - case UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR: - sharp_all = true; - break; - default: - ufbxi_unreachable("Bad boundary mode"); - } - - ufbxi_subdivide_sum_fn *sum_fn = input->sum_fn; - void *sum_user = input->sum_user; - - // Mark unused indices as `UFBX_NO_INDEX` so we can patch non-manifold - ufbxi_nounroll for (size_t i = 0; i < mesh->num_indices; i++) { - vertex_indices[i] = UFBX_NO_INDEX; - } - - // Face points - for (size_t fi = 0; fi < mesh->num_faces; fi++) { - ufbx_face face = mesh->faces.data[fi]; - char *dst = face_values + fi * stride; - - ufbx_real weight = 1.0f / (ufbx_real)face.num_indices; - for (uint32_t ci = 0; ci < face.num_indices; ci++) { - uint32_t ix = face.index_begin + ci; - inputs[ci].data = (const char*)input->values + input->indices[ix] * stride; - inputs[ci].weight = weight; - } - - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, face.num_indices)); - } - - // Edge points - for (uint32_t ix = 0; ix < mesh->num_indices; ix++) { - char *dst = edge_values + edge_indices[ix] * stride; - - uint32_t twin = topo[ix].twin; - bool split = ufbxi_is_edge_split(input, topo, ix); - - if (split || (topo[ix].flags & UFBX_TOPO_NON_MANIFOLD) != 0) { - output->unique_per_vertex = false; - } - - ufbx_real crease = 0.0f; - if (split || twin == UFBX_NO_INDEX) { - crease = 1.0f; - } else if (topo[ix].edge != UFBX_NO_INDEX && mesh->edge_crease.data) { - crease = mesh->edge_crease.data[topo[ix].edge] * (ufbx_real)10.0; - } - if (sharp_all) crease = 1.0f; - - const char *v0 = (const char*)input->values + input->indices[ix] * stride; - const char *v1 = (const char*)input->values + input->indices[topo[ix].next] * stride; - - // TODO: Unify - if (twin < ix && !split) { - // Already calculated - } else if (crease <= 0.0f) { - const char *f0 = face_values + topo[ix].face * stride; - const char *f1 = face_values + topo[twin].face * stride; - inputs[0].data = v0; - inputs[0].weight = 0.25f; - inputs[1].data = v1; - inputs[1].weight = 0.25f; - inputs[2].data = f0; - inputs[2].weight = 0.25f; - inputs[3].data = f1; - inputs[3].weight = 0.25f; - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 4)); - } else if (crease >= 1.0f) { - inputs[0].data = v0; - inputs[0].weight = 0.5f; - inputs[1].data = v1; - inputs[1].weight = 0.5f; - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 2)); - } else if (crease < 1.0f) { - const char *f0 = face_values + topo[ix].face * stride; - const char *f1 = face_values + topo[twin].face * stride; - ufbx_real w0 = 0.25f + 0.25f * crease; - ufbx_real w1 = 0.25f - 0.25f * crease; - - inputs[0].data = v0; - inputs[0].weight = w0; - inputs[1].data = v1; - inputs[1].weight = w0; - inputs[2].data = f0; - inputs[2].weight = w1; - inputs[3].data = f1; - inputs[3].weight = w1; - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 4)); - } - } - - // Vertex points - for (size_t vi = 0; vi < mesh->num_vertices; vi++) { - uint32_t original_start = mesh->vertex_first_index.data[vi]; - if (original_start == UFBX_NO_INDEX) continue; - - // Find a topological boundary, or if not found a split edge - uint32_t start = original_start; - for (uint32_t cur = start;;) { - uint32_t prev = ufbx_topo_prev_vertex_edge(topo, num_topo, cur); - if (prev == UFBX_NO_INDEX) { start = cur; break; } // Topological boundary: Stop and use as start - if (ufbxi_is_edge_split(input, topo, prev)) start = cur; // Split edge: Consider as start - if (prev == original_start) break; // Loop: Stop, use original start or split if found - cur = prev; - } - - original_start = start; - while (start != UFBX_NO_INDEX) { - if (start != original_start) { - output->unique_per_vertex = false; - } - - uint32_t value_index = (uint32_t)num_vertex_values++; - char *dst = vertex_values + value_index * stride; - - // We need to compute the average crease value and keep track of - // two creased edges, if there's more we use the corner rule that - // does not need the information. - ufbx_real total_crease = 0.0f; - size_t num_crease = 0; - size_t num_split = 0; - bool on_boundary = false; - bool non_manifold = false; - size_t crease_input_indices[2]; // ufbxi_uninit - - // At start we always have two edges and a single face - uint32_t start_prev = topo[start].prev; - uint32_t end_edge = topo[start_prev].twin; - size_t valence = 2; - - non_manifold |= (topo[start].flags & UFBX_TOPO_NON_MANIFOLD) != 0; - non_manifold |= (topo[start_prev].flags & UFBX_TOPO_NON_MANIFOLD) != 0; - - const char *v0 = (const char*)input->values + input->indices[start] * stride; - - size_t num_inputs = 4; - - { - const char *e0 = (const char*)input->values + input->indices[topo[start].next] * stride; - const char *e1 = (const char*)input->values + input->indices[start_prev] * stride; - const char *f0 = face_values + topo[start].face * stride; - inputs[0].data = v0; - inputs[1].data = e0; - inputs[2].data = e1; - inputs[3].data = f0; - } - - bool start_split = ufbxi_is_edge_split(input, topo, start); - bool prev_split = end_edge != UFBX_NO_INDEX && ufbxi_is_edge_split(input, topo, end_edge); - - // Either of the first two edges may be creased - ufbx_real start_crease = ufbxi_edge_crease(mesh, start_split, topo, start); - if (start_crease > 0.0f) { - total_crease += start_crease; - crease_input_indices[num_crease++] = 1; - } - ufbx_real prev_crease = ufbxi_edge_crease(mesh, prev_split, topo, start_prev); - if (prev_crease > 0.0f) { - total_crease += prev_crease; - crease_input_indices[num_crease++] = 2; - } - - if (end_edge != UFBX_NO_INDEX) { - if (prev_split) { - num_split++; - } - } else { - on_boundary = true; - } - - ufbxi_check_err(&sc->error, vertex_indices[start] == UFBX_NO_INDEX); - vertex_indices[start] = value_index; - - if (start_split) { - // We need to special case if the first edge is split as we have - // handled it already in the code above.. - start = ufbx_topo_next_vertex_edge(topo, num_topo, start); - num_split++; - } else { - // Follow vertex edges until we either hit a topological/split boundary - // or loop back to the left edge we accounted for in `start_prev` - uint32_t cur = start; - for (;;) { - cur = ufbx_topo_next_vertex_edge(topo, num_topo, cur); - - // Topological boundary: Finished - if (cur == UFBX_NO_INDEX) { - on_boundary = true; - start = UFBX_NO_INDEX; - break; - } - - non_manifold |= (topo[cur].flags & UFBX_TOPO_NON_MANIFOLD) != 0; - ufbxi_check_err(&sc->error, vertex_indices[cur] == UFBX_NO_INDEX); - vertex_indices[cur] = value_index; - - bool split = ufbxi_is_edge_split(input, topo, cur); - - // Looped: Add the face from the other side still if not split - if (cur == end_edge && !split) { - ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, num_inputs + 1)); - const char *f0 = face_values + topo[cur].face * stride; - inputs[num_inputs].data = f0; - start = UFBX_NO_INDEX; - num_inputs += 1; - break; - } - - // Add the edge crease, this also handles boundaries as they - // have an implicit crease of 1.0 using `ufbxi_edge_crease()` - ufbx_real cur_crease = ufbxi_edge_crease(mesh, split, topo, cur); - if (cur_crease > 0.0f) { - total_crease += cur_crease; - if (num_crease < 2) crease_input_indices[num_crease] = num_inputs; - num_crease++; - } - - // Add the new edge and face to the sum - { - ufbxi_check_err(&sc->error, ufbxi_grow_array(&sc->ator_tmp, &sc->inputs, &sc->inputs_cap, num_inputs + 2)); - inputs = sc->inputs; - - const char *e0 = (char*)input->values + input->indices[topo[cur].next] * stride; - const char *f0 = face_values + topo[cur].face * stride; - inputs[num_inputs + 0].data = e0; - inputs[num_inputs + 1].data = f0; - num_inputs += 2; - } - valence++; - - // If we landed at a split edge advance to the next one - // and continue from there in the outer loop - if (split) { - start = ufbx_topo_next_vertex_edge(topo, num_topo, cur); - num_split++; - break; - } - } - } - - if (start == original_start) start = UFBX_NO_INDEX; - - // Weights for various subdivision masks - ufbx_real fe_weight = 1.0f / (ufbx_real)(valence*valence); - ufbx_real v_weight = (ufbx_real)(valence - 2) / (ufbx_real)valence; - - // Select the right subdivision mask depending on valence and crease - if (num_crease > 2 - || (sharp_corners && valence == 2 && (num_split > 0 || on_boundary)) - || (sharp_splits && (num_split > 0 || on_boundary)) - || sharp_all - || non_manifold) { - // Corner: Copy as-is - inputs[0].data = v0; - inputs[0].weight = 1.0f; - num_inputs = 1; - } else if (num_crease == 2) { - // Boundary: Interpolate edge - total_crease *= 0.5f; - if (total_crease < 0.0f) total_crease = 0.0f; - if (total_crease > 1.0f) total_crease = 1.0f; - - inputs[0].weight = v_weight * (1.0f - total_crease) + 0.75f * total_crease; - ufbx_real few = fe_weight * (1.0f - total_crease); - for (size_t i = 1; i < num_inputs; i++) { - inputs[i].weight = few; - } - - // Add weight to the creased edges - inputs[crease_input_indices[0]].weight += 0.125f * total_crease; - inputs[crease_input_indices[1]].weight += 0.125f * total_crease; - } else { - // Regular: Weighted sum with the accumulated edge/face points - inputs[0].weight = v_weight; - for (size_t i = 1; i < num_inputs; i++) { - inputs[i].weight = fe_weight; - } - - } - - if (mesh->vertex_crease.exists) { - ufbx_real v = ufbx_get_vertex_real(&mesh->vertex_crease, original_start); - v *= (ufbx_real)10.0; - if (v > 0.0f) { - if (v > 1.0) v = 1.0f; - - ufbx_real iv = 1.0f - v; - inputs[0].weight = 1.0f * v + (inputs[0].weight) * iv; - for (size_t i = 1; i < num_inputs; i++) { - inputs[i].weight *= iv; - } - } - } - -#if defined(UFBX_REGRESSION) - { - ufbx_real total_weight = 0.0f; - for (size_t i = 0; i < num_inputs; i++) { - total_weight += inputs[i].weight; - } - ufbx_assert(ufbx_fabs(total_weight - 1.0f) < 0.001f); - } -#endif - - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, num_inputs)); - } - } - - // Copy non-manifold vertex values as-is - for (size_t old_ix = 0; old_ix < mesh->num_indices; old_ix++) { - uint32_t ix = vertex_indices[old_ix]; - if (ix == UFBX_NO_INDEX) { - ix = (uint32_t)num_vertex_values++; - vertex_indices[old_ix] = ix; - const char *src = (const char*)input->values + input->indices[old_ix] * stride; - char *dst = vertex_values + ix * stride; - - inputs[0].data = src; - inputs[0].weight = 1.0f; - ufbxi_check_err(&sc->error, sum_fn(sum_user, dst, inputs, 1)); - } - } - - ufbx_assert(num_vertex_values <= mesh->num_indices); - size_t num_values = num_edge_values + mesh->num_faces + num_vertex_values; - char *new_values = (char*)ufbxi_push_size(&sc->result, stride, (num_values+1)); - ufbxi_check_err(&sc->error, new_values); - - memset(new_values, 0, stride); - new_values += stride; - - memcpy(new_values, values, num_values * stride); - - output->values = new_values; - output->num_values = num_values; - - if (!input->ignore_indices) { - uint32_t *new_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices * 4); - ufbxi_check_err(&sc->error, new_indices); - - uint32_t face_start = 0; - uint32_t edge_start = (uint32_t)(face_start + mesh->num_faces); - uint32_t vert_start = (uint32_t)(edge_start + num_edge_values); - uint32_t *p_ix = new_indices; - for (size_t ix = 0; ix < mesh->num_indices; ix++) { - p_ix[0] = vert_start + vertex_indices[ix]; - p_ix[1] = edge_start + edge_indices[ix]; - p_ix[2] = face_start + topo[ix].face; - p_ix[3] = edge_start + edge_indices[topo[ix].prev]; - p_ix += 4; - } - output->indices = new_indices; - output->num_indices = mesh->num_indices * 4; - } else { - output->indices = NULL; - output->num_indices = 0; - } - - return 1; -} - -static ufbxi_noinline int ufbxi_subdivide_attrib(ufbxi_subdivide_context *sc, ufbx_vertex_attrib *attrib, ufbx_subdivision_boundary boundary, bool check_split_data) -{ - if (!attrib->exists) return 1; - - ufbx_assert(attrib->value_reals >= 2 && attrib->value_reals <= 4); - - ufbxi_subdivide_layer_input input; // ufbxi_uninit - input.sum_fn = ufbxi_real_sum_fns[attrib->value_reals - 1]; - input.sum_user = NULL; - input.values = attrib->values.data; - input.indices = attrib->indices.data; - input.stride = attrib->value_reals * sizeof(ufbx_real); - input.boundary = boundary; - input.check_split_data = check_split_data; - input.ignore_indices = false; - - ufbxi_subdivide_layer_output output; // ufbxi_uninit - ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); - - attrib->values.data = output.values; - attrib->indices.data = output.indices; - attrib->values.count = output.num_values; - attrib->indices.count = output.num_indices; - - return 1; -} - -static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_subdivision_copy_weights(ufbxi_subdivide_context *sc, ufbx_subdivision_weight_range_list ranges, ufbx_subdivision_weight_list weights) -{ - ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, ranges.count); - ufbxi_check_return_err(&sc->error, dst, NULL); - - ufbxi_nounroll for (size_t i = 0; i != ranges.count; i++) { - ufbx_subdivision_weight_range range = ranges.data[i]; - dst[i].weights = weights.data + range.weight_begin; - dst[i].num_weights = range.num_weights; - } - - return dst; -} - -static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_init_source_vertex_weights(ufbxi_subdivide_context *sc, size_t num_vertices) -{ - ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, num_vertices); - ufbx_subdivision_weight *weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, num_vertices); - ufbxi_check_return_err(&sc->error, dst && weights, NULL); - - ufbxi_nounroll for (size_t i = 0; i != num_vertices; i++) { - dst[i].weights = weights + i; - dst[i].num_weights = 1; - weights[i].index = (uint32_t)i; - weights[i].weight = 1.0f; - } - - return dst; -} - -static ufbxi_noinline ufbxi_subdivision_vertex_weights *ufbxi_init_skin_weights(ufbxi_subdivide_context *sc, size_t num_vertices, const ufbx_skin_deformer *skin) -{ - ufbxi_subdivision_vertex_weights *dst = ufbxi_push(&sc->tmp, ufbxi_subdivision_vertex_weights, num_vertices); - ufbxi_check_return_err(&sc->error, dst, NULL); - - for (size_t i = 0; i < num_vertices; i++) { - ufbxi_dev_assert(i < skin->vertices.count); - ufbx_skin_vertex vertex = skin->vertices.data[i]; - size_t num_weights = ufbxi_min_sz(sc->max_vertex_weights, vertex.num_weights); - - ufbx_subdivision_weight *weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, num_weights); - ufbxi_check_err(&sc->error, weights); - - const ufbx_skin_weight *skin_weights = skin->weights.data + vertex.weight_begin; - - dst[i].weights = weights; - dst[i].num_weights = num_weights; - ufbxi_nounroll for (size_t wi = 0; wi != num_weights; wi++) { - ufbxi_check_err(&sc->error, skin_weights[wi].cluster_index <= INT32_MAX); - weights[wi].index = skin_weights[wi].cluster_index; - weights[wi].weight = skin_weights[wi].weight; - } - } - - return dst; -} - -static ufbxi_noinline int ufbxi_subdivide_weights(ufbxi_subdivide_context *sc, ufbx_subdivision_weight_range_list *ranges, - ufbx_subdivision_weight_list *weights, const ufbxi_subdivision_vertex_weights *src) -{ - ufbxi_check_err(&sc->error, src); - - ufbxi_subdivide_layer_input input; // ufbxi_uninit - input.sum_fn = ufbxi_subdivide_sum_vertex_weights; - input.sum_user = sc; - input.values = src; - input.indices = sc->src_mesh.vertex_indices.data; - input.stride = sizeof(ufbxi_subdivision_vertex_weights); - input.boundary = sc->opts.boundary; - input.check_split_data = false; - input.ignore_indices = true; - - sc->total_weights = 0; - - ufbxi_subdivide_layer_output output; // ufbxi_uninit - ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); - - size_t num_vertices = output.num_values; - ufbx_assert(num_vertices == sc->dst_mesh.vertex_position.values.count); - - ufbx_subdivision_weight_range *dst_ranges = ufbxi_push(&sc->result, ufbx_subdivision_weight_range, num_vertices); - ufbx_subdivision_weight *dst_weights = ufbxi_push(&sc->result, ufbx_subdivision_weight, sc->total_weights); - ufbxi_check_err(&sc->error, ranges && weights); - - ufbxi_subdivision_vertex_weights *src_weights = (ufbxi_subdivision_vertex_weights*)output.values; - - size_t weight_offset = 0; - for (size_t vi = 0; vi < num_vertices; vi++) { - ufbxi_subdivision_vertex_weights ws = src_weights[vi]; - ufbxi_check_err(&sc->error, (size_t)UINT32_MAX - weight_offset >= ws.num_weights); - - dst_ranges[vi].weight_begin = (uint32_t)weight_offset; - dst_ranges[vi].num_weights = (uint32_t)ws.num_weights; - memcpy(dst_weights + weight_offset, ws.weights, ws.num_weights * sizeof(ufbx_subdivision_weight)); - weight_offset += ws.num_weights; - } - - ranges->data = dst_ranges; - ranges->count = num_vertices; - weights->data = dst_weights; - weights->count = sc->total_weights; - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_vertex_crease(ufbxi_subdivide_context *sc, ufbx_vertex_real *ufbxi_restrict dst, const ufbx_vertex_real *ufbxi_restrict src) -{ - size_t src_indices = src->indices.count; - size_t src_values = src->values.count; - - dst->values.count = src_values + 1; - dst->values.data = ufbxi_push(&sc->result, ufbx_real, dst->values.count); - ufbxi_check_err(&sc->error, dst->values.data); - dst->values.data[src_values] = 0.0f; - - dst->indices.count = src_indices * 4; - dst->indices.data = ufbxi_push(&sc->result, uint32_t, dst->indices.count); - ufbxi_check_err(&sc->error, dst->indices.data); - - // Reduce the amount of vertex crease on each iteration - ufbxi_nounroll for (size_t i = 0; i < src_values; i++) { - ufbx_real crease = src->values.data[i]; - if (crease < 0.999f) crease -= 0.1f; - if (crease < 0.0f) crease = 0.0f; - dst->values.data[i] = crease; - } - - // Write the crease at the vertex corner and zero (at `src_values`) on other ones - uint32_t zero_index = (uint32_t)src_values; - ufbxi_nounroll for (size_t i = 0; i < src_indices; i++) { - uint32_t *quad = dst->indices.data + i * 4; - quad[0] = src->indices.data[i]; - quad[1] = zero_index; - quad[2] = zero_index; - quad[3] = zero_index; - } - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_level(ufbxi_subdivide_context *sc) -{ - const ufbx_mesh *mesh = &sc->src_mesh; - ufbx_mesh *result = &sc->dst_mesh; - - *result = *mesh; - - ufbx_topo_edge *topo = ufbxi_push(&sc->tmp, ufbx_topo_edge, mesh->num_indices); - ufbxi_check_err(&sc->error, topo); - ufbx_compute_topology(mesh, topo, mesh->num_indices); - sc->topo = topo; - sc->num_topo = mesh->num_indices; - - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->vertex_position, sc->opts.boundary, false)); - - memset(&result->vertex_uv, 0, sizeof(result->vertex_uv)); - memset(&result->vertex_tangent, 0, sizeof(result->vertex_tangent)); - memset(&result->vertex_bitangent, 0, sizeof(result->vertex_bitangent)); - memset(&result->vertex_color, 0, sizeof(result->vertex_color)); - - result->uv_sets.data = ufbxi_push_copy(&sc->result, ufbx_uv_set, result->uv_sets.count, result->uv_sets.data); - ufbxi_check_err(&sc->error, result->uv_sets.data); - - result->color_sets.data = ufbxi_push_copy(&sc->result, ufbx_color_set, result->color_sets.count, result->color_sets.data); - ufbxi_check_err(&sc->error, result->color_sets.data); - - ufbxi_for_list(ufbx_uv_set, set, result->uv_sets) { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_uv, sc->opts.uv_boundary, true)); - if (sc->opts.interpolate_tangents) { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_tangent, sc->opts.uv_boundary, true)); - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_bitangent, sc->opts.uv_boundary, true)); - } else { - memset(&set->vertex_tangent, 0, sizeof(set->vertex_tangent)); - memset(&set->vertex_bitangent, 0, sizeof(set->vertex_bitangent)); - } - } - - ufbxi_for_list(ufbx_color_set, set, result->color_sets) { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&set->vertex_color, sc->opts.uv_boundary, true)); - } - - if (result->uv_sets.count > 0) { - result->vertex_uv = result->uv_sets.data[0].vertex_uv; - result->vertex_bitangent = result->uv_sets.data[0].vertex_bitangent; - result->vertex_tangent = result->uv_sets.data[0].vertex_tangent; - } - if (result->color_sets.count > 0) { - result->vertex_color = result->color_sets.data[0].vertex_color; - } - - if (sc->opts.interpolate_normals && !sc->opts.ignore_normals) { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->vertex_normal, sc->opts.boundary, true)); - ufbxi_for_list(ufbx_vec3, normal, result->vertex_normal.values) { - *normal = ufbxi_slow_normalize3(normal); - } - if (mesh->skinned_normal.values.data == mesh->vertex_normal.values.data) { - result->skinned_normal = result->vertex_normal; - } else { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->skinned_normal, sc->opts.boundary, true)); - ufbxi_for_list(ufbx_vec3, normal, result->skinned_normal.values) { - *normal = ufbxi_slow_normalize3(normal); - } - } - } - - if (result->vertex_crease.exists) { - ufbxi_check_err(&sc->error, ufbxi_subdivide_vertex_crease(sc, &result->vertex_crease, &mesh->vertex_crease)); - } - - if (mesh->skinned_position.values.data == mesh->vertex_position.values.data) { - result->skinned_position = result->vertex_position; - } else { - ufbxi_check_err(&sc->error, ufbxi_subdivide_attrib(sc, (ufbx_vertex_attrib*)&result->skinned_position, sc->opts.boundary, false)); - } - - ufbx_subdivision_result *result_sub = ufbxi_push_zero(&sc->result, ufbx_subdivision_result, 1); - ufbxi_check_err(&sc->error, result_sub); - result->subdivision_result = result_sub; - - if (sc->opts.evaluate_source_vertices || sc->opts.evaluate_skin_weights) { - ufbx_subdivision_result *mesh_sub = mesh->subdivision_result; - - ufbx_skin_deformer *skin = NULL; - if (sc->opts.evaluate_skin_weights) { - if (mesh->skin_deformers.count > 0) { - ufbxi_check_err(&sc->error, sc->opts.skin_deformer_index < mesh->skin_deformers.count); - skin = mesh->skin_deformers.data[sc->opts.skin_deformer_index]; - } - } - - size_t max_weights = 0; - if (sc->opts.evaluate_source_vertices) { - max_weights = ufbxi_max_sz(max_weights, mesh->num_vertices); - } - if (skin) { - max_weights = ufbxi_max_sz(max_weights, skin->clusters.count); - } - - sc->tmp_vertex_weights = ufbxi_push_zero(&sc->tmp, ufbx_real, mesh->num_vertices); - sc->tmp_weights = ufbxi_push(&sc->tmp, ufbx_subdivision_weight, max_weights); - ufbxi_check_err(&sc->error, sc->tmp_vertex_weights && sc->tmp_weights); - - if (sc->opts.evaluate_source_vertices) { - sc->max_vertex_weights = sc->opts.max_source_vertices ? sc->opts.max_source_vertices : SIZE_MAX; - - ufbxi_subdivision_vertex_weights *weights; - if (mesh_sub && mesh_sub->source_vertex_ranges.count > 0) { - weights = ufbxi_subdivision_copy_weights(sc, mesh_sub->source_vertex_ranges, mesh_sub->source_vertex_weights); - } else { - weights = ufbxi_init_source_vertex_weights(sc, mesh->num_vertices); - } - - ufbxi_check_err(&sc->error, ufbxi_subdivide_weights(sc, &result_sub->source_vertex_ranges, &result_sub->source_vertex_weights, weights)); - } - - if (skin) { - sc->max_vertex_weights = sc->opts.max_skin_weights ? sc->opts.max_skin_weights : SIZE_MAX; - - ufbxi_subdivision_vertex_weights *weights; - if (mesh_sub && mesh_sub->source_vertex_ranges.count > 0) { - weights = ufbxi_subdivision_copy_weights(sc, mesh_sub->skin_cluster_ranges, mesh_sub->skin_cluster_weights); - } else { - weights = ufbxi_init_skin_weights(sc, mesh->num_vertices, skin); - } - - ufbxi_check_err(&sc->error, ufbxi_subdivide_weights(sc, &result_sub->skin_cluster_ranges, &result_sub->skin_cluster_weights, weights)); - } - - } - - result->num_vertices = result->vertex_position.values.count; - result->num_indices = mesh->num_indices * 4; - result->num_faces = mesh->num_indices; - result->num_triangles = mesh->num_indices * 2; - - result->vertex_indices.data = result->vertex_position.indices.data; - result->vertex_indices.count = result->num_indices; - result->vertices.data = result->vertex_position.values.data; - result->vertices.count = result->num_vertices; - - result->faces.count = result->num_faces; - result->faces.data = ufbxi_push(&sc->result, ufbx_face, result->num_faces); - ufbxi_check_err(&sc->error, result->faces.data); - - for (size_t i = 0; i < result->num_faces; i++) { - result->faces.data[i].index_begin = (uint32_t)(i * 4); - result->faces.data[i].num_indices = 4; - } - - if (mesh->edges.data) { - result->num_edges = mesh->num_edges*2 + result->num_faces; - result->edges.count = result->num_edges; - result->edges.data = ufbxi_push(&sc->result, ufbx_edge, result->num_edges); - ufbxi_check_err(&sc->error, result->edges.data); - - if (mesh->edge_crease.data) { - result->edge_crease.count = result->num_edges; - result->edge_crease.data = ufbxi_push(&sc->result, ufbx_real, result->num_edges); - ufbxi_check_err(&sc->error, result->edge_crease.data); - } - if (mesh->edge_smoothing.data) { - result->edge_smoothing.count = result->num_edges; - result->edge_smoothing.data = ufbxi_push(&sc->result, bool, result->num_edges); - ufbxi_check_err(&sc->error, result->edge_smoothing.data); - } - if (mesh->edge_visibility.data) { - result->edge_visibility.count = result->num_edges; - result->edge_visibility.data = ufbxi_push(&sc->result, bool, result->num_edges); - ufbxi_check_err(&sc->error, result->edge_visibility.data); - } - - size_t di = 0; - for (size_t i = 0; i < mesh->num_edges; i++) { - ufbx_edge edge = mesh->edges.data[i]; - uint32_t face_ix = topo[edge.a].face; - ufbx_face face = mesh->faces.data[face_ix]; - uint32_t offset = edge.a - face.index_begin; - uint32_t next = (offset + 1) % (uint32_t)face.num_indices; - - uint32_t a = (face.index_begin + offset) * 4; - uint32_t b = (face.index_begin + next) * 4; - - result->edges.data[di + 0].a = a; - result->edges.data[di + 0].b = a + 1; - result->edges.data[di + 1].a = b + 3; - result->edges.data[di + 1].b = b; - - if (mesh->edge_crease.data) { - ufbx_real crease = mesh->edge_crease.data[i]; - if (crease < 0.999f) crease -= (ufbx_real)0.1; - if (crease < 0.0f) crease = 0.0f; - result->edge_crease.data[di + 0] = crease; - result->edge_crease.data[di + 1] = crease; - } - - if (mesh->edge_smoothing.data) { - result->edge_smoothing.data[di + 0] = mesh->edge_smoothing.data[i]; - result->edge_smoothing.data[di + 1] = mesh->edge_smoothing.data[i]; - } - - if (mesh->edge_visibility.data) { - result->edge_visibility.data[di + 0] = mesh->edge_visibility.data[i]; - result->edge_visibility.data[di + 1] = mesh->edge_visibility.data[i]; - } - - di += 2; - } - - for (size_t fi = 0; fi < result->num_faces; fi++) { - result->edges.data[di].a = (uint32_t)(fi * 4 + 1); - result->edges.data[di].b = (uint32_t)(fi * 4 + 2); - - if (result->edge_crease.data) { - result->edge_crease.data[di] = 0.0f; - } - - if (result->edge_smoothing.data) { - result->edge_smoothing.data[di + 0] = true; - } - - if (result->edge_visibility.data) { - result->edge_visibility.data[di + 0] = false; - } - - di++; - } - } - - if (mesh->face_material.data) { - result->face_material.count = result->num_faces; - result->face_material.data = ufbxi_push(&sc->result, uint32_t, result->num_faces); - ufbxi_check_err(&sc->error, result->face_material.data); - } - if (mesh->face_smoothing.data) { - result->face_smoothing.count = result->num_faces; - result->face_smoothing.data = ufbxi_push(&sc->result, bool, result->num_faces); - ufbxi_check_err(&sc->error, result->face_smoothing.data); - } - if (mesh->face_group.data) { - result->face_group.count = result->num_faces; - result->face_group.data = ufbxi_push(&sc->result, uint32_t, result->num_faces); - ufbxi_check_err(&sc->error, result->face_group.data); - } - if (mesh->face_hole.data) { - result->face_hole.count = result->num_faces; - result->face_hole.data = ufbxi_push(&sc->result, bool, result->num_faces); - ufbxi_check_err(&sc->error, result->face_hole.data); - } - - if (result->material_parts.count > 0) { - result->material_parts.data = ufbxi_push_zero(&sc->result, ufbx_mesh_part, result->material_parts.count); - ufbxi_check_err(&sc->error, result->materials.data); - } - - size_t index_offset = 0; - for (size_t i = 0; i < mesh->num_faces; i++) { - ufbx_face face = mesh->faces.data[i]; - - uint32_t mat = 0; - if (mesh->face_material.data) { - mat = mesh->face_material.data[i]; - for (size_t ci = 0; ci < face.num_indices; ci++) { - result->face_material.data[index_offset + ci] = mat; - } - } - if (mesh->face_smoothing.data) { - bool flag = mesh->face_smoothing.data[i]; - for (size_t ci = 0; ci < face.num_indices; ci++) { - result->face_smoothing.data[index_offset + ci] = flag; - } - } - if (mesh->face_group.data) { - uint32_t group = mesh->face_group.data[i]; - for (size_t ci = 0; ci < face.num_indices; ci++) { - result->face_group.data[index_offset + ci] = group; - } - } - if (mesh->face_hole.data) { - bool flag = mesh->face_hole.data[i]; - for (size_t ci = 0; ci < face.num_indices; ci++) { - result->face_hole.data[index_offset + ci] = flag; - } - } - index_offset += face.num_indices; - } - - // Will be filled in by `ufbxi_finalize_mesh()`. - result->vertex_first_index.count = 0; - - ufbxi_check_err(&sc->error, ufbxi_finalize_mesh_material(&sc->result, &sc->error, result)); - ufbxi_check_err(&sc->error, ufbxi_finalize_mesh(&sc->result, &sc->error, result)); - ufbxi_check_err(&sc->error, ufbxi_update_face_groups(&sc->result, &sc->error, result, true)); - - return 1; -} - -ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_imp(ufbxi_subdivide_context *sc, size_t level) -{ - if (sc->opts.boundary == UFBX_SUBDIVISION_BOUNDARY_DEFAULT) { - sc->opts.boundary = sc->src_mesh.subdivision_boundary; - } - - if (sc->opts.uv_boundary == UFBX_SUBDIVISION_BOUNDARY_DEFAULT) { - sc->opts.uv_boundary = sc->src_mesh.subdivision_uv_boundary; - } - - ufbxi_init_ator(&sc->error, &sc->ator_tmp, &sc->opts.temp_allocator, "temp"); - ufbxi_init_ator(&sc->error, &sc->ator_result, &sc->opts.result_allocator, "result"); - - sc->result.unordered = true; - sc->source.unordered = true; - sc->tmp.unordered = true; - - sc->source.ator = &sc->ator_tmp; - sc->tmp.ator = &sc->ator_tmp; - - for (size_t i = 1; i < level; i++) { - sc->result.ator = &sc->ator_tmp; - - ufbxi_check_err(&sc->error, ufbxi_subdivide_mesh_level(sc)); - - sc->src_mesh = sc->dst_mesh; - - ufbxi_buf_free(&sc->source); - ufbxi_buf_free(&sc->tmp); - sc->source = sc->result; - memset(&sc->result, 0, sizeof(sc->result)); - } - - sc->result.ator = &sc->ator_result; - ufbxi_check_err(&sc->error, ufbxi_subdivide_mesh_level(sc)); - ufbxi_buf_free(&sc->tmp); - - ufbx_mesh *mesh = &sc->dst_mesh; - - // Subdivision always results in a mesh that consists only of quads - mesh->max_face_triangles = 2; - mesh->num_empty_faces = 0; - mesh->num_point_faces = 0; - mesh->num_line_faces = 0; - - if (!sc->opts.interpolate_normals) { - memset(&mesh->vertex_normal, 0, sizeof(mesh->vertex_normal)); - memset(&mesh->skinned_normal, 0, sizeof(mesh->skinned_normal)); - } - - if (!sc->opts.interpolate_normals && !sc->opts.ignore_normals) { - - ufbx_topo_edge *topo = ufbxi_push(&sc->tmp, ufbx_topo_edge, mesh->num_indices); - ufbxi_check_err(&sc->error, topo); - ufbx_compute_topology(mesh, topo, mesh->num_indices); - - uint32_t *normal_indices = ufbxi_push(&sc->result, uint32_t, mesh->num_indices); - ufbxi_check_err(&sc->error, normal_indices); - - size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, mesh->num_indices, normal_indices, mesh->num_indices, true); - if (num_normals == mesh->num_vertices) { - mesh->skinned_normal.unique_per_vertex = true; - } - - ufbx_vec3 *normal_data = ufbxi_push(&sc->result, ufbx_vec3, num_normals + 1); - ufbxi_check_err(&sc->error, normal_data); - normal_data[0] = ufbx_zero_vec3; - normal_data++; - - ufbx_compute_normals(mesh, &mesh->skinned_position, normal_indices, mesh->num_indices, normal_data, num_normals); - - mesh->generated_normals = true; - mesh->vertex_normal.exists = true; - mesh->vertex_normal.values.data = normal_data; - mesh->vertex_normal.values.count = num_normals; - mesh->vertex_normal.indices.data = normal_indices; - mesh->vertex_normal.indices.count = mesh->num_indices; - - mesh->skinned_normal = mesh->vertex_normal; - } - - ufbxi_refcount *parent = NULL; - if (sc->src_mesh_ptr->subdivision_evaluated && sc->src_mesh_ptr->from_tessellated_nurbs) { - parent = &(ufbxi_get_imp(ufbxi_mesh_imp, sc->src_mesh_ptr))->refcount; - } else { - parent = &(ufbxi_get_imp(ufbxi_scene_imp, sc->src_mesh_ptr->element.scene))->refcount; - } - - ufbxi_patch_mesh_reals(mesh); - - sc->imp = ufbxi_push(&sc->result, ufbxi_mesh_imp, 1); - ufbxi_check_err(&sc->error, sc->imp); - - sc->dst_mesh.subdivision_result->result_memory_used = sc->ator_result.current_size; - sc->dst_mesh.subdivision_result->temp_memory_used = sc->ator_tmp.current_size; - sc->dst_mesh.subdivision_result->result_allocs = sc->ator_result.num_allocs; - sc->dst_mesh.subdivision_result->temp_allocs = sc->ator_tmp.num_allocs; - - ufbxi_init_ref(&sc->imp->refcount, UFBXI_MESH_IMP_MAGIC, parent); - - sc->imp->magic = UFBXI_MESH_IMP_MAGIC; - sc->imp->mesh = sc->dst_mesh; - sc->imp->refcount.ator = sc->ator_result; - sc->imp->refcount.buf = sc->result; - sc->imp->mesh.subdivision_evaluated = true; - - return 1; -} - -ufbxi_noinline static ufbx_mesh *ufbxi_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *user_opts, ufbx_error *p_error) -{ - ufbxi_subdivide_context sc = { 0 }; - if (user_opts) { - sc.opts = *user_opts; - } - - sc.src_mesh_ptr = (ufbx_mesh*)mesh; - sc.src_mesh = *mesh; - - int ok = ufbxi_subdivide_mesh_imp(&sc, level); - - ufbxi_free(&sc.ator_tmp, ufbxi_subdivide_input, sc.inputs, sc.inputs_cap); - ufbxi_buf_free(&sc.tmp); - ufbxi_buf_free(&sc.source); - - if (ok) { - ufbxi_free_ator(&sc.ator_tmp); - if (p_error) { - ufbxi_clear_error(p_error); - } - - ufbxi_mesh_imp *imp = sc.imp; - return &imp->mesh; - } else { - ufbxi_fix_error_type(&sc.error, "Failed to subdivide", p_error); - ufbxi_buf_free(&sc.result); - ufbxi_free_ator(&sc.ator_tmp); - ufbxi_free_ator(&sc.ator_result); - return NULL; - } -} - -#else - -ufbxi_noinline static ufbx_mesh *ufbxi_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *user_opts, ufbx_error *p_error) -{ - if (p_error) { - memset(p_error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(p_error, "UFBX_ENABLE_SUBDIVISION"); - ufbxi_report_err_msg(p_error, "UFBXI_FEATURE_SUBDIVISION", "Feature disabled"); - } - return NULL; -} - -#endif - -// -- Utility - -#if UFBXI_FEATURE_INDEX_GENERATION - -static int ufbxi_map_cmp_vertex(void *user, const void *va, const void *vb) -{ - size_t size = *(size_t*)user; -#if defined(UFBX_REGRESSION) - ufbx_assert(size % 8 == 0); -#endif - for (size_t i = 0; i < size; i += 8) { - uint64_t a = *(const uint64_t*)((const char*)va + i); - uint64_t b = *(const uint64_t*)((const char*)vb + i); - if (a != b) return a < b ? -1 : +1; - } - return 0; -} - -typedef struct { - char *begin, *ptr; - size_t vertex_size; - size_t packed_offset; -} ufbxi_vertex_stream; - -static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *user_streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) -{ - bool fail = false; - - ufbxi_allocator ator = { 0 }; - ufbxi_init_ator(error, &ator, allocator, "allocator"); - - ufbxi_vertex_stream local_streams[16]; // ufbxi_uninit - uint64_t local_packed_vertex[64]; // ufbxi_uninit - - ufbxi_vertex_stream *streams = NULL; - if (num_streams > ufbxi_arraycount(local_streams)) { - streams = ufbxi_alloc(&ator, ufbxi_vertex_stream, num_streams); - if (!streams) fail = true; - } else { - streams = local_streams; - } - - size_t packed_size = 0; - if (!fail) { - for (size_t i = 0; i < num_streams; i++) { - if (user_streams[i].vertex_count < num_indices) { - ufbxi_fmt_err_info(error, "%zu", i); - ufbxi_report_err_msg(error, "user_streams[i].vertex_count < num_indices", "Truncated vertex stream"); - fail = true; - break; - } - - size_t vertex_size = user_streams[i].vertex_size; - size_t align = ufbxi_size_align_mask(vertex_size); - packed_size = ufbxi_align_to_mask(packed_size, align); - streams[i].ptr = streams[i].begin = (char*)user_streams[i].data; - streams[i].vertex_size = vertex_size; - streams[i].packed_offset = packed_size; - packed_size += vertex_size; - } - packed_size = ufbxi_align_to_mask(packed_size, 7); - } - - if (!fail && packed_size == 0) { - ufbxi_report_err_msg(error, "packed_size != 0", "Zero vertex size"); - fail = true; - } - - char *packed_vertex = NULL; - if (!fail) { - if (packed_size > sizeof(local_packed_vertex)) { - ufbx_assert(packed_size % 8 == 0); - packed_vertex = (char*)ufbxi_alloc(&ator, uint64_t, packed_size / 8); - if (!packed_vertex) fail = true; - } else { - packed_vertex = (char*)local_packed_vertex; - } - } - - ufbxi_map map = { 0 }; - ufbxi_map_init(&map, &ator, &ufbxi_map_cmp_vertex, &packed_size); - - if (num_indices > 0 && !ufbxi_map_grow_size(&map, packed_size, num_indices)) { - fail = true; - } - - if (!fail) { - ufbx_assert(packed_vertex != NULL); - memset(packed_vertex, 0, packed_size); - - for (size_t i = 0; i < num_indices; i++) { - for (size_t si = 0; si < num_streams; si++) { - size_t size = streams[si].vertex_size, offset = streams[si].packed_offset; - char *ptr = streams[si].ptr; - memcpy(packed_vertex + offset, ptr, size); - streams[si].ptr = ptr + size; - } - - uint32_t hash = ufbxi_hash_string(packed_vertex, packed_size); - void *entry = ufbxi_map_find_size(&map, packed_size, hash, packed_vertex); - if (!entry) { - entry = ufbxi_map_insert_size(&map, packed_size, hash, packed_vertex); - if (!entry) { - fail = true; - break; - } - memcpy(entry, packed_vertex, packed_size); - } - uint32_t index = (uint32_t)(ufbxi_to_size((char*)entry - (char*)map.items) / packed_size); - indices[i] = index; - } - } - - size_t result_vertices = 0; - if (!fail) { - result_vertices = map.size; - - for (size_t si = 0; si < num_streams; si++) { - size_t vertex_size = streams[si].vertex_size; - char *dst = streams[si].begin; - char *src = ufbxi_add_ptr((char*)map.items, streams[si].packed_offset); - for (size_t i = 0; i < result_vertices; i++) { - memcpy(dst, src, vertex_size); - dst += vertex_size; - src += packed_size; - } - } - - ufbxi_clear_error(error); - } else { - ufbxi_fix_error_type(error, "Failed to generate indices", NULL); - } - - if (streams && streams != local_streams) { - ufbxi_free(&ator, ufbxi_vertex_stream, streams, num_streams); - } - if (packed_vertex && packed_vertex != (char*)local_packed_vertex) { - ufbxi_free(&ator, uint64_t, packed_vertex, packed_size / 8); - } - - ufbxi_map_free(&map); - ufbxi_free_ator(&ator); - - return result_vertices; -} - -#else - -static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *user_streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) -{ - if (error) { - memset(error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(error, "UFBX_ENABLE_INDEX_GENERATION"); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_INDEX_GENERATION", "Feature disabled"); - } - return 0; -} - -#endif - -static ufbxi_noinline void ufbxi_free_scene_imp(ufbxi_scene_imp *imp) -{ - ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); - ufbxi_buf_free(&imp->string_buf); -} - -static ufbxi_noinline void ufbxi_init_ref(ufbxi_refcount *refcount, uint32_t magic, ufbxi_refcount *parent) -{ - if (parent) { - ufbxi_retain_ref(parent); - } - - ufbxi_atomic_counter_init(&refcount->refcount); - refcount->self_magic = UFBXI_REFCOUNT_IMP_MAGIC; - refcount->type_magic = magic; - refcount->parent = parent; -} - -static ufbxi_noinline void ufbxi_retain_ref(ufbxi_refcount *refcount) -{ - ufbx_assert(refcount->self_magic == UFBXI_REFCOUNT_IMP_MAGIC); - size_t count = ufbxi_atomic_counter_inc(&refcount->refcount); - ufbxi_ignore(count); - ufbx_assert(count < SIZE_MAX / 2); -} - -static ufbxi_noinline void ufbxi_release_ref(ufbxi_refcount *refcount) -{ - while (refcount) { - ufbx_assert(refcount->self_magic == UFBXI_REFCOUNT_IMP_MAGIC); - if (ufbxi_atomic_counter_dec(&refcount->refcount) > 0) return; - ufbxi_atomic_counter_free(&refcount->refcount); - - ufbxi_refcount *parent = refcount->parent; - uint32_t type_magic = refcount->type_magic; - - refcount->self_magic = 0; - refcount->type_magic = 0; - - // Type-specific cleanup - switch (type_magic) { - case UFBXI_SCENE_IMP_MAGIC: ufbxi_free_scene_imp((ufbxi_scene_imp*)refcount); break; - case UFBXI_CACHE_IMP_MAGIC: ufbxi_free_geometry_cache_imp((ufbxi_geometry_cache_imp*)refcount); break; - default: break; - } - - // We need to free `data_buf` last and be careful to copy it to - // the stack since the `ufbxi_refcount` that contains it is allocated - // from the same result buffer! - ufbxi_allocator ator = refcount->ator; - ufbxi_buf buf = refcount->buf; - buf.ator = &ator; - ufbxi_buf_free(&buf); - ufbxi_free_ator(&ator); - - refcount = parent; - } -} - -static ufbxi_noinline void *ufbxi_uninitialized_options(ufbx_error *p_error) -{ - if (p_error) { - memset(p_error, 0, sizeof(ufbx_error)); - p_error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS; - p_error->description.data = "Uninitialized options"; - p_error->description.length = strlen("Uninitialized options"); - } - return NULL; -} - -#define ufbxi_check_opts_ptr(m_type, m_opts, m_error) do { if (m_opts) { \ - uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ - ufbx_assert(opts_cleared_to_zero == 0); \ - if (opts_cleared_to_zero != 0) return (m_type*)ufbxi_uninitialized_options(m_error); \ - } } while (0) - -#define ufbxi_check_opts_return(m_value, m_opts, m_error) do { if (m_opts) { \ - uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ - ufbx_assert(opts_cleared_to_zero == 0); \ - if (opts_cleared_to_zero != 0) { \ - ufbxi_uninitialized_options(m_error); \ - return m_value; \ - } \ - } } while (0) - -#define ufbxi_check_opts_return_no_error(m_value, m_opts) do { if (m_opts) { \ - uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ - if (opts_cleared_to_zero != 0) return m_value; \ - } } while (0) - -// -- API - -#ifdef __cplusplus -extern "C" { -#endif - -ufbx_abi_data_def const ufbx_string ufbx_empty_string = { ufbxi_empty_char, 0 }; -ufbx_abi_data_def const ufbx_blob ufbx_empty_blob = { NULL, 0 }; -ufbx_abi_data_def const ufbx_matrix ufbx_identity_matrix = { 1,0,0, 0,1,0, 0,0,1, 0,0,0 }; -ufbx_abi_data_def const ufbx_transform ufbx_identity_transform = { {0,0,0}, {0,0,0,1}, {1,1,1} }; -ufbx_abi_data_def const ufbx_vec2 ufbx_zero_vec2 = { 0,0 }; -ufbx_abi_data_def const ufbx_vec3 ufbx_zero_vec3 = { 0,0,0 }; -ufbx_abi_data_def const ufbx_vec4 ufbx_zero_vec4 = { 0,0,0,0 }; -ufbx_abi_data_def const ufbx_quat ufbx_identity_quat = { 0,0,0,1 }; - -ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_y_up = { - UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_POSITIVE_Z, -}; -ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_z_up = { - UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_NEGATIVE_Y, -}; -ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_y_up = { - UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_NEGATIVE_Z, -}; -ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_z_up = { - UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_POSITIVE_Y, -}; - -ufbx_abi_data_def const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = { - sizeof(ufbx_unknown), - sizeof(ufbx_node), - sizeof(ufbx_mesh), - sizeof(ufbx_light), - sizeof(ufbx_camera), - sizeof(ufbx_bone), - sizeof(ufbx_empty), - sizeof(ufbx_line_curve), - sizeof(ufbx_nurbs_curve), - sizeof(ufbx_nurbs_surface), - sizeof(ufbx_nurbs_trim_surface), - sizeof(ufbx_nurbs_trim_boundary), - sizeof(ufbx_procedural_geometry), - sizeof(ufbx_stereo_camera), - sizeof(ufbx_camera_switcher), - sizeof(ufbx_marker), - sizeof(ufbx_lod_group), - sizeof(ufbx_skin_deformer), - sizeof(ufbx_skin_cluster), - sizeof(ufbx_blend_deformer), - sizeof(ufbx_blend_channel), - sizeof(ufbx_blend_shape), - sizeof(ufbx_cache_deformer), - sizeof(ufbx_cache_file), - sizeof(ufbx_material), - sizeof(ufbx_texture), - sizeof(ufbx_video), - sizeof(ufbx_shader), - sizeof(ufbx_shader_binding), - sizeof(ufbx_anim_stack), - sizeof(ufbx_anim_layer), - sizeof(ufbx_anim_value), - sizeof(ufbx_anim_curve), - sizeof(ufbx_display_layer), - sizeof(ufbx_selection_set), - sizeof(ufbx_selection_node), - sizeof(ufbx_character), - sizeof(ufbx_constraint), - sizeof(ufbx_audio_layer), - sizeof(ufbx_audio_clip), - sizeof(ufbx_pose), - sizeof(ufbx_metadata_object), -}; - -ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info) -{ - (void)user; - return ufbx_open_file_ctx(stream, info->context, path, path_len, NULL, NULL); -} - -ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) -{ - return ufbx_open_file_ctx(stream, (ufbx_open_file_context)NULL, path, path_len, opts, error); -} - -ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error) -{ - bool ok = false; - ufbxi_file_context fc; // ufbxi_uninit - ufbxi_begin_file_context(&fc, ctx, NULL); - if (path_len == SIZE_MAX) path_len = strlen(path); -#if !defined(UFBX_NO_STDIO) - ok = ufbxi_stdio_open(&fc, stream, path, path_len, opts ? opts->filename_null_terminated : false); -#else - (void)stream; - (void)path; - (void)path_len; - (void)opts; - ufbxi_fmt_err_info(&fc.error, "UFBX_NO_STDIO"); - ufbxi_report_err_msg(&fc.error, "UFBX_NO_STDIO", "Feature disabled"); -#endif - ufbxi_end_file_context(&fc, error, ok); - return ok; -} - -ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) -{ - return ufbx_open_memory_ctx(stream, (ufbx_open_file_context)NULL, data, data_size, opts, error); -} - -ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) -{ - ufbx_open_memory_opts local_opts; // ufbxi_uninit - if (!opts) { - memset(&local_opts, 0, sizeof(local_opts)); - opts = &local_opts; - } - ufbx_assert(opts->_begin_zero == 0 && opts->_end_zero == 0); - - ufbxi_file_context fc; // ufbxi_uninit - ufbxi_begin_file_context(&fc, ctx, &opts->allocator); - - size_t copy_size = opts->no_copy ? 0 : data_size; - - // Align the allocation size to 8 bytes to make sure the header is aligned. - size_t self_size = ufbxi_align_to_mask(sizeof(ufbxi_memory_stream) + copy_size, 7); - - void *memory = ufbxi_alloc(&fc.ator, char, self_size); - if (!memory) { - ufbxi_end_file_context(&fc, error, false); - return false; - } - - ufbxi_memory_stream *mem = (ufbxi_memory_stream*)memory; - memset(mem, 0, sizeof(ufbxi_memory_stream)); - - mem->size = data_size; - mem->self_size = self_size; - mem->close_cb = opts->close_cb; - - if (opts->no_copy) { - mem->data = data; - } else { - memcpy(mem->data_copy, data, data_size); - mem->data = mem->data_copy; - } - - // Transplant the allocator in the result blob - if (fc.parent_ator) { - mem->parent_ator = fc.parent_ator; - } else { - fc.parent_ator = &mem->local_ator; - } - - stream->read_fn = ufbxi_memory_read; - stream->skip_fn = ufbxi_memory_skip; - stream->size_fn = ufbxi_memory_size; - stream->close_fn = ufbxi_memory_close; - stream->user = mem; - - ufbxi_end_file_context(&fc, error, true); - - return true; -} - -ufbx_abi bool ufbx_is_thread_safe(void) -{ - return UFBXI_THREAD_SAFE != 0; -} - -ufbx_abi ufbx_scene *ufbx_load_memory(const void *data, size_t size, const ufbx_load_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbxi_context uc; // ufbxi_uninit - memset(&uc, 0, sizeof(ufbxi_context)); - uc.data_begin = uc.data = (const char *)data; - uc.data_size = size; - uc.progress_bytes_total = size; - return ufbxi_load(&uc, opts, error); -} - -ufbx_abi ufbx_scene *ufbx_load_file(const char *filename, const ufbx_load_opts *opts, ufbx_error *error) -{ - return ufbx_load_file_len(filename, SIZE_MAX, opts, error); -} - -ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_len, const ufbx_load_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbxi_context uc; // ufbxi_uninit - memset(&uc, 0, sizeof(ufbxi_context)); - uc.deferred_load = true; - uc.load_filename = filename; - uc.load_filename_len = filename_len; - return ufbxi_load(&uc, opts, error); -} - -ufbx_abi ufbx_scene *ufbx_load_stdio(void *file_void, const ufbx_load_opts *opts, ufbx_error *error) -{ - return ufbx_load_stdio_prefix(file_void, NULL, 0, opts, error); -} - -ufbx_abi ufbx_scene *ufbx_load_stdio_prefix(void *file_void, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) -{ -#if !defined(UFBX_NO_STDIO) - if (!file_void) return NULL; - ufbx_stream stream = { 0 }; - ufbxi_stdio_init(&stream, file_void, false); - return ufbx_load_stream_prefix(&stream, prefix, prefix_size, opts, error); -#else - (void)file_void; - (void)prefix; - (void)prefix_size; - (void)opts; - - ufbxi_context uc; // ufbxi_uninit - memset(&uc, 0, sizeof(ufbxi_context)); - ufbxi_fmt_err_info(&uc.error, "UFBX_NO_STDIO"); - ufbxi_report_err_msg(&uc.error, "UFBX_NO_STDIO", "Feature disabled"); - uc.deferred_failure = true; - return ufbxi_load(&uc, NULL, error); -#endif -} - -ufbx_abi ufbx_scene *ufbx_load_stream(const ufbx_stream *stream, const ufbx_load_opts *opts, ufbx_error *error) -{ - return ufbx_load_stream_prefix(stream, NULL, 0, opts, error); -} - -ufbx_abi ufbx_scene *ufbx_load_stream_prefix(const ufbx_stream *stream, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_scene, opts, error); - ufbxi_context uc; // ufbxi_uninit - memset(&uc, 0, sizeof(ufbxi_context)); - uc.data_begin = uc.data = (const char *)prefix; - uc.data_size = prefix_size; - uc.read_fn = stream->read_fn; - uc.skip_fn = stream->skip_fn; - uc.size_fn = stream->size_fn; - uc.close_fn = stream->close_fn; - uc.read_user = stream->user; - - ufbx_scene *scene = ufbxi_load(&uc, opts, error); - return scene; -} - -ufbx_abi void ufbx_free_scene(ufbx_scene *scene) -{ - if (!scene) return; - - ufbxi_scene_imp *imp = ufbxi_get_imp(ufbxi_scene_imp, scene); - ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); - if (imp->magic != UFBXI_SCENE_IMP_MAGIC) return; - ufbxi_release_ref(&imp->refcount); -} - -ufbx_abi void ufbx_retain_scene(ufbx_scene *scene) -{ - if (!scene) return; - - ufbxi_scene_imp *imp = ufbxi_get_imp(ufbxi_scene_imp, scene); - ufbx_assert(imp->magic == UFBXI_SCENE_IMP_MAGIC); - if (imp->magic != UFBXI_SCENE_IMP_MAGIC) return; - ufbxi_retain_ref(&imp->refcount); -} - -ufbx_abi ufbxi_noinline size_t ufbx_format_error(char *dst, size_t dst_size, const ufbx_error *error) -{ - if (!dst || !dst_size) return 0; - if (!error) { - *dst = '\0'; - return 0; - } - - size_t offset = 0; - - { - int num; - if (error->info_length > 0 && error->info_length < UFBX_ERROR_INFO_LENGTH) { - num = ufbxi_snprintf(dst + offset, dst_size - offset, "ufbx v%u.%u.%u error: %s (%.*s)\n", - UFBX_SOURCE_VERSION/1000000, UFBX_SOURCE_VERSION/1000%1000, UFBX_SOURCE_VERSION%1000, - error->description.data ? error->description.data : "Unknown error", - (int)error->info_length, error->info); - } else { - num = ufbxi_snprintf(dst + offset, dst_size - offset, "ufbx v%u.%u.%u error: %s\n", - UFBX_SOURCE_VERSION/1000000, UFBX_SOURCE_VERSION/1000%1000, UFBX_SOURCE_VERSION%1000, - error->description.data ? error->description.data : "Unknown error"); - } - - if (num > 0) offset = ufbxi_min_sz(offset + (size_t)num, dst_size - 1); - } - - size_t stack_size = ufbxi_min_sz(error->stack_size, UFBX_ERROR_STACK_MAX_DEPTH); - int line_width = 6; - for (size_t i = 0; i < stack_size; i++) { - const ufbx_error_frame *frame = &error->stack[i]; - int num = ufbxi_snprintf(dst + offset, dst_size - offset, "%*u:%s: %s\n", line_width, frame->source_line, frame->function.data, frame->description.data); - if (num > 0) offset = ufbxi_min_sz(offset + (size_t)num, dst_size - 1); - } - - return offset; -} - -ufbx_abi ufbx_prop *ufbx_find_prop_len(const ufbx_props *props, const char *name, size_t name_len) -{ - uint32_t key = ufbxi_get_name_key(name, name_len); - ufbx_string name_str = ufbxi_safe_string(name, name_len); - - while (props) { - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_prop, 4, &index, props->props.data, 0, props->props.count, - ( ufbxi_cmp_prop_less_ref(a, name_str, key) ), ( a->_internal_key == key && ufbxi_str_equal(a->name, name_str) )); - if (index != SIZE_MAX) return &props->props.data[index]; - - props = props->defaults; - } - - return NULL; -} - -ufbx_abi ufbx_real ufbx_find_real_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_real def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_real; - } else { - return def; - } -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_find_vec3_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_vec3 def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_vec3; - } else { - return def; - } -} - -ufbx_abi ufbxi_noinline int64_t ufbx_find_int_len(const ufbx_props *props, const char *name, size_t name_len, int64_t def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_int; - } else { - return def; - } -} - -ufbx_abi bool ufbx_find_bool_len(const ufbx_props *props, const char *name, size_t name_len, bool def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_int != 0; - } else { - return def; - } -} - -ufbx_abi ufbxi_noinline ufbx_string ufbx_find_string_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_string def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_str; - } else { - return def; - } -} - -ufbx_abi ufbx_blob ufbx_find_blob_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_blob def) -{ - ufbx_prop *prop = ufbx_find_prop_len(props, name, name_len); - if (prop) { - return prop->value_blob; - } else { - return def; - } -} - -ufbx_abi ufbx_prop *ufbx_find_prop_concat(const ufbx_props *props, const ufbx_string *parts, size_t num_parts) -{ - uint32_t key = ufbxi_get_concat_key(parts, num_parts); - - while (props) { - size_t index = SIZE_MAX; - - ufbxi_macro_lower_bound_eq(ufbx_prop, 2, &index, props->props.data, 0, props->props.count, - ( ufbxi_cmp_prop_less_concat(a, parts, num_parts, key) ), - ( a->_internal_key == key && ufbxi_concat_str_cmp(&a->name, parts, num_parts) == 0 )); - if (index != SIZE_MAX) return &props->props.data[index]; - - props = props->defaults; - } - - return NULL; -} - -ufbx_abi ufbx_element *ufbx_find_element_len(const ufbx_scene *scene, ufbx_element_type type, const char *name, size_t name_len) -{ - if (!scene) return NULL; - ufbx_string name_str = ufbxi_safe_string(name, name_len); - uint32_t key = ufbxi_get_name_key(name, name_len); - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_name_element, 16, &index, scene->elements_by_name.data, 0, scene->elements_by_name.count, - ( ufbxi_cmp_name_element_less_ref(a, name_str, type, key) ), ( ufbxi_str_equal(a->name, name_str) && a->type == type )); - - return index < SIZE_MAX ? scene->elements_by_name.data[index].element : NULL; -} - -ufbx_abi ufbx_element *ufbx_get_prop_element(const ufbx_element *element, const ufbx_prop *prop, ufbx_element_type type) -{ - ufbx_assert(element && prop); - if (!element || !prop) return NULL; - return ufbxi_fetch_dst_element((ufbx_element*)element, false, prop->name.data, type); -} - -ufbx_abi ufbx_element *ufbx_find_prop_element_len(const ufbx_element *element, const char *name, size_t name_len, ufbx_element_type type) -{ - const ufbx_prop *prop = ufbx_find_prop_len(&element->props, name, name_len); - if (prop) { - return ufbx_get_prop_element(element, prop, type); - } else { - return NULL; - } -} - -ufbx_abi ufbx_node *ufbx_find_node_len(const ufbx_scene *scene, const char *name, size_t name_len) -{ - return (ufbx_node*)ufbx_find_element_len(scene, UFBX_ELEMENT_NODE, name, name_len); -} - -ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack_len(const ufbx_scene *scene, const char *name, size_t name_len) -{ - return (ufbx_anim_stack*)ufbx_find_element_len(scene, UFBX_ELEMENT_ANIM_STACK, name, name_len); -} - -ufbx_abi ufbx_material *ufbx_find_material_len(const ufbx_scene *scene, const char *name, size_t name_len) -{ - return (ufbx_material*)ufbx_find_element_len(scene, UFBX_ELEMENT_MATERIAL, name, name_len); -} - -ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop_len(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop, size_t prop_len) -{ - ufbx_assert(layer); - ufbx_assert(element); - if (!layer || !element) return NULL; - - ufbx_string prop_str = ufbxi_safe_string(prop, prop_len); - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &index, layer->anim_props.data, 0, layer->anim_props.count, - ( a->element != element ? a->element < element : ufbxi_str_less(a->prop_name, prop_str) ), - ( a->element == element && ufbxi_str_equal(a->prop_name, prop_str) )); - - if (index == SIZE_MAX) return NULL; - return &layer->anim_props.data[index]; -} - -ufbx_abi ufbxi_noinline ufbx_anim_prop_list ufbx_find_anim_props(const ufbx_anim_layer *layer, const ufbx_element *element) -{ - ufbx_anim_prop_list result = { 0 }; - ufbx_assert(layer); - ufbx_assert(element); - if (!layer || !element) return result; - - size_t begin = layer->anim_props.count, end = begin; - ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &begin, layer->anim_props.data, 0, layer->anim_props.count, - ( a->element < element ), ( a->element == element )); - - ufbxi_macro_upper_bound_eq(ufbx_anim_prop, 16, &end, layer->anim_props.data, begin, layer->anim_props.count, - ( a->element == element )); - - if (begin != end) { - result.data = layer->anim_props.data + begin; - result.count = end - begin; - } - - return result; -} - -ufbx_abi ufbxi_noinline ufbx_matrix ufbx_get_compatible_matrix_for_normals(const ufbx_node *node) -{ - if (!node) return ufbx_identity_matrix; - - ufbx_transform geom_rot = ufbx_identity_transform; - geom_rot.rotation = node->geometry_transform.rotation; - ufbx_matrix geom_rot_mat = ufbx_transform_to_matrix(&geom_rot); - - ufbx_matrix norm_mat = ufbx_matrix_mul(&node->node_to_world, &geom_rot_mat); - norm_mat = ufbx_matrix_for_normals(&norm_mat); - return norm_mat; -} - -ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value) -{ - return ufbx_evaluate_curve_flags(curve, time, default_value, 0); -} - -ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags) -{ - if (!curve) return default_value; - if (curve->keyframes.count <= 1) { - if (curve->keyframes.count == 1) { - return curve->keyframes.data[0].value; - } else { - return default_value; - } - } - - if ((flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) == 0) { - if (time < curve->min_time || time > curve->max_time) { - return ufbxi_extrapolate_curve(curve, time, flags); - } - } - - size_t begin = 0; - size_t end = curve->keyframes.count; - const ufbx_keyframe *keys = curve->keyframes.data; - while (end - begin >= 8) { - size_t mid = (begin + end) >> 1; - if (keys[mid].time <= time) { - begin = mid + 1; - } else { - end = mid; - } - } - - end = curve->keyframes.count; - for (; begin < end; begin++) { - const ufbx_keyframe *next = &keys[begin]; - if (next->time <= time) continue; - - // First keyframe - if (begin == 0) return next->value; - - const ufbx_keyframe *prev = next - 1; - - // Exact keyframe - if (prev->time == time) return prev->value; - - double rcp_delta = 1.0 / (next->time - prev->time); - double t = (time - prev->time) * rcp_delta; - - switch (prev->interpolation) { - - case UFBX_INTERPOLATION_CONSTANT_PREV: - return prev->value; - - case UFBX_INTERPOLATION_CONSTANT_NEXT: - return next->value; - - case UFBX_INTERPOLATION_LINEAR: - return (ufbx_real)(prev->value*(1.0 - t) + next->value*t); - - case UFBX_INTERPOLATION_CUBIC: - { - double x1 = prev->right.dx * rcp_delta; - double x2 = 1.0 - next->left.dx * rcp_delta; - t = ufbxi_find_cubic_bezier_t(x1, x2, t); - - double t2 = t*t, t3 = t2*t; - double u = 1.0 - t, u2 = u*u, u3 = u2*u; - - double y0 = prev->value; - double y3 = next->value; - double y1 = y0 + prev->right.dy; - double y2 = y3 - next->left.dy; - - return (ufbx_real)(u3*y0 + 3.0 * (u2*t*y1 + u*t2*y2) + t3*y3); - } - - default: - ufbxi_unreachable("Bad interpolation mode"); - return 0.0f; - - } - } - - // Last keyframe - return curve->keyframes.data[curve->keyframes.count - 1].value; -} - -ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time) -{ - return ufbx_evaluate_anim_value_real_flags(anim_value, time, 0); -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time) -{ - return ufbx_evaluate_anim_value_vec3_flags(anim_value, time, 0); -} - -ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) -{ - if (!anim_value) { - return 0.0f; - } - - ufbx_real res = anim_value->default_value.x; - if (anim_value->curves[0]) res = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res, flags); - return res; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) -{ - if (!anim_value) { - ufbx_vec3 zero = { 0.0f }; - return zero; - } - - ufbx_vec3 res = anim_value->default_value; - if (anim_value->curves[0]) res.x = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res.x, flags); - if (anim_value->curves[1]) res.y = ufbx_evaluate_curve_flags(anim_value->curves[1], time, res.y, flags); - if (anim_value->curves[2]) res.z = ufbx_evaluate_curve_flags(anim_value->curves[2], time, res.z, flags); - return res; -} - -ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time) -{ - return ufbx_evaluate_prop_len_flags(anim, element, name, name_len, time, 0); -} - -ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags) -{ - ufbx_prop result; - - ufbx_prop *prop = ufbx_find_prop_len(&element->props, name, name_len); - if (prop) { - result = *prop; - } else { - memset(&result, 0, sizeof(result)); - result.name.data = name; - result.name.length = name_len; - result._internal_key = ufbxi_get_name_key(name, name_len); - result.flags = UFBX_PROP_FLAG_NOT_FOUND; - result.value_str.data = ufbxi_empty_char; - result.value_str.length = 0; - result.value_blob.data = NULL; - result.value_blob.size = 0; - } - - if (anim->prop_overrides.count > 0) { - ufbxi_find_prop_override(&anim->prop_overrides, element->element_id, &result); - return result; - } - - if ((result.flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_CONNECTED)) == 0) return result; - - if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { - ufbxi_evaluate_connected_prop(&result, anim, element, prop->name.data, time, flags); - } - - ufbxi_evaluate_props(anim, element, time, &result, 1, flags); - - return result; -} - -ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size) -{ - return ufbx_evaluate_props_flags(anim, element, time, buffer, buffer_size, 0); -} - -ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags) -{ - ufbx_props ret = { NULL }; - if (!element) return ret; - - size_t num_anim = 0; - ufbxi_prop_iter iter; // ufbxi_uninit - ufbxi_init_prop_iter(&iter, anim, element); - const ufbx_prop *prop = NULL; - while ((prop = ufbxi_next_prop(&iter)) != NULL) { - if (!(prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN|UFBX_PROP_FLAG_CONNECTED))) continue; - if (num_anim >= buffer_size) break; - - ufbx_prop *dst = &buffer[num_anim++]; - *dst = *prop; - - if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { - ufbxi_evaluate_connected_prop(dst, anim, element, prop->name.data, time, flags); - } - } - - ufbxi_evaluate_props(anim, element, time, buffer, num_anim, flags); - - ret.props.data = buffer; - ret.props.count = ret.num_animated = num_anim; - ret.defaults = (ufbx_props*)&element->props; - return ret; -} - -ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time) -{ - return ufbx_evaluate_transform_flags(anim, node, time, 0); -} - -static const char *const ufbxi_transform_props_all[] = { - ufbxi_Lcl_Rotation, - ufbxi_Lcl_Scaling, - ufbxi_Lcl_Translation, - ufbxi_PostRotation, - ufbxi_PreRotation, - ufbxi_RotationOffset, - ufbxi_RotationOrder, - ufbxi_RotationPivot, - ufbxi_ScalingOffset, - ufbxi_ScalingPivot, -}; - -static const char *const ufbxi_transform_props_rotation[] = { - ufbxi_Lcl_Rotation, - ufbxi_PostRotation, - ufbxi_PreRotation, - ufbxi_RotationOrder, -}; - -static const char *const ufbxi_transform_props_scale[] = { - ufbxi_Lcl_Scaling, -}; - -static const char *const ufbxi_transform_props_rotation_scale[] = { - ufbxi_Lcl_Rotation, - ufbxi_Lcl_Scaling, - ufbxi_PostRotation, - ufbxi_PreRotation, - ufbxi_RotationOrder, -}; - -ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags) -{ - ufbx_assert(anim); - ufbx_assert(node); - if (!node) return ufbx_identity_transform; - if (!anim) return node->local_transform; - if (node->is_root) return node->local_transform; - - if ((flags & UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES) == 0) { - flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; - } - - const char *const *prop_names = ufbxi_transform_props_all; - size_t num_prop_names = ufbxi_arraycount(ufbxi_transform_props_all); - uint32_t components = flags & (UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION); - if (components == (UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION|UFBX_TRANSFORM_FLAG_INCLUDE_SCALE)) { - prop_names = ufbxi_transform_props_rotation_scale; - num_prop_names = ufbxi_arraycount(ufbxi_transform_props_rotation_scale); - } else if (components == UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) { - prop_names = ufbxi_transform_props_rotation; - num_prop_names = ufbxi_arraycount(ufbxi_transform_props_rotation); - } else if (components == UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) { - prop_names = ufbxi_transform_props_scale; - num_prop_names = ufbxi_arraycount(ufbxi_transform_props_scale); - } else if (components == 0) { - return ufbx_identity_transform; - } - - const ufbx_vec3 *translation_scale = NULL; - ufbx_prop helper_scale; // ufbxi_uninit - ufbx_vec3 scale_factor = ufbxi_one_vec3; - bool use_scale_factor = false; - - if (node->parent && (flags & (UFBX_TRANSFORM_FLAG_INCLUDE_SCALE|UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION)) != 0) { - ufbx_node *parent = node->parent; - - if ((flags & UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE) == 0 && parent->inherit_scale_node) { - ufbx_node *p = parent->inherit_scale_node; - - if (node->is_scale_helper) { - use_scale_factor = true; - } - - while (p && p->scale_helper) { - ufbx_prop scale = ufbx_evaluate_prop(anim, &p->scale_helper->element, ufbxi_Lcl_Scaling, time); - scale_factor.x *= scale.value_vec3.x; - scale_factor.y *= scale.value_vec3.y; - scale_factor.z *= scale.value_vec3.z; - p = p->inherit_scale_node; - } - } - - if (parent->scale_helper && (flags & UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER) == 0) { - helper_scale = ufbx_evaluate_prop(anim, &parent->scale_helper->element, ufbxi_Lcl_Scaling, time); - if (helper_scale.flags & UFBX_PROP_FLAG_NOT_FOUND) { - helper_scale.value_vec3.x = 1.0f; - helper_scale.value_vec3.y = 1.0f; - helper_scale.value_vec3.z = 1.0f; - } - helper_scale.value_vec3.x *= scale_factor.x; - helper_scale.value_vec3.y *= scale_factor.y; - helper_scale.value_vec3.z *= scale_factor.z; - translation_scale = &helper_scale.value_vec3; - } - } - - uint32_t eval_flags = 0; - if (flags & UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION) { - eval_flags |= UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION; - } - - ufbx_prop buf[ufbxi_arraycount(ufbxi_transform_props_all)]; // ufbxi_uninit - ufbx_props props = ufbxi_evaluate_selected_props(anim, &node->element, time, buf, prop_names, num_prop_names, eval_flags); - ufbx_rotation_order order = (ufbx_rotation_order)ufbxi_find_enum(&props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); - - ufbx_transform transform; // ufbxi_uninit - if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) != 0) { - transform = ufbxi_get_transform(&props, order, node, translation_scale); - } else { - transform.translation = ufbx_zero_vec3; - if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) != 0) { - transform.rotation = ufbxi_get_rotation(&props, order, node); - } else { - transform.rotation = ufbx_identity_quat; - } - if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) != 0) { - transform.scale = ufbxi_get_scale(&props, node); - } else { - transform.scale = ufbxi_one_vec3; - } - } - - if (use_scale_factor) { - transform.scale.x *= scale_factor.x; - transform.scale.y *= scale_factor.y; - transform.scale.z *= scale_factor.z; - } - return transform; -} - -ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time) -{ - return ufbx_evaluate_blend_weight_flags(anim, channel, time, 0); -} - -ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags) -{ - const char *prop_names[] = { - ufbxi_DeformPercent, - }; - - ufbx_prop buf[ufbxi_arraycount(prop_names)]; // ufbxi_uninit - ufbx_props props = ufbxi_evaluate_selected_props(anim, &channel->element, time, buf, prop_names, ufbxi_arraycount(prop_names), flags); - return ufbxi_find_real(&props, ufbxi_DeformPercent, channel->weight * (ufbx_real)100.0) * (ufbx_real)0.01; -} - -ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_scene, opts, error); -#if UFBXI_FEATURE_SCENE_EVALUATION - ufbxi_eval_context ec = { 0 }; - return ufbxi_evaluate_scene(&ec, (ufbx_scene*)scene, anim, time, opts, error); -#else - if (error) { - memset(error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(error, "UFBX_ENABLE_SCENE_EVALUATION"); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_SCENE_EVALUATION", "Feature disabled"); - } - return NULL; -#endif -} - -ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_anim, opts, error); - ufbx_assert(scene); - - ufbxi_create_anim_context ac = { UFBX_ERROR_NONE }; - if (opts) { - ac.opts = *opts; - } - - ac.scene = scene; - - int ok = ufbxi_create_anim_imp(&ac); - - if (ok) { - ufbxi_clear_error(error); - ufbxi_anim_imp *imp = ac.imp; - return &imp->anim; - } else { - ufbxi_fix_error_type(&ac.error, "Failed to create anim", error); - ufbxi_buf_free(&ac.result); - ufbxi_free_ator(&ac.ator_result); - return NULL; - } -} - -ufbx_abi void ufbx_free_anim(ufbx_anim *anim) -{ - if (!anim) return; - if (!anim->custom) return; - - ufbxi_anim_imp *imp = ufbxi_get_imp(ufbxi_anim_imp, anim); - ufbx_assert(imp->magic == UFBXI_ANIM_IMP_MAGIC); - if (imp->magic != UFBXI_ANIM_IMP_MAGIC) return; - ufbxi_release_ref(&imp->refcount); -} - -ufbx_abi void ufbx_retain_anim(ufbx_anim *anim) -{ - if (!anim) return; - if (!anim->custom) return; - - ufbxi_anim_imp *imp = ufbxi_get_imp(ufbxi_anim_imp, anim); - ufbx_assert(imp->magic == UFBXI_ANIM_IMP_MAGIC); - if (imp->magic != UFBXI_ANIM_IMP_MAGIC) return; - ufbxi_retain_ref(&imp->refcount); -} - -ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_anim *anim, const ufbx_bake_opts *opts, ufbx_error *error) -{ - ufbx_assert(scene); -#if UFBXI_FEATURE_ANIMATION_BAKING - ufbxi_check_opts_ptr(ufbx_baked_anim, opts, error); - if (!anim) { - anim = scene->anim; - } - - ufbxi_bake_context bc = { UFBX_ERROR_NONE }; - if (opts) { - bc.opts = *opts; - } - - bc.scene = scene; - - int ok = ufbxi_bake_anim_imp(&bc, anim); - - ufbxi_buf_free(&bc.tmp); - ufbxi_buf_free(&bc.tmp_prop); - ufbxi_buf_free(&bc.tmp_times); - ufbxi_buf_free(&bc.tmp_bake_props); - ufbxi_buf_free(&bc.tmp_nodes); - ufbxi_buf_free(&bc.tmp_elements); - ufbxi_buf_free(&bc.tmp_props); - ufbxi_buf_free(&bc.tmp_bake_stack); - ufbxi_free(&bc.ator_tmp, char, bc.tmp_arr, bc.tmp_arr_size); - ufbxi_free_ator(&bc.ator_tmp); - - if (ok) { - ufbxi_clear_error(error); - ufbxi_baked_anim_imp *imp = bc.imp; - return &imp->bake; - } else { - ufbxi_fix_error_type(&bc.error, "Failed to bake anim", error); - ufbxi_buf_free(&bc.result); - ufbxi_free_ator(&bc.ator_result); - return NULL; - } -#else - if (error) { - memset(error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(error, "UFBX_ENABLE_ANIMATION_BAKING"); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_ANIMATION_BAKING", "Feature disabled"); - } - return NULL; -#endif -} - -ufbx_abi void ufbx_retain_baked_anim(ufbx_baked_anim *bake) -{ - if (!bake) return; - - ufbxi_baked_anim_imp *imp = ufbxi_get_imp(ufbxi_baked_anim_imp, bake); - ufbx_assert(imp->magic == UFBXI_BAKED_ANIM_IMP_MAGIC); - if (imp->magic != UFBXI_BAKED_ANIM_IMP_MAGIC) return; - ufbxi_retain_ref(&imp->refcount); -} - -ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake) -{ - if (!bake) return; - - ufbxi_baked_anim_imp *imp = ufbxi_get_imp(ufbxi_baked_anim_imp, bake); - ufbx_assert(imp->magic == UFBXI_BAKED_ANIM_IMP_MAGIC); - if (imp->magic != UFBXI_BAKED_ANIM_IMP_MAGIC) return; - ufbxi_release_ref(&imp->refcount); -} - - -ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id) -{ - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_baked_node, 8, &index, bake->nodes.data, 0, bake->nodes.count, - ( a->typed_id < typed_id ), ( a->typed_id == typed_id) ); - return index < SIZE_MAX ? &bake->nodes.data[index] : NULL; -} - -ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node) -{ - if (!bake || !node) return NULL; - return ufbx_find_baked_node_by_typed_id(bake, node->typed_id); -} - -ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id) -{ - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_baked_element, 8, &index, bake->elements.data, 0, bake->elements.count, - ( a->element_id < element_id ), ( a->element_id == element_id) ); - return index < SIZE_MAX ? &bake->elements.data[index] : NULL; -} - -ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element) -{ - if (!bake || !element) return NULL; - return ufbx_find_baked_element_by_element_id(bake, element->element_id); -} - -ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time) -{ - size_t begin = 0; - size_t end = keyframes.count; - const ufbx_baked_vec3 *keys = keyframes.data; - while (end - begin >= 8) { - size_t mid = (begin + end) >> 1; - if (keys[mid].time <= time) { - begin = mid + 1; - } else { - end = mid; - } - } - - end = keyframes.count; - for (; begin < end; begin++) { - const ufbx_baked_vec3 *next = &keys[begin]; - if (next->time <= time) continue; - if (begin == 0) return next->value; - - const ufbx_baked_vec3 *prev = next - 1; - if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; - if (time == prev->time) return prev->value; - double t = (time - prev->time) / (next->time - prev->time); - if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; - if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; - return ufbxi_lerp3(prev->value, next->value, (ufbx_real)t); - } - - return keyframes.data[keyframes.count - 1].value; -} - -ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time) -{ - size_t begin = 0; - size_t end = keyframes.count; - const ufbx_baked_quat *keys = keyframes.data; - while (end - begin >= 8) { - size_t mid = (begin + end) >> 1; - if (keys[mid].time <= time) { - begin = mid + 1; - } else { - end = mid; - } - } - - end = keyframes.count; - for (; begin < end; begin++) { - const ufbx_baked_quat *next = &keys[begin]; - if (next->time <= time) continue; - if (begin == 0) return next->value; - - const ufbx_baked_quat *prev = next - 1; - if (prev > keys && prev[-1].time == time) prev--; - if (time == prev->time) return prev->value; - double t = (time - prev->time) / (next->time - prev->time); - if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; - if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; - if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; - return ufbx_quat_slerp(prev->value, next->value, (ufbx_real)t); - } - - return keyframes.data[keyframes.count - 1].value; -} - -ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node) -{ - if (!pose || !node) return NULL; - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_bone_pose, 8, &index, pose->bone_poses.data, 0, pose->bone_poses.count, - ( a->bone_node->typed_id < node->typed_id ), ( a->bone_node == node )); - return index < SIZE_MAX ? &pose->bone_poses.data[index] : NULL; -} - -ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len) -{ - ufbx_string name_str = ufbxi_safe_string(name, name_len); - if (!material) return NULL; - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_material_texture, 4, &index, material->textures.data, 0, material->textures.count, - ( ufbxi_str_less(a->material_prop, name_str) ), ( ufbxi_str_equal(a->material_prop, name_str) )); - return index < SIZE_MAX ? material->textures.data[index].texture : NULL; -} - -ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len) -{ - ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, name, name_len); - if (bindings.count > 0) { - return bindings.data[0].material_prop; - } - return ufbx_empty_string; -} - -ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len) -{ - ufbx_shader_prop_binding_list bindings = { NULL, 0 }; - - ufbx_string name_str = ufbxi_safe_string(name, name_len); - if (!shader) return bindings; - - ufbxi_for_ptr_list(ufbx_shader_binding, p_bind, shader->bindings) { - ufbx_shader_binding *bind = *p_bind; - - size_t begin = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_shader_prop_binding, 4, &begin, bind->prop_bindings.data, 0, bind->prop_bindings.count, - ( ufbxi_str_less(a->shader_prop, name_str) ), ( ufbxi_str_equal(a->shader_prop, name_str) )); - - if (begin != SIZE_MAX) { - - size_t end = begin; - ufbxi_macro_upper_bound_eq(ufbx_shader_prop_binding, 4, &end, bind->prop_bindings.data, begin, bind->prop_bindings.count, - ( ufbxi_str_equal(a->shader_prop, name_str) )); - - bindings.data = bind->prop_bindings.data + begin; - bindings.count = end - begin; - break; - } - } - - return bindings; -} - -ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len) -{ - ufbx_string name_str = ufbxi_safe_string(name, name_len); - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_shader_texture_input, 4, &index, shader->inputs.data, 0, shader->inputs.count, - ( ufbxi_str_less(a->name, name_str) ), ( ufbxi_str_equal(a->name, name_str) )); - - if (index != SIZE_MAX) { - return &shader->inputs.data[index]; - } - - return NULL; -} - -ufbx_abi bool ufbx_coordinate_axes_valid(ufbx_coordinate_axes axes) -{ - if (axes.right < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.right > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; - if (axes.up < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.up > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; - if (axes.front < UFBX_COORDINATE_AXIS_POSITIVE_X || axes.front > UFBX_COORDINATE_AXIS_NEGATIVE_Z) return false; - - // Check that all the positive/negative axes are used - uint32_t mask = 0; - mask |= 1u << ((uint32_t)axes.right >> 1); - mask |= 1u << ((uint32_t)axes.up >> 1); - mask |= 1u << ((uint32_t)axes.front >> 1); - return (mask & 0x7u) == 0x7u; -} - -ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b) -{ - return ufbxi_mul_quat(a, b); -} - -ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v) -{ - return ufbxi_normalize3(v); -} - -ufbx_abi ufbxi_noinline ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b) -{ - return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; -} - -ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_normalize(ufbx_quat q) -{ - ufbx_real norm = ufbx_quat_dot(q, q); - if (norm == 0.0) return ufbx_identity_quat; - norm = (ufbx_real)ufbx_sqrt(norm); - q.x /= norm; - q.y /= norm; - q.z /= norm; - q.w /= norm; - return q; -} - -ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_fix_antipodal(ufbx_quat q, ufbx_quat reference) -{ - if (ufbx_quat_dot(q, reference) < 0.0f) { - q.x = -q.x; q.y = -q.y; q.z = -q.z; q.w = -q.w; - } - return q; -} - -ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_slerp(ufbx_quat a, ufbx_quat b, ufbx_real t) -{ - double dot = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; - if (dot < 0.0) { - dot = -dot; - b.x = -b.x; b.y = -b.y; b.z = -b.z; b.w = -b.w; - } - double omega = ufbx_acos(ufbx_fmin(ufbx_fmax(dot, 0.0), 1.0)); - if (omega <= 1.175494351e-38f) return a; - double rcp_so = 1.0 / ufbx_sin(omega); - double af = ufbx_sin((1.0 - t) * omega) * rcp_so; - double bf = ufbx_sin(t * omega) * rcp_so; - - double x = af*a.x + bf*b.x; - double y = af*a.y + bf*b.y; - double z = af*a.z + bf*b.z; - double w = af*a.w + bf*b.w; - double rcp_len = 1.0 / ufbx_sqrt(x*x + y*y + z*z + w*w); - - ufbx_quat ret; - ret.x = (ufbx_real)(x * rcp_len); - ret.y = (ufbx_real)(y * rcp_len); - ret.z = (ufbx_real)(z * rcp_len); - ret.w = (ufbx_real)(w * rcp_len); - return ret; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_quat_rotate_vec3(ufbx_quat q, ufbx_vec3 v) -{ - ufbx_real xy = q.x*v.y - q.y*v.x; - ufbx_real xz = q.x*v.z - q.z*v.x; - ufbx_real yz = q.y*v.z - q.z*v.y; - ufbx_vec3 r; - r.x = 2.0f * (+ q.w*yz + q.y*xy + q.z*xz) + v.x; - r.y = 2.0f * (- q.x*xy - q.w*xz + q.z*yz) + v.y; - r.z = 2.0f * (- q.x*xz - q.y*yz + q.w*xy) + v.z; - return r; -} - -ufbx_abi ufbxi_noinline ufbx_quat ufbx_euler_to_quat(ufbx_vec3 v, ufbx_rotation_order order) -{ - double vx = v.x * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); - double vy = v.y * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); - double vz = v.z * (UFBXI_DEG_TO_RAD_DOUBLE * 0.5); - double cx = ufbx_cos(vx), sx = ufbx_sin(vx); - double cy = ufbx_cos(vy), sy = ufbx_sin(vy); - double cz = ufbx_cos(vz), sz = ufbx_sin(vz); - ufbx_quat q; - - // Generated by `misc/gen_rotation_order.py` - switch (order) { - case UFBX_ROTATION_ORDER_XYZ: - q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); - break; - case UFBX_ROTATION_ORDER_XZY: - q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); - break; - case UFBX_ROTATION_ORDER_YZX: - q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); - break; - case UFBX_ROTATION_ORDER_YXZ: - q.x = (ufbx_real)(-cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy + cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); - break; - case UFBX_ROTATION_ORDER_ZXY: - q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz - cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz + sx*sy*sz); - break; - case UFBX_ROTATION_ORDER_ZYX: - q.x = (ufbx_real)(cx*sy*sz + cy*cz*sx); - q.y = (ufbx_real)(cx*cz*sy - cy*sx*sz); - q.z = (ufbx_real)(cx*cy*sz + cz*sx*sy); - q.w = (ufbx_real)(cx*cy*cz - sx*sy*sz); - break; - default: - q.x = q.y = q.z = 0.0f; q.w = 1.0f; - break; - } - - return q; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_quat_to_euler(ufbx_quat q, ufbx_rotation_order order) -{ - // TODO: Derive these rigorously - #if defined(UFBX_REAL_IS_FLOAT) - const double eps = 0.9999999; - #else - const double eps = 0.999999999; - #endif - - double vx, vy, vz; - double t; - - double qx = q.x, qy = q.y, qz = q.z, qw = q.w; - - // Generated by `misc/gen_quat_to_euler.py` - switch (order) { - case UFBX_ROTATION_ORDER_XYZ: - t = 2.0f*(qw*qy - qx*qz); - if (ufbx_fabs(t) < eps) { - vy = ufbx_asin(t); - vz = ufbx_atan2(2.0f*(qw*qz + qx*qy), 2.0f*(qw*qw + qx*qx) - 1.0f); - vx = -ufbx_atan2(-2.0f*(qw*qx + qy*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); - } else { - vy = ufbx_copysign(UFBXI_DPI*0.5, t); - vz = ufbx_atan2(-2.0f*t*(qw*qx - qy*qz), t*(2.0f*qw*qy + 2.0f*qx*qz)); - vx = 0.0f; - } - break; - case UFBX_ROTATION_ORDER_XZY: - t = 2.0f*(qw*qz + qx*qy); - if (ufbx_fabs(t) < eps) { - vz = ufbx_asin(t); - vy = ufbx_atan2(2.0f*(qw*qy - qx*qz), 2.0f*(qw*qw + qx*qx) - 1.0f); - vx = -ufbx_atan2(-2.0f*(qw*qx - qy*qz), 2.0f*(qw*qw + qy*qy) - 1.0f); - } else { - vz = ufbx_copysign(UFBXI_DPI*0.5, t); - vy = ufbx_atan2(2.0f*t*(qw*qx + qy*qz), -t*(2.0f*qx*qy - 2.0f*qw*qz)); - vx = 0.0f; - } - break; - case UFBX_ROTATION_ORDER_YZX: - t = 2.0f*(qw*qz - qx*qy); - if (ufbx_fabs(t) < eps) { - vz = ufbx_asin(t); - vx = ufbx_atan2(2.0f*(qw*qx + qy*qz), 2.0f*(qw*qw + qy*qy) - 1.0f); - vy = -ufbx_atan2(-2.0f*(qw*qy + qx*qz), 2.0f*(qw*qw + qx*qx) - 1.0f); - } else { - vz = ufbx_copysign(UFBXI_DPI*0.5, t); - vx = ufbx_atan2(-2.0f*t*(qw*qy - qx*qz), t*(2.0f*qw*qz + 2.0f*qx*qy)); - vy = 0.0f; - } - break; - case UFBX_ROTATION_ORDER_YXZ: - t = 2.0f*(qw*qx + qy*qz); - if (ufbx_fabs(t) < eps) { - vx = ufbx_asin(t); - vz = ufbx_atan2(2.0f*(qw*qz - qx*qy), 2.0f*(qw*qw + qy*qy) - 1.0f); - vy = -ufbx_atan2(-2.0f*(qw*qy - qx*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); - } else { - vx = ufbx_copysign(UFBXI_DPI*0.5, t); - vz = ufbx_atan2(2.0f*t*(qw*qy + qx*qz), -t*(2.0f*qy*qz - 2.0f*qw*qx)); - vy = 0.0f; - } - break; - case UFBX_ROTATION_ORDER_ZXY: - t = 2.0f*(qw*qx - qy*qz); - if (ufbx_fabs(t) < eps) { - vx = ufbx_asin(t); - vy = ufbx_atan2(2.0f*(qw*qy + qx*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); - vz = -ufbx_atan2(-2.0f*(qw*qz + qx*qy), 2.0f*(qw*qw + qy*qy) - 1.0f); - } else { - vx = ufbx_copysign(UFBXI_DPI*0.5, t); - vy = ufbx_atan2(-2.0f*t*(qw*qz - qx*qy), t*(2.0f*qw*qx + 2.0f*qy*qz)); - vz = 0.0f; - } - break; - case UFBX_ROTATION_ORDER_ZYX: - t = 2.0f*(qw*qy + qx*qz); - if (ufbx_fabs(t) < eps) { - vy = ufbx_asin(t); - vx = ufbx_atan2(2.0f*(qw*qx - qy*qz), 2.0f*(qw*qw + qz*qz) - 1.0f); - vz = -ufbx_atan2(-2.0f*(qw*qz - qx*qy), 2.0f*(qw*qw + qx*qx) - 1.0f); - } else { - vy = ufbx_copysign(UFBXI_DPI*0.5, t); - vx = ufbx_atan2(2.0f*t*(qw*qz + qx*qy), -t*(2.0f*qx*qz - 2.0f*qw*qy)); - vz = 0.0f; - } - break; - default: - vx = vy = vz = 0.0; - break; - } - - vx *= UFBXI_RAD_TO_DEG_DOUBLE; - vy *= UFBXI_RAD_TO_DEG_DOUBLE; - vz *= UFBXI_RAD_TO_DEG_DOUBLE; - - ufbx_vec3 v = { (ufbx_real)vx, (ufbx_real)vy, (ufbx_real)vz }; - return v; -} - -ufbx_abi ufbxi_noinline ufbx_matrix ufbx_matrix_mul(const ufbx_matrix *a, const ufbx_matrix *b) -{ - ufbx_assert(a && b); - if (!a || !b) return ufbx_identity_matrix; - - ufbx_matrix dst; - - dst.m03 = a->m00*b->m03 + a->m01*b->m13 + a->m02*b->m23 + a->m03; - dst.m13 = a->m10*b->m03 + a->m11*b->m13 + a->m12*b->m23 + a->m13; - dst.m23 = a->m20*b->m03 + a->m21*b->m13 + a->m22*b->m23 + a->m23; - - dst.m00 = a->m00*b->m00 + a->m01*b->m10 + a->m02*b->m20; - dst.m10 = a->m10*b->m00 + a->m11*b->m10 + a->m12*b->m20; - dst.m20 = a->m20*b->m00 + a->m21*b->m10 + a->m22*b->m20; - - dst.m01 = a->m00*b->m01 + a->m01*b->m11 + a->m02*b->m21; - dst.m11 = a->m10*b->m01 + a->m11*b->m11 + a->m12*b->m21; - dst.m21 = a->m20*b->m01 + a->m21*b->m11 + a->m22*b->m21; - - dst.m02 = a->m00*b->m02 + a->m01*b->m12 + a->m02*b->m22; - dst.m12 = a->m10*b->m02 + a->m11*b->m12 + a->m12*b->m22; - dst.m22 = a->m20*b->m02 + a->m21*b->m12 + a->m22*b->m22; - - return dst; -} - -ufbx_abi ufbx_real ufbx_matrix_determinant(const ufbx_matrix *m) -{ - return - - m->m02*m->m11*m->m20 + m->m01*m->m12*m->m20 + m->m02*m->m10*m->m21 - - m->m00*m->m12*m->m21 - m->m01*m->m10*m->m22 + m->m00*m->m11*m->m22; -} - -ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m) -{ - ufbx_real det = ufbx_matrix_determinant(m); - - ufbx_matrix r; - if (ufbx_fabs(det) <= UFBX_EPSILON) { - memset(&r, 0, sizeof(r)); - return r; - } - - ufbx_real rcp_det = 1.0f / det; - - r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * rcp_det; - r.m10 = ( + m->m12*m->m20 - m->m10*m->m22) * rcp_det; - r.m20 = ( - m->m11*m->m20 + m->m10*m->m21) * rcp_det; - r.m01 = ( + m->m02*m->m21 - m->m01*m->m22) * rcp_det; - r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * rcp_det; - r.m21 = ( + m->m01*m->m20 - m->m00*m->m21) * rcp_det; - r.m02 = ( - m->m02*m->m11 + m->m01*m->m12) * rcp_det; - r.m12 = ( + m->m02*m->m10 - m->m00*m->m12) * rcp_det; - r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * rcp_det; - r.m03 = (m->m03*m->m12*m->m21 - m->m02*m->m13*m->m21 - m->m03*m->m11*m->m22 + m->m01*m->m13*m->m22 + m->m02*m->m11*m->m23 - m->m01*m->m12*m->m23) * rcp_det; - r.m13 = (m->m02*m->m13*m->m20 - m->m03*m->m12*m->m20 + m->m03*m->m10*m->m22 - m->m00*m->m13*m->m22 - m->m02*m->m10*m->m23 + m->m00*m->m12*m->m23) * rcp_det; - r.m23 = (m->m03*m->m11*m->m20 - m->m01*m->m13*m->m20 - m->m03*m->m10*m->m21 + m->m00*m->m13*m->m21 + m->m01*m->m10*m->m23 - m->m00*m->m11*m->m23) * rcp_det; - - return r; -} - -ufbx_abi ufbxi_noinline ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m) -{ - ufbx_real det = ufbx_matrix_determinant(m); - ufbx_real det_sign = det >= 0.0f ? 1.0f : -1.0f; - - ufbx_matrix r; - r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * det_sign; - r.m01 = ( + m->m12*m->m20 - m->m10*m->m22) * det_sign; - r.m02 = ( - m->m11*m->m20 + m->m10*m->m21) * det_sign; - r.m10 = ( + m->m02*m->m21 - m->m01*m->m22) * det_sign; - r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * det_sign; - r.m12 = ( + m->m01*m->m20 - m->m00*m->m21) * det_sign; - r.m20 = ( - m->m02*m->m11 + m->m01*m->m12) * det_sign; - r.m21 = ( + m->m02*m->m10 - m->m00*m->m12) * det_sign; - r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * det_sign; - r.m03 = r.m13 = r.m23 = 0.0f; - - return r; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_transform_position(const ufbx_matrix *m, ufbx_vec3 v) -{ - ufbx_assert(m); - if (!m) return ufbx_zero_vec3; - - ufbx_vec3 r; - r.x = m->m00*v.x + m->m01*v.y + m->m02*v.z + m->m03; - r.y = m->m10*v.x + m->m11*v.y + m->m12*v.z + m->m13; - r.z = m->m20*v.x + m->m21*v.y + m->m22*v.z + m->m23; - return r; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_transform_direction(const ufbx_matrix *m, ufbx_vec3 v) -{ - ufbx_assert(m); - if (!m) return ufbx_zero_vec3; - - ufbx_vec3 r; - r.x = m->m00*v.x + m->m01*v.y + m->m02*v.z; - r.y = m->m10*v.x + m->m11*v.y + m->m12*v.z; - r.z = m->m20*v.x + m->m21*v.y + m->m22*v.z; - return r; -} - -ufbx_abi ufbxi_noinline ufbx_matrix ufbx_transform_to_matrix(const ufbx_transform *t) -{ - ufbx_assert(t); - if (!t) return ufbx_identity_matrix; - - ufbx_quat q = t->rotation; - ufbx_real sx = 2.0f * t->scale.x, sy = 2.0f * t->scale.y, sz = 2.0f * t->scale.z; - ufbx_real xx = q.x*q.x, xy = q.x*q.y, xz = q.x*q.z, xw = q.x*q.w; - ufbx_real yy = q.y*q.y, yz = q.y*q.z, yw = q.y*q.w; - ufbx_real zz = q.z*q.z, zw = q.z*q.w; - ufbx_matrix m; - m.m00 = sx * (- yy - zz + 0.5f); - m.m10 = sx * (+ xy + zw); - m.m20 = sx * (- yw + xz); - m.m01 = sy * (- zw + xy); - m.m11 = sy * (- xx - zz + 0.5f); - m.m21 = sy * (+ xw + yz); - m.m02 = sz * (+ xz + yw); - m.m12 = sz * (- xw + yz); - m.m22 = sz * (- xx - yy + 0.5f); - m.m03 = t->translation.x; - m.m13 = t->translation.y; - m.m23 = t->translation.z; - return m; -} - -ufbx_abi ufbxi_noinline ufbx_transform ufbx_matrix_to_transform(const ufbx_matrix *m) -{ - ufbx_assert(m); - if (!m) return ufbx_identity_transform; - - ufbx_real det = ufbx_matrix_determinant(m); - - ufbx_transform t; - t.translation = m->cols[3]; - t.scale.x = ufbxi_length3(m->cols[0]); - t.scale.y = ufbxi_length3(m->cols[1]); - t.scale.z = ufbxi_length3(m->cols[2]); - - // Flip a single non-zero axis if negative determinant - ufbx_real sign_x = 1.0f; - ufbx_real sign_y = 1.0f; - ufbx_real sign_z = 1.0f; - if (det < 0.0f) { - if (t.scale.x > 0.0f) sign_x = -1.0f; - else if (t.scale.y > 0.0f) sign_y = -1.0f; - else if (t.scale.z > 0.0f) sign_z = -1.0f; - } - - ufbx_vec3 x = ufbxi_mul3(m->cols[0], t.scale.x > 0.0f ? sign_x / t.scale.x : 0.0f); - ufbx_vec3 y = ufbxi_mul3(m->cols[1], t.scale.y > 0.0f ? sign_y / t.scale.y : 0.0f); - ufbx_vec3 z = ufbxi_mul3(m->cols[2], t.scale.z > 0.0f ? sign_z / t.scale.z : 0.0f); - ufbx_real trace = x.x + y.y + z.z; - if (trace > 0.0f) { - ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, trace + 1.0)), b = (a != 0.0f) ? 0.5f / a : 0.0f; - t.rotation.x = (y.z - z.y) * b; - t.rotation.y = (z.x - x.z) * b; - t.rotation.z = (x.y - y.x) * b; - t.rotation.w = 0.5f * a; - } else if (x.x > y.y && x.x > z.z) { - ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 + x.x - y.y - z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; - t.rotation.x = 0.5f * a; - t.rotation.y = (y.x + x.y) * b; - t.rotation.z = (z.x + x.z) * b; - t.rotation.w = (y.z - z.y) * b; - } - else if (y.y > z.z) { - ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 - x.x + y.y - z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; - t.rotation.x = (y.x + x.y) * b; - t.rotation.y = 0.5f * a; - t.rotation.z = (z.y + y.z) * b; - t.rotation.w = (z.x - x.z) * b; - } - else { - ufbx_real a = (ufbx_real)ufbx_sqrt(ufbx_fmax(0.0, 1.0 - x.x - y.y + z.z)), b = (a != 0.0f) ? 0.5f / a : 0.0f; - t.rotation.x = (z.x + x.z) * b; - t.rotation.y = (z.y + y.z) * b; - t.rotation.z = 0.5f * a; - t.rotation.w = (x.y - y.x) * b; - } - - ufbx_real len = t.rotation.x*t.rotation.x + t.rotation.y*t.rotation.y + t.rotation.z*t.rotation.z + t.rotation.w*t.rotation.w; - if (ufbx_fabs(len - 1.0f) > UFBX_EPSILON) { - if (ufbx_fabs(len) <= UFBX_EPSILON) { - t.rotation = ufbx_identity_quat; - } else { - t.rotation.x /= len; - t.rotation.y /= len; - t.rotation.z /= len; - t.rotation.w /= len; - } - } - - t.scale.x *= sign_x; - t.scale.y *= sign_y; - t.scale.z *= sign_z; - - return t; -} - -ufbx_abi ufbxi_noinline ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic *panic, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) -{ - ufbx_assert(skin); - if (ufbxi_panicf(panic, vertex < skin->vertices.count, "vertex (%zu) out of bounds (%zu)", vertex, skin->vertices.count)) return ufbx_identity_matrix; - - if (!skin || vertex >= skin->vertices.count) return ufbx_identity_matrix; - ufbx_skin_vertex skin_vertex = skin->vertices.data[vertex]; - - ufbx_matrix mat = { 0.0f }; - ufbx_quat q0 = { 0.0f }, qe = { 0.0f }; - ufbx_quat first_q0 = { 0.0f }; - ufbx_vec3 qs = { 0.0f, 0.0f, 0.0f }; - ufbx_real total_weight = 0.0f; - - for (uint32_t i = 0; i < skin_vertex.num_weights; i++) { - ufbx_skin_weight weight = skin->weights.data[skin_vertex.weight_begin + i]; - ufbx_skin_cluster *cluster = skin->clusters.data[weight.cluster_index]; - const ufbx_node *node = cluster->bone_node; - if (!node) continue; - - total_weight += weight.weight; - if (skin_vertex.dq_weight > 0.0f) { - ufbx_transform t = cluster->geometry_to_world_transform; - ufbx_quat vq0 = t.rotation; - if (i == 0) first_q0 = vq0; - - if (ufbx_quat_dot(first_q0, vq0) < 0.0f) { - vq0.x = -vq0.x; - vq0.y = -vq0.y; - vq0.z = -vq0.z; - vq0.w = -vq0.w; - } - - ufbx_quat vqt = { 0.5f * t.translation.x, 0.5f * t.translation.y, 0.5f * t.translation.z }; - ufbx_quat vqe = ufbxi_mul_quat(vqt, vq0); - ufbxi_add_weighted_quat(&q0, vq0, weight.weight); - ufbxi_add_weighted_quat(&qe, vqe, weight.weight); - ufbxi_add_weighted_vec3(&qs, t.scale, weight.weight); - } - - if (skin_vertex.dq_weight < 1.0f) { - ufbxi_add_weighted_mat(&mat, &cluster->geometry_to_world, (1.0f-skin_vertex.dq_weight) * weight.weight); - } - } - - if (total_weight <= 0.0f) { - if (fallback) { - return *fallback; - } else { - return ufbx_identity_matrix; - } - } - - if (ufbx_fabs(total_weight - 1.0f) > UFBX_EPSILON) { - ufbx_real rcp_weight = ufbx_fabs(total_weight) > UFBX_EPSILON ? 1.0f / total_weight : 0.0f; - if (skin_vertex.dq_weight > 0.0f) { - q0.x *= rcp_weight; q0.y *= rcp_weight; q0.z *= rcp_weight; q0.w *= rcp_weight; - qe.x *= rcp_weight; qe.y *= rcp_weight; qe.z *= rcp_weight; qe.w *= rcp_weight; - qs.x *= rcp_weight; qs.y *= rcp_weight; qs.z *= rcp_weight; - } - if (skin_vertex.dq_weight < 1.0f) { - mat.m00 *= rcp_weight; mat.m01 *= rcp_weight; mat.m02 *= rcp_weight; mat.m03 *= rcp_weight; - mat.m10 *= rcp_weight; mat.m11 *= rcp_weight; mat.m12 *= rcp_weight; mat.m13 *= rcp_weight; - mat.m20 *= rcp_weight; mat.m21 *= rcp_weight; mat.m22 *= rcp_weight; mat.m23 *= rcp_weight; - } - } - - if (skin_vertex.dq_weight > 0.0f) { - ufbx_transform dqt; // ufbxi_uninit - ufbx_real rcp_len = (ufbx_real)(1.0 / ufbx_sqrt(q0.x*q0.x + q0.y*q0.y + q0.z*q0.z + q0.w*q0.w)); - ufbx_real rcp_len2x2 = 2.0f * rcp_len * rcp_len; - dqt.rotation.x = q0.x * rcp_len; - dqt.rotation.y = q0.y * rcp_len; - dqt.rotation.z = q0.z * rcp_len; - dqt.rotation.w = q0.w * rcp_len; - dqt.scale.x = qs.x; - dqt.scale.y = qs.y; - dqt.scale.z = qs.z; - dqt.translation.x = rcp_len2x2 * (- qe.w*q0.x + qe.x*q0.w - qe.y*q0.z + qe.z*q0.y); - dqt.translation.y = rcp_len2x2 * (- qe.w*q0.y + qe.x*q0.z + qe.y*q0.w - qe.z*q0.x); - dqt.translation.z = rcp_len2x2 * (- qe.w*q0.z - qe.x*q0.y + qe.y*q0.x + qe.z*q0.w); - ufbx_matrix dqm = ufbx_transform_to_matrix(&dqt); - if (skin_vertex.dq_weight < 1.0f) { - ufbxi_add_weighted_mat(&mat, &dqm, skin_vertex.dq_weight); - } else { - mat = dqm; - } - } - - return mat; -} - -ufbx_abi ufbxi_noinline uint32_t ufbx_get_blend_shape_offset_index(const ufbx_blend_shape *shape, size_t vertex) -{ - ufbx_assert(shape); - if (!shape) return UFBX_NO_INDEX; - - size_t index = SIZE_MAX; - uint32_t vertex_ix = (uint32_t)vertex; - - ufbxi_macro_lower_bound_eq(uint32_t, 16, &index, shape->offset_vertices.data, 0, shape->num_offsets, - ( *a < vertex_ix ), ( *a == vertex_ix )); - if (index >= UINT32_MAX) return UFBX_NO_INDEX; - - return (uint32_t)index; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_get_blend_shape_vertex_offset(const ufbx_blend_shape *shape, size_t vertex) -{ - uint32_t index = ufbx_get_blend_shape_offset_index(shape, vertex); - if (index == UFBX_NO_INDEX) return ufbx_zero_vec3; - return shape->position_offsets.data[index]; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_get_blend_vertex_offset(const ufbx_blend_deformer *blend, size_t vertex) -{ - ufbx_assert(blend); - if (!blend) return ufbx_zero_vec3; - - ufbx_vec3 offset = ufbx_zero_vec3; - - ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, blend->channels) { - ufbx_blend_channel *chan = *p_chan; - ufbxi_for_list(ufbx_blend_keyframe, key, chan->keyframes) { - if (key->effective_weight == 0.0f) continue; - - ufbx_vec3 key_offset = ufbx_get_blend_shape_vertex_offset(key->shape, vertex); - ufbxi_add_weighted_vec3(&offset, key_offset, key->effective_weight); - } - } - - return offset; -} - -ufbx_abi void ufbx_add_blend_shape_vertex_offsets(const ufbx_blend_shape *shape, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight) -{ - if (weight == 0.0f) return; - if (!vertices) return; - - size_t num_offsets = shape->num_offsets; - uint32_t *vertex_indices = shape->offset_vertices.data; - ufbx_vec3 *offsets = shape->position_offsets.data; - ufbx_real_list weights = shape->offset_weights; - for (size_t i = 0; i < num_offsets; i++) { - uint32_t index = vertex_indices[i]; - if (index < num_vertices) { - ufbx_real vertex_weight = weight; - if (i < weights.count) { - vertex_weight *= weights.data[i]; - } - ufbxi_add_weighted_vec3(&vertices[index], offsets[i], vertex_weight); - } - } -} - -ufbx_abi void ufbx_add_blend_vertex_offsets(const ufbx_blend_deformer *blend, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight) -{ - ufbx_assert(blend); - if (!blend) return; - - ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, blend->channels) { - ufbx_blend_channel *chan = *p_chan; - ufbxi_for_list(ufbx_blend_keyframe, key, chan->keyframes) { - if (key->effective_weight == 0.0f) continue; - ufbx_add_blend_shape_vertex_offsets(key->shape, vertices, num_vertices, weight * key->effective_weight); - } - } -} - -ufbx_abi size_t ufbx_evaluate_nurbs_basis(const ufbx_nurbs_basis *basis, ufbx_real u, ufbx_real *weights, size_t num_weights, ufbx_real *derivatives, size_t num_derivatives) -{ - ufbx_assert(basis); - if (!basis) return SIZE_MAX; - if (basis->order == 0) return SIZE_MAX; - if (!basis->valid) return SIZE_MAX; - - size_t degree = basis->order - 1; - ufbx_assert(degree >= 1); - - // Binary search for the knot span `[min_u, max_u]` where `min_u <= u < max_u` - ufbx_real_list knots = basis->knot_vector; - size_t knot = SIZE_MAX; - - if (u <= basis->t_min) { - knot = degree; - u = basis->t_min; - } else if (u >= basis->t_max) { - knot = basis->knot_vector.count - degree - 2; - u = basis->t_max; - } else { - ufbxi_macro_lower_bound_eq(ufbx_real, 8, &knot, knots.data, 0, knots.count - 1, - ( a[1] <= u ), ( a[0] <= u && u < a[1] )); - } - - // The found effective control points are found left from `knot`, locally - // we use `knot - ix` here as it's more convenient for the following algorithm - // but we return it as `knot - degree` so that users can find the control points - // at `points[knot], points[knot+1], ..., points[knot+degree]` - if (knot < degree) return SIZE_MAX; - - if (num_derivatives == 0) derivatives = NULL; - if (num_weights < basis->order) return knot - degree; - if (!weights) return knot - degree; - - weights[0] = 1.0f; - for (size_t p = 1; p <= degree; p++) { - - ufbx_real prev = 0.0f; - ufbx_real g = 1.0f - ufbxi_nurbs_weight(&knots, knot - p + 1, p, u); - ufbx_real dg = 0.0f; - if (derivatives && p == degree) { - dg = ufbxi_nurbs_deriv(&knots, knot - p + 1, p); - } - - for (size_t i = p; i > 0; i--) { - ufbx_real f = ufbxi_nurbs_weight(&knots, knot - p + i, p, u); - ufbx_real weight = weights[i - 1]; - weights[i] = f*weight + g*prev; - - if (derivatives && p == degree) { - ufbx_real df = ufbxi_nurbs_deriv(&knots, knot - p + i, p); - if (i < num_derivatives) { - derivatives[i] = df*weight - dg*prev; - } - dg = df; - } - - prev = weight; - g = 1.0f - f; - } - - weights[0] = g*prev; - if (derivatives && p == degree) { - derivatives[0] = -dg*prev; - } - } - - return knot - degree; -} - -ufbx_abi ufbxi_noinline ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nurbs_curve *curve, ufbx_real u) -{ - ufbx_curve_point result = { false }; - - ufbx_assert(curve); - if (!curve) return result; - - ufbx_real weights[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit - ufbx_real derivs[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit - size_t base = ufbx_evaluate_nurbs_basis(&curve->basis, u, weights, UFBXI_MAX_NURBS_ORDER, derivs, UFBXI_MAX_NURBS_ORDER); - if (base == SIZE_MAX) return result; - - ufbx_vec4 p = { 0 }; - ufbx_vec4 d = { 0 }; - - size_t order = curve->basis.order; - if (order > UFBXI_MAX_NURBS_ORDER) return result; - if (curve->control_points.count == 0) return result; - - for (size_t i = 0; i < order; i++) { - size_t ix = (base + i) % curve->control_points.count; - ufbx_vec4 cp = curve->control_points.data[ix]; - ufbx_real weight = weights[i] * cp.w, deriv = derivs[i] * cp.w; - - p.x += cp.x * weight; - p.y += cp.y * weight; - p.z += cp.z * weight; - p.w += weight; - - d.x += cp.x * deriv; - d.y += cp.y * deriv; - d.z += cp.z * deriv; - d.w += deriv; - } - - ufbx_real rcp_w = 1.0f / p.w; - result.valid = true; - result.position.x = p.x * rcp_w; - result.position.y = p.y * rcp_w; - result.position.z = p.z * rcp_w; - result.derivative.x = (d.x - d.w*result.position.x) * rcp_w; - result.derivative.y = (d.y - d.w*result.position.y) * rcp_w; - result.derivative.z = (d.z - d.w*result.position.z) * rcp_w; - return result; -} - -ufbx_abi ufbxi_noinline ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v) -{ - ufbx_surface_point result = { false }; - - ufbx_assert(surface); - if (!surface) return result; - - ufbx_real weights_u[UFBXI_MAX_NURBS_ORDER], weights_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit - ufbx_real derivs_u[UFBXI_MAX_NURBS_ORDER], derivs_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit - size_t base_u = ufbx_evaluate_nurbs_basis(&surface->basis_u, u, weights_u, UFBXI_MAX_NURBS_ORDER, derivs_u, UFBXI_MAX_NURBS_ORDER); - size_t base_v = ufbx_evaluate_nurbs_basis(&surface->basis_v, v, weights_v, UFBXI_MAX_NURBS_ORDER, derivs_v, UFBXI_MAX_NURBS_ORDER); - if (base_u == SIZE_MAX || base_v == SIZE_MAX) return result; - - ufbx_vec4 p = { 0 }; - ufbx_vec4 du = { 0 }; - ufbx_vec4 dv = { 0 }; - - size_t num_u = surface->num_control_points_u; - size_t num_v = surface->num_control_points_v; - size_t order_u = surface->basis_u.order; - size_t order_v = surface->basis_v.order; - if (order_u > UFBXI_MAX_NURBS_ORDER || order_v > UFBXI_MAX_NURBS_ORDER) return result; - if (num_u == 0 || num_v == 0) return result; - - for (size_t vi = 0; vi < order_v; vi++) { - size_t vix = (base_v + vi) % num_v; - ufbx_real weight_v = weights_v[vi], deriv_v = derivs_v[vi]; - - for (size_t ui = 0; ui < order_u; ui++) { - size_t uix = (base_u + ui) % num_u; - ufbx_real weight_u = weights_u[ui], deriv_u = derivs_u[ui]; - ufbx_vec4 cp = surface->control_points.data[vix * num_u + uix]; - - ufbx_real weight = weight_u * weight_v * cp.w; - ufbx_real wderiv_u = deriv_u * weight_v * cp.w; - ufbx_real wderiv_v = deriv_v * weight_u * cp.w; - - p.x += cp.x * weight; - p.y += cp.y * weight; - p.z += cp.z * weight; - p.w += weight; - - du.x += cp.x * wderiv_u; - du.y += cp.y * wderiv_u; - du.z += cp.z * wderiv_u; - du.w += wderiv_u; - - dv.x += cp.x * wderiv_v; - dv.y += cp.y * wderiv_v; - dv.z += cp.z * wderiv_v; - dv.w += wderiv_v; - } - } - - ufbx_real rcp_w = 1.0f / p.w; - result.valid = true; - result.position.x = p.x * rcp_w; - result.position.y = p.y * rcp_w; - result.position.z = p.z * rcp_w; - result.derivative_u.x = (du.x - du.w*result.position.x) * rcp_w; - result.derivative_u.y = (du.y - du.w*result.position.y) * rcp_w; - result.derivative_u.z = (du.z - du.w*result.position.z) * rcp_w; - result.derivative_v.x = (dv.x - dv.w*result.position.x) * rcp_w; - result.derivative_v.y = (dv.y - dv.w*result.position.y) * rcp_w; - result.derivative_v.z = (dv.z - dv.w*result.position.z) * rcp_w; - return result; -} - -ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error) -{ -#if UFBXI_FEATURE_TESSELLATION - ufbxi_check_opts_ptr(ufbx_line_curve, opts, error); - ufbx_assert(curve); - if (!curve) return NULL; - - ufbxi_tessellate_curve_context tc = { UFBX_ERROR_NONE }; - if (opts) { - tc.opts = *opts; - } - - tc.curve = curve; - - int ok = ufbxi_tessellate_nurbs_curve_imp(&tc); - - ufbxi_free_ator(&tc.ator_tmp); - - if (ok) { - ufbxi_clear_error(error); - ufbxi_line_curve_imp *imp = tc.imp; - return &imp->curve; - } else { - ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); - ufbxi_buf_free(&tc.result); - ufbxi_free_ator(&tc.ator_result); - return NULL; - } -#else - if (error) { - memset(error, 0, sizeof(ufbx_error)); - ufbxi_fmt_err_info(error, "UFBX_ENABLE_TESSELLATION"); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_TESSELLATION", "Feature disabled"); - } - return NULL; -#endif -} - -ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surface, const ufbx_tessellate_surface_opts *opts, ufbx_error *error) -{ -#if UFBXI_FEATURE_TESSELLATION - ufbx_assert(surface); - ufbxi_check_opts_ptr(ufbx_mesh, opts, error); - if (!surface) return NULL; - - ufbxi_tessellate_surface_context tc = { UFBX_ERROR_NONE }; - if (opts) { - tc.opts = *opts; - } - - tc.surface = surface; - - int ok = ufbxi_tessellate_nurbs_surface_imp(&tc); - - ufbxi_buf_free(&tc.tmp); - ufbxi_map_free(&tc.position_map); - ufbxi_free_ator(&tc.ator_tmp); - - if (ok) { - ufbxi_clear_error(error); - ufbxi_mesh_imp *imp = tc.imp; - return &imp->mesh; - } else { - ufbxi_fix_error_type(&tc.error, "Failed to tessellate", error); - ufbxi_buf_free(&tc.result); - ufbxi_free_ator(&tc.ator_result); - return NULL; - } -#else - if (error) { - memset(error, 0, sizeof(ufbx_error)); - ufbxi_report_err_msg(error, "UFBXI_FEATURE_TESSELLATION", "Feature disabled"); - } - return NULL; -#endif -} - -ufbx_abi void ufbx_free_line_curve(ufbx_line_curve *line_curve) -{ - if (!line_curve) return; - if (!line_curve->from_tessellated_nurbs) return; - - ufbxi_line_curve_imp *imp = ufbxi_get_imp(ufbxi_line_curve_imp, line_curve); - ufbx_assert(imp->magic == UFBXI_LINE_CURVE_IMP_MAGIC); - if (imp->magic != UFBXI_LINE_CURVE_IMP_MAGIC) return; - ufbxi_release_ref(&imp->refcount); -} - -ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *line_curve) -{ - if (!line_curve) return; - if (!line_curve->from_tessellated_nurbs) return; - - ufbxi_line_curve_imp *imp = ufbxi_get_imp(ufbxi_line_curve_imp, line_curve); - ufbx_assert(imp->magic == UFBXI_LINE_CURVE_IMP_MAGIC); - if (imp->magic != UFBXI_LINE_CURVE_IMP_MAGIC) return; - ufbxi_retain_ref(&imp->refcount); -} - -ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index) -{ - if (!mesh || index > UINT32_MAX) return UFBX_NO_INDEX; - uint32_t ix = (uint32_t)index; - - size_t face_ix = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_face, 4, &face_ix, mesh->faces.data, 0, mesh->faces.count, - ( a->index_begin + a->num_indices <= ix ), ( ix >= a->index_begin && ix < a->index_begin + a->num_indices )); - return (uint32_t)face_ix; -} - -ufbx_abi ufbxi_noinline uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) -{ -#if UFBXI_FEATURE_TRIANGULATION - if (face.num_indices < 3) return 0; - - size_t required_indices = ((size_t)face.num_indices - 2) * 3; - if (ufbxi_panicf(panic, num_indices >= required_indices, "Face needs at least %zu indices for triangles, got space for %zu", required_indices, num_indices)) return 0; - if (ufbxi_panicf(panic, face.index_begin < mesh->num_indices, "Face index begin (%u) out of bounds (%zu)", face.index_begin, mesh->num_indices)) return 0; - if (ufbxi_panicf(panic, mesh->num_indices - face.index_begin >= face.num_indices, "Face index end (%u + %u) out of bounds (%zu)", face.index_begin, face.num_indices, mesh->num_indices)) return 0; - - if (face.num_indices == 3) { - // Fast case: Already a triangle - indices[0] = face.index_begin + 0; - indices[1] = face.index_begin + 1; - indices[2] = face.index_begin + 2; - return 1; - } else if (face.num_indices == 4) { - // Quad: Split along the shortest axis unless a vertex crosses the axis - uint32_t i0 = face.index_begin + 0; - uint32_t i1 = face.index_begin + 1; - uint32_t i2 = face.index_begin + 2; - uint32_t i3 = face.index_begin + 3; - ufbx_vec3 v0 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i0]]; - ufbx_vec3 v1 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i1]]; - ufbx_vec3 v2 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i2]]; - ufbx_vec3 v3 = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[i3]]; - - ufbx_vec3 a = ufbxi_sub3(v2, v0); - ufbx_vec3 b = ufbxi_sub3(v3, v1); - - ufbx_vec3 na1 = ufbxi_normalize3(ufbxi_cross3(a, ufbxi_sub3(v1, v0))); - ufbx_vec3 na3 = ufbxi_normalize3(ufbxi_cross3(a, ufbxi_sub3(v0, v3))); - ufbx_vec3 nb0 = ufbxi_normalize3(ufbxi_cross3(b, ufbxi_sub3(v1, v0))); - ufbx_vec3 nb2 = ufbxi_normalize3(ufbxi_cross3(b, ufbxi_sub3(v2, v1))); - - ufbx_real dot_aa = ufbxi_dot3(a, a); - ufbx_real dot_bb = ufbxi_dot3(b, b); - ufbx_real dot_na = ufbxi_dot3(na1, na3); - ufbx_real dot_nb = ufbxi_dot3(nb0, nb2); - - bool split_a = dot_aa <= dot_bb; - - if (dot_na < 0.0f || dot_nb < 0.0f) { - split_a = dot_na >= dot_nb; - } - - if (split_a) { - indices[0] = i0; - indices[1] = i1; - indices[2] = i2; - indices[3] = i2; - indices[4] = i3; - indices[5] = i0; - } else { - indices[0] = i1; - indices[1] = i2; - indices[2] = i3; - indices[3] = i3; - indices[4] = i0; - indices[5] = i1; - } - - return 2; - } else { - ufbxi_ngon_context nc = { 0 }; - nc.positions = mesh->vertex_position; - nc.face = face; - - uint32_t num_indices_u32 = num_indices < UINT32_MAX ? (uint32_t)num_indices : UINT32_MAX; - - uint32_t local_indices[12]; // ufbxi_uninit - if (num_indices_u32 < 12) { - uint32_t num_tris = ufbxi_triangulate_ngon(&nc, local_indices, 12); - memcpy(indices, local_indices, num_tris * 3 * sizeof(uint32_t)); - return num_tris; - } else { - return ufbxi_triangulate_ngon(&nc, indices, num_indices_u32); - } - } -#else - ufbxi_panicf_imp(panic, "Triangulation disabled"); - return 0; -#endif -} - -ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *mesh, ufbx_topo_edge *indices, size_t num_indices) -{ - if (ufbxi_panicf(panic, num_indices >= mesh->num_indices, "Required mesh.num_indices (%zu) indices, got %zu", mesh->num_indices, num_indices)) return; - - ufbxi_compute_topology(mesh, indices); -} - -ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) -{ - if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; - if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; - uint32_t twin = topo[index].twin; - if (twin == UFBX_NO_INDEX) return UFBX_NO_INDEX; - if (ufbxi_panicf(panic, (size_t)twin < num_topo, "Corrupted topology structure")) return UFBX_NO_INDEX; - return topo[twin].next; -} - -ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) -{ - if (index == UFBX_NO_INDEX) return UFBX_NO_INDEX; - if (ufbxi_panicf(panic, (size_t)index < num_topo, "index (%u) out of bounds (%zu)", index, num_topo)) return UFBX_NO_INDEX; - return topo[topo[index].prev].twin; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face) -{ - if (ufbxi_panicf(panic, face.index_begin <= positions->indices.count, "Face index begin (%u) out of bounds (%zu)", face.index_begin, positions->indices.count)) return ufbx_zero_vec3; - if (ufbxi_panicf(panic, positions->indices.count - face.index_begin >= face.num_indices, "Face index end (%u + %u) out of bounds (%zu)", face.index_begin, face.num_indices, positions->indices.count)) return ufbx_zero_vec3; - - if (face.num_indices < 3) { - return ufbx_zero_vec3; - } else if (face.num_indices == 3) { - ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); - ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); - ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); - return ufbxi_cross3(ufbxi_sub3(b, a), ufbxi_sub3(c, a)); - } else if (face.num_indices == 4) { - ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); - ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); - ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); - ufbx_vec3 d = ufbx_get_vertex_vec3(positions, face.index_begin + 3); - return ufbxi_cross3(ufbxi_sub3(c, a), ufbxi_sub3(d, b)); - } else { - // Newell's Method - ufbx_vec3 result = ufbx_zero_vec3; - for (size_t i = 0; i < face.num_indices; i++) { - size_t next = i + 1 < face.num_indices ? i + 1 : 0; - ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + i); - ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + next); - result.x += (a.y - b.y) * (a.z + b.z); - result.y += (a.z - b.z) * (a.x + b.x); - result.z += (a.x - b.x) * (a.y + b.y); - } - return result; - } -} - -size_t ufbx_catch_generate_normal_mapping(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth) -{ - uint32_t next_index = 0; - if (ufbxi_panicf(panic, num_normal_indices >= mesh->num_indices, "Expected at least mesh.num_indices (%zu), got %zu", mesh->num_indices, num_normal_indices)) return 0; - - for (size_t i = 0; i < mesh->num_indices; i++) { - normal_indices[i] = UFBX_NO_INDEX; - } - - // Walk around vertices and merge around smooth edges - for (size_t vi = 0; vi < mesh->num_vertices; vi++) { - uint32_t original_start = mesh->vertex_first_index.data[vi]; - if (original_start == UFBX_NO_INDEX) continue; - uint32_t start = original_start, cur = start; - - for (;;) { - uint32_t prev = ufbx_topo_next_vertex_edge(topo, num_topo, cur); - if (!ufbxi_is_edge_smooth(mesh, topo, num_topo, cur, assume_smooth)) start = cur; - if (prev == UFBX_NO_INDEX) { start = cur; break; } - if (prev == original_start) break; - cur = prev; - } - - normal_indices[start] = next_index++; - uint32_t next = start; - for (;;) { - next = ufbx_topo_prev_vertex_edge(topo, num_topo, next); - if (next == UFBX_NO_INDEX || next == start) break; - - if (!ufbxi_is_edge_smooth(mesh, topo, num_topo, next, assume_smooth)) { - ++next_index; - } - normal_indices[next] = next_index - 1; - } - } - - // Assign non-manifold indices - for (size_t i = 0; i < mesh->num_indices; i++) { - if (normal_indices[i] == UFBX_NO_INDEX) { - normal_indices[i] = next_index++; - } - } - - return (size_t)next_index; -} - -ufbx_abi size_t ufbx_generate_normal_mapping(const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth) -{ - return ufbx_catch_generate_normal_mapping(NULL, mesh, topo, num_topo, normal_indices, num_normal_indices, assume_smooth); -} - -ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals) -{ - if (ufbxi_panicf(panic, num_normal_indices >= mesh->num_indices, "Expected at least mesh.num_indices (%zu), got %zu", mesh->num_indices, num_normal_indices)) return; - - memset(normals, 0, sizeof(ufbx_vec3)*num_normals); - - for (size_t fi = 0; fi < mesh->num_faces; fi++) { - ufbx_face face = mesh->faces.data[fi]; - ufbx_vec3 normal = ufbx_get_weighted_face_normal(positions, face); - for (size_t ix = 0; ix < face.num_indices; ix++) { - uint32_t index = normal_indices[face.index_begin + ix]; - - if (ufbxi_panicf(panic, index < num_normals, "Normal index (%u) out of bounds (%zu) at %zu", index, num_normals, ix)) return; - - ufbx_vec3 *n = &normals[index]; - *n = ufbxi_add3(*n, normal); - } - } - - for (size_t i = 0; i < num_normals; i++) { - ufbx_real len = ufbxi_length3(normals[i]); - if (len > 0.0f) { - normals[i].x /= len; - normals[i].y /= len; - normals[i].z /= len; - } - } -} - -ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals) -{ - ufbx_catch_compute_normals(NULL, mesh, positions, normal_indices, num_normal_indices, normals, num_normals); -} - -ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_mesh, opts, error); - if (!mesh) return NULL; - if (level == 0) return (ufbx_mesh*)mesh; - return ufbxi_subdivide_mesh(mesh, level, opts, error); -} - -ufbx_abi void ufbx_free_mesh(ufbx_mesh *mesh) -{ - if (!mesh) return; - if (!mesh->subdivision_evaluated && !mesh->from_tessellated_nurbs) return; - - ufbxi_mesh_imp *imp = ufbxi_get_imp(ufbxi_mesh_imp, mesh); - ufbx_assert(imp->magic == UFBXI_MESH_IMP_MAGIC); - if (imp->magic != UFBXI_MESH_IMP_MAGIC) return; - ufbxi_release_ref(&imp->refcount); -} - -ufbx_abi void ufbx_retain_mesh(ufbx_mesh *mesh) -{ - if (!mesh) return; - if (!mesh->subdivision_evaluated && !mesh->from_tessellated_nurbs) return; - - ufbxi_mesh_imp *imp = ufbxi_get_imp(ufbxi_mesh_imp, mesh); - ufbx_assert(imp->magic == UFBXI_MESH_IMP_MAGIC); - if (imp->magic != UFBXI_MESH_IMP_MAGIC) return; - ufbxi_retain_ref(&imp->refcount); -} - -ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache( - const char *filename, - const ufbx_geometry_cache_opts *opts, ufbx_error *error) -{ - return ufbx_load_geometry_cache_len(filename, strlen(filename), - opts, error); -} - -ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( - const char *filename, size_t filename_len, - const ufbx_geometry_cache_opts *opts, ufbx_error *error) -{ - ufbxi_check_opts_ptr(ufbx_geometry_cache, opts, error); - ufbx_string str = ufbxi_safe_string(filename, filename_len); - return ufbxi_load_geometry_cache(str, opts, error); -} - -ufbx_abi void ufbx_free_geometry_cache(ufbx_geometry_cache *cache) -{ - if (!cache) return; - - ufbxi_geometry_cache_imp *imp = ufbxi_get_imp(ufbxi_geometry_cache_imp, cache); - ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); - if (imp->magic != UFBXI_CACHE_IMP_MAGIC) return; - if (imp->owned_by_scene) return; - ufbxi_release_ref(&imp->refcount); -} - -ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache) -{ - if (!cache) return; - - ufbxi_geometry_cache_imp *imp = ufbxi_get_imp(ufbxi_geometry_cache_imp, cache); - ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC); - if (imp->magic != UFBXI_CACHE_IMP_MAGIC) return; - if (imp->owned_by_scene) return; - ufbxi_retain_ref(&imp->refcount); -} - -typedef struct { - union { - double f64[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; - float f32[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; - } src; - ufbx_real dst[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; -} ufbxi_geometry_cache_buffer; - -ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) -{ -#if UFBXI_FEATURE_GEOMETRY_CACHE - ufbxi_check_opts_return_no_error(0, user_opts); - if (!frame || count == 0) return 0; - ufbx_assert(data); - if (!data) return 0; - - ufbx_geometry_cache_data_opts opts; // ufbxi_uninit - if (user_opts) { - opts = *user_opts; - } else { - memset(&opts, 0, sizeof(opts)); - } - - if (!opts.open_file_cb.fn) { - opts.open_file_cb.fn = ufbx_default_open_file; - } - - bool use_double = false; - - size_t src_count = 0; - - switch (frame->data_format) { - case UFBX_CACHE_DATA_FORMAT_UNKNOWN: src_count = 0; break; - case UFBX_CACHE_DATA_FORMAT_REAL_FLOAT: src_count = frame->data_count; break; - case UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT: src_count = frame->data_count * 3; break; - case UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE: src_count = frame->data_count; use_double = true; break; - case UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE: src_count = frame->data_count * 3; use_double = true; break; - default: ufbxi_unreachable("Bad data_format"); break; - } - - bool src_big_endian = false; - switch (frame->data_encoding) { - case UFBX_CACHE_DATA_ENCODING_UNKNOWN: return 0; - case UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN: src_big_endian = false; break; - case UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN: src_big_endian = true; break; - default: ufbxi_unreachable("Bad data_encoding"); break; - } - - // Test endianness - bool dst_big_endian; - { - uint8_t buf[2]; - uint16_t val = 0xbbaa; - memcpy(buf, &val, 2); - dst_big_endian = buf[0] == 0xbb; - } - - if (src_count == 0) return 0; - src_count = ufbxi_min_sz(src_count, count); - - ufbx_stream stream = { 0 }; - if (!ufbxi_open_file(&opts.open_file_cb, &stream, frame->filename.data, frame->filename.length, NULL, NULL, UFBX_OPEN_FILE_GEOMETRY_CACHE)) { - return 0; - } - - // Skip to the correct point in the file - uint64_t offset = frame->data_offset; - if (stream.skip_fn) { - while (offset > 0) { - size_t to_skip = (size_t)ufbxi_min64(offset, UFBXI_MAX_SKIP_SIZE); - if (!stream.skip_fn(stream.user, to_skip)) break; - offset -= to_skip; - } - } else { - char buffer[4096]; // ufbxi_uninit - while (offset > 0) { - size_t to_skip = (size_t)ufbxi_min64(offset, sizeof(buffer)); - size_t num_read = stream.read_fn(stream.user, buffer, to_skip); - if (num_read != to_skip) break; - offset -= to_skip; - } - } - - // Failed to skip all the way - if (offset > 0) { - if (stream.close_fn) { - stream.close_fn(stream.user); - } - return 0; - } - - ufbx_real *dst = data; - size_t mirror_ix = (size_t)frame->mirror_axis - 1; - ufbxi_geometry_cache_buffer buffer; // ufbxi_uninit - while (src_count > 0) { - size_t to_read = ufbxi_min_sz(src_count, UFBXI_GEOMETRY_CACHE_BUFFER_SIZE); - src_count -= to_read; - size_t num_read = 0; - if (use_double) { - size_t bytes_read = stream.read_fn(stream.user, buffer.src.f64, to_read * sizeof(double)); - if (bytes_read == SIZE_MAX) bytes_read = 0; - num_read = bytes_read / sizeof(double); - if (src_big_endian != dst_big_endian) { - for (size_t i = 0; i < num_read; i++) { - char t, *v = (char*)&buffer.src.f64[i]; - t = v[0]; v[0] = v[7]; v[7] = t; - t = v[1]; v[1] = v[6]; v[6] = t; - t = v[2]; v[2] = v[5]; v[5] = t; - t = v[3]; v[3] = v[4]; v[4] = t; - } - } - ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { - buffer.dst[i] = (ufbx_real)buffer.src.f64[i]; - } - } else { - size_t bytes_read = stream.read_fn(stream.user, buffer.src.f32, to_read * sizeof(float)); - if (bytes_read == SIZE_MAX) bytes_read = 0; - num_read = bytes_read / sizeof(float); - if (src_big_endian != dst_big_endian) { - for (size_t i = 0; i < num_read; i++) { - char t, *v = (char*)&buffer.src.f32[i]; - t = v[0]; v[0] = v[3]; v[3] = t; - t = v[1]; v[1] = v[2]; v[2] = t; - } - } - ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { - buffer.dst[i] = (ufbx_real)buffer.src.f32[i]; - } - } - - if (!opts.ignore_transform) { - ufbx_real scale = frame->scale_factor; - if (scale != 1.0f) { - for (size_t i = 0; i < num_read; i++) { - buffer.dst[i] *= scale; - } - } - if (frame->mirror_axis) { - while (mirror_ix < num_read) { - buffer.dst[mirror_ix] = -buffer.dst[mirror_ix]; - mirror_ix += 3; - } - mirror_ix -= num_read; - } - } - - if (dst) { - ufbx_real weight = opts.use_weight ? opts.weight : 1.0f; - if (opts.additive) { - ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { - dst[i] += buffer.dst[i] * weight; - } - } else { - ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { - dst[i] = buffer.dst[i] * weight; - } - } - dst += num_read; - } - - if (num_read != to_read) break; - } - - if (stream.close_fn) { - stream.close_fn(stream.user); - } - - return ufbxi_to_size(dst - data); -#else - return 0; -#endif -} - -ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) -{ -#if UFBXI_FEATURE_GEOMETRY_CACHE - ufbxi_check_opts_return_no_error(0, user_opts); - if (!channel || count == 0) return 0; - ufbx_assert(data); - if (!data) return 0; - if (channel->frames.count == 0) return 0; - - ufbx_geometry_cache_data_opts opts; - if (user_opts) { - opts = *user_opts; - } else { - memset(&opts, 0, sizeof(opts)); - } - - size_t begin = 0; - size_t end = channel->frames.count; - const ufbx_cache_frame *frames = channel->frames.data; - while (end - begin >= 8) { - size_t mid = (begin + end) >> 1; - if (frames[mid].time < time) { - begin = mid + 1; - } else { - end = mid; - } - } - - const double eps = 0.00000001; - - end = channel->frames.count; - for (; begin < end; begin++) { - const ufbx_cache_frame *next = &frames[begin]; - if (next->time < time) continue; - - // First keyframe - if (begin == 0) { - return ufbx_read_geometry_cache_real(next, data, count, &opts); - } - - const ufbx_cache_frame *prev = next - 1; - - // Snap to exact frames if near - if (ufbx_fabs(next->time - time) < eps) { - return ufbx_read_geometry_cache_real(next, data, count, &opts); - } - if (ufbx_fabs(prev->time - time) < eps) { - return ufbx_read_geometry_cache_real(prev, data, count, &opts); - } - - double rcp_delta = 1.0 / (next->time - prev->time); - double t = (time - prev->time) * rcp_delta; - - ufbx_real original_weight = opts.use_weight ? opts.weight : 1.0f; - - opts.use_weight = true; - opts.weight = (ufbx_real)(original_weight * (1.0 - t)); - size_t num_prev = ufbx_read_geometry_cache_real(prev, data, count, &opts); - - opts.additive = true; - opts.weight = (ufbx_real)(original_weight * t); - return ufbx_read_geometry_cache_real(next, data, num_prev, &opts); - } - - // Last frame - const ufbx_cache_frame *last = &frames[end - 1]; - return ufbx_read_geometry_cache_real(last, data, count, &opts); -#else - return 0; -#endif -} - -ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_vec3(const ufbx_cache_frame *frame, ufbx_vec3 *data, size_t count, const ufbx_geometry_cache_data_opts *opts) -{ -#if UFBXI_FEATURE_GEOMETRY_CACHE - if (!frame || count == 0) return 0; - ufbx_assert(data); - if (!data) return 0; - return ufbx_read_geometry_cache_real(frame, (ufbx_real*)data, count * 3, opts) / 3; -#else - return 0; -#endif -} - -ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channel, double time, ufbx_vec3 *data, size_t count, const ufbx_geometry_cache_data_opts *opts) -{ -#if UFBXI_FEATURE_GEOMETRY_CACHE - if (!channel || count == 0) return 0; - ufbx_assert(data); - if (!data) return 0; - return ufbx_sample_geometry_cache_real(channel, time, (ufbx_real*)data, count * 3, opts) / 3; -#else - return 0; -#endif -} - -ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len) -{ - ufbx_string ref = ufbxi_safe_string(name, name_len); - ufbxi_for_ptr_list(ufbx_dom_node, p_child, parent->children) { - if (ufbxi_str_equal((*p_child)->name, ref)) return (ufbx_dom_node*)*p_child; - } - return NULL; -} - -ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error) -{ - ufbx_error local_error; // ufbxi_uninit - if (!error) { - error = &local_error; - } - memset(error, 0, sizeof(ufbx_error)); - return ufbxi_generate_indices(streams, num_streams, indices, num_indices, allocator, error); -} - -ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx, uint32_t index) -{ - ufbxi_thread_pool_execute((ufbxi_thread_pool*)ctx, index); -} - -ufbx_abi void ufbx_thread_pool_set_user_ptr(ufbx_thread_pool_context ctx, void *user) -{ - ufbxi_thread_pool *pool = (ufbxi_thread_pool*)ctx; - pool->user_ptr = user; -} - -ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx) -{ - ufbxi_thread_pool *pool = (ufbxi_thread_pool*)ctx; - return pool->user_ptr; -} - -ufbx_abi ufbxi_noinline ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index) -{ - if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; - uint32_t ix = v->indices.data[index]; - if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return 0.0f; - return v->values.data[(int32_t)ix]; -} - -ufbx_abi ufbxi_noinline ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index) -{ - if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec2; - uint32_t ix = v->indices.data[index]; - if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec2; - return v->values.data[(int32_t)ix]; -} - -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) -{ - if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec3; - uint32_t ix = v->indices.data[index]; - if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec3; - return v->values.data[(int32_t)ix]; -} - -ufbx_abi ufbxi_noinline ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index) -{ - if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec4; - uint32_t ix = v->indices.data[index]; - if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return ufbx_zero_vec4; - return v->values.data[(int32_t)ix]; -} - -ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) -{ - if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; - if (v->values_w.count == 0) return 0.0f; - uint32_t ix = v->indices.data[index]; - if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return 0.0f; - return v->values_w.data[(int32_t)ix]; -} - -ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_UNKNOWN ? (ufbx_unknown*)element : NULL; } -ufbx_abi ufbx_node *ufbx_as_node(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NODE ? (ufbx_node*)element : NULL; } -ufbx_abi ufbx_mesh *ufbx_as_mesh(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MESH ? (ufbx_mesh*)element : NULL; } -ufbx_abi ufbx_light *ufbx_as_light(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LIGHT ? (ufbx_light*)element : NULL; } -ufbx_abi ufbx_camera *ufbx_as_camera(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CAMERA ? (ufbx_camera*)element : NULL; } -ufbx_abi ufbx_bone *ufbx_as_bone(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BONE ? (ufbx_bone*)element : NULL; } -ufbx_abi ufbx_empty *ufbx_as_empty(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_EMPTY ? (ufbx_empty*)element : NULL; } -ufbx_abi ufbx_line_curve *ufbx_as_line_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LINE_CURVE ? (ufbx_line_curve*)element : NULL; } -ufbx_abi ufbx_nurbs_curve *ufbx_as_nurbs_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_CURVE ? (ufbx_nurbs_curve*)element : NULL; } -ufbx_abi ufbx_nurbs_surface *ufbx_as_nurbs_surface(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_SURFACE ? (ufbx_nurbs_surface*)element : NULL; } -ufbx_abi ufbx_nurbs_trim_surface *ufbx_as_nurbs_trim_surface(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_TRIM_SURFACE ? (ufbx_nurbs_trim_surface*)element : NULL; } -ufbx_abi ufbx_nurbs_trim_boundary *ufbx_as_nurbs_trim_boundary(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_NURBS_TRIM_BOUNDARY ? (ufbx_nurbs_trim_boundary*)element : NULL; } -ufbx_abi ufbx_procedural_geometry *ufbx_as_procedural_geometry(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_PROCEDURAL_GEOMETRY ? (ufbx_procedural_geometry*)element : NULL; } -ufbx_abi ufbx_stereo_camera *ufbx_as_stereo_camera(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_STEREO_CAMERA ? (ufbx_stereo_camera*)element : NULL; } -ufbx_abi ufbx_camera_switcher *ufbx_as_camera_switcher(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CAMERA_SWITCHER ? (ufbx_camera_switcher*)element : NULL; } -ufbx_abi ufbx_marker *ufbx_as_marker(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MARKER ? (ufbx_marker*)element : NULL; } -ufbx_abi ufbx_lod_group *ufbx_as_lod_group(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_LOD_GROUP ? (ufbx_lod_group*)element : NULL; } -ufbx_abi ufbx_skin_deformer *ufbx_as_skin_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SKIN_DEFORMER ? (ufbx_skin_deformer*)element : NULL; } -ufbx_abi ufbx_skin_cluster *ufbx_as_skin_cluster(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SKIN_CLUSTER ? (ufbx_skin_cluster*)element : NULL; } -ufbx_abi ufbx_blend_deformer *ufbx_as_blend_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_DEFORMER ? (ufbx_blend_deformer*)element : NULL; } -ufbx_abi ufbx_blend_channel *ufbx_as_blend_channel(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_CHANNEL ? (ufbx_blend_channel*)element : NULL; } -ufbx_abi ufbx_blend_shape *ufbx_as_blend_shape(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_BLEND_SHAPE ? (ufbx_blend_shape*)element : NULL; } -ufbx_abi ufbx_cache_deformer *ufbx_as_cache_deformer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CACHE_DEFORMER ? (ufbx_cache_deformer*)element : NULL; } -ufbx_abi ufbx_cache_file *ufbx_as_cache_file(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CACHE_FILE ? (ufbx_cache_file*)element : NULL; } -ufbx_abi ufbx_material *ufbx_as_material(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_MATERIAL ? (ufbx_material*)element : NULL; } -ufbx_abi ufbx_texture *ufbx_as_texture(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_TEXTURE ? (ufbx_texture*)element : NULL; } -ufbx_abi ufbx_video *ufbx_as_video(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_VIDEO ? (ufbx_video*)element : NULL; } -ufbx_abi ufbx_shader *ufbx_as_shader(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SHADER ? (ufbx_shader*)element : NULL; } -ufbx_abi ufbx_shader_binding *ufbx_as_shader_binding(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SHADER_BINDING ? (ufbx_shader_binding*)element : NULL; } -ufbx_abi ufbx_anim_stack *ufbx_as_anim_stack(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_STACK ? (ufbx_anim_stack*)element : NULL; } -ufbx_abi ufbx_anim_layer *ufbx_as_anim_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_LAYER ? (ufbx_anim_layer*)element : NULL; } -ufbx_abi ufbx_anim_value *ufbx_as_anim_value(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_VALUE ? (ufbx_anim_value*)element : NULL; } -ufbx_abi ufbx_anim_curve *ufbx_as_anim_curve(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_ANIM_CURVE ? (ufbx_anim_curve*)element : NULL; } -ufbx_abi ufbx_display_layer *ufbx_as_display_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_DISPLAY_LAYER ? (ufbx_display_layer*)element : NULL; } -ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SELECTION_SET ? (ufbx_selection_set*)element : NULL; } -ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SELECTION_NODE ? (ufbx_selection_node*)element : NULL; } -ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CHARACTER ? (ufbx_character*)element : NULL; } -ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CONSTRAINT ? (ufbx_constraint*)element : NULL; } -ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_LAYER ? (ufbx_audio_layer*)element : NULL; } -ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_CLIP ? (ufbx_audio_clip*)element : NULL; } -ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_POSE ? (ufbx_pose*)element : NULL; } -ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_METADATA_OBJECT ? (ufbx_metadata_object*)element : NULL; } - -ufbx_abi bool ufbx_dom_is_array(const ufbx_dom_node *node) { - if (!node || node->values.count != 1) return false; - ufbx_dom_value v = node->values.data[0]; - return v.type >= UFBX_DOM_VALUE_ARRAY_I32 && v.type <= UFBX_DOM_VALUE_ARRAY_BLOB; -} -ufbx_abi size_t ufbx_dom_array_size(const ufbx_dom_node *node) { - return ufbx_dom_is_array(node) ? (size_t)node->values.data[0].value_int : (size_t)0; -} -ufbx_abi ufbx_int32_list ufbx_dom_as_int32_list(const ufbx_dom_node *node) { - ufbx_int32_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_I32) { - ufbx_dom_value value = node->values.data[0]; - list.data = (int32_t*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(int32_t); - } - return list; -} -ufbx_abi ufbx_int64_list ufbx_dom_as_int64_list(const ufbx_dom_node *node) { - ufbx_int64_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_I64) { - ufbx_dom_value value = node->values.data[0]; - list.data = (int64_t*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(int64_t); - } - return list; -} -ufbx_abi ufbx_float_list ufbx_dom_as_float_list(const ufbx_dom_node *node) { - ufbx_float_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_F32) { - ufbx_dom_value value = node->values.data[0]; - list.data = (float*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(float); - } - return list; -} -ufbx_abi ufbx_double_list ufbx_dom_as_double_list(const ufbx_dom_node *node) { - ufbx_double_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_F64) { - ufbx_dom_value value = node->values.data[0]; - list.data = (double*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(double); - } - return list; -} -ufbx_abi ufbx_real_list ufbx_dom_as_real_list(const ufbx_dom_node *node) { - ufbx_real_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == (sizeof(ufbx_real) == sizeof(double) ? UFBX_DOM_VALUE_ARRAY_F64 : UFBX_DOM_VALUE_ARRAY_F32)) { - ufbx_dom_value value = node->values.data[0]; - list.data = (ufbx_real*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(ufbx_real); - } - return list; -} -ufbx_abi ufbx_blob_list ufbx_dom_as_blob_list(const ufbx_dom_node *node) { - ufbx_blob_list list = { NULL, 0 }; - if (node && node->values.count == 1 && node->values.data[0].type == UFBX_DOM_VALUE_ARRAY_BLOB) { - ufbx_dom_value value = node->values.data[0]; - list.data = (ufbx_blob*)value.value_blob.data; - list.count = value.value_blob.size / sizeof(ufbx_blob); - } - return list; -} - -// -- String API - -ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name) { return ufbx_find_prop_len(props, name, strlen(name)); } -ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def) { return ufbx_find_real_len(props, name, strlen(name), def); } -ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name, strlen(name), def); } -ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def) { return ufbx_find_int_len(props, name, strlen(name), def); } -ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def) { return ufbx_find_bool_len(props, name, strlen(name), def); } -ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def) { return ufbx_find_string_len(props, name, strlen(name), def); } -ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def) { return ufbx_find_blob_len(props, name, strlen(name), def); } -ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name, strlen(name), type); } -ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name) { return ufbx_find_element_len(scene, type, name, strlen(name)); } -ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name) { return ufbx_find_node_len(scene, name, strlen(name)); } -ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name) { return ufbx_find_anim_stack_len(scene, name, strlen(name)); } -ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name) { return ufbx_find_material_len(scene, name, strlen(name)); } -ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop) { return ufbx_find_anim_prop_len(layer, element, prop, strlen(prop)); } -ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) { return ufbx_evaluate_prop_len(anim, element, name, strlen(name), time); } -ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) { return ufbx_evaluate_prop_len_flags(anim, element, name, strlen(name), time, flags); } -ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { return ufbx_find_prop_texture_len(material, name, strlen(name)); } -ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_len(shader, name, strlen(name)); } -ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_bindings_len(shader, name, strlen(name)); } -ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name) { return ufbx_find_shader_texture_input_len(shader, name, strlen(name)); } -ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name) { return ufbx_dom_find_len(parent, name, strlen(name)); } - -// -- Catch API - -ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) { - return ufbx_catch_triangulate_face(NULL, indices, num_indices, mesh, face); -} -ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo) { - ufbx_catch_compute_topology(NULL, mesh, topo, num_topo); -} -ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { - return ufbx_catch_topo_next_vertex_edge(NULL, topo, num_topo, index); -} -ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { - return ufbx_catch_topo_prev_vertex_edge(NULL, topo, num_topo, index); -} -ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face) { - return ufbx_catch_get_weighted_face_normal(NULL, positions, face); -} - -#ifdef __cplusplus -} -#endif - -#endif - -#if defined(UFBX_STRING_PREFIX) - #undef strlen - #undef memcpy - #undef memmove - #undef memset - #undef memchr - #undef memcmp - #undef strcmp - #undef strncmp -#endif - -#if defined(_MSC_VER) - #pragma warning(pop) -#elif defined(__clang__) - #pragma clang diagnostic pop -#elif defined(__GNUC__) - #pragma GCC diagnostic pop -#endif - diff --git a/src/ObjLoading/Game/T6/BSP/fbx/ufbx.h b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.h deleted file mode 100644 index 9078a9608..000000000 --- a/src/ObjLoading/Game/T6/BSP/fbx/ufbx.h +++ /dev/null @@ -1,6062 +0,0 @@ -#ifndef UFBX_UFBX_H_INCLUDED -#define UFBX_UFBX_H_INCLUDED - -// -- User configuration - -#if defined(UFBX_CONFIG_HEADER) - #include UFBX_CONFIG_HEADER -#endif - -// -- Headers - -#if !defined(UFBX_NO_LIBC_TYPES) - #include - #include - #include -#endif - -// -- Platform - -#ifndef UFBX_STDC - #if defined(__STDC_VERSION__) - #define UFBX_STDC __STDC_VERSION__ - #else - #define UFBX_STDC 0 - #endif -#endif - -#ifndef UFBX_CPP - #if defined(__cplusplus) - #define UFBX_CPP __cplusplus - #else - #define UFBX_CPP 0 - #endif -#endif - -#ifndef UFBX_PLATFORM_MSC - #if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) - #define UFBX_PLATFORM_MSC _MSC_VER - #else - #define UFBX_PLATFORM_MSC 0 - #endif -#endif - -#ifndef UFBX_PLATFORM_GNUC - #if !defined(UFBX_STANDARD_C) && defined(__GNUC__) - #define UFBX_PLATFORM_GNUC __GNUC__ - #else - #define UFBX_PLATFORM_GNUC 0 - #endif -#endif - -#ifndef UFBX_CPP11 - // MSVC does not advertise C++11 by default so we need special detection - #if UFBX_CPP >= 201103L || (UFBX_CPP > 0 && UFBX_PLATFORM_MSC >= 1900) - #define UFBX_CPP11 1 - #else - #define UFBX_CPP11 0 - #endif -#endif - -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable: 4061) // enumerator 'ENUM' in switch of enum 'enum' is not explicitly handled by a case label - #pragma warning(disable: 4201) // nonstandard extension used: nameless struct/union - #pragma warning(disable: 4505) // unreferenced local function has been removed - #pragma warning(disable: 4820) // type': 'N' bytes padding added after data member 'member' -#elif defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wpedantic" - #pragma clang diagnostic ignored "-Wpadded" - #if defined(__cplusplus) - #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" - #pragma clang diagnostic ignored "-Wold-style-cast" - #endif -#elif defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" - #pragma GCC diagnostic ignored "-Wpadded" - #if defined(__cplusplus) - #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" - #pragma GCC diagnostic ignored "-Wold-style-cast" - #else - #if __GNUC__ >= 5 - #pragma GCC diagnostic ignored "-Wc90-c99-compat" - #pragma GCC diagnostic ignored "-Wc99-c11-compat" - #endif - #endif -#endif - -#if UFBX_PLATFORM_MSC - #define ufbx_inline static __forceinline -#elif UFBX_PLATFORM_GNUC - #define ufbx_inline static inline __attribute__((always_inline, unused)) -#else - #define ufbx_inline static -#endif - -// Assertion function used in ufbx, defaults to C standard `assert()`. -// You can define this to your custom preferred assert macro, but in that case -// make sure that it is also used within `ufbx.c`. -// Defining `UFBX_NO_ASSERT` to any value disables assertions. -#ifndef ufbx_assert - #if defined(UFBX_NO_ASSERT) || defined(UFBX_NO_LIBC) - #define ufbx_assert(cond) (void)0 - #else - #include - #define ufbx_assert(cond) assert(cond) - #endif -#endif - -// Pointer may be `NULL`. -#define ufbx_nullable - -// Changing this value from default or calling this function can lead into -// breaking API guarantees. -#define ufbx_unsafe - -// Linkage of the main ufbx API functions. -// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. -// If you want to isolate ufbx to a single translation unit you can do the following: -// #define UFBX_STATIC -// #include "ufbx.h" -// #include "ufbx.c" -#ifndef ufbx_abi - #if defined(UFBX_STATIC) - #define ufbx_abi static - #else - #define ufbx_abi - #endif -#endif - -// Linkage of the main ufbx data fields in the header. -// Defaults to `extern`, or `static` if `UFBX_STATIC` is defined. -#ifndef ufbx_abi_data - #if defined(UFBX_STATIC) - #define ufbx_abi_data static - #else - #define ufbx_abi_data extern - #endif -#endif - -// Linkage of the main ufbx data fields in the source. -// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. -#ifndef ufbx_abi_data_definition - #if defined(UFBX_STATIC) - #define ufbx_abi_data_def static - #else - #define ufbx_abi_data_def - #endif -#endif - -// -- Configuration - -#ifndef UFBX_REAL_TYPE - #if defined(UFBX_REAL_IS_FLOAT) - #define UFBX_REAL_TYPE float - #else - #define UFBX_REAL_TYPE double - #endif -#endif - -// Limits for embedded arrays within structures. -#define UFBX_ERROR_STACK_MAX_DEPTH 8 -#define UFBX_PANIC_MESSAGE_LENGTH 128 -#define UFBX_ERROR_INFO_LENGTH 256 - -// Number of thread groups to use if threading is enabled. -// A thread group processes a number of tasks and is then waited and potentially -// re-used later. In essence, this controls the granularity of threading. -#define UFBX_THREAD_GROUP_COUNT 4 - -// -- Language - -// bindgen-disable - -#if UFBX_CPP11 - -template -struct ufbxi_type_is { }; - -template -struct ufbxi_type_is { using type = int; }; - -template -struct ufbx_converter { }; - -#define UFBX_CONVERSION_IMPL(p_name) \ - template ::from(*(const p_name*)nullptr))>::type> \ - operator T() const { return ufbx_converter::from(*this); } - -#define UFBX_CONVERSION_TO_IMPL(p_name) \ - template ::to(*(const T*)nullptr))>::type> \ - p_name(const T &t) { *this = ufbx_converter::to(t); } - -#define UFBX_CONVERSION_LIST_IMPL(p_name) \ - template ::from_list((p_name*)nullptr, (size_t)0))>::type> \ - operator T() const { return ufbx_converter::from_list(data, count); } - -#else - -#define UFBX_CONVERSION_IMPL(p_name) -#define UFBX_CONVERSION_TO_IMPL(p_name) -#define UFBX_CONVERSION_LIST_IMPL(p_name) - -#endif - -#if defined(__cplusplus) - #define UFBX_LIST_TYPE(p_name, p_type) struct p_name { p_type *data; size_t count; \ - p_type &operator[](size_t index) const { ufbx_assert(index < count); return data[index]; } \ - p_type *begin() const { return data; } \ - p_type *end() const { return data + count; } \ - UFBX_CONVERSION_LIST_IMPL(p_type) \ - } -#else - #define UFBX_LIST_TYPE(p_name, p_type) typedef struct p_name { p_type *data; size_t count; } p_name -#endif - -// This cannot be enabled automatically if supported as the source file may be -// compiled with a different compiler using different settings than the header -// consumers, in practice it should work but it causes issues such as #70. -#if (UFBX_STDC >= 202311L || UFBX_CPP11) && defined(UFBX_USE_EXPLICIT_ENUM) - #define UFBX_ENUM_REPR : int - #define UFBX_ENUM_FORCE_WIDTH(p_prefix) - #define UFBX_FLAG_REPR : int - #define UFBX_FLAG_FORCE_WIDTH(p_prefix) - #define UFBX_HAS_FORCE_32BIT 0 -#else - #define UFBX_ENUM_REPR - #define UFBX_ENUM_FORCE_WIDTH(p_prefix) p_prefix##_FORCE_32BIT = 0x7fffffff - #define UFBX_FLAG_REPR - #define UFBX_FLAG_FORCE_WIDTH(p_prefix) p_prefix##_FORCE_32BIT = 0x7fffffff - #define UFBX_HAS_FORCE_32BIT 1 -#endif - -#define UFBX_ENUM_TYPE(p_name, p_prefix, p_last) \ - enum { p_prefix##_COUNT = p_last + 1 } - -#if UFBX_CPP - #define UFBX_VERTEX_ATTRIB_IMPL(p_type) \ - p_type &operator[](size_t index) const { ufbx_assert(index < indices.count); return values.data[indices.data[index]]; } -#else - #define UFBX_VERTEX_ATTRIB_IMPL(p_type) -#endif - -#if UFBX_CPP11 - #define UFBX_CALLBACK_IMPL(p_name, p_fn, p_return, p_params, p_args) \ - template static p_return _cpp_adapter p_params { F &f = *static_cast(user); return f p_args; } \ - p_name() = default; \ - p_name(p_fn *f) : fn(f), user(nullptr) { } \ - template p_name(F *f) : fn(&_cpp_adapter), user(static_cast(f)) { } -#else - #define UFBX_CALLBACK_IMPL(p_name, p_fn, p_return, p_params, p_args) -#endif - -// bindgen-enable - -// -- Version - -// Packing/unpacking for `UFBX_HEADER_VERSION` and `ufbx_source_version`. -#define ufbx_pack_version(major, minor, patch) ((major)*1000000u + (minor)*1000u + (patch)) -#define ufbx_version_major(version) ((uint32_t)(version)/1000000u%1000u) -#define ufbx_version_minor(version) ((uint32_t)(version)/1000u%1000u) -#define ufbx_version_patch(version) ((uint32_t)(version)%1000u) - -// Version of the ufbx header. -// `UFBX_VERSION` is simply an alias of `UFBX_HEADER_VERSION`. -// `ufbx_source_version` contains the version of the corresponding source file. -// HINT: The version can be compared numerically to the result of `ufbx_pack_version()`, -// for example `#if UFBX_VERSION >= ufbx_pack_version(0, 12, 0)`. -#define UFBX_HEADER_VERSION ufbx_pack_version(0, 20, 0) -#define UFBX_VERSION UFBX_HEADER_VERSION - -// -- Basic types - -// Main floating point type used everywhere in ufbx, defaults to `double`. -// If you define `UFBX_REAL_IS_FLOAT` to any value, `ufbx_real` will be defined -// as `float` instead. -// You can also manually define `UFBX_REAL_TYPE` to any floating point type. -typedef UFBX_REAL_TYPE ufbx_real; - -// Null-terminated UTF-8 encoded string within an FBX file -typedef struct ufbx_string { - const char *data; - size_t length; - - UFBX_CONVERSION_IMPL(ufbx_string) -} ufbx_string; - -// Opaque byte buffer blob -typedef struct ufbx_blob { - const void *data; - size_t size; - - UFBX_CONVERSION_IMPL(ufbx_blob) -} ufbx_blob; - -// 2D vector -typedef struct ufbx_vec2 { - union { - struct { ufbx_real x, y; }; - ufbx_real v[2]; - }; - - UFBX_CONVERSION_IMPL(ufbx_vec2) -} ufbx_vec2; - -// 3D vector -typedef struct ufbx_vec3 { - union { - struct { ufbx_real x, y, z; }; - ufbx_real v[3]; - }; - - UFBX_CONVERSION_IMPL(ufbx_vec3) -} ufbx_vec3; - -// 4D vector -typedef struct ufbx_vec4 { - union { - struct { ufbx_real x, y, z, w; }; - ufbx_real v[4]; - }; - - UFBX_CONVERSION_IMPL(ufbx_vec4) -} ufbx_vec4; - -// Quaternion -typedef struct ufbx_quat { - union { - struct { ufbx_real x, y, z, w; }; - ufbx_real v[4]; - }; - - UFBX_CONVERSION_IMPL(ufbx_quat) -} ufbx_quat; - -// Order in which Euler-angle rotation axes are applied for a transform -// NOTE: The order in the name refers to the order of axes *applied*, -// not the multiplication order: eg. `UFBX_ROTATION_ORDER_XYZ` is `Z*Y*X` -// [TODO: Figure out what the spheric rotation order is...] -typedef enum ufbx_rotation_order UFBX_ENUM_REPR { - UFBX_ROTATION_ORDER_XYZ, - UFBX_ROTATION_ORDER_XZY, - UFBX_ROTATION_ORDER_YZX, - UFBX_ROTATION_ORDER_YXZ, - UFBX_ROTATION_ORDER_ZXY, - UFBX_ROTATION_ORDER_ZYX, - UFBX_ROTATION_ORDER_SPHERIC, - - UFBX_ENUM_FORCE_WIDTH(UFBX_ROTATION_ORDER) -} ufbx_rotation_order; - -UFBX_ENUM_TYPE(ufbx_rotation_order, UFBX_ROTATION_ORDER, UFBX_ROTATION_ORDER_SPHERIC); - -// Explicit translation+rotation+scale transformation. -// NOTE: Rotation is a quaternion, not Euler angles! -typedef struct ufbx_transform { - ufbx_vec3 translation; - ufbx_quat rotation; - ufbx_vec3 scale; - - UFBX_CONVERSION_IMPL(ufbx_transform) -} ufbx_transform; - -// 4x3 matrix encoding an affine transformation. -// `cols[0..2]` are the X/Y/Z basis vectors, `cols[3]` is the translation -typedef struct ufbx_matrix { - union { - struct { - ufbx_real m00, m10, m20; - ufbx_real m01, m11, m21; - ufbx_real m02, m12, m22; - ufbx_real m03, m13, m23; - }; - ufbx_vec3 cols[4]; - ufbx_real v[12]; - }; - - UFBX_CONVERSION_IMPL(ufbx_matrix) -} ufbx_matrix; - -typedef struct ufbx_void_list { - void *data; - size_t count; -} ufbx_void_list; - -UFBX_LIST_TYPE(ufbx_bool_list, bool); -UFBX_LIST_TYPE(ufbx_uint32_list, uint32_t); -UFBX_LIST_TYPE(ufbx_real_list, ufbx_real); -UFBX_LIST_TYPE(ufbx_vec2_list, ufbx_vec2); -UFBX_LIST_TYPE(ufbx_vec3_list, ufbx_vec3); -UFBX_LIST_TYPE(ufbx_vec4_list, ufbx_vec4); -UFBX_LIST_TYPE(ufbx_string_list, ufbx_string); - -// Sentinel value used to represent a missing index. -#define UFBX_NO_INDEX ((uint32_t)~0u) - -// -- Document object model - -typedef enum ufbx_dom_value_type UFBX_ENUM_REPR { - UFBX_DOM_VALUE_NUMBER, - UFBX_DOM_VALUE_STRING, - UFBX_DOM_VALUE_BLOB, - UFBX_DOM_VALUE_ARRAY_I32, - UFBX_DOM_VALUE_ARRAY_I64, - UFBX_DOM_VALUE_ARRAY_F32, - UFBX_DOM_VALUE_ARRAY_F64, - UFBX_DOM_VALUE_ARRAY_BLOB, - UFBX_DOM_VALUE_ARRAY_IGNORED, - - UFBX_ENUM_FORCE_WIDTH(UFBX_DOM_VALUE_TYPE) -} ufbx_dom_value_type; - -UFBX_ENUM_TYPE(ufbx_dom_value_type, UFBX_DOM_VALUE_TYPE, UFBX_DOM_VALUE_ARRAY_IGNORED); - -typedef struct ufbx_dom_node ufbx_dom_node; - -UFBX_LIST_TYPE(ufbx_int32_list, int32_t); -UFBX_LIST_TYPE(ufbx_int64_list, int64_t); -UFBX_LIST_TYPE(ufbx_float_list, float); -UFBX_LIST_TYPE(ufbx_double_list, double); -UFBX_LIST_TYPE(ufbx_blob_list, ufbx_blob); - -typedef struct ufbx_dom_value { - ufbx_dom_value_type type; - ufbx_string value_str; - ufbx_blob value_blob; - int64_t value_int; - double value_float; -} ufbx_dom_value; - -UFBX_LIST_TYPE(ufbx_dom_node_list, ufbx_dom_node*); -UFBX_LIST_TYPE(ufbx_dom_value_list, ufbx_dom_value); - -struct ufbx_dom_node { - ufbx_string name; - ufbx_dom_node_list children; - ufbx_dom_value_list values; -}; - -// -- Properties - -// FBX elements have properties which are arbitrary key/value pairs that can -// have inherited default values or be animated. In most cases you don't need -// to access these unless you need a feature not implemented directly in ufbx. -// NOTE: Prefer using `ufbx_find_prop[_len](...)` to search for a property by -// name as it can find it from the defaults if necessary. - -typedef struct ufbx_prop ufbx_prop; -typedef struct ufbx_props ufbx_props; - -// Data type contained within the property. All the data fields are always -// populated regardless of type, so there's no need to switch by type usually -// eg. `prop->value_real` and `prop->value_int` have the same value (well, close) -// if `prop->type == UFBX_PROP_INTEGER`. String values are not converted from/to. -typedef enum ufbx_prop_type UFBX_ENUM_REPR { - UFBX_PROP_UNKNOWN, - UFBX_PROP_BOOLEAN, - UFBX_PROP_INTEGER, - UFBX_PROP_NUMBER, - UFBX_PROP_VECTOR, - UFBX_PROP_COLOR, - UFBX_PROP_COLOR_WITH_ALPHA, - UFBX_PROP_STRING, - UFBX_PROP_DATE_TIME, - UFBX_PROP_TRANSLATION, - UFBX_PROP_ROTATION, - UFBX_PROP_SCALING, - UFBX_PROP_DISTANCE, - UFBX_PROP_COMPOUND, - UFBX_PROP_BLOB, - UFBX_PROP_REFERENCE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_PROP_TYPE) -} ufbx_prop_type; - -UFBX_ENUM_TYPE(ufbx_prop_type, UFBX_PROP_TYPE, UFBX_PROP_REFERENCE); - -// Property flags: Advanced information about properties, not usually needed. -typedef enum ufbx_prop_flags UFBX_FLAG_REPR { - // Supports animation. - // NOTE: ufbx ignores this and allows animations on non-animatable properties. - UFBX_PROP_FLAG_ANIMATABLE = 0x1, - - // User defined (custom) property. - UFBX_PROP_FLAG_USER_DEFINED = 0x2, - - // Hidden in UI. - UFBX_PROP_FLAG_HIDDEN = 0x4, - - // Disallow modification from UI for components. - UFBX_PROP_FLAG_LOCK_X = 0x10, - UFBX_PROP_FLAG_LOCK_Y = 0x20, - UFBX_PROP_FLAG_LOCK_Z = 0x40, - UFBX_PROP_FLAG_LOCK_W = 0x80, - - // Disable animation from components. - UFBX_PROP_FLAG_MUTE_X = 0x100, - UFBX_PROP_FLAG_MUTE_Y = 0x200, - UFBX_PROP_FLAG_MUTE_Z = 0x400, - UFBX_PROP_FLAG_MUTE_W = 0x800, - - // Property created by ufbx when an element has a connected `ufbx_anim_prop` - // but doesn't contain the `ufbx_prop` it's referring to. - // NOTE: The property may have been found in the templated defaults. - UFBX_PROP_FLAG_SYNTHETIC = 0x1000, - - // The property has at least one `ufbx_anim_prop` in some layer. - UFBX_PROP_FLAG_ANIMATED = 0x2000, - - // Used by `ufbx_evaluate_prop()` to indicate the the property was not found. - UFBX_PROP_FLAG_NOT_FOUND = 0x4000, - - // The property is connected to another one. - // This use case is relatively rare so `ufbx_prop` does not track connections - // directly. You can find connections from `ufbx_element.connections_dst` where - // `ufbx_connection.dst_prop` is this property and `ufbx_connection.src_prop` is defined. - UFBX_PROP_FLAG_CONNECTED = 0x8000, - - // The value of this property is undefined (represented as zero). - UFBX_PROP_FLAG_NO_VALUE = 0x10000, - - // This property has been overridden by the user. - // See `ufbx_anim.prop_overrides` for more information. - UFBX_PROP_FLAG_OVERRIDDEN = 0x20000, - - // Value type. - // `REAL/VEC2/VEC3/VEC4` are mutually exclusive but may coexist with eg. `STRING` - // in some rare cases where the string defines the unit for the vector. - UFBX_PROP_FLAG_VALUE_REAL = 0x100000, - UFBX_PROP_FLAG_VALUE_VEC2 = 0x200000, - UFBX_PROP_FLAG_VALUE_VEC3 = 0x400000, - UFBX_PROP_FLAG_VALUE_VEC4 = 0x800000, - UFBX_PROP_FLAG_VALUE_INT = 0x1000000, - UFBX_PROP_FLAG_VALUE_STR = 0x2000000, - UFBX_PROP_FLAG_VALUE_BLOB = 0x4000000, - - UFBX_FLAG_FORCE_WIDTH(UFBX_PROP_FLAGS) -} ufbx_prop_flags; - -// Single property with name/type/value. -struct ufbx_prop { - ufbx_string name; - - uint32_t _internal_key; - - ufbx_prop_type type; - ufbx_prop_flags flags; - - ufbx_string value_str; - ufbx_blob value_blob; - int64_t value_int; - union { - ufbx_real value_real_arr[4]; - ufbx_real value_real; - ufbx_vec2 value_vec2; - ufbx_vec3 value_vec3; - ufbx_vec4 value_vec4; - }; -}; - -UFBX_LIST_TYPE(ufbx_prop_list, ufbx_prop); - -// List of alphabetically sorted properties with potential defaults. -// For animated objects in as scene from `ufbx_evaluate_scene()` this list -// only has the animated properties, the originals are stored under `defaults`. -struct ufbx_props { - ufbx_prop_list props; - size_t num_animated; - - ufbx_nullable ufbx_props *defaults; -}; - -typedef struct ufbx_scene ufbx_scene; - -// -- Elements - -// Element is the lowest level representation of the FBX file in ufbx. -// An element contains type, id, name, and properties (see `ufbx_props` above) -// Elements may be connected to each other arbitrarily via `ufbx_connection` - -typedef struct ufbx_element ufbx_element; - -// Unknown -typedef struct ufbx_unknown ufbx_unknown; - -// Nodes -typedef struct ufbx_node ufbx_node; - -// Node attributes (common) -typedef struct ufbx_mesh ufbx_mesh; -typedef struct ufbx_light ufbx_light; -typedef struct ufbx_camera ufbx_camera; -typedef struct ufbx_bone ufbx_bone; -typedef struct ufbx_empty ufbx_empty; - -// Node attributes (curves/surfaces) -typedef struct ufbx_line_curve ufbx_line_curve; -typedef struct ufbx_nurbs_curve ufbx_nurbs_curve; -typedef struct ufbx_nurbs_surface ufbx_nurbs_surface; -typedef struct ufbx_nurbs_trim_surface ufbx_nurbs_trim_surface; -typedef struct ufbx_nurbs_trim_boundary ufbx_nurbs_trim_boundary; - -// Node attributes (advanced) -typedef struct ufbx_procedural_geometry ufbx_procedural_geometry; -typedef struct ufbx_stereo_camera ufbx_stereo_camera; -typedef struct ufbx_camera_switcher ufbx_camera_switcher; -typedef struct ufbx_marker ufbx_marker; -typedef struct ufbx_lod_group ufbx_lod_group; - -// Deformers -typedef struct ufbx_skin_deformer ufbx_skin_deformer; -typedef struct ufbx_skin_cluster ufbx_skin_cluster; -typedef struct ufbx_blend_deformer ufbx_blend_deformer; -typedef struct ufbx_blend_channel ufbx_blend_channel; -typedef struct ufbx_blend_shape ufbx_blend_shape; -typedef struct ufbx_cache_deformer ufbx_cache_deformer; -typedef struct ufbx_cache_file ufbx_cache_file; - -// Materials -typedef struct ufbx_material ufbx_material; -typedef struct ufbx_texture ufbx_texture; -typedef struct ufbx_video ufbx_video; -typedef struct ufbx_shader ufbx_shader; -typedef struct ufbx_shader_binding ufbx_shader_binding; - -// Animation -typedef struct ufbx_anim_stack ufbx_anim_stack; -typedef struct ufbx_anim_layer ufbx_anim_layer; -typedef struct ufbx_anim_value ufbx_anim_value; -typedef struct ufbx_anim_curve ufbx_anim_curve; - -// Collections -typedef struct ufbx_display_layer ufbx_display_layer; -typedef struct ufbx_selection_set ufbx_selection_set; -typedef struct ufbx_selection_node ufbx_selection_node; - -// Constraints -typedef struct ufbx_character ufbx_character; -typedef struct ufbx_constraint ufbx_constraint; - -// Audio -typedef struct ufbx_audio_layer ufbx_audio_layer; -typedef struct ufbx_audio_clip ufbx_audio_clip; - -// Miscellaneous -typedef struct ufbx_pose ufbx_pose; -typedef struct ufbx_metadata_object ufbx_metadata_object; - -UFBX_LIST_TYPE(ufbx_element_list, ufbx_element*); -UFBX_LIST_TYPE(ufbx_unknown_list, ufbx_unknown*); -UFBX_LIST_TYPE(ufbx_node_list, ufbx_node*); -UFBX_LIST_TYPE(ufbx_mesh_list, ufbx_mesh*); -UFBX_LIST_TYPE(ufbx_light_list, ufbx_light*); -UFBX_LIST_TYPE(ufbx_camera_list, ufbx_camera*); -UFBX_LIST_TYPE(ufbx_bone_list, ufbx_bone*); -UFBX_LIST_TYPE(ufbx_empty_list, ufbx_empty*); -UFBX_LIST_TYPE(ufbx_line_curve_list, ufbx_line_curve*); -UFBX_LIST_TYPE(ufbx_nurbs_curve_list, ufbx_nurbs_curve*); -UFBX_LIST_TYPE(ufbx_nurbs_surface_list, ufbx_nurbs_surface*); -UFBX_LIST_TYPE(ufbx_nurbs_trim_surface_list, ufbx_nurbs_trim_surface*); -UFBX_LIST_TYPE(ufbx_nurbs_trim_boundary_list, ufbx_nurbs_trim_boundary*); -UFBX_LIST_TYPE(ufbx_procedural_geometry_list, ufbx_procedural_geometry*); -UFBX_LIST_TYPE(ufbx_stereo_camera_list, ufbx_stereo_camera*); -UFBX_LIST_TYPE(ufbx_camera_switcher_list, ufbx_camera_switcher*); -UFBX_LIST_TYPE(ufbx_marker_list, ufbx_marker*); -UFBX_LIST_TYPE(ufbx_lod_group_list, ufbx_lod_group*); -UFBX_LIST_TYPE(ufbx_skin_deformer_list, ufbx_skin_deformer*); -UFBX_LIST_TYPE(ufbx_skin_cluster_list, ufbx_skin_cluster*); -UFBX_LIST_TYPE(ufbx_blend_deformer_list, ufbx_blend_deformer*); -UFBX_LIST_TYPE(ufbx_blend_channel_list, ufbx_blend_channel*); -UFBX_LIST_TYPE(ufbx_blend_shape_list, ufbx_blend_shape*); -UFBX_LIST_TYPE(ufbx_cache_deformer_list, ufbx_cache_deformer*); -UFBX_LIST_TYPE(ufbx_cache_file_list, ufbx_cache_file*); -UFBX_LIST_TYPE(ufbx_material_list, ufbx_material*); -UFBX_LIST_TYPE(ufbx_texture_list, ufbx_texture*); -UFBX_LIST_TYPE(ufbx_video_list, ufbx_video*); -UFBX_LIST_TYPE(ufbx_shader_list, ufbx_shader*); -UFBX_LIST_TYPE(ufbx_shader_binding_list, ufbx_shader_binding*); -UFBX_LIST_TYPE(ufbx_anim_stack_list, ufbx_anim_stack*); -UFBX_LIST_TYPE(ufbx_anim_layer_list, ufbx_anim_layer*); -UFBX_LIST_TYPE(ufbx_anim_value_list, ufbx_anim_value*); -UFBX_LIST_TYPE(ufbx_anim_curve_list, ufbx_anim_curve*); -UFBX_LIST_TYPE(ufbx_display_layer_list, ufbx_display_layer*); -UFBX_LIST_TYPE(ufbx_selection_set_list, ufbx_selection_set*); -UFBX_LIST_TYPE(ufbx_selection_node_list, ufbx_selection_node*); -UFBX_LIST_TYPE(ufbx_character_list, ufbx_character*); -UFBX_LIST_TYPE(ufbx_constraint_list, ufbx_constraint*); -UFBX_LIST_TYPE(ufbx_audio_layer_list, ufbx_audio_layer*); -UFBX_LIST_TYPE(ufbx_audio_clip_list, ufbx_audio_clip*); -UFBX_LIST_TYPE(ufbx_pose_list, ufbx_pose*); -UFBX_LIST_TYPE(ufbx_metadata_object_list, ufbx_metadata_object*); - -typedef enum ufbx_element_type UFBX_ENUM_REPR { - UFBX_ELEMENT_UNKNOWN, // < `ufbx_unknown` - UFBX_ELEMENT_NODE, // < `ufbx_node` - UFBX_ELEMENT_MESH, // < `ufbx_mesh` - UFBX_ELEMENT_LIGHT, // < `ufbx_light` - UFBX_ELEMENT_CAMERA, // < `ufbx_camera` - UFBX_ELEMENT_BONE, // < `ufbx_bone` - UFBX_ELEMENT_EMPTY, // < `ufbx_empty` - UFBX_ELEMENT_LINE_CURVE, // < `ufbx_line_curve` - UFBX_ELEMENT_NURBS_CURVE, // < `ufbx_nurbs_curve` - UFBX_ELEMENT_NURBS_SURFACE, // < `ufbx_nurbs_surface` - UFBX_ELEMENT_NURBS_TRIM_SURFACE, // < `ufbx_nurbs_trim_surface` - UFBX_ELEMENT_NURBS_TRIM_BOUNDARY, // < `ufbx_nurbs_trim_boundary` - UFBX_ELEMENT_PROCEDURAL_GEOMETRY, // < `ufbx_procedural_geometry` - UFBX_ELEMENT_STEREO_CAMERA, // < `ufbx_stereo_camera` - UFBX_ELEMENT_CAMERA_SWITCHER, // < `ufbx_camera_switcher` - UFBX_ELEMENT_MARKER, // < `ufbx_marker` - UFBX_ELEMENT_LOD_GROUP, // < `ufbx_lod_group` - UFBX_ELEMENT_SKIN_DEFORMER, // < `ufbx_skin_deformer` - UFBX_ELEMENT_SKIN_CLUSTER, // < `ufbx_skin_cluster` - UFBX_ELEMENT_BLEND_DEFORMER, // < `ufbx_blend_deformer` - UFBX_ELEMENT_BLEND_CHANNEL, // < `ufbx_blend_channel` - UFBX_ELEMENT_BLEND_SHAPE, // < `ufbx_blend_shape` - UFBX_ELEMENT_CACHE_DEFORMER, // < `ufbx_cache_deformer` - UFBX_ELEMENT_CACHE_FILE, // < `ufbx_cache_file` - UFBX_ELEMENT_MATERIAL, // < `ufbx_material` - UFBX_ELEMENT_TEXTURE, // < `ufbx_texture` - UFBX_ELEMENT_VIDEO, // < `ufbx_video` - UFBX_ELEMENT_SHADER, // < `ufbx_shader` - UFBX_ELEMENT_SHADER_BINDING, // < `ufbx_shader_binding` - UFBX_ELEMENT_ANIM_STACK, // < `ufbx_anim_stack` - UFBX_ELEMENT_ANIM_LAYER, // < `ufbx_anim_layer` - UFBX_ELEMENT_ANIM_VALUE, // < `ufbx_anim_value` - UFBX_ELEMENT_ANIM_CURVE, // < `ufbx_anim_curve` - UFBX_ELEMENT_DISPLAY_LAYER, // < `ufbx_display_layer` - UFBX_ELEMENT_SELECTION_SET, // < `ufbx_selection_set` - UFBX_ELEMENT_SELECTION_NODE, // < `ufbx_selection_node` - UFBX_ELEMENT_CHARACTER, // < `ufbx_character` - UFBX_ELEMENT_CONSTRAINT, // < `ufbx_constraint` - UFBX_ELEMENT_AUDIO_LAYER, // < `ufbx_audio_layer` - UFBX_ELEMENT_AUDIO_CLIP, // < `ufbx_audio_clip` - UFBX_ELEMENT_POSE, // < `ufbx_pose` - UFBX_ELEMENT_METADATA_OBJECT, // < `ufbx_metadata_object` - - UFBX_ELEMENT_TYPE_FIRST_ATTRIB = UFBX_ELEMENT_MESH, - UFBX_ELEMENT_TYPE_LAST_ATTRIB = UFBX_ELEMENT_LOD_GROUP, - - UFBX_ENUM_FORCE_WIDTH(UFBX_ELEMENT_TYPE) -} ufbx_element_type; - -UFBX_ENUM_TYPE(ufbx_element_type, UFBX_ELEMENT_TYPE, UFBX_ELEMENT_METADATA_OBJECT); - -// Connection between two elements. -// Source and destination are somewhat arbitrary but the destination is -// often the "container" like a parent node or mesh containing a deformer. -typedef struct ufbx_connection { - ufbx_element *src; - ufbx_element *dst; - ufbx_string src_prop; - ufbx_string dst_prop; -} ufbx_connection; - -UFBX_LIST_TYPE(ufbx_connection_list, ufbx_connection); - -// Element "base-class" common to each element. -// Some fields (like `connections_src`) are advanced and not visible -// in the specialized element structs. -// NOTE: The `element_id` value is consistent when loading the -// _same_ file, but re-exporting the file will invalidate them. -struct ufbx_element { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - ufbx_element_type type; - ufbx_connection_list connections_src; - ufbx_connection_list connections_dst; - ufbx_nullable ufbx_dom_node *dom_node; - ufbx_scene *scene; -}; - -// -- Unknown - -struct ufbx_unknown { - // Shared "base-class" header, see `ufbx_element`. - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // FBX format specific type information. - // In ASCII FBX format: - // super_type: ID, "type::name", "sub_type" { ... } - ufbx_string type; - ufbx_string super_type; - ufbx_string sub_type; -}; - -// -- Nodes - -// Inherit type specifies how hierarchial node transforms are combined. -// This only affects the final scaling, as rotation and translation are always -// inherited correctly. -// NOTE: These don't map to `"InheritType"` property as there may be new ones for -// compatibility with various exporters. -typedef enum ufbx_inherit_mode UFBX_ENUM_REPR { - - // Normal matrix composition of hierarchy: `R*S*r*s`. - // child.node_to_world = parent.node_to_world * child.node_to_parent; - UFBX_INHERIT_MODE_NORMAL, - - // Ignore parent scale when computing the transform: `R*r*s`. - // ufbx_transform t = node.local_transform; - // t.translation *= parent.inherit_scale; - // t.scale *= node.inherit_scale_node.inherit_scale; - // child.node_to_world = parent.unscaled_node_to_world * t; - // Also known as "Segment scale compensate" in some software. - UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE, - - // Apply parent scale component-wise: `R*r*S*s`. - // ufbx_transform t = node.local_transform; - // t.translation *= parent.inherit_scale; - // t.scale *= node.inherit_scale_node.inherit_scale; - // child.node_to_world = parent.unscaled_node_to_world * t; - UFBX_INHERIT_MODE_COMPONENTWISE_SCALE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_INHERIT_MODE) -} ufbx_inherit_mode; - -UFBX_ENUM_TYPE(ufbx_inherit_mode, UFBX_INHERIT_MODE, UFBX_INHERIT_MODE_COMPONENTWISE_SCALE); - -// Axis used to mirror transformations for handedness conversion. -typedef enum ufbx_mirror_axis UFBX_ENUM_REPR { - - UFBX_MIRROR_AXIS_NONE, - UFBX_MIRROR_AXIS_X, - UFBX_MIRROR_AXIS_Y, - UFBX_MIRROR_AXIS_Z, - - UFBX_ENUM_FORCE_WIDTH(UFBX_MIRROR_AXIS) -} ufbx_mirror_axis; - -UFBX_ENUM_TYPE(ufbx_mirror_axis, UFBX_MIRROR_AXIS, UFBX_MIRROR_AXIS_Z); - -// Nodes form the scene transformation hierarchy and can contain attached -// elements such as meshes or lights. In normal cases a single `ufbx_node` -// contains only a single attached element, so using `type/mesh/...` is safe. -struct ufbx_node { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Node hierarchy - - // Parent node containing this one if not root. - // - // Always non-`NULL` for non-root nodes unless - // `ufbx_load_opts.allow_nodes_out_of_root` is enabled. - ufbx_nullable ufbx_node *parent; - - // List of child nodes parented to this node. - ufbx_node_list children; - - // Common attached element type and typed pointers. Set to `NULL` if not in - // use, so checking `attrib_type` is not required. - // - // HINT: If you need less common attributes access `ufbx_node.attrib`, you - // can use utility functions like `ufbx_as_nurbs_curve(attrib)` to convert - // and check the attribute in one step. - ufbx_nullable ufbx_mesh *mesh; - ufbx_nullable ufbx_light *light; - ufbx_nullable ufbx_camera *camera; - ufbx_nullable ufbx_bone *bone; - - // Less common attributes use these fields. - // - // Defined even if it is one of the above, eg. `ufbx_mesh`. In case there - // is multiple attributes this will be the first one. - ufbx_nullable ufbx_element *attrib; - - // Geometry transform helper if one exists. - // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. - ufbx_nullable ufbx_node *geometry_transform_helper; - - // Scale helper if one exists. - // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. - ufbx_nullable ufbx_node *scale_helper; - - // `attrib->type` if `attrib` is defined, otherwise `UFBX_ELEMENT_UNKNOWN`. - ufbx_element_type attrib_type; - - // List of _all_ attached attribute elements. - // - // In most cases there is only zero or one attributes per node, but if you - // have a very exotic FBX file nodes may have multiple attributes. - ufbx_element_list all_attribs; - - // Local transform in parent, geometry transform is a non-inherited - // transform applied only to attachments like meshes - ufbx_inherit_mode inherit_mode; - ufbx_inherit_mode original_inherit_mode; - ufbx_transform local_transform; - ufbx_transform geometry_transform; - - // Combined scale when using `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE`. - // Contains `local_transform.scale` otherwise. - ufbx_vec3 inherit_scale; - - // Node where scale is inherited from for `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE` - // and even for `UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE`. - // For componentwise-scale nodes, this will point to `parent`, for scale ignoring - // nodes this will point to the parent of the nearest componentwise-scaled node - // in the parent chain. - ufbx_nullable ufbx_node *inherit_scale_node; - - // Raw Euler angles in degrees for those who want them - - // Specifies the axis order `euler_rotation` is applied in. - ufbx_rotation_order rotation_order; - // Rotation around the local X/Y/Z axes in `rotation_order`. - // The angles are specified in degrees. - ufbx_vec3 euler_rotation; - - // Matrices derived from the transformations, for transforming geometry - // prefer using `geometry_to_world` as that supports geometric transforms. - - // Transform from this node to `parent` space. - // Equivalent to `ufbx_transform_to_matrix(&local_transform)`. - ufbx_matrix node_to_parent; - // Transform from this node to the world space, ie. multiplying all the - // `node_to_parent` matrices of the parent chain together. - ufbx_matrix node_to_world; - // Transform from the attribute to this node. Does not affect the transforms - // of `children`! - // Equivalent to `ufbx_transform_to_matrix(&geometry_transform)`. - ufbx_matrix geometry_to_node; - // Transform from attribute space to world space. - // Equivalent to `ufbx_matrix_mul(&node_to_world, &geometry_to_node)`. - ufbx_matrix geometry_to_world; - // Transform from this node to world space, ignoring self scaling. - ufbx_matrix unscaled_node_to_world; - - // ufbx-specific adjustment for switching between coodrinate/unit systems. - // HINT: In most cases you don't need to deal with these as these are baked - // into all the transforms above and into `ufbx_evaluate_transform()`. - ufbx_vec3 adjust_pre_translation; // < Translation applied between parent and self - ufbx_quat adjust_pre_rotation; // < Rotation applied between parent and self - ufbx_real adjust_pre_scale; // < Scaling applied between parent and self - ufbx_quat adjust_post_rotation; // < Rotation applied in local space at the end - ufbx_real adjust_post_scale; // < Scaling applied in local space at the end - ufbx_real adjust_translation_scale; // < Scaling applied to translation only - ufbx_mirror_axis adjust_mirror_axis; // < Mirror translation and rotation on this axis - - // Materials used by `mesh` or other `attrib`. - // There may be multiple copies of a single `ufbx_mesh` with different materials - // in the `ufbx_node` instances. - ufbx_material_list materials; - - // Bind pose - ufbx_nullable ufbx_pose *bind_pose; - - // Visibility state. - bool visible; - - // True if this node is the implicit root node of the scene. - bool is_root; - - // True if the node has a non-identity `geometry_transform`. - bool has_geometry_transform; - - // If `true` the transform is adjusted by ufbx, not enabled by default. - // See `adjust_pre_rotation`, `adjust_pre_scale`, `adjust_post_rotation`, - // and `adjust_post_scale`. - bool has_adjust_transform; - - // Scale is adjusted by root scale. - bool has_root_adjust_transform; - - // True if this node is a synthetic geometry transform helper. - // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. - bool is_geometry_transform_helper; - - // True if the node is a synthetic scale compensation helper. - // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. - bool is_scale_helper; - - // Parent node to children that can compensate for parent scale. - bool is_scale_compensate_parent; - - // How deep is this node in the parent hierarchy. Root node is at depth `0` - // and the immediate children of root at `1`. - uint32_t node_depth; -}; - -// Vertex attribute: All attributes are stored in a consistent indexed format -// regardless of how it's actually stored in the file. -// -// `values` is a contiguous array of attribute values. -// `indices` maps each mesh index into a value in the `values` array. -// -// If `unique_per_vertex` is set then the attribute is guaranteed to have a -// single defined value per vertex accessible via: -// attrib.values.data[attrib.indices.data[mesh->vertex_first_index[vertex_ix]] -typedef struct ufbx_vertex_attrib { - // Is this attribute defined by the mesh. - bool exists; - // List of values the attribute uses. - ufbx_void_list values; - // Indices into `values[]`, indexed up to `ufbx_mesh.num_indices`. - ufbx_uint32_list indices; - // Number of `ufbx_real` entries per value. - size_t value_reals; - // `true` if this attribute is defined per vertex, instead of per index. - bool unique_per_vertex; - // Optional 4th 'W' component for the attribute. - // May be defined for the following: - // ufbx_mesh.vertex_normal - // ufbx_mesh.vertex_tangent / ufbx_uv_set.vertex_tangent - // ufbx_mesh.vertex_bitangent / ufbx_uv_set.vertex_bitangent - // NOTE: This is not loaded by default, set `ufbx_load_opts.retain_vertex_attrib_w`. - ufbx_real_list values_w; -} ufbx_vertex_attrib; - -// 1D vertex attribute, see `ufbx_vertex_attrib` for information -typedef struct ufbx_vertex_real { - bool exists; - ufbx_real_list values; - ufbx_uint32_list indices; - size_t value_reals; - bool unique_per_vertex; - ufbx_real_list values_w; - - UFBX_VERTEX_ATTRIB_IMPL(ufbx_real) -} ufbx_vertex_real; - -// 2D vertex attribute, see `ufbx_vertex_attrib` for information -typedef struct ufbx_vertex_vec2 { - bool exists; - ufbx_vec2_list values; - ufbx_uint32_list indices; - size_t value_reals; - bool unique_per_vertex; - ufbx_real_list values_w; - - UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec2) -} ufbx_vertex_vec2; - -// 3D vertex attribute, see `ufbx_vertex_attrib` for information -typedef struct ufbx_vertex_vec3 { - bool exists; - ufbx_vec3_list values; - ufbx_uint32_list indices; - size_t value_reals; - bool unique_per_vertex; - ufbx_real_list values_w; - - UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec3) -} ufbx_vertex_vec3; - -// 4D vertex attribute, see `ufbx_vertex_attrib` for information -typedef struct ufbx_vertex_vec4 { - bool exists; - ufbx_vec4_list values; - ufbx_uint32_list indices; - size_t value_reals; - bool unique_per_vertex; - ufbx_real_list values_w; - - UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec4) -} ufbx_vertex_vec4; - -// Vertex UV set/layer -typedef struct ufbx_uv_set { - ufbx_string name; - uint32_t index; - - // Vertex attributes, see `ufbx_mesh` attributes for more information - ufbx_vertex_vec2 vertex_uv; // < UV / texture coordinates - ufbx_vertex_vec3 vertex_tangent; // < (optional) Tangent vector in UV.x direction - ufbx_vertex_vec3 vertex_bitangent; // < (optional) Tangent vector in UV.y direction -} ufbx_uv_set; - -// Vertex color set/layer -typedef struct ufbx_color_set { - ufbx_string name; - uint32_t index; - - // Vertex attributes, see `ufbx_mesh` attributes for more information - ufbx_vertex_vec4 vertex_color; // < Per-vertex RGBA color -} ufbx_color_set; - -UFBX_LIST_TYPE(ufbx_uv_set_list, ufbx_uv_set); -UFBX_LIST_TYPE(ufbx_color_set_list, ufbx_color_set); - -// Edge between two _indices_ in a mesh -typedef struct ufbx_edge { - union { - struct { uint32_t a, b; }; - uint32_t indices[2]; - }; -} ufbx_edge; - -UFBX_LIST_TYPE(ufbx_edge_list, ufbx_edge); - -// Polygonal face with arbitrary number vertices, a single face contains a -// contiguous range of mesh indices, eg. `{5,3}` would have indices 5, 6, 7 -// -// NOTE: `num_indices` maybe less than 3 in which case the face is invalid! -// [TODO #23: should probably remove the bad faces at load time] -typedef struct ufbx_face { - uint32_t index_begin; - uint32_t num_indices; -} ufbx_face; - -UFBX_LIST_TYPE(ufbx_face_list, ufbx_face); - -// Subset of mesh faces used by a single material or group. -typedef struct ufbx_mesh_part { - - // Index of the mesh part. - uint32_t index; - - // Sub-set of the geometry - size_t num_faces; // < Number of faces (polygons) - size_t num_triangles; // < Number of triangles if triangulated - - size_t num_empty_faces; // < Number of faces with zero vertices - size_t num_point_faces; // < Number of faces with a single vertex - size_t num_line_faces; // < Number of faces with two vertices - - // Indices to `ufbx_mesh.faces[]`. - // Always contains `num_faces` elements. - ufbx_uint32_list face_indices; - -} ufbx_mesh_part; - -UFBX_LIST_TYPE(ufbx_mesh_part_list, ufbx_mesh_part); - -typedef struct ufbx_face_group { - int32_t id; // < Numerical ID for this group. - ufbx_string name; // < Name for the face group. -} ufbx_face_group; - -UFBX_LIST_TYPE(ufbx_face_group_list, ufbx_face_group); - -typedef struct ufbx_subdivision_weight_range { - uint32_t weight_begin; - uint32_t num_weights; -} ufbx_subdivision_weight_range; - -UFBX_LIST_TYPE(ufbx_subdivision_weight_range_list, ufbx_subdivision_weight_range); - -typedef struct ufbx_subdivision_weight { - ufbx_real weight; - uint32_t index; -} ufbx_subdivision_weight; - -UFBX_LIST_TYPE(ufbx_subdivision_weight_list, ufbx_subdivision_weight); - -typedef struct ufbx_subdivision_result { - size_t result_memory_used; - size_t temp_memory_used; - size_t result_allocs; - size_t temp_allocs; - - // Weights of vertices in the source model. - // Defined if `ufbx_subdivide_opts.evaluate_source_vertices` is set. - ufbx_subdivision_weight_range_list source_vertex_ranges; - ufbx_subdivision_weight_list source_vertex_weights; - - // Weights of skin clusters in the source model. - // Defined if `ufbx_subdivide_opts.evaluate_skin_weights` is set. - ufbx_subdivision_weight_range_list skin_cluster_ranges; - ufbx_subdivision_weight_list skin_cluster_weights; - -} ufbx_subdivision_result; - -typedef enum ufbx_subdivision_display_mode UFBX_ENUM_REPR { - UFBX_SUBDIVISION_DISPLAY_DISABLED, - UFBX_SUBDIVISION_DISPLAY_HULL, - UFBX_SUBDIVISION_DISPLAY_HULL_AND_SMOOTH, - UFBX_SUBDIVISION_DISPLAY_SMOOTH, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SUBDIVISION_DISPLAY_MODE) -} ufbx_subdivision_display_mode; - -UFBX_ENUM_TYPE(ufbx_subdivision_display_mode, UFBX_SUBDIVISION_DISPLAY_MODE, UFBX_SUBDIVISION_DISPLAY_SMOOTH); - -typedef enum ufbx_subdivision_boundary UFBX_ENUM_REPR { - UFBX_SUBDIVISION_BOUNDARY_DEFAULT, - UFBX_SUBDIVISION_BOUNDARY_LEGACY, - // OpenSubdiv: `VTX_BOUNDARY_EDGE_AND_CORNER` / `FVAR_LINEAR_CORNERS_ONLY` - UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS, - // OpenSubdiv: `VTX_BOUNDARY_EDGE_ONLY` / `FVAR_LINEAR_NONE` - UFBX_SUBDIVISION_BOUNDARY_SHARP_NONE, - // OpenSubdiv: `FVAR_LINEAR_BOUNDARIES` - UFBX_SUBDIVISION_BOUNDARY_SHARP_BOUNDARY, - // OpenSubdiv: `FVAR_LINEAR_ALL` - UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SUBDIVISION_BOUNDARY) -} ufbx_subdivision_boundary; - -UFBX_ENUM_TYPE(ufbx_subdivision_boundary, UFBX_SUBDIVISION_BOUNDARY, UFBX_SUBDIVISION_BOUNDARY_SHARP_INTERIOR); - -// Polygonal mesh geometry. -// -// Example mesh with two triangles (x, z) and a quad (y). -// The faces have a constant UV coordinate x/y/z. -// The vertices have _per vertex_ normals that point up/down. -// -// ^ ^ ^ -// A---B-----C -// |x / /| -// | / y / | -// |/ / z| -// D-----E---F -// v v v -// -// Attributes may have multiple values within a single vertex, for example a -// UV seam vertex has two UV coordinates. Thus polygons are defined using -// an index that counts each corner of each face polygon. If an attribute is -// defined (even per-vertex) it will always have a valid `indices` array. -// -// {0,3} {3,4} {7,3} faces ({ index_begin, num_indices }) -// 0 1 2 3 4 5 6 7 8 9 index -// -// 0 1 3 1 2 4 3 2 4 5 vertex_indices[index] -// A B D B C E D C E F vertices[vertex_indices[index]] -// -// 0 0 1 0 0 1 1 0 1 1 vertex_normal.indices[index] -// ^ ^ v ^ ^ v v ^ v v vertex_normal.data[vertex_normal.indices[index]] -// -// 0 0 0 1 1 1 1 2 2 2 vertex_uv.indices[index] -// x x x y y y y z z z vertex_uv.data[vertex_uv.indices[index]] -// -// Vertex position can also be accessed uniformly through an accessor: -// 0 1 3 1 2 4 3 2 4 5 vertex_position.indices[index] -// A B D B C E D C E F vertex_position.data[vertex_position.indices[index]] -// -// Some geometry data is specified per logical vertex. Vertex positions are -// the only attribute that is guaranteed to be defined _uniquely_ per vertex. -// Vertex attributes _may_ be defined per vertex if `unique_per_vertex == true`. -// You can access the per-vertex values by first finding the first index that -// refers to the given vertex. -// -// 0 1 2 3 4 5 vertex -// A B C D E F vertices[vertex] -// -// 0 1 4 2 5 9 vertex_first_index[vertex] -// 0 0 0 1 1 1 vertex_normal.indices[vertex_first_index[vertex]] -// ^ ^ ^ v v v vertex_normal.data[vertex_normal.indices[vertex_first_index[vertex]]] -// -struct ufbx_mesh { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Number of "logical" vertices that would be treated as a single point, - // one vertex may be split to multiple indices for split attributes, eg. UVs - size_t num_vertices; // < Number of logical "vertex" points - size_t num_indices; // < Number of combiend vertex/attribute tuples - size_t num_faces; // < Number of faces (polygons) in the mesh - size_t num_triangles; // < Number of triangles if triangulated - - // Number of edges in the mesh. - // NOTE: May be zero in valid meshes if the file doesn't contain edge adjacency data! - size_t num_edges; - - size_t max_face_triangles; // < Maximum number of triangles in a face in this mesh - - size_t num_empty_faces; // < Number of faces with zero vertices - size_t num_point_faces; // < Number of faces with a single vertex - size_t num_line_faces; // < Number of faces with two vertices - - // Faces and optional per-face extra data - ufbx_face_list faces; // < Face index range - ufbx_bool_list face_smoothing; // < Should the face have soft normals - ufbx_uint32_list face_material; // < Indices to `ufbx_mesh.materials[]` and `ufbx_node.materials[]` - ufbx_uint32_list face_group; // < Face polygon group index, indices to `ufbx_mesh.face_groups[]` - ufbx_bool_list face_hole; // < Should the face be hidden as a "hole" - - // Edges and optional per-edge extra data - ufbx_edge_list edges; // < Edge index range - ufbx_bool_list edge_smoothing; // < Should the edge have soft normals - ufbx_real_list edge_crease; // < Crease value for subdivision surfaces - ufbx_bool_list edge_visibility; // < Should the edge be visible - - // Logical vertices and positions, alternatively you can use - // `vertex_position` for consistent interface with other attributes. - ufbx_uint32_list vertex_indices; - ufbx_vec3_list vertices; - - // First index referring to a given vertex, `UFBX_NO_INDEX` if the vertex is unused. - ufbx_uint32_list vertex_first_index; - - // Vertex attributes, see the comment over the struct. - // - // NOTE: Not all meshes have all attributes, in that case `indices/data == NULL`! - // - // NOTE: UV/tangent/bitangent and color are the from first sets, - // use `uv_sets/color_sets` to access the other layers. - ufbx_vertex_vec3 vertex_position; // < Vertex positions - ufbx_vertex_vec3 vertex_normal; // < (optional) Normal vectors, always defined if `ufbx_load_opts.generate_missing_normals` - ufbx_vertex_vec2 vertex_uv; // < (optional) UV / texture coordinates - ufbx_vertex_vec3 vertex_tangent; // < (optional) Tangent vector in UV.x direction - ufbx_vertex_vec3 vertex_bitangent; // < (optional) Tangent vector in UV.y direction - ufbx_vertex_vec4 vertex_color; // < (optional) Per-vertex RGBA color - ufbx_vertex_real vertex_crease; // < (optional) Crease value for subdivision surfaces - - // Multiple named UV/color sets - // NOTE: The first set contains the same data as `vertex_uv/color`! - ufbx_uv_set_list uv_sets; - ufbx_color_set_list color_sets; - - // Materials used by the mesh. - // NOTE: These can be wrong if you want to support per-instance materials! - // Use `ufbx_node.materials[]` to get the per-instance materials at the same indices. - ufbx_material_list materials; - - // Face groups for this mesh. - ufbx_face_group_list face_groups; - - // Segments that use a given material. - // Defined even if the mesh doesn't have any materials. - ufbx_mesh_part_list material_parts; - - // Segments for each face group. - ufbx_mesh_part_list face_group_parts; - - // Order of `material_parts` by first face that refers to it. - // Useful for compatibility with FBX SDK and various importers using it, - // as they use this material order by default. - ufbx_uint32_list material_part_usage_order; - - // Skinned vertex positions, for efficiency the skinned positions are the - // same as the static ones for non-skinned meshes and `skinned_is_local` - // is set to true meaning you need to transform them manually using - // `ufbx_transform_position(&node->geometry_to_world, skinned_pos)`! - bool skinned_is_local; - ufbx_vertex_vec3 skinned_position; - ufbx_vertex_vec3 skinned_normal; - - // Deformers - ufbx_skin_deformer_list skin_deformers; - ufbx_blend_deformer_list blend_deformers; - ufbx_cache_deformer_list cache_deformers; - ufbx_element_list all_deformers; - - // Subdivision - uint32_t subdivision_preview_levels; - uint32_t subdivision_render_levels; - ufbx_subdivision_display_mode subdivision_display_mode; - ufbx_subdivision_boundary subdivision_boundary; - ufbx_subdivision_boundary subdivision_uv_boundary; - - // The winding of the faces has been reversed. - bool reversed_winding; - - // Normals have been generated instead of evaluated. - // Either from missing normals (via `ufbx_load_opts.generate_missing_normals`), skinning, - // tessellation, or subdivision. - bool generated_normals; - - // Subdivision (result) - bool subdivision_evaluated; - ufbx_nullable ufbx_subdivision_result *subdivision_result; - - // Tessellation (result) - bool from_tessellated_nurbs; -}; - -// The kind of light source -typedef enum ufbx_light_type UFBX_ENUM_REPR { - // Single point at local origin, at `node->world_transform.position` - UFBX_LIGHT_POINT, - // Infinite directional light pointing locally towards `light->local_direction` - // For global: `ufbx_transform_direction(&node->node_to_world, light->local_direction)` - UFBX_LIGHT_DIRECTIONAL, - // Cone shaped light towards `light->local_direction`, between `light->inner/outer_angle`. - // For global: `ufbx_transform_direction(&node->node_to_world, light->local_direction)` - UFBX_LIGHT_SPOT, - // Area light, shape specified by `light->area_shape` - // TODO: Units? - UFBX_LIGHT_AREA, - // Volumetric light source - // TODO: How does this work - UFBX_LIGHT_VOLUME, - - UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_TYPE) -} ufbx_light_type; - -UFBX_ENUM_TYPE(ufbx_light_type, UFBX_LIGHT_TYPE, UFBX_LIGHT_VOLUME); - -// How fast does the light intensity decay at a distance -typedef enum ufbx_light_decay UFBX_ENUM_REPR { - UFBX_LIGHT_DECAY_NONE, // < 1 (no decay) - UFBX_LIGHT_DECAY_LINEAR, // < 1 / d - UFBX_LIGHT_DECAY_QUADRATIC, // < 1 / d^2 (physically accurate) - UFBX_LIGHT_DECAY_CUBIC, // < 1 / d^3 - - UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_DECAY) -} ufbx_light_decay; - -UFBX_ENUM_TYPE(ufbx_light_decay, UFBX_LIGHT_DECAY, UFBX_LIGHT_DECAY_CUBIC); - -typedef enum ufbx_light_area_shape UFBX_ENUM_REPR { - UFBX_LIGHT_AREA_SHAPE_RECTANGLE, - UFBX_LIGHT_AREA_SHAPE_SPHERE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_LIGHT_AREA_SHAPE) -} ufbx_light_area_shape; - -UFBX_ENUM_TYPE(ufbx_light_area_shape, UFBX_LIGHT_AREA_SHAPE, UFBX_LIGHT_AREA_SHAPE_SPHERE); - -// Light source attached to a `ufbx_node` -struct ufbx_light { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Color and intensity of the light, usually you want to use `color * intensity` - // NOTE: `intensity` is 0.01x of the property `"Intensity"` as that matches - // matches values in DCC programs before exporting. - ufbx_vec3 color; - ufbx_real intensity; - - // Direction the light is aimed at in node's local space, usually -Y - ufbx_vec3 local_direction; - - // Type of the light and shape parameters - ufbx_light_type type; - ufbx_light_decay decay; - ufbx_light_area_shape area_shape; - ufbx_real inner_angle; - ufbx_real outer_angle; - - bool cast_light; - bool cast_shadows; -}; - -typedef enum ufbx_projection_mode UFBX_ENUM_REPR { - // Perspective projection. - UFBX_PROJECTION_MODE_PERSPECTIVE, - - // Orthographic projection. - UFBX_PROJECTION_MODE_ORTHOGRAPHIC, - - UFBX_ENUM_FORCE_WIDTH(UFBX_PROJECTION_MODE) -} ufbx_projection_mode; - -UFBX_ENUM_TYPE(ufbx_projection_mode, UFBX_PROJECTION_MODE, UFBX_PROJECTION_MODE_ORTHOGRAPHIC); - -// Method of specifying the rendering resolution from properties -// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! -typedef enum ufbx_aspect_mode UFBX_ENUM_REPR { - // No defined resolution - UFBX_ASPECT_MODE_WINDOW_SIZE, - // `"AspectWidth"` and `"AspectHeight"` are relative to each other - UFBX_ASPECT_MODE_FIXED_RATIO, - // `"AspectWidth"` and `"AspectHeight"` are both pixels - UFBX_ASPECT_MODE_FIXED_RESOLUTION, - // `"AspectWidth"` is pixels, `"AspectHeight"` is relative to width - UFBX_ASPECT_MODE_FIXED_WIDTH, - // < `"AspectHeight"` is pixels, `"AspectWidth"` is relative to height - UFBX_ASPECT_MODE_FIXED_HEIGHT, - - UFBX_ENUM_FORCE_WIDTH(UFBX_ASPECT_MODE) -} ufbx_aspect_mode; - -UFBX_ENUM_TYPE(ufbx_aspect_mode, UFBX_ASPECT_MODE, UFBX_ASPECT_MODE_FIXED_HEIGHT); - -// Method of specifying the field of view from properties -// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! -typedef enum ufbx_aperture_mode UFBX_ENUM_REPR { - // Use separate `"FieldOfViewX"` and `"FieldOfViewY"` as horizontal/vertical FOV angles - UFBX_APERTURE_MODE_HORIZONTAL_AND_VERTICAL, - // Use `"FieldOfView"` as horizontal FOV angle, derive vertical angle via aspect ratio - UFBX_APERTURE_MODE_HORIZONTAL, - // Use `"FieldOfView"` as vertical FOV angle, derive horizontal angle via aspect ratio - UFBX_APERTURE_MODE_VERTICAL, - // Compute the field of view from the render gate size and focal length - UFBX_APERTURE_MODE_FOCAL_LENGTH, - - UFBX_ENUM_FORCE_WIDTH(UFBX_APERTURE_MODE) -} ufbx_aperture_mode; - -UFBX_ENUM_TYPE(ufbx_aperture_mode, UFBX_APERTURE_MODE, UFBX_APERTURE_MODE_FOCAL_LENGTH); - -// Method of specifying the render gate size from properties -// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! -typedef enum ufbx_gate_fit UFBX_ENUM_REPR { - // Use the film/aperture size directly as the render gate - UFBX_GATE_FIT_NONE, - // Fit the render gate to the height of the film, derive width from aspect ratio - UFBX_GATE_FIT_VERTICAL, - // Fit the render gate to the width of the film, derive height from aspect ratio - UFBX_GATE_FIT_HORIZONTAL, - // Fit the render gate so that it is fully contained within the film gate - UFBX_GATE_FIT_FILL, - // Fit the render gate so that it fully contains the film gate - UFBX_GATE_FIT_OVERSCAN, - // Stretch the render gate to match the film gate - // TODO: Does this differ from `UFBX_GATE_FIT_NONE`? - UFBX_GATE_FIT_STRETCH, - - UFBX_ENUM_FORCE_WIDTH(UFBX_GATE_FIT) -} ufbx_gate_fit; - -UFBX_ENUM_TYPE(ufbx_gate_fit, UFBX_GATE_FIT, UFBX_GATE_FIT_STRETCH); - -// Camera film/aperture size defaults -// NOTE: Handled internally by ufbx, ignore unless you interpret `ufbx_props` directly! -typedef enum ufbx_aperture_format UFBX_ENUM_REPR { - UFBX_APERTURE_FORMAT_CUSTOM, // < Use `"FilmWidth"` and `"FilmHeight"` - UFBX_APERTURE_FORMAT_16MM_THEATRICAL, // < 0.404 x 0.295 inches - UFBX_APERTURE_FORMAT_SUPER_16MM, // < 0.493 x 0.292 inches - UFBX_APERTURE_FORMAT_35MM_ACADEMY, // < 0.864 x 0.630 inches - UFBX_APERTURE_FORMAT_35MM_TV_PROJECTION, // < 0.816 x 0.612 inches - UFBX_APERTURE_FORMAT_35MM_FULL_APERTURE, // < 0.980 x 0.735 inches - UFBX_APERTURE_FORMAT_35MM_185_PROJECTION, // < 0.825 x 0.446 inches - UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC, // < 0.864 x 0.732 inches (squeeze ratio: 2) - UFBX_APERTURE_FORMAT_70MM_PROJECTION, // < 2.066 x 0.906 inches - UFBX_APERTURE_FORMAT_VISTAVISION, // < 1.485 x 0.991 inches - UFBX_APERTURE_FORMAT_DYNAVISION, // < 2.080 x 1.480 inches - UFBX_APERTURE_FORMAT_IMAX, // < 2.772 x 2.072 inches - - UFBX_ENUM_FORCE_WIDTH(UFBX_APERTURE_FORMAT) -} ufbx_aperture_format; - -UFBX_ENUM_TYPE(ufbx_aperture_format, UFBX_APERTURE_FORMAT, UFBX_APERTURE_FORMAT_IMAX); - -typedef enum ufbx_coordinate_axis UFBX_ENUM_REPR { - UFBX_COORDINATE_AXIS_POSITIVE_X, - UFBX_COORDINATE_AXIS_NEGATIVE_X, - UFBX_COORDINATE_AXIS_POSITIVE_Y, - UFBX_COORDINATE_AXIS_NEGATIVE_Y, - UFBX_COORDINATE_AXIS_POSITIVE_Z, - UFBX_COORDINATE_AXIS_NEGATIVE_Z, - UFBX_COORDINATE_AXIS_UNKNOWN, - - UFBX_ENUM_FORCE_WIDTH(UFBX_COORDINATE_AXIS) -} ufbx_coordinate_axis; - -UFBX_ENUM_TYPE(ufbx_coordinate_axis, UFBX_COORDINATE_AXIS, UFBX_COORDINATE_AXIS_UNKNOWN); - -// Coordinate axes the scene is represented in. -// NOTE: `front` is the _opposite_ from forward! -typedef struct ufbx_coordinate_axes { - ufbx_coordinate_axis right; - ufbx_coordinate_axis up; - ufbx_coordinate_axis front; -} ufbx_coordinate_axes; - -// Camera attached to a `ufbx_node` -struct ufbx_camera { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Projection mode (perspective/orthographic). - ufbx_projection_mode projection_mode; - - // If set to `true`, `resolution` represents actual pixel values, otherwise - // it's only useful for its aspect ratio. - bool resolution_is_pixels; - - // Render resolution, either in pixels or arbitrary units, depending on above - ufbx_vec2 resolution; - - // Horizontal/vertical field of view in degrees - // Valid if `projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE`. - ufbx_vec2 field_of_view_deg; - - // Component-wise `tan(field_of_view_deg)`, also represents the size of the - // proection frustum slice at distance of 1. - // Valid if `projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE`. - ufbx_vec2 field_of_view_tan; - - // Orthographic camera extents. - // Valid if `projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC`. - ufbx_real orthographic_extent; - - // Orthographic camera size. - // Valid if `projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC`. - ufbx_vec2 orthographic_size; - - // Size of the projection plane at distance 1. - // Equal to `field_of_view_tan` if perspective, `orthographic_size` if orthographic. - ufbx_vec2 projection_plane; - - // Aspect ratio of the camera. - ufbx_real aspect_ratio; - - // Near plane of the frustum in units from the camera. - ufbx_real near_plane; - - // Far plane of the frustum in units from the camera. - ufbx_real far_plane; - - // Coordinate system that the projection uses. - // FBX saves cameras with +X forward and +Y up, but you can override this using - // `ufbx_load_opts.target_camera_axes` and it will be reflected here. - ufbx_coordinate_axes projection_axes; - - // Advanced properties used to compute the above - ufbx_aspect_mode aspect_mode; - ufbx_aperture_mode aperture_mode; - ufbx_gate_fit gate_fit; - ufbx_aperture_format aperture_format; - ufbx_real focal_length_mm; // < Focal length in millimeters - ufbx_vec2 film_size_inch; // < Film size in inches - ufbx_vec2 aperture_size_inch; // < Aperture/film gate size in inches - ufbx_real squeeze_ratio; // < Anamoprhic stretch ratio -}; - -// Bone attached to a `ufbx_node`, provides the logical length of the bone -// but most interesting information is directly in `ufbx_node`. -struct ufbx_bone { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Visual radius of the bone - ufbx_real radius; - - // Length of the bone relative to the distance between two nodes - ufbx_real relative_length; - - // Is the bone a root bone - bool is_root; -}; - -// Empty/NULL/locator connected to a node, actual details in `ufbx_node` -struct ufbx_empty { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; -}; - -// -- Node attributes (curves/surfaces) - -// Segment of a `ufbx_line_curve`, indices refer to `ufbx_line_curve.point_indices[]` -typedef struct ufbx_line_segment { - uint32_t index_begin; - uint32_t num_indices; -} ufbx_line_segment; - -UFBX_LIST_TYPE(ufbx_line_segment_list, ufbx_line_segment); - -struct ufbx_line_curve { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - ufbx_vec3 color; - - ufbx_vec3_list control_points; // < List of possible values the line passes through - ufbx_uint32_list point_indices; // < Indices to `control_points[]` the line goes through - - ufbx_line_segment_list segments; - - // Tessellation (result) - bool from_tessellated_nurbs; -}; - -typedef enum ufbx_nurbs_topology UFBX_ENUM_REPR { - // The endpoints are not connected. - UFBX_NURBS_TOPOLOGY_OPEN, - // Repeats first `ufbx_nurbs_basis.order - 1` control points after the end. - UFBX_NURBS_TOPOLOGY_PERIODIC, - // Repeats the first control point after the end. - UFBX_NURBS_TOPOLOGY_CLOSED, - - UFBX_ENUM_FORCE_WIDTH(UFBX_NURBS_TOPOLOGY) -} ufbx_nurbs_topology; - -UFBX_ENUM_TYPE(ufbx_nurbs_topology, UFBX_NURBS_TOPOLOGY, UFBX_NURBS_TOPOLOGY_CLOSED); - -// NURBS basis functions for an axis -typedef struct ufbx_nurbs_basis { - - // Number of control points influencing a point on the curve/surface. - // Equal to the degree plus one. - uint32_t order; - - // Topology (periodicity) of the dimension. - ufbx_nurbs_topology topology; - - // Subdivision of the parameter range to control points. - ufbx_real_list knot_vector; - - // Range for the parameter value. - ufbx_real t_min; - ufbx_real t_max; - - // Parameter values of control points. - ufbx_real_list spans; - - // `true` if this axis is two-dimensional. - bool is_2d; - - // Number of control points that need to be copied to the end. - // This is just for convenience as it could be derived from `topology` and - // `order`. If for example `num_wrap_control_points == 3` you should repeat - // the first 3 control points after the end. - // HINT: You don't need to worry about this if you use ufbx functions - // like `ufbx_evaluate_nurbs_curve()` as they handle this internally. - size_t num_wrap_control_points; - - // `true` if the parametrization is well defined. - bool valid; - -} ufbx_nurbs_basis; - -struct ufbx_nurbs_curve { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Basis in the U axis - ufbx_nurbs_basis basis; - - // Linear array of control points - // NOTE: The control points are _not_ homogeneous, meaning you have to multiply - // them by `w` before evaluating the surface. - ufbx_vec4_list control_points; -}; - -struct ufbx_nurbs_surface { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Basis in the U/V axes - ufbx_nurbs_basis basis_u; - ufbx_nurbs_basis basis_v; - - // Number of control points for the U/V axes - size_t num_control_points_u; - size_t num_control_points_v; - - // 2D array of control points. - // Memory layout: `V * num_control_points_u + U` - // NOTE: The control points are _not_ homogeneous, meaning you have to multiply - // them by `w` before evaluating the surface. - ufbx_vec4_list control_points; - - // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. - uint32_t span_subdivision_u; - uint32_t span_subdivision_v; - - // If `true` the resulting normals should be flipped when evaluated. - bool flip_normals; - - // Material for the whole surface. - // NOTE: May be `NULL`! - ufbx_nullable ufbx_material *material; -}; - -struct ufbx_nurbs_trim_surface { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; -}; - -struct ufbx_nurbs_trim_boundary { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; -}; - -// -- Node attributes (advanced) - -struct ufbx_procedural_geometry { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; -}; - -struct ufbx_stereo_camera { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - ufbx_nullable ufbx_camera *left; - ufbx_nullable ufbx_camera *right; -}; - -struct ufbx_camera_switcher { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; -}; - -typedef enum ufbx_marker_type UFBX_ENUM_REPR { - UFBX_MARKER_UNKNOWN, // < Unknown marker type - UFBX_MARKER_FK_EFFECTOR, // < FK (Forward Kinematics) effector - UFBX_MARKER_IK_EFFECTOR, // < IK (Inverse Kinematics) effector - - UFBX_ENUM_FORCE_WIDTH(UFBX_MARKER_TYPE) -} ufbx_marker_type; - -UFBX_ENUM_TYPE(ufbx_marker_type, UFBX_MARKER_TYPE, UFBX_MARKER_IK_EFFECTOR); - -// Tracking marker for effectors -struct ufbx_marker { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // Type of the marker - ufbx_marker_type type; -}; - -// LOD level display mode. -typedef enum ufbx_lod_display UFBX_ENUM_REPR { - UFBX_LOD_DISPLAY_USE_LOD, // < Display the LOD level if the distance is appropriate. - UFBX_LOD_DISPLAY_SHOW, // < Always display the LOD level. - UFBX_LOD_DISPLAY_HIDE, // < Never display the LOD level. - - UFBX_ENUM_FORCE_WIDTH(UFBX_LOD_DISPLAY) -} ufbx_lod_display; - -UFBX_ENUM_TYPE(ufbx_lod_display, UFBX_LOD_DISPLAY, UFBX_LOD_DISPLAY_HIDE); - -// Single LOD level within an LOD group. -// Specifies properties of the Nth child of the _node_ containing the LOD group. -typedef struct ufbx_lod_level { - - // Minimum distance to show this LOD level. - // NOTE: In world units by default, or in screen percentage if - // `ufbx_lod_group.relative_distances` is set. - ufbx_real distance; - - // LOD display mode. - // NOTE: Mostly for editing, you should probably ignore this - // unless making a modeling program. - ufbx_lod_display display; - -} ufbx_lod_level; - -UFBX_LIST_TYPE(ufbx_lod_level_list, ufbx_lod_level); - -// Group of LOD (Level of Detail) levels for an object. -// The actual LOD models are defined in the parent `ufbx_node.children`. -struct ufbx_lod_group { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - ufbx_node_list instances; - }; }; - - // If set to `true`, `ufbx_lod_level.distance` represents a screen size percentage. - bool relative_distances; - - // LOD levels matching in order to `ufbx_node.children`. - ufbx_lod_level_list lod_levels; - - // If set to `true` don't account for parent transform when computing the distance. - bool ignore_parent_transform; - - // If `use_distance_limit` is enabled hide the group if the distance is not between - // `distance_limit_min` and `distance_limit_max`. - bool use_distance_limit; - ufbx_real distance_limit_min; - ufbx_real distance_limit_max; -}; - -// -- Deformers - -// Method to evaluate the skinning on a per-vertex level -typedef enum ufbx_skinning_method UFBX_ENUM_REPR { - // Linear blend skinning: Blend transformation matrices by vertex weights - UFBX_SKINNING_METHOD_LINEAR, - // One vertex should have only one bone attached - UFBX_SKINNING_METHOD_RIGID, - // Convert the transformations to dual quaternions and blend in that space - UFBX_SKINNING_METHOD_DUAL_QUATERNION, - // Blend between `UFBX_SKINNING_METHOD_LINEAR` and `UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR` - // The blend weight can be found either per-vertex in `ufbx_skin_vertex.dq_weight` - // or in `ufbx_skin_deformer.dq_vertices/dq_weights` (indexed by vertex). - UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SKINNING_METHOD) -} ufbx_skinning_method; - -UFBX_ENUM_TYPE(ufbx_skinning_method, UFBX_SKINNING_METHOD, UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR); - -// Skin weight information for a single mesh vertex -typedef struct ufbx_skin_vertex { - - // Each vertex is influenced by weights from `ufbx_skin_deformer.weights[]` - // The weights are sorted by decreasing weight so you can take the first N - // weights to get a cheaper approximation of the vertex. - // NOTE: The weights are not guaranteed to be normalized! - uint32_t weight_begin; // < Index to start from in the `weights[]` array - uint32_t num_weights; // < Number of weights influencing the vertex - - // Blend weight between Linear Blend Skinning (0.0) and Dual Quaternion (1.0). - // Should be used if `skinning_method == UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR` - ufbx_real dq_weight; - -} ufbx_skin_vertex; - -UFBX_LIST_TYPE(ufbx_skin_vertex_list, ufbx_skin_vertex); - -// Single per-vertex per-cluster weight, see `ufbx_skin_vertex` -typedef struct ufbx_skin_weight { - uint32_t cluster_index; // < Index into `ufbx_skin_deformer.clusters[]` - ufbx_real weight; // < Amount this bone influence the vertex -} ufbx_skin_weight; - -UFBX_LIST_TYPE(ufbx_skin_weight_list, ufbx_skin_weight); - -// Skin deformer specifies a binding between a logical set of bones (a skeleton) -// and a mesh. Each bone is represented by a `ufbx_skin_cluster` that contains -// the binding matrix and a `ufbx_node *bone` that has the current transformation. -struct ufbx_skin_deformer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - ufbx_skinning_method skinning_method; - - // Clusters (bones) in the skin - ufbx_skin_cluster_list clusters; - - // Per-vertex weight information - ufbx_skin_vertex_list vertices; - ufbx_skin_weight_list weights; - - // Largest amount of weights a single vertex can have - size_t max_weights_per_vertex; - - // Blend weights between Linear Blend Skinning (0.0) and Dual Quaternion (1.0). - // HINT: You probably want to use `vertices` and `ufbx_skin_vertex.dq_weight` instead! - // NOTE: These may be out-of-bounds for a given mesh, `vertices` is always safe. - size_t num_dq_weights; - ufbx_uint32_list dq_vertices; - ufbx_real_list dq_weights; -}; - -// Cluster of vertices bound to a single bone. -struct ufbx_skin_cluster { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // The bone node the cluster is attached to - // NOTE: Always valid if found from `ufbx_skin_deformer.clusters[]` unless - // `ufbx_load_opts.connect_broken_elements` is `true`. - ufbx_nullable ufbx_node *bone_node; - - // Binding matrix from local mesh vertices to the bone - ufbx_matrix geometry_to_bone; - - // Binding matrix from local mesh _node_ to the bone. - // NOTE: Prefer `geometry_to_bone` in most use cases! - ufbx_matrix mesh_node_to_bone; - - // Matrix that specifies the rest/bind pose transform of the node, - // not generally needed for skinning, use `geometry_to_bone` instead. - ufbx_matrix bind_to_world; - - // Precomputed matrix/transform that accounts for the current bone transform - // ie. `ufbx_matrix_mul(&cluster->bone->node_to_world, &cluster->geometry_to_bone)` - ufbx_matrix geometry_to_world; - ufbx_transform geometry_to_world_transform; - - // Raw weights indexed by each _vertex_ of a mesh (not index!) - // HINT: It may be simpler to use `ufbx_skin_deformer.vertices[]/weights[]` instead! - // NOTE: These may be out-of-bounds for a given mesh, `ufbx_skin_deformer.vertices` is always safe. - size_t num_weights; // < Number of vertices in the cluster - ufbx_uint32_list vertices; // < Vertex indices in `ufbx_mesh.vertices[]` - ufbx_real_list weights; // < Per-vertex weight values -}; - -// Blend shape deformer can contain multiple channels (think of sliders between morphs) -// that may optionally have in-between keyframes. -struct ufbx_blend_deformer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Independent morph targets of the deformer. - ufbx_blend_channel_list channels; -}; - -// Blend shape associated with a target weight in a series of morphs -typedef struct ufbx_blend_keyframe { - // The target blend shape offsets. - ufbx_blend_shape *shape; - - // Weight value at which to apply the keyframe at full strength - ufbx_real target_weight; - - // The weight the shape should be currently applied with - ufbx_real effective_weight; -} ufbx_blend_keyframe; - -UFBX_LIST_TYPE(ufbx_blend_keyframe_list, ufbx_blend_keyframe); - -// Blend channel consists of multiple morph-key targets that are interpolated. -// In simple cases there will be only one keyframe that is the target shape. -struct ufbx_blend_channel { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Current weight of the channel - ufbx_real weight; - - // Key morph targets to blend between depending on `weight` - // In usual cases there's only one target per channel - ufbx_blend_keyframe_list keyframes; - - // Final blend shape ignoring any intermediate blend shapes. - ufbx_nullable ufbx_blend_shape *target_shape; -}; - -// Blend shape target containing the actual vertex offsets -struct ufbx_blend_shape { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Vertex offsets to apply over the base mesh - // NOTE: The `offset_vertices` may be out-of-bounds for a given mesh! - size_t num_offsets; // < Number of vertex offsets in the following arrays - ufbx_uint32_list offset_vertices; // < Indices to `ufbx_mesh.vertices[]` - ufbx_vec3_list position_offsets; // < Always specified per-vertex offsets - ufbx_vec3_list normal_offsets; // < Empty if not specified - - // Optional weights for the offsets. - // NOTE: These are technically not supported in FBX and are only written by Blender. - ufbx_real_list offset_weights; -}; - -typedef enum ufbx_cache_file_format UFBX_ENUM_REPR { - UFBX_CACHE_FILE_FORMAT_UNKNOWN, // < Unknown cache file format - UFBX_CACHE_FILE_FORMAT_PC2, // < .pc2 Point cache file - UFBX_CACHE_FILE_FORMAT_MC, // < .mc/.mcx Maya cache file - - UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_FILE_FORMAT) -} ufbx_cache_file_format; - -UFBX_ENUM_TYPE(ufbx_cache_file_format, UFBX_CACHE_FILE_FORMAT, UFBX_CACHE_FILE_FORMAT_MC); - -typedef enum ufbx_cache_data_format UFBX_ENUM_REPR { - UFBX_CACHE_DATA_FORMAT_UNKNOWN, // < Unknown data format - UFBX_CACHE_DATA_FORMAT_REAL_FLOAT, // < `float data[]` - UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT, // < `struct { float x, y, z; } data[]` - UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE, // < `double data[]` - UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE, // < `struct { double x, y, z; } data[]` - - UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_DATA_FORMAT) -} ufbx_cache_data_format; - -UFBX_ENUM_TYPE(ufbx_cache_data_format, UFBX_CACHE_DATA_FORMAT, UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE); - -typedef enum ufbx_cache_data_encoding UFBX_ENUM_REPR { - UFBX_CACHE_DATA_ENCODING_UNKNOWN, // < Unknown data encoding - UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN, // < Contiguous little-endian array - UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN, // < Contiguous big-endian array - - UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_DATA_ENCODING) -} ufbx_cache_data_encoding; - -UFBX_ENUM_TYPE(ufbx_cache_data_encoding, UFBX_CACHE_DATA_ENCODING, UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN); - -// Known interpretations of geometry cache data. -typedef enum ufbx_cache_interpretation UFBX_ENUM_REPR { - // Unknown interpretation, see `ufbx_cache_channel.interpretation_name` for more information. - UFBX_CACHE_INTERPRETATION_UNKNOWN, - - // Generic "points" interpretation, FBX SDK default. Usually fine to interpret - // as vertex positions if no other cache channels are specified. - UFBX_CACHE_INTERPRETATION_POINTS, - - // Vertex positions. - UFBX_CACHE_INTERPRETATION_VERTEX_POSITION, - - // Vertex normals. - UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL, - - UFBX_ENUM_FORCE_WIDTH(UFBX_CACHE_INTERPRETATION) -} ufbx_cache_interpretation; - -UFBX_ENUM_TYPE(ufbx_cache_interpretation, UFBX_CACHE_INTERPRETATION, UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL); - -typedef struct ufbx_cache_frame { - - // Name of the channel this frame belongs to. - ufbx_string channel; - - // Time of this frame in seconds. - double time; - - // Name of the file containing the data. - // The specified file may contain multiple frames, use `data_offset` etc. to - // read at the right position. - ufbx_string filename; - - // Format of the wrapper file. - ufbx_cache_file_format file_format; - - // Axis to mirror the read data by. - ufbx_mirror_axis mirror_axis; - - // Factor to scale the geometry by. - ufbx_real scale_factor; - - ufbx_cache_data_format data_format; // < Format of the data in the file - ufbx_cache_data_encoding data_encoding; // < Binary encoding of the data - uint64_t data_offset; // < Byte offset into the file - uint32_t data_count; // < Number of data elements - uint32_t data_element_bytes; // < Size of a single data element in bytes - uint64_t data_total_bytes; // < Size of the whole data blob in bytes -} ufbx_cache_frame; - -UFBX_LIST_TYPE(ufbx_cache_frame_list, ufbx_cache_frame); - -typedef struct ufbx_cache_channel { - - // Name of the geometry cache channel. - ufbx_string name; - - // What does the data in this channel represent. - ufbx_cache_interpretation interpretation; - - // Source name for `interpretation`, especially useful if `interpretation` is - // `UFBX_CACHE_INTERPRETATION_UNKNOWN`. - ufbx_string interpretation_name; - - // List of frames belonging to this channel. - // Sorted by time (`ufbx_cache_frame.time`). - ufbx_cache_frame_list frames; - - // Axis to mirror the frames by. - ufbx_mirror_axis mirror_axis; - - // Factor to scale the geometry by. - ufbx_real scale_factor; - -} ufbx_cache_channel; - -UFBX_LIST_TYPE(ufbx_cache_channel_list, ufbx_cache_channel); - -typedef struct ufbx_geometry_cache { - ufbx_string root_filename; - ufbx_cache_channel_list channels; - ufbx_cache_frame_list frames; - ufbx_string_list extra_info; -} ufbx_geometry_cache; - -struct ufbx_cache_deformer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - ufbx_string channel; - ufbx_nullable ufbx_cache_file *file; - - // Only valid if `ufbx_load_opts.load_external_files` is set! - ufbx_nullable ufbx_geometry_cache *external_cache; - ufbx_nullable ufbx_cache_channel *external_channel; -}; - -struct ufbx_cache_file { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Filename relative to the currently loaded file. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_string filename; - // Absolute filename specified in the file. - ufbx_string absolute_filename; - // Relative filename specified in the file. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_string relative_filename; - - // Filename relative to the loaded file, non-UTF-8 encoded. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_blob raw_filename; - // Absolute filename specified in the file, non-UTF-8 encoded. - ufbx_blob raw_absolute_filename; - // Relative filename specified in the file, non-UTF-8 encoded. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_blob raw_relative_filename; - - ufbx_cache_file_format format; - - // Only valid if `ufbx_load_opts.load_external_files` is set! - ufbx_nullable ufbx_geometry_cache *external_cache; -}; - -// -- Materials - -// Material property, either specified with a constant value or a mapped texture -typedef struct ufbx_material_map { - - // Constant value or factor for the map. - // May be specified simultaneously with a texture, in this case most shading models - // use multiplicative tinting of the texture values. - union { - ufbx_real value_real; - ufbx_vec2 value_vec2; - ufbx_vec3 value_vec3; - ufbx_vec4 value_vec4; - }; - int64_t value_int; - - // Texture if connected, otherwise `NULL`. - // May be valid but "disabled" (application specific) if `texture_enabled == false`. - ufbx_nullable ufbx_texture *texture; - - // `true` if the file has specified any of the values above. - // NOTE: The value may be set to a non-zero default even if `has_value == false`, - // for example missing factors are set to `1.0` if a color is defined. - bool has_value; - - // Controls whether shading should use `texture`. - // NOTE: Some shading models allow this to be `true` even if `texture == NULL`. - bool texture_enabled; - - // Set to `true` if this feature should be disabled (specific to shader type). - bool feature_disabled; - - // Number of components in the value from 1 to 4 if defined, 0 if not. - uint8_t value_components; - -} ufbx_material_map; - -// Material feature -typedef struct ufbx_material_feature_info { - - // Whether the material model uses this feature or not. - // NOTE: The feature can be enabled but still not used if eg. the corresponding factor is at zero! - bool enabled; - - // Explicitly enabled/disabled by the material. - bool is_explicit; - -} ufbx_material_feature_info; - -// Texture attached to an FBX property -typedef struct ufbx_material_texture { - ufbx_string material_prop; // < Name of the property in `ufbx_material.props` - ufbx_string shader_prop; // < Shader-specific property mapping name - - // Texture attached to the property. - ufbx_texture *texture; - -} ufbx_material_texture; - -UFBX_LIST_TYPE(ufbx_material_texture_list, ufbx_material_texture); - -// Shading model type -typedef enum ufbx_shader_type UFBX_ENUM_REPR { - // Unknown shading model - UFBX_SHADER_UNKNOWN, - // FBX builtin diffuse material - UFBX_SHADER_FBX_LAMBERT, - // FBX builtin diffuse+specular material - UFBX_SHADER_FBX_PHONG, - // Open Shading Language standard surface - // https://github.com/Autodesk/standard-surface - UFBX_SHADER_OSL_STANDARD_SURFACE, - // Arnold standard surface - // https://docs.arnoldrenderer.com/display/A5AFMUG/Standard+Surface - UFBX_SHADER_ARNOLD_STANDARD_SURFACE, - // 3ds Max Physical Material - // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2022/ENU/3DSMax-Lighting-Shading/files/GUID-C1328905-7783-4917-AB86-FC3CC19E8972-htm.html - UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL, - // 3ds Max PBR (Metal/Rough) material - // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2021/ENU/3DSMax-Lighting-Shading/files/GUID-A16234A5-6500-4662-8B20-A5EC9FE1B255-htm.html - UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH, - // 3ds Max PBR (Spec/Gloss) material - // https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2021/ENU/3DSMax-Lighting-Shading/files/GUID-18087194-B2A6-43EF-9B80-8FD1736FAE52-htm.html - UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS, - // 3ds glTF Material - // https://help.autodesk.com/view/3DSMAX/2023/ENU/?guid=GUID-7ABFB805-1D9F-417E-9C22-704BFDF160FA - UFBX_SHADER_GLTF_MATERIAL, - // 3ds OpenPBR Material - // https://help.autodesk.com/view/3DSMAX/2025/ENU/?guid=GUID-CD90329C-1E2B-4BBA-9285-3BB46253B9C2 - UFBX_SHADER_OPENPBR_MATERIAL, - // Stingray ShaderFX shader graph. - // Contains a serialized `"ShaderGraph"` in `ufbx_props`. - UFBX_SHADER_SHADERFX_GRAPH, - // Variation of the FBX phong shader that can recover PBR properties like - // `metalness` or `roughness` from the FBX non-physical values. - // NOTE: Enable `ufbx_load_opts.use_blender_pbr_material`. - UFBX_SHADER_BLENDER_PHONG, - // Wavefront .mtl format shader (used by .obj files) - UFBX_SHADER_WAVEFRONT_MTL, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SHADER_TYPE) -} ufbx_shader_type; - -UFBX_ENUM_TYPE(ufbx_shader_type, UFBX_SHADER_TYPE, UFBX_SHADER_WAVEFRONT_MTL); - -// FBX builtin material properties, matches maps in `ufbx_material_fbx_maps` -typedef enum ufbx_material_fbx_map UFBX_ENUM_REPR { - UFBX_MATERIAL_FBX_DIFFUSE_FACTOR, - UFBX_MATERIAL_FBX_DIFFUSE_COLOR, - UFBX_MATERIAL_FBX_SPECULAR_FACTOR, - UFBX_MATERIAL_FBX_SPECULAR_COLOR, - UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, - UFBX_MATERIAL_FBX_REFLECTION_FACTOR, - UFBX_MATERIAL_FBX_REFLECTION_COLOR, - UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, - UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, - UFBX_MATERIAL_FBX_EMISSION_FACTOR, - UFBX_MATERIAL_FBX_EMISSION_COLOR, - UFBX_MATERIAL_FBX_AMBIENT_FACTOR, - UFBX_MATERIAL_FBX_AMBIENT_COLOR, - UFBX_MATERIAL_FBX_NORMAL_MAP, - UFBX_MATERIAL_FBX_BUMP, - UFBX_MATERIAL_FBX_BUMP_FACTOR, - UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR, - UFBX_MATERIAL_FBX_DISPLACEMENT, - UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR, - UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT, - - UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_FBX_MAP) -} ufbx_material_fbx_map; - -UFBX_ENUM_TYPE(ufbx_material_fbx_map, UFBX_MATERIAL_FBX_MAP, UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT); - -// Known PBR material properties, matches maps in `ufbx_material_pbr_maps` -typedef enum ufbx_material_pbr_map UFBX_ENUM_REPR { - UFBX_MATERIAL_PBR_BASE_FACTOR, - UFBX_MATERIAL_PBR_BASE_COLOR, - UFBX_MATERIAL_PBR_ROUGHNESS, - UFBX_MATERIAL_PBR_METALNESS, - UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, - UFBX_MATERIAL_PBR_SPECULAR_FACTOR, - UFBX_MATERIAL_PBR_SPECULAR_COLOR, - UFBX_MATERIAL_PBR_SPECULAR_IOR, - UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, - UFBX_MATERIAL_PBR_SPECULAR_ROTATION, - UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, - UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, - UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, - UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, - UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, - UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, - UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, - UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, - UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY, - UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV, - UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, - UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, - UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, - UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, - UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, - UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR, - UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, - UFBX_MATERIAL_PBR_SHEEN_FACTOR, - UFBX_MATERIAL_PBR_SHEEN_COLOR, - UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, - UFBX_MATERIAL_PBR_COAT_FACTOR, - UFBX_MATERIAL_PBR_COAT_COLOR, - UFBX_MATERIAL_PBR_COAT_ROUGHNESS, - UFBX_MATERIAL_PBR_COAT_IOR, - UFBX_MATERIAL_PBR_COAT_ANISOTROPY, - UFBX_MATERIAL_PBR_COAT_ROTATION, - UFBX_MATERIAL_PBR_COAT_NORMAL, - UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, - UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, - UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, - UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, - UFBX_MATERIAL_PBR_THIN_FILM_IOR, - UFBX_MATERIAL_PBR_EMISSION_FACTOR, - UFBX_MATERIAL_PBR_EMISSION_COLOR, - UFBX_MATERIAL_PBR_OPACITY, - UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE, - UFBX_MATERIAL_PBR_INDIRECT_SPECULAR, - UFBX_MATERIAL_PBR_NORMAL_MAP, - UFBX_MATERIAL_PBR_TANGENT_MAP, - UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, - UFBX_MATERIAL_PBR_MATTE_FACTOR, - UFBX_MATERIAL_PBR_MATTE_COLOR, - UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, - UFBX_MATERIAL_PBR_GLOSSINESS, - UFBX_MATERIAL_PBR_COAT_GLOSSINESS, - UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS, - - UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_PBR_MAP) -} ufbx_material_pbr_map; - -UFBX_ENUM_TYPE(ufbx_material_pbr_map, UFBX_MATERIAL_PBR_MAP, UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS); - -// Known material features -typedef enum ufbx_material_feature UFBX_ENUM_REPR { - UFBX_MATERIAL_FEATURE_PBR, - UFBX_MATERIAL_FEATURE_METALNESS, - UFBX_MATERIAL_FEATURE_DIFFUSE, - UFBX_MATERIAL_FEATURE_SPECULAR, - UFBX_MATERIAL_FEATURE_EMISSION, - UFBX_MATERIAL_FEATURE_TRANSMISSION, - UFBX_MATERIAL_FEATURE_COAT, - UFBX_MATERIAL_FEATURE_SHEEN, - UFBX_MATERIAL_FEATURE_OPACITY, - UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION, - UFBX_MATERIAL_FEATURE_MATTE, - UFBX_MATERIAL_FEATURE_UNLIT, - UFBX_MATERIAL_FEATURE_IOR, - UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, - UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, - UFBX_MATERIAL_FEATURE_THIN_WALLED, - UFBX_MATERIAL_FEATURE_CAUSTICS, - UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, - UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, - UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, - UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, - UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, - UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, - - UFBX_ENUM_FORCE_WIDTH(UFBX_MATERIAL_FEATURE) -} ufbx_material_feature; - -UFBX_ENUM_TYPE(ufbx_material_feature, UFBX_MATERIAL_FEATURE, UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS); - -typedef struct ufbx_material_fbx_maps { - union { - ufbx_material_map maps[UFBX_MATERIAL_FBX_MAP_COUNT]; - struct { - ufbx_material_map diffuse_factor; - ufbx_material_map diffuse_color; - ufbx_material_map specular_factor; - ufbx_material_map specular_color; - ufbx_material_map specular_exponent; - ufbx_material_map reflection_factor; - ufbx_material_map reflection_color; - ufbx_material_map transparency_factor; - ufbx_material_map transparency_color; - ufbx_material_map emission_factor; - ufbx_material_map emission_color; - ufbx_material_map ambient_factor; - ufbx_material_map ambient_color; - ufbx_material_map normal_map; - ufbx_material_map bump; - ufbx_material_map bump_factor; - ufbx_material_map displacement_factor; - ufbx_material_map displacement; - ufbx_material_map vector_displacement_factor; - ufbx_material_map vector_displacement; - }; - }; -} ufbx_material_fbx_maps; - -typedef struct ufbx_material_pbr_maps { - union { - ufbx_material_map maps[UFBX_MATERIAL_PBR_MAP_COUNT]; - struct { - ufbx_material_map base_factor; - ufbx_material_map base_color; - ufbx_material_map roughness; - ufbx_material_map metalness; - ufbx_material_map diffuse_roughness; - ufbx_material_map specular_factor; - ufbx_material_map specular_color; - ufbx_material_map specular_ior; - ufbx_material_map specular_anisotropy; - ufbx_material_map specular_rotation; - ufbx_material_map transmission_factor; - ufbx_material_map transmission_color; - ufbx_material_map transmission_depth; - ufbx_material_map transmission_scatter; - ufbx_material_map transmission_scatter_anisotropy; - ufbx_material_map transmission_dispersion; - ufbx_material_map transmission_roughness; - ufbx_material_map transmission_extra_roughness; - ufbx_material_map transmission_priority; - ufbx_material_map transmission_enable_in_aov; - ufbx_material_map subsurface_factor; - ufbx_material_map subsurface_color; - ufbx_material_map subsurface_radius; - ufbx_material_map subsurface_scale; - ufbx_material_map subsurface_anisotropy; - ufbx_material_map subsurface_tint_color; - ufbx_material_map subsurface_type; - ufbx_material_map sheen_factor; - ufbx_material_map sheen_color; - ufbx_material_map sheen_roughness; - ufbx_material_map coat_factor; - ufbx_material_map coat_color; - ufbx_material_map coat_roughness; - ufbx_material_map coat_ior; - ufbx_material_map coat_anisotropy; - ufbx_material_map coat_rotation; - ufbx_material_map coat_normal; - ufbx_material_map coat_affect_base_color; - ufbx_material_map coat_affect_base_roughness; - ufbx_material_map thin_film_factor; - ufbx_material_map thin_film_thickness; - ufbx_material_map thin_film_ior; - ufbx_material_map emission_factor; - ufbx_material_map emission_color; - ufbx_material_map opacity; - ufbx_material_map indirect_diffuse; - ufbx_material_map indirect_specular; - ufbx_material_map normal_map; - ufbx_material_map tangent_map; - ufbx_material_map displacement_map; - ufbx_material_map matte_factor; - ufbx_material_map matte_color; - ufbx_material_map ambient_occlusion; - ufbx_material_map glossiness; - ufbx_material_map coat_glossiness; - ufbx_material_map transmission_glossiness; - }; - }; -} ufbx_material_pbr_maps; - -typedef struct ufbx_material_features { - union { - ufbx_material_feature_info features[UFBX_MATERIAL_FEATURE_COUNT]; - struct { - ufbx_material_feature_info pbr; - ufbx_material_feature_info metalness; - ufbx_material_feature_info diffuse; - ufbx_material_feature_info specular; - ufbx_material_feature_info emission; - ufbx_material_feature_info transmission; - ufbx_material_feature_info coat; - ufbx_material_feature_info sheen; - ufbx_material_feature_info opacity; - ufbx_material_feature_info ambient_occlusion; - ufbx_material_feature_info matte; - ufbx_material_feature_info unlit; - ufbx_material_feature_info ior; - ufbx_material_feature_info diffuse_roughness; - ufbx_material_feature_info transmission_roughness; - ufbx_material_feature_info thin_walled; - ufbx_material_feature_info caustics; - ufbx_material_feature_info exit_to_background; - ufbx_material_feature_info internal_reflections; - ufbx_material_feature_info double_sided; - ufbx_material_feature_info roughness_as_glossiness; - ufbx_material_feature_info coat_roughness_as_glossiness; - ufbx_material_feature_info transmission_roughness_as_glossiness; - }; - }; -} ufbx_material_features; - -// Surface material properties such as color, roughness, etc. Each property may -// be optionally bound to an `ufbx_texture`. -struct ufbx_material { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // FBX builtin properties - // NOTE: These may be empty if the material is using a custom shader - ufbx_material_fbx_maps fbx; - - // PBR material properties, defined for all shading models but may be - // somewhat approximate if `shader == NULL`. - ufbx_material_pbr_maps pbr; - - // Material features, primarily applies to `pbr`. - ufbx_material_features features; - - // Shading information - ufbx_shader_type shader_type; // < Always defined - ufbx_nullable ufbx_shader *shader; // < Optional extended shader information - ufbx_string shading_model_name; // < Often one of `{ "lambert", "phong", "unknown" }` - - // Prefix before shader property names with trailing `|`. - // For example `"3dsMax|Parameters|"` where properties would have names like - // `"3dsMax|Parameters|base_color"`. You can ignore this if you use the built-in - // `ufbx_material_fbx_maps fbx` and `ufbx_material_pbr_maps pbr` structures. - ufbx_string shader_prop_prefix; - - // All textures attached to the material, if you want specific maps if might be - // more convenient to use eg. `fbx.diffuse_color.texture` or `pbr.base_color.texture` - ufbx_material_texture_list textures; // < Sorted by `material_prop` -}; - -typedef enum ufbx_texture_type UFBX_ENUM_REPR { - - // Texture associated with an image file/sequence. `texture->filename` and - // and `texture->relative_filename` contain the texture's path. If the file - // has embedded content `texture->content` may hold `texture->content_size` - // bytes of raw image data. - UFBX_TEXTURE_FILE, - - // The texture consists of multiple texture layers blended together. - UFBX_TEXTURE_LAYERED, - - // Reserved as these _should_ exist in FBX files. - UFBX_TEXTURE_PROCEDURAL, - - // Node in a shader graph. - // Use `ufbx_texture.shader` for more information. - UFBX_TEXTURE_SHADER, - - UFBX_ENUM_FORCE_WIDTH(UFBX_TEXTURE_TYPE) -} ufbx_texture_type; - -UFBX_ENUM_TYPE(ufbx_texture_type, UFBX_TEXTURE_TYPE, UFBX_TEXTURE_SHADER); - -// Blend modes to combine layered textures with, compatible with common blend -// mode definitions in many art programs. Simpler blend modes have equations -// specified below where `src` is the layer to composite over `dst`. -// See eg. https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendingseparable -typedef enum ufbx_blend_mode UFBX_ENUM_REPR { - UFBX_BLEND_TRANSLUCENT, // < `src` effects result alpha - UFBX_BLEND_ADDITIVE, // < `src + dst` - UFBX_BLEND_MULTIPLY, // < `src * dst` - UFBX_BLEND_MULTIPLY_2X, // < `2 * src * dst` - UFBX_BLEND_OVER, // < `src * src_alpha + dst * (1-src_alpha)` - UFBX_BLEND_REPLACE, // < `src` Replace the contents - UFBX_BLEND_DISSOLVE, // < `random() + src_alpha >= 1.0 ? src : dst` - UFBX_BLEND_DARKEN, // < `min(src, dst)` - UFBX_BLEND_COLOR_BURN, // < `src > 0 ? 1 - min(1, (1-dst) / src) : 0` - UFBX_BLEND_LINEAR_BURN, // < `src + dst - 1` - UFBX_BLEND_DARKER_COLOR, // < `value(src) < value(dst) ? src : dst` - UFBX_BLEND_LIGHTEN, // < `max(src, dst)` - UFBX_BLEND_SCREEN, // < `1 - (1-src)*(1-dst)` - UFBX_BLEND_COLOR_DODGE, // < `src < 1 ? dst / (1 - src)` : (dst>0?1:0)` - UFBX_BLEND_LINEAR_DODGE, // < `src + dst` - UFBX_BLEND_LIGHTER_COLOR, // < `value(src) > value(dst) ? src : dst` - UFBX_BLEND_SOFT_LIGHT, // < https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendingsoftlight - UFBX_BLEND_HARD_LIGHT, // < https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendinghardlight - UFBX_BLEND_VIVID_LIGHT, // < Combination of `COLOR_DODGE` and `COLOR_BURN` - UFBX_BLEND_LINEAR_LIGHT, // < Combination of `LINEAR_DODGE` and `LINEAR_BURN` - UFBX_BLEND_PIN_LIGHT, // < Combination of `DARKEN` and `LIGHTEN` - UFBX_BLEND_HARD_MIX, // < Produces primary colors depending on similarity - UFBX_BLEND_DIFFERENCE, // < `abs(src - dst)` - UFBX_BLEND_EXCLUSION, // < `dst + src - 2 * src * dst` - UFBX_BLEND_SUBTRACT, // < `dst - src` - UFBX_BLEND_DIVIDE, // < `dst / src` - UFBX_BLEND_HUE, // < Replace hue - UFBX_BLEND_SATURATION, // < Replace saturation - UFBX_BLEND_COLOR, // < Replace hue and saturatio - UFBX_BLEND_LUMINOSITY, // < Replace value - UFBX_BLEND_OVERLAY, // < Same as `HARD_LIGHT` but with `src` and `dst` swapped - - UFBX_ENUM_FORCE_WIDTH(UFBX_BLEND_MODE) -} ufbx_blend_mode; - -UFBX_ENUM_TYPE(ufbx_blend_mode, UFBX_BLEND_MODE, UFBX_BLEND_OVERLAY); - -// Blend modes to combine layered textures with, compatible with common blend -typedef enum ufbx_wrap_mode UFBX_ENUM_REPR { - UFBX_WRAP_REPEAT, // < Repeat the texture past the [0,1] range - UFBX_WRAP_CLAMP, // < Clamp the normalized texture coordinates to [0,1] - - UFBX_ENUM_FORCE_WIDTH(UFBX_WRAP_MODE) -} ufbx_wrap_mode; - -UFBX_ENUM_TYPE(ufbx_wrap_mode, UFBX_WRAP_MODE, UFBX_WRAP_CLAMP); - -// Single layer in a layered texture -typedef struct ufbx_texture_layer { - ufbx_texture *texture; // < The inner texture to evaluate, never `NULL` - ufbx_blend_mode blend_mode; // < Equation to combine the layer to the background - ufbx_real alpha; // < Blend weight of this layer -} ufbx_texture_layer; - -UFBX_LIST_TYPE(ufbx_texture_layer_list, ufbx_texture_layer); - -typedef enum ufbx_shader_texture_type UFBX_ENUM_REPR { - UFBX_SHADER_TEXTURE_UNKNOWN, - - // Select an output of a multi-output shader. - // HINT: If this type is used the `ufbx_shader_texture.main_texture` and - // `ufbx_shader_texture.main_texture_output_index` fields are set. - UFBX_SHADER_TEXTURE_SELECT_OUTPUT, - - // Open Shading Language (OSL) shader. - // https://github.com/AcademySoftwareFoundation/OpenShadingLanguage - UFBX_SHADER_TEXTURE_OSL, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SHADER_TEXTURE_TYPE) -} ufbx_shader_texture_type; - -UFBX_ENUM_TYPE(ufbx_shader_texture_type, UFBX_SHADER_TEXTURE_TYPE, UFBX_SHADER_TEXTURE_OSL); - -// Input to a shader texture, see `ufbx_shader_texture`. -typedef struct ufbx_shader_texture_input { - - // Name of the input. - ufbx_string name; - - // Constant value of the input. - union { - ufbx_real value_real; - ufbx_vec2 value_vec2; - ufbx_vec3 value_vec3; - ufbx_vec4 value_vec4; - }; - int64_t value_int; - ufbx_string value_str; - ufbx_blob value_blob; - - // Texture connected to this input. - ufbx_nullable ufbx_texture *texture; - - // Index of the output to use if `texture` is a multi-output shader node. - int64_t texture_output_index; - - // Controls whether shading should use `texture`. - // NOTE: Some shading models allow this to be `true` even if `texture == NULL`. - bool texture_enabled; - - // Property representing this input. - ufbx_prop *prop; - - // Property representing `texture`. - ufbx_nullable ufbx_prop *texture_prop; - - // Property representing `texture_enabled`. - ufbx_nullable ufbx_prop *texture_enabled_prop; - -} ufbx_shader_texture_input; - -UFBX_LIST_TYPE(ufbx_shader_texture_input_list, ufbx_shader_texture_input); - -// Texture that emulates a shader graph node. -// 3ds Max exports some materials as node graphs serialized to textures. -// ufbx can parse a small subset of these, as normal maps are often hidden behind -// some kind of bump node. -// NOTE: These encode a lot of details of 3ds Max internals, not recommended for direct use. -// HINT: `ufbx_texture.file_textures[]` contains a list of "real" textures that are connected -// to the `ufbx_texture` that is pretending to be a shader node. -typedef struct ufbx_shader_texture { - - // Type of this shader node. - ufbx_shader_texture_type type; - - // Name of the shader to use. - ufbx_string shader_name; - - // 64-bit opaque identifier for the shader type. - uint64_t shader_type_id; - - // Input values/textures (possibly further shader textures) to the shader. - // Sorted by `ufbx_shader_texture_input.name`. - ufbx_shader_texture_input_list inputs; - - // Shader source code if found. - ufbx_string shader_source; - ufbx_blob raw_shader_source; - - // Representative texture for this shader. - // Only specified if `main_texture.outputs[main_texture_output_index]` is semantically - // equivalent to this texture. - ufbx_texture *main_texture; - - // Output index of `main_texture` if it is a multi-output shader. - int64_t main_texture_output_index; - - // Prefix for properties related to this shader in `ufbx_texture`. - // NOTE: Contains the trailing '|' if not empty. - ufbx_string prop_prefix; - -} ufbx_shader_texture; - -// Unique texture within the file. -typedef struct ufbx_texture_file { - - // Index in `ufbx_scene.texture_files[]`. - uint32_t index; - - // Paths to the resource. - - // Filename relative to the currently loaded file. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_string filename; - // Absolute filename specified in the file. - ufbx_string absolute_filename; - // Relative filename specified in the file. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_string relative_filename; - - // Filename relative to the loaded file, non-UTF-8 encoded. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_blob raw_filename; - // Absolute filename specified in the file, non-UTF-8 encoded. - ufbx_blob raw_absolute_filename; - // Relative filename specified in the file, non-UTF-8 encoded. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_blob raw_relative_filename; - - // Optional embedded content blob, eg. raw .png format data - ufbx_blob content; - -} ufbx_texture_file; - -UFBX_LIST_TYPE(ufbx_texture_file_list, ufbx_texture_file); - -// Texture that controls material appearance -struct ufbx_texture { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Texture type (file / layered / procedural / shader) - ufbx_texture_type type; - - // FILE: Paths to the resource - - // Filename relative to the currently loaded file. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_string filename; - // Absolute filename specified in the file. - ufbx_string absolute_filename; - // Relative filename specified in the file. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_string relative_filename; - - // Filename relative to the loaded file, non-UTF-8 encoded. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_blob raw_filename; - // Absolute filename specified in the file, non-UTF-8 encoded. - ufbx_blob raw_absolute_filename; - // Relative filename specified in the file, non-UTF-8 encoded. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_blob raw_relative_filename; - - // FILE: Optional embedded content blob, eg. raw .png format data - ufbx_blob content; - - // FILE: Optional video texture - ufbx_nullable ufbx_video *video; - - // FILE: Index into `ufbx_scene.texture_files[]` or `UFBX_NO_INDEX`. - uint32_t file_index; - - // FILE: True if `file_index` has a valid value. - bool has_file; - - // LAYERED: Inner texture layers, ordered from _bottom_ to _top_ - ufbx_texture_layer_list layers; - - // SHADER: Shader information - // NOTE: May be specified even if `type == UFBX_TEXTURE_FILE` if `ufbx_load_opts.disable_quirks` - // is _not_ specified. Some known shaders that represent files are interpreted as `UFBX_TEXTURE_FILE`. - ufbx_nullable ufbx_shader_texture *shader; - - // List of file textures representing this texture. - // Defined even if `type == UFBX_TEXTURE_FILE` in which case the array contains only itself. - ufbx_texture_list file_textures; - - // Name of the UV set to use - ufbx_string uv_set; - - // Wrapping mode - ufbx_wrap_mode wrap_u; - ufbx_wrap_mode wrap_v; - - // UV transform - bool has_uv_transform; // < Has a non-identity `transform` and derived matrices. - ufbx_transform uv_transform; // < Texture transformation in UV space - ufbx_matrix texture_to_uv; // < Matrix representation of `transform` - ufbx_matrix uv_to_texture; // < UV coordinate to normalized texture coordinate matrix -}; - -// TODO: Video textures -struct ufbx_video { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Paths to the resource - - // Filename relative to the currently loaded file. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_string filename; - // Absolute filename specified in the file. - ufbx_string absolute_filename; - // Relative filename specified in the file. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_string relative_filename; - - // Filename relative to the loaded file, non-UTF-8 encoded. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_blob raw_filename; - // Absolute filename specified in the file, non-UTF-8 encoded. - ufbx_blob raw_absolute_filename; - // Relative filename specified in the file, non-UTF-8 encoded. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_blob raw_relative_filename; - - // Optional embedded content blob - ufbx_blob content; -}; - -// Shader specifies a shading model and contains `ufbx_shader_binding` elements -// that define how to interpret FBX properties in the shader. -struct ufbx_shader { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Known shading model - ufbx_shader_type type; - - // TODO: Expose actual properties here - - // Bindings from FBX properties to the shader - // HINT: `ufbx_find_shader_prop()` translates shader properties to FBX properties - ufbx_shader_binding_list bindings; -}; - -// Binding from a material property to shader implementation -typedef struct ufbx_shader_prop_binding { - ufbx_string shader_prop; // < Property name used by the shader implementation - ufbx_string material_prop; // < Property name inside `ufbx_material.props` -} ufbx_shader_prop_binding; - -UFBX_LIST_TYPE(ufbx_shader_prop_binding_list, ufbx_shader_prop_binding); - -// Shader binding table -struct ufbx_shader_binding { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - ufbx_shader_prop_binding_list prop_bindings; // < Sorted by `shader_prop` -}; - -// -- Animation - -typedef struct ufbx_prop_override { - uint32_t element_id; - - uint32_t _internal_key; - - ufbx_string prop_name; - ufbx_vec4 value; - ufbx_string value_str; - int64_t value_int; -} ufbx_prop_override; - -UFBX_LIST_TYPE(ufbx_prop_override_list, ufbx_prop_override); - -typedef struct ufbx_transform_override { - uint32_t node_id; - ufbx_transform transform; -} ufbx_transform_override; - -UFBX_LIST_TYPE(ufbx_transform_override_list, ufbx_transform_override); - -// Animation descriptor used for evaluating animation. -// Usually obtained from `ufbx_scene` via either global animation `ufbx_scene.anim`, -// per-stack animation `ufbx_anim_stack.anim` or per-layer animation `ufbx_anim_layer.anim`. -// -// For advanced usage you can use `ufbx_create_anim()` to create animation descriptors -// with custom layers, property overrides, special flags, etc. -typedef struct ufbx_anim { - - // Time begin/end for the animation, both may be zero if absent. - double time_begin; - double time_end; - - // List of layers in the animation. - ufbx_anim_layer_list layers; - - // Optional overrides for weights for each layer in `layers[]`. - ufbx_real_list override_layer_weights; - - // Sorted by `element_id, prop_name` - ufbx_prop_override_list prop_overrides; - - // Sorted by `node_id` - ufbx_transform_override_list transform_overrides; - - // Evaluate connected properties as if they would not be connected. - bool ignore_connections; - - // Custom `ufbx_anim` created by `ufbx_create_anim()`. - bool custom; - -} ufbx_anim; - -struct ufbx_anim_stack { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - double time_begin; - double time_end; - - ufbx_anim_layer_list layers; - ufbx_anim *anim; -}; - -typedef struct ufbx_anim_prop { - ufbx_element *element; - - uint32_t _internal_key; - - ufbx_string prop_name; - ufbx_anim_value *anim_value; -} ufbx_anim_prop; - -UFBX_LIST_TYPE(ufbx_anim_prop_list, ufbx_anim_prop); - -struct ufbx_anim_layer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - ufbx_real weight; - bool weight_is_animated; - bool blended; - bool additive; - bool compose_rotation; - bool compose_scale; - - ufbx_anim_value_list anim_values; - ufbx_anim_prop_list anim_props; // < Sorted by `element,prop_name` - - ufbx_anim *anim; - - uint32_t _min_element_id; - uint32_t _max_element_id; - uint32_t _element_id_bitmask[4]; -}; - -struct ufbx_anim_value { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - ufbx_vec3 default_value; - ufbx_nullable ufbx_anim_curve *curves[3]; -}; - -// Animation curve segment interpolation mode between two keyframes -typedef enum ufbx_interpolation UFBX_ENUM_REPR { - UFBX_INTERPOLATION_CONSTANT_PREV, // < Hold previous key value - UFBX_INTERPOLATION_CONSTANT_NEXT, // < Hold next key value - UFBX_INTERPOLATION_LINEAR, // < Linear interpolation between two keys - UFBX_INTERPOLATION_CUBIC, // < Cubic interpolation, see `ufbx_tangent` - - UFBX_ENUM_FORCE_WIDTH(UFBX_INTERPOLATION) -} ufbx_interpolation; - -UFBX_ENUM_TYPE(ufbx_interpolation, UFBX_INTERPOLATION, UFBX_INTERPOLATION_CUBIC); - -typedef enum ufbx_extrapolation_mode UFBX_ENUM_REPR { - UFBX_EXTRAPOLATION_CONSTANT, // < Use the value of the first/last keyframe - UFBX_EXTRAPOLATION_REPEAT, // < Repeat the whole animation curve - UFBX_EXTRAPOLATION_MIRROR, // < Repeat with mirroring - UFBX_EXTRAPOLATION_SLOPE, // < Use the tangent of the last keyframe to linearly extrapolate - UFBX_EXTRAPOLATION_REPEAT_RELATIVE, // < Repeat the animation curve but connect the first and last keyframe values - - UFBX_ENUM_FORCE_WIDTH(UFBX_EXTRAPOLATION) -} ufbx_extrapolation_mode; - -UFBX_ENUM_TYPE(ufbx_extrapolation_mode, UFBX_EXTRAPOLATION_MODE, UFBX_EXTRAPOLATION_REPEAT_RELATIVE); - -typedef struct ufbx_extrapolation { - ufbx_extrapolation_mode mode; - - // Count used for repeating modes. - // Negative values mean infinite repetition. - int32_t repeat_count; -} ufbx_extrapolation; - -// Tangent vector at a keyframe, may be split into left/right -typedef struct ufbx_tangent { - float dx; // < Derivative in the time axis - float dy; // < Derivative in the (curve specific) value axis -} ufbx_tangent; - -// Single real `value` at a specified `time`, interpolation between two keyframes -// is determined by the `interpolation` field of the _previous_ key. -// If `interpolation == UFBX_INTERPOLATION_CUBIC` the span is evaluated as a -// cubic bezier curve through the following points: -// -// (prev->time, prev->value) -// (prev->time + prev->right.dx, prev->value + prev->right.dy) -// (next->time - next->left.dx, next->value - next->left.dy) -// (next->time, next->value) -// -// HINT: You can use `ufbx_evaluate_curve(ufbx_anim_curve *curve, double time)` -// rather than trying to manually handle all the interpolation modes. -typedef struct ufbx_keyframe { - double time; - ufbx_real value; - ufbx_interpolation interpolation; - ufbx_tangent left; - ufbx_tangent right; -} ufbx_keyframe; - -UFBX_LIST_TYPE(ufbx_keyframe_list, ufbx_keyframe); - -struct ufbx_anim_curve { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // List of keyframes that define the curve. - ufbx_keyframe_list keyframes; - - // Extrapolation before the curve. - ufbx_extrapolation pre_extrapolation; - // Extrapolation after the curve. - ufbx_extrapolation post_extrapolation; - - // Value range for all the keyframes. - ufbx_real min_value; - ufbx_real max_value; - - // Time range for all the keyframes. - double min_time; - double max_time; -}; - -// -- Collections - -// Collection of nodes to hide/freeze -struct ufbx_display_layer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Nodes included in the layer (exclusively at most one layer per node) - ufbx_node_list nodes; - - // Layer state - bool visible; // < Contained nodes are visible - bool frozen; // < Contained nodes cannot be edited - - ufbx_vec3 ui_color; // < Visual color for UI -}; - -// Named set of nodes/geometry features to select. -struct ufbx_selection_set { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Included nodes and geometry features - ufbx_selection_node_list nodes; -}; - -// Selection state of a node, potentially contains vertex/edge/face selection as well. -struct ufbx_selection_node { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Selection targets, possibly `NULL` - ufbx_nullable ufbx_node *target_node; - ufbx_nullable ufbx_mesh *target_mesh; - bool include_node; // < Is `target_node` included in the selection - - // Indices to selected components. - // Guaranteed to be valid as per `ufbx_load_opts.index_error_handling` - // if `target_mesh` is not `NULL`. - ufbx_uint32_list vertices; // < Indices to `ufbx_mesh.vertices` - ufbx_uint32_list edges; // < Indices to `ufbx_mesh.edges` - ufbx_uint32_list faces; // < Indices to `ufbx_mesh.faces` -}; - -// -- Constraints - -struct ufbx_character { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; -}; - -// Type of property constrain eg. position or look-at -typedef enum ufbx_constraint_type UFBX_ENUM_REPR { - UFBX_CONSTRAINT_UNKNOWN, - UFBX_CONSTRAINT_AIM, - UFBX_CONSTRAINT_PARENT, - UFBX_CONSTRAINT_POSITION, - UFBX_CONSTRAINT_ROTATION, - UFBX_CONSTRAINT_SCALE, - // Inverse kinematic chain to a single effector `ufbx_constraint.ik_effector` - // `targets` optionally contains a list of pole targets! - UFBX_CONSTRAINT_SINGLE_CHAIN_IK, - - UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_TYPE) -} ufbx_constraint_type; - -UFBX_ENUM_TYPE(ufbx_constraint_type, UFBX_CONSTRAINT_TYPE, UFBX_CONSTRAINT_SINGLE_CHAIN_IK); - -// Target to follow with a constraint -typedef struct ufbx_constraint_target { - ufbx_node *node; // < Target node reference - ufbx_real weight; // < Relative weight to other targets (does not always sum to 1) - ufbx_transform transform; // < Offset from the actual target -} ufbx_constraint_target; - -UFBX_LIST_TYPE(ufbx_constraint_target_list, ufbx_constraint_target); - -// Method to determine the up vector in aim constraints -typedef enum ufbx_constraint_aim_up_type UFBX_ENUM_REPR { - UFBX_CONSTRAINT_AIM_UP_SCENE, // < Align the up vector to the scene global up vector - UFBX_CONSTRAINT_AIM_UP_TO_NODE, // < Aim the up vector at `ufbx_constraint.aim_up_node` - UFBX_CONSTRAINT_AIM_UP_ALIGN_NODE, // < Copy the up vector from `ufbx_constraint.aim_up_node` - UFBX_CONSTRAINT_AIM_UP_VECTOR, // < Use `ufbx_constraint.aim_up_vector` as the up vector - UFBX_CONSTRAINT_AIM_UP_NONE, // < Don't align the up vector to anything - - UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_AIM_UP_TYPE) -} ufbx_constraint_aim_up_type; - -UFBX_ENUM_TYPE(ufbx_constraint_aim_up_type, UFBX_CONSTRAINT_AIM_UP_TYPE, UFBX_CONSTRAINT_AIM_UP_NONE); - -// Method to determine the up vector in aim constraints -typedef enum ufbx_constraint_ik_pole_type UFBX_ENUM_REPR { - UFBX_CONSTRAINT_IK_POLE_VECTOR, // < Use towards calculated from `ufbx_constraint.targets` - UFBX_CONSTRAINT_IK_POLE_NODE, // < Use `ufbx_constraint.ik_pole_vector` directly - - UFBX_ENUM_FORCE_WIDTH(UFBX_CONSTRAINT_IK_POLE_TYPE) -} ufbx_constraint_ik_pole_type; - -UFBX_ENUM_TYPE(ufbx_constraint_ik_pole_type, UFBX_CONSTRAINT_IK_POLE_TYPE, UFBX_CONSTRAINT_IK_POLE_NODE); - -struct ufbx_constraint { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Type of constraint to use - ufbx_constraint_type type; - ufbx_string type_name; - - // Node to be constrained - ufbx_nullable ufbx_node *node; - - // List of weighted targets for the constraint (pole vectors for IK) - ufbx_constraint_target_list targets; - - // State of the constraint - ufbx_real weight; - bool active; - - // Translation/rotation/scale axes the constraint is applied to - bool constrain_translation[3]; - bool constrain_rotation[3]; - bool constrain_scale[3]; - - // Offset from the constrained position - ufbx_transform transform_offset; - - // AIM: Target and up vectors - ufbx_vec3 aim_vector; - ufbx_constraint_aim_up_type aim_up_type; - ufbx_nullable ufbx_node *aim_up_node; - ufbx_vec3 aim_up_vector; - - // SINGLE_CHAIN_IK: Target for the IK, `targets` contains pole vectors! - ufbx_nullable ufbx_node *ik_effector; - ufbx_nullable ufbx_node *ik_end_node; - ufbx_vec3 ik_pole_vector; -}; - -// -- Audio - -struct ufbx_audio_layer { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Clips contained in this layer. - ufbx_audio_clip_list clips; -}; - -struct ufbx_audio_clip { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Filename relative to the currently loaded file. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_string filename; - // Absolute filename specified in the file. - ufbx_string absolute_filename; - // Relative filename specified in the file. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_string relative_filename; - - // Filename relative to the loaded file, non-UTF-8 encoded. - // HINT: If using functions other than `ufbx_load_file()`, you can provide - // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. - ufbx_blob raw_filename; - // Absolute filename specified in the file, non-UTF-8 encoded. - ufbx_blob raw_absolute_filename; - // Relative filename specified in the file, non-UTF-8 encoded. - // NOTE: May be absolute if the file is saved in a different drive. - ufbx_blob raw_relative_filename; - - // Optional embedded content blob, eg. raw .png format data - ufbx_blob content; -}; - -// -- Miscellaneous - -typedef struct ufbx_bone_pose { - - // Node to apply the pose to. - ufbx_node *bone_node; - - // Matrix from node local space to world space. - ufbx_matrix bone_to_world; - - // Matrix from node local space to parent space. - // NOTE: FBX only stores world transformations so this is approximated from - // the parent world transform. - ufbx_matrix bone_to_parent; - -} ufbx_bone_pose; - -UFBX_LIST_TYPE(ufbx_bone_pose_list, ufbx_bone_pose); - -struct ufbx_pose { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; - - // Set if this pose is marked as a bind pose. - bool is_bind_pose; - - // List of bone poses. - // Sorted by `ufbx_node.typed_id`. - ufbx_bone_pose_list bone_poses; -}; - -struct ufbx_metadata_object { - union { ufbx_element element; struct { - ufbx_string name; - ufbx_props props; - uint32_t element_id; - uint32_t typed_id; - }; }; -}; - -// -- Named elements - -typedef struct ufbx_name_element { - ufbx_string name; - ufbx_element_type type; - - uint32_t _internal_key; - - ufbx_element *element; -} ufbx_name_element; - -UFBX_LIST_TYPE(ufbx_name_element_list, ufbx_name_element); - -// -- Scene - -// Scene is the root object loaded by ufbx that everything is accessed from. - -typedef enum ufbx_exporter UFBX_ENUM_REPR { - UFBX_EXPORTER_UNKNOWN, - UFBX_EXPORTER_FBX_SDK, - UFBX_EXPORTER_BLENDER_BINARY, - UFBX_EXPORTER_BLENDER_ASCII, - UFBX_EXPORTER_MOTION_BUILDER, - - UFBX_ENUM_FORCE_WIDTH(UFBX_EXPORTER) -} ufbx_exporter; - -UFBX_ENUM_TYPE(ufbx_exporter, UFBX_EXPORTER, UFBX_EXPORTER_MOTION_BUILDER); - -typedef struct ufbx_application { - ufbx_string vendor; - ufbx_string name; - ufbx_string version; -} ufbx_application; - -typedef enum ufbx_file_format UFBX_ENUM_REPR { - UFBX_FILE_FORMAT_UNKNOWN, // < Unknown file format - UFBX_FILE_FORMAT_FBX, // < .fbx Kaydara/Autodesk FBX file - UFBX_FILE_FORMAT_OBJ, // < .obj Wavefront OBJ file - UFBX_FILE_FORMAT_MTL, // < .mtl Wavefront MTL (Material template library) file - - UFBX_ENUM_FORCE_WIDTH(UFBX_FILE_FORMAT) -} ufbx_file_format; - -UFBX_ENUM_TYPE(ufbx_file_format, UFBX_FILE_FORMAT, UFBX_FILE_FORMAT_MTL); - -typedef enum ufbx_warning_type UFBX_ENUM_REPR { - // Missing external file file (for example .mtl for Wavefront .obj file or a - // geometry cache) - UFBX_WARNING_MISSING_EXTERNAL_FILE, - - // Loaded a Wavefront .mtl file derived from the filename instead of a proper - // `mtllib` statement. - UFBX_WARNING_IMPLICIT_MTL, - - // Truncated array has been auto-expanded. - UFBX_WARNING_TRUNCATED_ARRAY, - - // Geometry data has been defined but has no data. - UFBX_WARNING_MISSING_GEOMETRY_DATA, - - // Duplicated connection between two elements that shouldn't have. - UFBX_WARNING_DUPLICATE_CONNECTION, - - // Vertex 'W' attribute length differs from main attribute. - UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, - - // Missing polygon mapping type. - UFBX_WARNING_MISSING_POLYGON_MAPPING, - - // Unsupported version, loaded but may be incorrect. - // If the loading fails `UFBX_ERROR_UNSUPPORTED_VERSION` is issued instead. - UFBX_WARNING_UNSUPPORTED_VERSION, - - // Out-of-bounds index has been clamped to be in-bounds. - // HINT: You can use `ufbx_index_error_handling` to adjust behavior. - UFBX_WARNING_INDEX_CLAMPED, - - // Non-UTF8 encoded strings. - // HINT: You can use `ufbx_unicode_error_handling` to adjust behavior. - UFBX_WARNING_BAD_UNICODE, - - // Invalid base64-encoded embedded content ignored. - UFBX_WARNING_BAD_BASE64_CONTENT, - - // Non-node element connected to root. - UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, - - // Duplicated object ID in the file, connections will be wrong. - UFBX_WARNING_DUPLICATE_OBJECT_ID, - - // Empty face has been removed. - // Use `ufbx_load_opts.allow_empty_faces` if you want to allow them. - UFBX_WARNING_EMPTY_FACE_REMOVED, - - // Unknown .obj file directive. - UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, - - // Warnings after this one are deduplicated. - // See `ufbx_warning.count` for how many times they happened. - UFBX_WARNING_TYPE_FIRST_DEDUPLICATED = UFBX_WARNING_INDEX_CLAMPED, - - UFBX_ENUM_FORCE_WIDTH(UFBX_WARNING_TYPE) -} ufbx_warning_type; - -UFBX_ENUM_TYPE(ufbx_warning_type, UFBX_WARNING_TYPE, UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE); - -// Warning about a non-fatal issue in the file. -// Often contains information about issues that ufbx has corrected about the -// file but it might indicate something is not working properly. -typedef struct ufbx_warning { - // Type of the warning. - ufbx_warning_type type; - // Description of the warning. - ufbx_string description; - // The element related to this warning or `UFBX_NO_INDEX` if not related to a specific element. - uint32_t element_id; - // Number of times this warning was encountered. - size_t count; -} ufbx_warning; - -UFBX_LIST_TYPE(ufbx_warning_list, ufbx_warning); - -typedef enum ufbx_thumbnail_format UFBX_ENUM_REPR { - UFBX_THUMBNAIL_FORMAT_UNKNOWN, // < Unknown format - UFBX_THUMBNAIL_FORMAT_RGB_24, // < 8-bit RGB pixels, in memory R,G,B - UFBX_THUMBNAIL_FORMAT_RGBA_32, // < 8-bit RGBA pixels, in memory R,G,B,A - - UFBX_ENUM_FORCE_WIDTH(UFBX_THUMBNAIL_FORMAT) -} ufbx_thumbnail_format; - -UFBX_ENUM_TYPE(ufbx_thumbnail_format, UFBX_THUMBNAIL_FORMAT, UFBX_THUMBNAIL_FORMAT_RGBA_32); - -// Specify how unit / coordinate system conversion should be performed. -// Affects how `ufbx_load_opts.target_axes` and `ufbx_load_opts.target_unit_meters` work, -// has no effect if neither is specified. -typedef enum ufbx_space_conversion UFBX_ENUM_REPR { - - // Store the space conversion transform in the root node. - // Sets `ufbx_node.local_transform` of the root node. - UFBX_SPACE_CONVERSION_TRANSFORM_ROOT, - - // Perform the conversion by using "adjust" transforms. - // Compensates for the transforms using `ufbx_node.adjust_pre_rotation` and - // `ufbx_node.adjust_pre_scale`. You don't need to account for these unless - // you are manually building transforms from `ufbx_props`. - UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS, - - // Perform the conversion by scaling geometry in addition to adjusting transforms. - // Compensates transforms like `UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS` but - // applies scaling to geometry as well. - UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SPACE_CONVERSION) -} ufbx_space_conversion; - -UFBX_ENUM_TYPE(ufbx_space_conversion, UFBX_SPACE_CONVERSION, UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY); - -// How to handle FBX node geometry transforms. -// FBX nodes can have "geometry transforms" that affect only the attached meshes, -// but not the children. This is not allowed in many scene representations so -// ufbx provides some ways to simplify them. -// Geometry transforms can also be used to transform any other attributes such -// as lights or cameras. -typedef enum ufbx_geometry_transform_handling UFBX_ENUM_REPR { - - // Preserve the geometry transforms as-is. - // To be correct for all files you have to use `ufbx_node.geometry_transform`, - // `ufbx_node.geometry_to_node`, or `ufbx_node.geometry_to_world` to compensate - // for any potential geometry transforms. - UFBX_GEOMETRY_TRANSFORM_HANDLING_PRESERVE, - - // Add helper nodes between the nodes and geometry where needed. - // The created nodes have `ufbx_node.is_geometry_transform_helper` set and are - // named `ufbx_load_opts.geometry_transform_helper_name`. - UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES, - - // Modify the geometry of meshes attached to nodes with geometry transforms. - // Will add helper nodes like `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES` if - // necessary, for example if there are multiple instances of the same mesh with - // geometry transforms. - UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY, - - // Modify the geometry of meshes attached to nodes with geometry transforms. - // NOTE: This will not work correctly for instanced geometry. - UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK, - - UFBX_ENUM_FORCE_WIDTH(UFBX_GEOMETRY_TRANSFORM_HANDLING) -} ufbx_geometry_transform_handling; - -UFBX_ENUM_TYPE(ufbx_geometry_transform_handling, UFBX_GEOMETRY_TRANSFORM_HANDLING, UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK); - -// How to handle FBX transform inherit modes. -typedef enum ufbx_inherit_mode_handling UFBX_ENUM_REPR { - - // Preserve inherit mode in `ufbx_node.inherit_mode`. - // NOTE: To correctly handle all scenes you would need to handle the - // non-standard inherit modes. - UFBX_INHERIT_MODE_HANDLING_PRESERVE, - - // Create scale helper nodes parented to nodes that need special inheritance. - // Scale helper nodes will have `ufbx_node.is_scale_helper` and parents of - // scale helpers will have `ufbx_node.scale_helper` pointing to it. - UFBX_INHERIT_MODE_HANDLING_HELPER_NODES, - - // Attempt to compensate for bone scale by inversely scaling children. - // NOTE: This only works for uniform non-animated scaling, if scale is - // non-uniform or animated, ufbx will add scale helpers in the same way - // as `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. - UFBX_INHERIT_MODE_HANDLING_COMPENSATE, - - // Attempt to compensate for bone scale by inversely scaling children. - // Will never create helper nodes. - UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK, - - // Ignore non-standard inheritance modes. - // Forces all nodes to have `UFBX_INHERIT_MODE_NORMAL` regardless of the - // inherit mode specified in the file. This can be useful for emulating - // results from importers/programs that don't support inherit modes. - UFBX_INHERIT_MODE_HANDLING_IGNORE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_INHERIT_MODE_HANDLING) -} ufbx_inherit_mode_handling; - -UFBX_ENUM_TYPE(ufbx_inherit_mode_handling, UFBX_INHERIT_MODE_HANDLING, UFBX_INHERIT_MODE_HANDLING_IGNORE); - -// How to handle FBX transform pivots. -typedef enum ufbx_pivot_handling UFBX_ENUM_REPR { - - // Take pivots into account when computing the transform. - UFBX_PIVOT_HANDLING_RETAIN, - - // Translate objects to be located at their pivot. - // NOTE: Only applied if rotation and scaling pivots are equal. - // NOTE: Results in geometric translation. Use `ufbx_geometry_transform_handling` - // to interpret these in a standard scene graph. - UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT, - - // Translate objects to be located at their rotation pivot. - // NOTE: Results in geometric translation. Use `ufbx_geometry_transform_handling` - // to interpret these in a standard scene graph. - // NOTE: By default the original transforms of empties are not retained when using this, - // use `ufbx_load_opts.pivot_handling_retain_empties` to prevent adjusting these pivots. - UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT, - - UFBX_ENUM_FORCE_WIDTH(UFBX_PIVOT_HANDLING) -} ufbx_pivot_handling; - -UFBX_ENUM_TYPE(ufbx_pivot_handling, UFBX_PIVOT_HANDLING, UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT); - -// Embedded thumbnail in the file, valid if the dimensions are non-zero. -typedef struct ufbx_thumbnail { - ufbx_props props; - - // Extents of the thumbnail - uint32_t width; - uint32_t height; - - // Format of `ufbx_thumbnail.data`. - ufbx_thumbnail_format format; - - // Thumbnail pixel data, layout as contiguous rows from bottom to top. - // See `ufbx_thumbnail.format` for the pixel format. - ufbx_blob data; -} ufbx_thumbnail; - -// Miscellaneous data related to the loaded file -typedef struct ufbx_metadata { - - // List of non-fatal warnings about the file. - // If you need to only check whether a specific warning was triggered you - // can use `ufbx_metadata.has_warning[]`. - ufbx_warning_list warnings; - - // FBX ASCII file format. - bool ascii; - - // FBX version in integer format, eg. 7400 for 7.4. - uint32_t version; - - // File format of the source file. - ufbx_file_format file_format; - - // Index arrays may contain `UFBX_NO_INDEX` instead of a valid index - // to indicate gaps. - bool may_contain_no_index; - - // May contain meshes with no defined vertex position. - // NOTE: `ufbx_mesh.vertex_position.exists` may be `false`! - bool may_contain_missing_vertex_position; - - // Arrays may contain items with `NULL` element references. - // See `ufbx_load_opts.connect_broken_elements`. - bool may_contain_broken_elements; - - // Some API guarantees do not apply (depending on unsafe options used). - // Loaded with `ufbx_load_opts.allow_unsafe` enabled. - bool is_unsafe; - - // Flag for each possible warning type. - // See `ufbx_metadata.warnings[]` for detailed warning information. - bool has_warning[UFBX_WARNING_TYPE_COUNT]; - - ufbx_string creator; - bool big_endian; - - ufbx_string filename; - ufbx_string relative_root; - - ufbx_blob raw_filename; - ufbx_blob raw_relative_root; - - ufbx_exporter exporter; - uint32_t exporter_version; - - ufbx_props scene_props; - - ufbx_application original_application; - ufbx_application latest_application; - - ufbx_thumbnail thumbnail; - - bool geometry_ignored; - bool animation_ignored; - bool embedded_ignored; - - size_t max_face_triangles; - - size_t result_memory_used; - size_t temp_memory_used; - size_t result_allocs; - size_t temp_allocs; - - size_t element_buffer_size; - size_t num_shader_textures; - - ufbx_real bone_prop_size_unit; - bool bone_prop_limb_length_relative; - - ufbx_real ortho_size_unit; - - int64_t ktime_second; // < One second in internal KTime units - - ufbx_string original_file_path; - ufbx_blob raw_original_file_path; - - // Conversion methods applied for the scene. - ufbx_space_conversion space_conversion; - ufbx_geometry_transform_handling geometry_transform_handling; - ufbx_inherit_mode_handling inherit_mode_handling; - ufbx_pivot_handling pivot_handling; - ufbx_mirror_axis handedness_conversion_axis; - - // Transform that has been applied to root for axis/unit conversion. - ufbx_quat root_rotation; - ufbx_real root_scale; - - // Axis that the scene has been mirrored by. - // All geometry has been mirrored in this axis. - ufbx_mirror_axis mirror_axis; - - // Amount geometry has been scaled. - // See `UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY`. - ufbx_real geometry_scale; - -} ufbx_metadata; - -typedef enum ufbx_time_mode UFBX_ENUM_REPR { - UFBX_TIME_MODE_DEFAULT, - UFBX_TIME_MODE_120_FPS, - UFBX_TIME_MODE_100_FPS, - UFBX_TIME_MODE_60_FPS, - UFBX_TIME_MODE_50_FPS, - UFBX_TIME_MODE_48_FPS, - UFBX_TIME_MODE_30_FPS, - UFBX_TIME_MODE_30_FPS_DROP, - UFBX_TIME_MODE_NTSC_DROP_FRAME, - UFBX_TIME_MODE_NTSC_FULL_FRAME, - UFBX_TIME_MODE_PAL, - UFBX_TIME_MODE_24_FPS, - UFBX_TIME_MODE_1000_FPS, - UFBX_TIME_MODE_FILM_FULL_FRAME, - UFBX_TIME_MODE_CUSTOM, - UFBX_TIME_MODE_96_FPS, - UFBX_TIME_MODE_72_FPS, - UFBX_TIME_MODE_59_94_FPS, - - UFBX_ENUM_FORCE_WIDTH(UFBX_TIME_MODE) -} ufbx_time_mode; - -UFBX_ENUM_TYPE(ufbx_time_mode, UFBX_TIME_MODE, UFBX_TIME_MODE_59_94_FPS); - -typedef enum ufbx_time_protocol UFBX_ENUM_REPR { - UFBX_TIME_PROTOCOL_SMPTE, - UFBX_TIME_PROTOCOL_FRAME_COUNT, - UFBX_TIME_PROTOCOL_DEFAULT, - - UFBX_ENUM_FORCE_WIDTH(UFBX_TIME_PROTOCOL) -} ufbx_time_protocol; - -UFBX_ENUM_TYPE(ufbx_time_protocol, UFBX_TIME_PROTOCOL, UFBX_TIME_PROTOCOL_DEFAULT); - -typedef enum ufbx_snap_mode UFBX_ENUM_REPR { - UFBX_SNAP_MODE_NONE, - UFBX_SNAP_MODE_SNAP, - UFBX_SNAP_MODE_PLAY, - UFBX_SNAP_MODE_SNAP_AND_PLAY, - - UFBX_ENUM_FORCE_WIDTH(UFBX_SNAP_MODE) -} ufbx_snap_mode; - -UFBX_ENUM_TYPE(ufbx_snap_mode, UFBX_SNAP_MODE, UFBX_SNAP_MODE_SNAP_AND_PLAY); - -// Global settings: Axes and time/unit scales -typedef struct ufbx_scene_settings { - ufbx_props props; - - // Mapping of X/Y/Z axes to world-space directions. - // HINT: Use `ufbx_load_opts.target_axes` to normalize this. - // NOTE: This contains the _original_ axes even if you supply `ufbx_load_opts.target_axes`. - ufbx_coordinate_axes axes; - - // How many meters does a single world-space unit represent. - // FBX files usually default to centimeters, reported as `0.01` here. - // HINT: Use `ufbx_load_opts.target_unit_meters` to normalize this. - ufbx_real unit_meters; - - // Frames per second the animation is defined at. - double frames_per_second; - - ufbx_vec3 ambient_color; - ufbx_string default_camera; - - // Animation user interface settings. - // HINT: Use `ufbx_scene_settings.frames_per_second` instead of interpreting these yourself. - ufbx_time_mode time_mode; - ufbx_time_protocol time_protocol; - ufbx_snap_mode snap_mode; - - // Original settings (?) - ufbx_coordinate_axis original_axis_up; - ufbx_real original_unit_meters; -} ufbx_scene_settings; - -struct ufbx_scene { - ufbx_metadata metadata; - - // Global settings - ufbx_scene_settings settings; - - // Node instances in the scene - ufbx_node *root_node; - - // Default animation descriptor - ufbx_anim *anim; - - union { - struct { - ufbx_unknown_list unknowns; - - // Nodes - ufbx_node_list nodes; - - // Node attributes (common) - ufbx_mesh_list meshes; - ufbx_light_list lights; - ufbx_camera_list cameras; - ufbx_bone_list bones; - ufbx_empty_list empties; - - // Node attributes (curves/surfaces) - ufbx_line_curve_list line_curves; - ufbx_nurbs_curve_list nurbs_curves; - ufbx_nurbs_surface_list nurbs_surfaces; - ufbx_nurbs_trim_surface_list nurbs_trim_surfaces; - ufbx_nurbs_trim_boundary_list nurbs_trim_boundaries; - - // Node attributes (advanced) - ufbx_procedural_geometry_list procedural_geometries; - ufbx_stereo_camera_list stereo_cameras; - ufbx_camera_switcher_list camera_switchers; - ufbx_marker_list markers; - ufbx_lod_group_list lod_groups; - - // Deformers - ufbx_skin_deformer_list skin_deformers; - ufbx_skin_cluster_list skin_clusters; - ufbx_blend_deformer_list blend_deformers; - ufbx_blend_channel_list blend_channels; - ufbx_blend_shape_list blend_shapes; - ufbx_cache_deformer_list cache_deformers; - ufbx_cache_file_list cache_files; - - // Materials - ufbx_material_list materials; - ufbx_texture_list textures; - ufbx_video_list videos; - ufbx_shader_list shaders; - ufbx_shader_binding_list shader_bindings; - - // Animation - ufbx_anim_stack_list anim_stacks; - ufbx_anim_layer_list anim_layers; - ufbx_anim_value_list anim_values; - ufbx_anim_curve_list anim_curves; - - // Collections - ufbx_display_layer_list display_layers; - ufbx_selection_set_list selection_sets; - ufbx_selection_node_list selection_nodes; - - // Constraints - ufbx_character_list characters; - ufbx_constraint_list constraints; - - // Audio - ufbx_audio_layer_list audio_layers; - ufbx_audio_clip_list audio_clips; - - // Miscellaneous - ufbx_pose_list poses; - ufbx_metadata_object_list metadata_objects; - }; - - ufbx_element_list elements_by_type[UFBX_ELEMENT_TYPE_COUNT]; - }; - - // Unique texture files referenced by the scene. - ufbx_texture_file_list texture_files; - - // All elements and connections in the whole file - ufbx_element_list elements; // < Sorted by `id` - ufbx_connection_list connections_src; // < Sorted by `src,src_prop` - ufbx_connection_list connections_dst; // < Sorted by `dst,dst_prop` - - // Elements sorted by name, type - ufbx_name_element_list elements_by_name; - - // Enabled if `ufbx_load_opts.retain_dom == true`. - ufbx_nullable ufbx_dom_node *dom_root; -}; - -// -- Curves - -typedef struct ufbx_curve_point { - bool valid; - ufbx_vec3 position; - ufbx_vec3 derivative; -} ufbx_curve_point; - -typedef struct ufbx_surface_point { - bool valid; - ufbx_vec3 position; - ufbx_vec3 derivative_u; - ufbx_vec3 derivative_v; -} ufbx_surface_point; - -// -- Mesh topology - -typedef enum ufbx_topo_flags UFBX_FLAG_REPR { - UFBX_TOPO_NON_MANIFOLD = 0x1, // < Edge with three or more faces - - UFBX_FLAG_FORCE_WIDTH(UFBX_TOPO_FLAGS) -} ufbx_topo_flags; - -typedef struct ufbx_topo_edge { - uint32_t index; // < Starting index of the edge, always defined - uint32_t next; // < Ending index of the edge / next per-face `ufbx_topo_edge`, always defined - uint32_t prev; // < Previous per-face `ufbx_topo_edge`, always defined - uint32_t twin; // < `ufbx_topo_edge` on the opposite side, `UFBX_NO_INDEX` if not found - uint32_t face; // < Index into `mesh->faces[]`, always defined - uint32_t edge; // < Index into `mesh->edges[]`, `UFBX_NO_INDEX` if not found - - ufbx_topo_flags flags; -} ufbx_topo_edge; - -// Vertex data array for `ufbx_generate_indices()`. -// NOTE: `ufbx_generate_indices()` compares the vertices using `memcmp()`, so -// any padding should be cleared to zero. -typedef struct ufbx_vertex_stream { - void *data; // < Data pointer of shape `char[vertex_count][vertex_size]`. - size_t vertex_count; // < Number of vertices in this stream, for sanity checking. - size_t vertex_size; // < Size of a vertex in bytes. -} ufbx_vertex_stream; - -// -- Memory callbacks - -// You can optionally provide an allocator to ufbx, the default is to use the -// CRT malloc/realloc/free - -// Allocate `size` bytes, must be at least 8 byte aligned -typedef void *ufbx_alloc_fn(void *user, size_t size); - -// Reallocate `old_ptr` from `old_size` to `new_size` -// NOTE: If omit `alloc_fn` and `free_fn` they will be translated to: -// `alloc(size)` -> `realloc_fn(user, NULL, 0, size)` -// `free_fn(ptr, size)` -> `realloc_fn(user, ptr, size, 0)` -typedef void *ufbx_realloc_fn(void *user, void *old_ptr, size_t old_size, size_t new_size); - -// Free pointer `ptr` (of `size` bytes) returned by `alloc_fn` or `realloc_fn` -typedef void ufbx_free_fn(void *user, void *ptr, size_t size); - -// Free the allocator itself -typedef void ufbx_free_allocator_fn(void *user); - -// Allocator callbacks and user context -// NOTE: The allocator will be stored to the loaded scene and will be called -// again from `ufbx_free_scene()` so make sure `user` outlives that! -// You can use `free_allocator_fn()` to free the allocator yourself. -typedef struct ufbx_allocator { - // Callback functions, see `typedef`s above for information - ufbx_alloc_fn *alloc_fn; - ufbx_realloc_fn *realloc_fn; - ufbx_free_fn *free_fn; - ufbx_free_allocator_fn *free_allocator_fn; - void *user; -} ufbx_allocator; - -typedef struct ufbx_allocator_opts { - // Allocator callbacks - ufbx_allocator allocator; - - // Maximum number of bytes to allocate before failing - size_t memory_limit; - - // Maximum number of allocations to attempt before failing - size_t allocation_limit; - - // Threshold to swap from batched allocations to individual ones - // Defaults to 1MB if set to zero - // NOTE: If set to `1` ufbx will allocate everything in the smallest - // possible chunks which may be useful for debugging (eg. ASAN) - size_t huge_threshold; - - // Maximum size of a single allocation containing sub-allocations. - // Defaults to 16MB if set to zero - // The maximum amount of wasted memory depends on `max_chunk_size` and - // `huge_threshold`: each chunk can waste up to `huge_threshold` bytes - // internally and the last chunk might be incomplete. So for example - // with the defaults we can waste around 1MB/16MB = 6.25% overall plus - // up to 32MB due to the two incomplete blocks. The actual amounts differ - // slightly as the chunks start out at 4kB and double in size each time, - // meaning that the maximum fixed overhead (up to 32MB with defaults) is - // at most ~30% of the total allocation size. - size_t max_chunk_size; - -} ufbx_allocator_opts; - -// -- IO callbacks - -// Try to read up to `size` bytes to `data`, return the amount of read bytes. -// Return `SIZE_MAX` to indicate an IO error. -typedef size_t ufbx_read_fn(void *user, void *data, size_t size); - -// Skip `size` bytes in the file. -typedef bool ufbx_skip_fn(void *user, size_t size); - -// Get the size of the file. -// Return `0` if unknown, `UINT64_MAX` if error. -typedef uint64_t ufbx_size_fn(void *user); - -// Close the file -typedef void ufbx_close_fn(void *user); - -typedef struct ufbx_stream { - ufbx_read_fn *read_fn; // < Required - ufbx_skip_fn *skip_fn; // < Optional: Will use `read_fn()` if missing - ufbx_size_fn *size_fn; // < Optional - ufbx_close_fn *close_fn; // < Optional - - // Context passed to other functions - void *user; -} ufbx_stream; - -typedef enum ufbx_open_file_type UFBX_ENUM_REPR { - UFBX_OPEN_FILE_MAIN_MODEL, // < Main model file - UFBX_OPEN_FILE_GEOMETRY_CACHE, // < Unknown geometry cache file - UFBX_OPEN_FILE_OBJ_MTL, // < .mtl material library file - - UFBX_ENUM_FORCE_WIDTH(UFBX_OPEN_FILE_TYPE) -} ufbx_open_file_type; - -UFBX_ENUM_TYPE(ufbx_open_file_type, UFBX_OPEN_FILE_TYPE, UFBX_OPEN_FILE_OBJ_MTL); - -typedef uintptr_t ufbx_open_file_context; - -typedef struct ufbx_open_file_info { - // Context that can be passed to the following functions to use a shared allocator: - // ufbx_open_file_ctx() - // ufbx_open_memory_ctx() - ufbx_open_file_context context; - - // Kind of file to load. - ufbx_open_file_type type; - - // Original filename in the file, not resolved or UTF-8 encoded. - // NOTE: Not necessarily NULL-terminated! - ufbx_blob original_filename; -} ufbx_open_file_info; - -// Callback for opening an external file from the filesystem -typedef bool ufbx_open_file_fn(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info); - -typedef struct ufbx_open_file_cb { - ufbx_open_file_fn *fn; - void *user; - - UFBX_CALLBACK_IMPL(ufbx_open_file_cb, ufbx_open_file_fn, bool, - (void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info), - (stream, path, path_len, info)) -} ufbx_open_file_cb; - -// Options for `ufbx_open_file()`. -typedef struct ufbx_open_file_opts { - uint32_t _begin_zero; - - // Allocator to allocate the memory with. - ufbx_allocator_opts allocator; - - // The filename is guaranteed to be NULL-terminated. - ufbx_unsafe bool filename_null_terminated; - - uint32_t _end_zero; -} ufbx_open_file_opts; - -// Memory stream options -typedef void ufbx_close_memory_fn(void *user, void *data, size_t data_size); - -typedef struct ufbx_close_memory_cb { - ufbx_close_memory_fn *fn; - void *user; - - UFBX_CALLBACK_IMPL(ufbx_close_memory_cb, ufbx_close_memory_fn, void, - (void *user, void *data, size_t data_size), - (data, data_size)) -} ufbx_close_memory_cb; - -// Options for `ufbx_open_memory()`. -typedef struct ufbx_open_memory_opts { - uint32_t _begin_zero; - - // Allocator to allocate the memory with. - // NOTE: Used even if no copy is made to allocate a small metadata block. - ufbx_allocator_opts allocator; - - // Do not copy the memory. - // You can use `close_cb` to free the memory when the stream is closed. - // NOTE: This means the provided data pointer is referenced after creating - // the memory stream, make sure the data stays valid until the stream is closed! - ufbx_unsafe bool no_copy; - - // Callback to free the memory blob. - ufbx_close_memory_cb close_cb; - - uint32_t _end_zero; -} ufbx_open_memory_opts; - -// Detailed error stack frame. -// NOTE: You must compile `ufbx.c` with `UFBX_ENABLE_ERROR_STACK` to enable the error stack. -typedef struct ufbx_error_frame { - uint32_t source_line; - ufbx_string function; - ufbx_string description; -} ufbx_error_frame; - -// Error causes (and `UFBX_ERROR_NONE` for no error). -typedef enum ufbx_error_type UFBX_ENUM_REPR { - - // No error, operation has been performed successfully. - UFBX_ERROR_NONE, - - // Unspecified error, most likely caused by an invalid FBX file or a file - // that contains something ufbx can't handle. - UFBX_ERROR_UNKNOWN, - - // File not found. - UFBX_ERROR_FILE_NOT_FOUND, - - // Empty file. - UFBX_ERROR_EMPTY_FILE, - - // External file not found. - // See `ufbx_load_opts.load_external_files` for more information. - UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND, - - // Out of memory (allocator returned `NULL`). - UFBX_ERROR_OUT_OF_MEMORY, - - // `ufbx_allocator_opts.memory_limit` exhausted. - UFBX_ERROR_MEMORY_LIMIT, - - // `ufbx_allocator_opts.allocation_limit` exhausted. - UFBX_ERROR_ALLOCATION_LIMIT, - - // File ended abruptly. - UFBX_ERROR_TRUNCATED_FILE, - - // IO read error. - // eg. returning `SIZE_MAX` from `ufbx_stream.read_fn` or stdio `ferror()` condition. - UFBX_ERROR_IO, - - // User cancelled the loading via `ufbx_load_opts.progress_cb` returning `UFBX_PROGRESS_CANCEL`. - UFBX_ERROR_CANCELLED, - - // Could not detect file format from file data or filename. - // HINT: You can supply it manually using `ufbx_load_opts.file_format` or use `ufbx_load_opts.filename` - // when using `ufbx_load_memory()` to let ufbx guess the format from the extension. - UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT, - - // Options struct (eg. `ufbx_load_opts`) is not cleared to zero. - // Make sure you initialize the structure to zero via eg. - // ufbx_load_opts opts = { 0 }; // C - // ufbx_load_opts opts = { }; // C++ - UFBX_ERROR_UNINITIALIZED_OPTIONS, - - // The vertex streams in `ufbx_generate_indices()` are empty. - UFBX_ERROR_ZERO_VERTEX_SIZE, - - // Vertex stream passed to `ufbx_generate_indices()`. - UFBX_ERROR_TRUNCATED_VERTEX_STREAM, - - // Invalid UTF-8 encountered in a file when loading with `UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING`. - UFBX_ERROR_INVALID_UTF8, - - // Feature needed for the operation has been compiled out. - UFBX_ERROR_FEATURE_DISABLED, - - // Attempting to tessellate an invalid NURBS object. - // See `ufbx_nurbs_basis.valid`. - UFBX_ERROR_BAD_NURBS, - - // Out of bounds index in the file when loading with `UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING`. - UFBX_ERROR_BAD_INDEX, - - // Node is deeper than `ufbx_load_opts.node_depth_limit` in the hierarchy. - UFBX_ERROR_NODE_DEPTH_LIMIT, - - // Error parsing ASCII array in a thread. - // Threaded ASCII parsing is slightly more strict than non-threaded, for cursed files, - // set `ufbx_load_opts.force_single_thread_ascii_parsing` to `true`. - UFBX_ERROR_THREADED_ASCII_PARSE, - - // Unsafe options specified without enabling `ufbx_load_opts.allow_unsafe`. - UFBX_ERROR_UNSAFE_OPTIONS, - - // Duplicated override property in `ufbx_create_anim()` - UFBX_ERROR_DUPLICATE_OVERRIDE, - - // Unsupported file format version. - // ufbx still tries to load files with unsupported versions, see `UFBX_WARNING_UNSUPPORTED_VERSION`. - UFBX_ERROR_UNSUPPORTED_VERSION, - - UFBX_ENUM_FORCE_WIDTH(UFBX_ERROR_TYPE) -} ufbx_error_type; - -UFBX_ENUM_TYPE(ufbx_error_type, UFBX_ERROR_TYPE, UFBX_ERROR_UNSUPPORTED_VERSION); - -// Error description with detailed stack trace -// HINT: You can use `ufbx_format_error()` for formatting the error -typedef struct ufbx_error { - - // Type of the error, or `UFBX_ERROR_NONE` if successful. - ufbx_error_type type; - - // Description of the error type. - ufbx_string description; - - // Internal error stack. - // NOTE: You must compile `ufbx.c` with `UFBX_ENABLE_ERROR_STACK` to enable the error stack. - uint32_t stack_size; - ufbx_error_frame stack[UFBX_ERROR_STACK_MAX_DEPTH]; - - // Additional error information, such as missing file filename. - // `info` is a NULL-terminated UTF-8 string containing `info_length` bytes, excluding the trailing `'\0'`. - size_t info_length; - char info[UFBX_ERROR_INFO_LENGTH]; - -} ufbx_error; - -// -- Progress callbacks - -// Loading progress information. -typedef struct ufbx_progress { - uint64_t bytes_read; - uint64_t bytes_total; -} ufbx_progress; - -// Progress result returned from `ufbx_progress_fn()` callback. -// Determines whether ufbx should continue or abort the loading. -typedef enum ufbx_progress_result UFBX_ENUM_REPR { - - // Continue loading the file. - UFBX_PROGRESS_CONTINUE = 0x100, - - // Cancel loading and fail with `UFBX_ERROR_CANCELLED`. - UFBX_PROGRESS_CANCEL = 0x200, - - UFBX_ENUM_FORCE_WIDTH(UFBX_PROGRESS_RESULT) -} ufbx_progress_result; - -// Called periodically with the current progress. -// Return `UFBX_PROGRESS_CANCEL` to cancel further processing. -typedef ufbx_progress_result ufbx_progress_fn(void *user, const ufbx_progress *progress); - -typedef struct ufbx_progress_cb { - ufbx_progress_fn *fn; - void *user; - - UFBX_CALLBACK_IMPL(ufbx_progress_cb, ufbx_progress_fn, ufbx_progress_result, - (void *user, const ufbx_progress *progress), - (progress)) -} ufbx_progress_cb; - -// -- Inflate - -typedef struct ufbx_inflate_input ufbx_inflate_input; -typedef struct ufbx_inflate_retain ufbx_inflate_retain; - -// Source data/stream to decompress with `ufbx_inflate()` -struct ufbx_inflate_input { - // Total size of the data in bytes - size_t total_size; - - // (optional) Initial or complete data chunk - const void *data; - size_t data_size; - - // (optional) Temporary buffer, defaults to 256b stack buffer - void *buffer; - size_t buffer_size; - - // (optional) Streaming read function, concatenated after `data` - ufbx_read_fn *read_fn; - void *read_user; - - // (optional) Progress reporting - ufbx_progress_cb progress_cb; - uint64_t progress_interval_hint; // < Bytes between progress report calls - - // (optional) Change the progress scope - uint64_t progress_size_before; - uint64_t progress_size_after; - - // (optional) No the DEFLATE header - bool no_header; - - // (optional) No the Adler32 checksum - bool no_checksum; - - // (optional) Force internal fast lookup bit amount - size_t internal_fast_bits; -}; - -// Persistent data between `ufbx_inflate()` calls -// NOTE: You must set `initialized` to `false`, but `data` may be uninitialized -struct ufbx_inflate_retain { - bool initialized; - uint64_t data[1024]; -}; - -typedef enum ufbx_index_error_handling UFBX_ENUM_REPR { - // Clamp to a valid value. - UFBX_INDEX_ERROR_HANDLING_CLAMP, - // Set bad indices to `UFBX_NO_INDEX`. - // This is the recommended way if you need to deal with files with gaps in information. - // HINT: If you use this `ufbx_get_vertex_TYPE()` functions will return zero - // on invalid indices instead of failing. - UFBX_INDEX_ERROR_HANDLING_NO_INDEX, - // Fail loading entierely when encountering a bad index. - UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING, - // Pass bad indices through as-is. - // Requires `ufbx_load_opts.allow_unsafe`. - // UNSAFE: Breaks any API guarantees regarding indexes being in bounds and makes - // `ufbx_get_vertex_TYPE()` memory-unsafe to use. - UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_INDEX_ERROR_HANDLING) -} ufbx_index_error_handling; - -UFBX_ENUM_TYPE(ufbx_index_error_handling, UFBX_INDEX_ERROR_HANDLING, UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE); - -typedef enum ufbx_unicode_error_handling UFBX_ENUM_REPR { - // Replace errors with U+FFFD "Replacement Character" - UFBX_UNICODE_ERROR_HANDLING_REPLACEMENT_CHARACTER, - // Replace errors with '_' U+5F "Low Line" - UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE, - // Replace errors with '?' U+3F "Question Mark" - UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK, - // Remove errors from the output - UFBX_UNICODE_ERROR_HANDLING_REMOVE, - // Fail loading on encountering an Unicode error - UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING, - // Ignore and pass-through non-UTF-8 string data. - // Requires `ufbx_load_opts.allow_unsafe`. - // UNSAFE: Breaks API guarantee that `ufbx_string` is UTF-8 encoded. - UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE, - - UFBX_ENUM_FORCE_WIDTH(UFBX_UNICODE_ERROR_HANDLING) -} ufbx_unicode_error_handling; - -UFBX_ENUM_TYPE(ufbx_unicode_error_handling, UFBX_UNICODE_ERROR_HANDLING, UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE); - -typedef enum ufbx_baked_key_flags UFBX_FLAG_REPR { - // This keyframe represents a constant step from the left side - UFBX_BAKED_KEY_STEP_LEFT = 0x1, - // This keyframe represents a constant step from the right side - UFBX_BAKED_KEY_STEP_RIGHT = 0x2, - // This keyframe is the main part of a step - // Bordering either `UFBX_BAKED_KEY_STEP_LEFT` or `UFBX_BAKED_KEY_STEP_RIGHT`. - UFBX_BAKED_KEY_STEP_KEY = 0x4, - // This keyframe is a real keyframe in the source animation - UFBX_BAKED_KEY_KEYFRAME = 0x8, - // This keyframe has been reduced by maximum sample rate. - // See `ufbx_bake_opts.maximum_sample_rate`. - UFBX_BAKED_KEY_REDUCED = 0x10, - - UFBX_FLAG_FORCE_WIDTH(UFBX_BAKED_KEY) -} ufbx_baked_key_flags; - -typedef struct ufbx_baked_vec3 { - double time; // < Time of the keyframe, in seconds - ufbx_vec3 value; // < Value at `time`, can be linearly interpolated - ufbx_baked_key_flags flags; // < Additional information about the keyframe -} ufbx_baked_vec3; - -UFBX_LIST_TYPE(ufbx_baked_vec3_list, ufbx_baked_vec3); - -typedef struct ufbx_baked_quat { - double time; // < Time of the keyframe, in seconds - ufbx_quat value; // < Value at `time`, can be (spherically) linearly interpolated - ufbx_baked_key_flags flags; // < Additional information about the keyframe -} ufbx_baked_quat; - -UFBX_LIST_TYPE(ufbx_baked_quat_list, ufbx_baked_quat); - -// Baked transform animation for a single node. -typedef struct ufbx_baked_node { - - // Typed ID of the node, maps to `ufbx_scene.nodes[]`. - uint32_t typed_id; - // Element ID of the element, maps to `ufbx_scene.elements[]`. - uint32_t element_id; - - // The translation channel has constant values for the whole animation. - bool constant_translation; - // The rotation channel has constant values for the whole animation. - bool constant_rotation; - // The scale channel has constant values for the whole animation. - bool constant_scale; - - // Translation keys for the animation, maps to `ufbx_node.local_transform.translation`. - ufbx_baked_vec3_list translation_keys; - // Rotation keyframes, maps to `ufbx_node.local_transform.rotation`. - ufbx_baked_quat_list rotation_keys; - // Scale keyframes, maps to `ufbx_node.local_transform.scale`. - ufbx_baked_vec3_list scale_keys; - -} ufbx_baked_node; - -UFBX_LIST_TYPE(ufbx_baked_node_list, ufbx_baked_node); - -// Baked property animation. -typedef struct ufbx_baked_prop { - // Name of the property, eg. `"Visibility"`. - ufbx_string name; - // The value of the property is constant for the whole animation. - bool constant_value; - // Property value keys. - ufbx_baked_vec3_list keys; -} ufbx_baked_prop; - -UFBX_LIST_TYPE(ufbx_baked_prop_list, ufbx_baked_prop); - -// Baked property animation for a single element. -typedef struct ufbx_baked_element { - // Element ID of the element, maps to `ufbx_scene.elements[]`. - uint32_t element_id; - // List of properties the animation modifies. - ufbx_baked_prop_list props; -} ufbx_baked_element; - -UFBX_LIST_TYPE(ufbx_baked_element_list, ufbx_baked_element); - -typedef struct ufbx_baked_anim_metadata { - // Memory statistics - size_t result_memory_used; - size_t temp_memory_used; - size_t result_allocs; - size_t temp_allocs; -} ufbx_baked_anim_metadata; - -// Animation baked into linearly interpolated keyframes. -// See `ufbx_bake_anim()`. -typedef struct ufbx_baked_anim { - - // Nodes that are modified by the animation. - // Some nodes may be missing if the specified animation does not transform them. - // Conversely, some non-obviously animated nodes may be included as exporters - // often may add dummy keyframes for objects. - ufbx_baked_node_list nodes; - - // Element properties modified by the animation. - ufbx_baked_element_list elements; - - // Playback time range for the animation. - double playback_time_begin; - double playback_time_end; - double playback_duration; - - // Keyframe time range. - double key_time_min; - double key_time_max; - - // Additional bake information. - ufbx_baked_anim_metadata metadata; - -} ufbx_baked_anim; - -// -- Thread API - -// Internal thread pool handle. -// Passed to `ufbx_thread_pool_run_task()` from an user thread to run ufbx tasks. -// HINT: This context can store a user pointer via `ufbx_thread_pool_set_user_ptr()`. -typedef uintptr_t ufbx_thread_pool_context; - -// Thread pool creation information from ufbx. -typedef struct ufbx_thread_pool_info { - uint32_t max_concurrent_tasks; -} ufbx_thread_pool_info; - -// Initialize the thread pool. -// Return `true` on success. -typedef bool ufbx_thread_pool_init_fn(void *user, ufbx_thread_pool_context ctx, const ufbx_thread_pool_info *info); - -// Run tasks `count` tasks in threads. -// You must call `ufbx_thread_pool_run_task()` with indices `[start_index, start_index + count)`. -// The threads are launched in batches indicated by `group`, see `UFBX_THREAD_GROUP_COUNT` for more information. -// Ideally, you should run all the task indices in parallel within each `ufbx_thread_pool_run_fn()` call. -typedef void ufbx_thread_pool_run_fn(void *user, ufbx_thread_pool_context ctx, uint32_t group, uint32_t start_index, uint32_t count); - -// Wait for previous tasks spawned in `ufbx_thread_pool_run_fn()` to finish. -// `group` specifies the batch to wait for, `max_index` contains `start_index + count` from that group instance. -typedef void ufbx_thread_pool_wait_fn(void *user, ufbx_thread_pool_context ctx, uint32_t group, uint32_t max_index); - -// Free the thread pool. -typedef void ufbx_thread_pool_free_fn(void *user, ufbx_thread_pool_context ctx); - -// Thread pool interface. -// See functions above for more information. -// -// Hypothetical example of calls, where `UFBX_THREAD_GROUP_COUNT=2` for simplicity: -// -// run_fn(group=0, start_index=0, count=4) -> t0 := threaded { ufbx_thread_pool_run_task(0..3) } -// run_fn(group=1, start_index=4, count=10) -> t1 := threaded { ufbx_thread_pool_run_task(4..10) } -// wait_fn(group=0, max_index=4) -> wait_threads(t0) -// run_fn(group=0, start_index=10, count=15) -> t0 := threaded { ufbx_thread_pool_run_task(10..14) } -// wait_fn(group=1, max_index=10) -> wait_threads(t1) -// wait_fn(group=0, max_index=15) -> wait_threads(t0) -// -typedef struct ufbx_thread_pool { - ufbx_thread_pool_init_fn *init_fn; // < Optional - ufbx_thread_pool_run_fn *run_fn; // < Required - ufbx_thread_pool_wait_fn *wait_fn; // < Required - ufbx_thread_pool_free_fn *free_fn; // < Optional - void *user; -} ufbx_thread_pool; - -// Thread pool options. -typedef struct ufbx_thread_opts { - - // Thread pool interface. - // HINT: You can use `extra/ufbx_os.h` to provide a thread pool. - ufbx_thread_pool pool; - - // Maximum of tasks to have in-flight. - // Default: 2048 - size_t num_tasks; - - // Maximum amount of memory to use for batched threaded processing. - // Default: 32MB - // NOTE: The actual used memory usage might be higher, if there are individual tasks - // that rqeuire a high amount of memory. - size_t memory_limit; - -} ufbx_thread_opts; - -// Flags to control nanimation evaluation functions. -typedef enum ufbx_evaluate_flags UFBX_FLAG_REPR { - - // Do not extrapolate past the keyframes. - UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION = 0x1, - - UFBX_FLAG_FORCE_WIDTH(ufbx_evaluate_flags) -} ufbx_evaluate_flags; - -// -- Main API - -// Options for `ufbx_load_file/memory/stream/stdio()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_load_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during loading - ufbx_allocator_opts result_allocator; // < Allocator used for the final scene - ufbx_thread_opts thread_opts; // < Threading options - - // Preferences - bool ignore_geometry; // < Do not load geometry datsa (vertices, indices, etc) - bool ignore_animation; // < Do not load animation curves - bool ignore_embedded; // < Do not load embedded content - bool ignore_all_content; // < Do not load any content (geometry, animation, embedded) - - bool evaluate_skinning; // < Evaluate skinning (see ufbx_mesh.skinned_vertices) - bool evaluate_caches; // < Evaluate vertex caches (see ufbx_mesh.skinned_vertices) - - // Try to open external files referenced by the main file automatically. - // Applies to geometry caches and .mtl files for OBJ. - // NOTE: This may be risky for untrusted data as the input files may contain - // references to arbitrary paths in the filesystem. - // NOTE: This only applies to files *implicitly* referenced by the scene, if - // you request additional files via eg. `ufbx_load_opts.obj_mtl_path` they - // are still loaded. - // NOTE: Will fail loading if any external files are not found by default, use - // `ufbx_load_opts.ignore_missing_external_files` to suppress this, in this case - // you can find the errors at `ufbx_metadata.warnings[]` as `UFBX_WARNING_MISSING_EXTERNAL_FILE`. - bool load_external_files; - - // Don't fail loading if external files are not found. - bool ignore_missing_external_files; - - // Don't compute `ufbx_skin_deformer` `vertices` and `weights` arrays saving - // a bit of memory and time if not needed - bool skip_skin_vertices; - - // Skip computing `ufbx_mesh.material_parts[]` and `ufbx_mesh.face_group_parts[]`. - bool skip_mesh_parts; - - // Clean-up skin weights by removing negative, zero and NAN weights. - bool clean_skin_weights; - - // Read Blender materials as PBR values. - // Blender converts PBR materials to legacy FBX Phong materials in a deterministic way. - // If this setting is enabled, such materials will be read as `UFBX_SHADER_BLENDER_PHONG`, - // which means ufbx will be able to parse roughness and metallic textures. - bool use_blender_pbr_material; - - // Don't adjust reading the FBX file depending on the detected exporter - bool disable_quirks; - - // Don't allow partially broken FBX files to load - bool strict; - - // Force ASCII parsing to use a single thread. - // The multi-threaded ASCII parsing is slightly more lenient as it ignores - // the self-reported size of ASCII arrays, that threaded parsing depends on. - bool force_single_thread_ascii_parsing; - - // UNSAFE: If enabled allows using unsafe options that may fundamentally - // break the API guarantees. - ufbx_unsafe bool allow_unsafe; - - // Specify how to handle broken indices. - ufbx_index_error_handling index_error_handling; - - // Connect related elements even if they are broken. If `false` (default) - // `ufbx_skin_cluster` with a missing `bone` field are _not_ included in - // the `ufbx_skin_deformer.clusters[]` array for example. - bool connect_broken_elements; - - // Allow nodes that are not connected in any way to the root. Conversely if - // disabled, all lone nodes will be parented under `ufbx_scene.root_node`. - bool allow_nodes_out_of_root; - - // Allow meshes with no vertex position attribute. - // NOTE: If this is set `ufbx_mesh.vertex_position.exists` may be `false`. - bool allow_missing_vertex_position; - - // Allow faces with zero indices. - bool allow_empty_faces; - - // Generate vertex normals for a meshes that are missing normals. - // You can see if the normals have been generated from `ufbx_mesh.generated_normals`. - bool generate_missing_normals; - - // Ignore `open_file_cb` when loading the main file. - bool open_main_file_with_default; - - // Path separator character, defaults to '\' on Windows and '/' otherwise. - char path_separator; - - // Maximum depth of the node hirerachy. - // Will fail with `UFBX_ERROR_NODE_DEPTH_LIMIT` if a node is deeper than this limit. - // NOTE: The default of 0 allows arbitrarily deep hierarchies. Be careful if using - // recursive algorithms without setting this limit. - uint32_t node_depth_limit; - - // Estimated file size for progress reporting - uint64_t file_size_estimate; - - // Buffer size in bytes to use for reading from files or IO callbacks - size_t read_buffer_size; - - // Filename to use as a base for relative file paths if not specified using - // `ufbx_load_file()`. Use `length = SIZE_MAX` for NULL-terminated strings. - // `raw_filename` will be derived from this if empty. - ufbx_string filename; - - // Raw non-UTF8 filename. Does not support NULL termination. - // `filename` will be derived from this if empty. - ufbx_blob raw_filename; - - // Progress reporting - ufbx_progress_cb progress_cb; - uint64_t progress_interval_hint; // < Bytes between progress report calls - - // External file callbacks (defaults to stdio.h) - ufbx_open_file_cb open_file_cb; - - // How to handle geometry transforms in the nodes. - // See `ufbx_geometry_transform_handling` for an explanation. - ufbx_geometry_transform_handling geometry_transform_handling; - - // How to handle unconventional transform inherit modes. - // See `ufbx_inherit_mode_handling` for an explanation. - ufbx_inherit_mode_handling inherit_mode_handling; - - // How to perform space conversion by `target_axes` and `target_unit_meters`. - // See `ufbx_space_conversion` for an explanation. - ufbx_space_conversion space_conversion; - - // How to handle pivots. - // See `ufbx_pivot_handling` for an explanation. - ufbx_pivot_handling pivot_handling; - - // Retain the original transforms of empties when converting pivots. - bool pivot_handling_retain_empties; - - // Axis used to mirror for conversion between left-handed and right-handed coordinates. - ufbx_mirror_axis handedness_conversion_axis; - - // Do not change winding of faces when converting handedness. - bool handedness_conversion_retain_winding; - - // Reverse winding of all faces. - // If `handedness_conversion_retain_winding` is not specified, mirrored meshes - // will retain their original winding. - bool reverse_winding; - - // Apply an implicit root transformation to match axes. - // Used if `ufbx_coordinate_axes_valid(target_axes)`. - ufbx_coordinate_axes target_axes; - - // Scale the scene so that one world-space unit is `target_unit_meters` meters. - // By default units are not scaled. - ufbx_real target_unit_meters; - - // Target space for camera. - // By default FBX cameras point towards the positive X axis. - // Used if `ufbx_coordinate_axes_valid(target_camera_axes)`. - ufbx_coordinate_axes target_camera_axes; - - // Target space for directed lights. - // By default FBX lights point towards the negative Y axis. - // Used if `ufbx_coordinate_axes_valid(target_light_axes)`. - ufbx_coordinate_axes target_light_axes; - - // Name for dummy geometry transform helper nodes. - // See `UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES`. - ufbx_string geometry_transform_helper_name; - - // Name for dummy scale helper nodes. - // See `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. - ufbx_string scale_helper_name; - - // Normalize vertex normals. - bool normalize_normals; - - // Normalize tangents and bitangents. - bool normalize_tangents; - - // Override for the root transform - bool use_root_transform; - ufbx_transform root_transform; - - // Animation keyframe clamp threshold, only applies to specific interpolation modes. - double key_clamp_threshold; - - // Specify how to handle Unicode errors in strings. - ufbx_unicode_error_handling unicode_error_handling; - - // Retain the 'W' component of mesh normal/tangent/bitangent. - // See `ufbx_vertex_attrib.values_w`. - bool retain_vertex_attrib_w; - - // Retain the raw document structure using `ufbx_dom_node`. - bool retain_dom; - - // Force a specific file format instead of detecting it. - ufbx_file_format file_format; - - // How far to read into the file to determine the file format. - // Default: 16kB - size_t file_format_lookahead; - - // Do not attempt to detect file format from file content. - bool no_format_from_content; - - // Do not attempt to detect file format from filename extension. - // ufbx primarily detects file format from the file header, - // this is just used as a fallback. - bool no_format_from_extension; - - // (.obj) Try to find .mtl file with matching filename as the .obj file. - // Used if the file specified `mtllib` line is not found, eg. for a file called - // `model.obj` that contains the line `usemtl materials.mtl`, ufbx would first - // try to open `materials.mtl` and if that fails it tries to open `model.mtl`. - bool obj_search_mtl_by_filename; - - // (.obj) Don't split geometry into meshes by object. - bool obj_merge_objects; - - // (.obj) Don't split geometry into meshes by groups. - bool obj_merge_groups; - - // (.obj) Force splitting groups even on object boundaries. - bool obj_split_groups; - - // (.obj) Path to the .mtl file. - // Use `length = SIZE_MAX` for NULL-terminated strings. - // NOTE: This is used _instead_ of the one in the file even if not found - // and sidesteps `load_external_files` as it's _explicitly_ requested. - ufbx_string obj_mtl_path; - - // (.obj) Data for the .mtl file. - ufbx_blob obj_mtl_data; - - // The world unit in meters that .obj files are assumed to be in. - // .obj files do not define the working units. By default the unit scale - // is read as zero, and no unit conversion is performed. - ufbx_real obj_unit_meters; - - // Coordinate space .obj files are assumed to be in. - // .obj files do not define the coordinate space they use. By default no - // coordinate space is assumed and no conversion is performed. - ufbx_coordinate_axes obj_axes; - - uint32_t _end_zero; -} ufbx_load_opts; - -// Options for `ufbx_evaluate_scene()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_evaluate_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during evaluation - ufbx_allocator_opts result_allocator; // < Allocator used for the final scene - - bool evaluate_skinning; // < Evaluate skinning (see ufbx_mesh.skinned_vertices) - bool evaluate_caches; // < Evaluate vertex caches (see ufbx_mesh.skinned_vertices) - - // Evaluation flags. - // See `ufbx_evaluate_flags` for information. - uint32_t evaluate_flags; - - // WARNING: Potentially unsafe! Try to open external files such as geometry caches - bool load_external_files; - - // External file callbacks (defaults to stdio.h) - ufbx_open_file_cb open_file_cb; - - uint32_t _end_zero; -} ufbx_evaluate_opts; - -UFBX_LIST_TYPE(ufbx_const_uint32_list, const uint32_t); -UFBX_LIST_TYPE(ufbx_const_real_list, const ufbx_real); - -typedef struct ufbx_prop_override_desc { - // Element (`ufbx_element.element_id`) to override the property from - uint32_t element_id; - - // Property name to override. - ufbx_string prop_name; - - // Override value, use `value.x` for scalars. `value_int` is initialized - // from `value.x` if zero so keep `value` zeroed even if you don't need it! - ufbx_vec4 value; - ufbx_string value_str; - int64_t value_int; -} ufbx_prop_override_desc; - -UFBX_LIST_TYPE(ufbx_const_prop_override_desc_list, const ufbx_prop_override_desc); - -UFBX_LIST_TYPE(ufbx_const_transform_override_list, const ufbx_transform_override); - -typedef struct ufbx_anim_opts { - uint32_t _begin_zero; - - // Animation layers indices. - // Corresponding to `ufbx_scene.anim_layers[]`, aka `ufbx_anim_layer.typed_id`. - ufbx_const_uint32_list layer_ids; - - // Override layer weights, parallel to `ufbx_anim_opts.layer_ids[]`. - ufbx_const_real_list override_layer_weights; - - // Property overrides. - // These allow you to override FBX properties, such as 'UFBX_Lcl_Rotation`. - ufbx_const_prop_override_desc_list prop_overrides; - - // Transform overrides. - // These allow you to override individual nodes' `ufbx_node.local_transform`. - ufbx_const_transform_override_list transform_overrides; - - // Ignore connected properties - bool ignore_connections; - - ufbx_allocator_opts result_allocator; // < Allocator used to create the `ufbx_anim` - - uint32_t _end_zero; -} ufbx_anim_opts; - -// Specifies how to handle stepped tangents. -typedef enum ufbx_bake_step_handling UFBX_ENUM_REPR { - - // One millisecond default step duration, with potential extra slack for converting to `float`. - UFBX_BAKE_STEP_HANDLING_DEFAULT, - - // Use a custom interpolation duration for the constant step. - // See `ufbx_bake_opts.step_custom_duration` and optionally `ufbx_bake_opts.step_custom_epsilon`. - UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION, - - // Stepped keyframes are represented as keyframes at the exact same time. - // Use flags `UFBX_BAKED_KEY_STEP_LEFT` and `UFBX_BAKED_KEY_STEP_RIGHT` to differentiate - // between the primary key and edge limits. - UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME, - - // Represent stepped keyframe times as the previous/next representable `double` value. - // Using this and robust linear interpolation will handle stepped tangents correctly - // without having to look at the key flags. - // NOTE: Casting these values to `float` or otherwise modifying them can collapse - // the keyframes to have the identical time. - UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE, - - // Treat all stepped tangents as linearly interpolated. - UFBX_BAKE_STEP_HANDLING_IGNORE, - - UFBX_ENUM_FORCE_WIDTH(ufbx_bake_step_handling) -} ufbx_bake_step_handling; - -UFBX_ENUM_TYPE(ufbx_bake_step_handling, UFBX_BAKE_STEP_HANDLING, UFBX_BAKE_STEP_HANDLING_IGNORE); - -typedef struct ufbx_bake_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during loading - ufbx_allocator_opts result_allocator; // < Allocator used for the final baked animation - - // Move the keyframe times to start from zero regardless of the animation start time. - // For example, for an animation spanning between frames [30, 60] will be moved to - // [0, 30] in the baked animation. - // NOTE: This is in general not equivalent to subtracting `ufbx_anim.time_begin` - // from each keyframe, as this trimming is done exactly using internal FBX ticks. - bool trim_start_time; - - // Samples per second to use for resampling non-linear animation. - // Default: 30 - double resample_rate; - - // Minimum sample rate to not resample. - // Many exporters resample animation by default. To avoid double-resampling - // keyframe rates higher or equal to this will not be resampled. - // Default: 19.5 - double minimum_sample_rate; - - // Maximum sample rate to use, this will remove keys if they are too close together. - // Default: unlimited - double maximum_sample_rate; - - // Bake the raw versions of properties related to transforms. - bool bake_transform_props; - - // Do not bake node transforms. - bool skip_node_transforms; - - // Do not resample linear rotation keyframes. - // FBX interpolates rotation in Euler angles, so this might cause incorrect interpolation. - bool no_resample_rotation; - - // Ignore layer weight animation. - bool ignore_layer_weight_animation; - - // Maximum number of segments to generate from one keyframe. - // Default: 32 - size_t max_keyframe_segments; - - // How to handle stepped tangents. - ufbx_bake_step_handling step_handling; - - // Interpolation duration used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. - double step_custom_duration; - - // Interpolation epsilon used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. - // Defined as the minimum fractional decrease/increase in key time, ie. - // `time / (1.0 + step_custom_epsilon)` and `time * (1.0 + step_custom_epsilon)`. - double step_custom_epsilon; - - // Flags passed to animation evaluation functions. - // See `ufbx_evaluate_flags`. - uint32_t evaluate_flags; - - // Enable key reduction. - bool key_reduction_enabled; - - // Enable key reduction for non-constant rotations. - // Assumes rotations will be interpolated using a spherical linear interpolation at runtime. - bool key_reduction_rotation; - - // Threshold for reducing keys for linear segments. - // Default `0.000001`, use negative to disable. - double key_reduction_threshold; - - // Maximum passes over the keys to reduce. - // Every pass can potentially halve the the amount of keys. - // Default: `4` - size_t key_reduction_passes; - - uint32_t _end_zero; -} ufbx_bake_opts; - -// Options for `ufbx_tessellate_nurbs_curve()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_tessellate_curve_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during tessellation - ufbx_allocator_opts result_allocator; // < Allocator used for the final line curve - - // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. - size_t span_subdivision; - - uint32_t _end_zero; -} ufbx_tessellate_curve_opts; - -// Options for `ufbx_tessellate_nurbs_surface()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_tessellate_surface_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during tessellation - ufbx_allocator_opts result_allocator; // < Allocator used for the final mesh - - // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. - // NOTE: Default is `4`, _not_ `ufbx_nurbs_surface.span_subdivision_u/v` as that - // would make it easy to create an FBX file with an absurdly high subdivision - // rate (similar to mesh subdivision). Please enforce copy the value yourself - // enforcing whatever limits you deem reasonable. - size_t span_subdivision_u; - size_t span_subdivision_v; - - // Skip computing `ufbx_mesh.material_parts[]` - bool skip_mesh_parts; - - uint32_t _end_zero; -} ufbx_tessellate_surface_opts; - -// Options for `ufbx_subdivide_mesh()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_subdivide_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during subdivision - ufbx_allocator_opts result_allocator; // < Allocator used for the final mesh - - ufbx_subdivision_boundary boundary; - ufbx_subdivision_boundary uv_boundary; - - // Do not generate normals - bool ignore_normals; - - // Interpolate existing normals using the subdivision rules - // instead of generating new normals - bool interpolate_normals; - - // Subdivide also tangent attributes - bool interpolate_tangents; - - // Map subdivided vertices into weighted original vertices. - // NOTE: May be O(n^2) if `max_source_vertices` is not specified! - bool evaluate_source_vertices; - - // Limit source vertices per subdivided vertex. - size_t max_source_vertices; - - // Calculate bone influences over subdivided vertices (if applicable). - // NOTE: May be O(n^2) if `max_skin_weights` is not specified! - bool evaluate_skin_weights; - - // Limit bone influences per subdivided vertex. - size_t max_skin_weights; - - // Index of the skin deformer to use for `evaluate_skin_weights`. - size_t skin_deformer_index; - - uint32_t _end_zero; -} ufbx_subdivide_opts; - -// Options for `ufbx_load_geometry_cache()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_geometry_cache_opts { - uint32_t _begin_zero; - - ufbx_allocator_opts temp_allocator; // < Allocator used during loading - ufbx_allocator_opts result_allocator; // < Allocator used for the final scene - - // External file callbacks (defaults to stdio.h) - ufbx_open_file_cb open_file_cb; - - // FPS value for converting frame times to seconds - double frames_per_second; - - // Axis to mirror the geometry by. - ufbx_mirror_axis mirror_axis; - - // Enable scaling `scale_factor` all geometry by. - bool use_scale_factor; - - // Factor to scale the geometry by. - ufbx_real scale_factor; - - uint32_t _end_zero; -} ufbx_geometry_cache_opts; - -// Options for `ufbx_read_geometry_cache_TYPE()` -// NOTE: Initialize to zero with `{ 0 }` (C) or `{ }` (C++) -typedef struct ufbx_geometry_cache_data_opts { - uint32_t _begin_zero; - - // External file callbacks (defaults to stdio.h) - ufbx_open_file_cb open_file_cb; - - bool additive; - bool use_weight; - ufbx_real weight; - - // Ignore scene transform. - bool ignore_transform; - - uint32_t _end_zero; -} ufbx_geometry_cache_data_opts; - -typedef struct ufbx_panic { - bool did_panic; - size_t message_length; - char message[UFBX_PANIC_MESSAGE_LENGTH]; -} ufbx_panic; - -// -- API - -#ifdef __cplusplus -extern "C" { -#endif - -// Various zero/empty/identity values -ufbx_abi_data const ufbx_string ufbx_empty_string; -ufbx_abi_data const ufbx_blob ufbx_empty_blob; -ufbx_abi_data const ufbx_matrix ufbx_identity_matrix; -ufbx_abi_data const ufbx_transform ufbx_identity_transform; -ufbx_abi_data const ufbx_vec2 ufbx_zero_vec2; -ufbx_abi_data const ufbx_vec3 ufbx_zero_vec3; -ufbx_abi_data const ufbx_vec4 ufbx_zero_vec4; -ufbx_abi_data const ufbx_quat ufbx_identity_quat; - -// Commonly used coordinate axes. -ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_y_up; -ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_z_up; -ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_y_up; -ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_z_up; - -// Sizes of element types. eg `sizeof(ufbx_node)` -ufbx_abi_data const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT]; - -// Version of the source file, comparable to `UFBX_HEADER_VERSION` -ufbx_abi_data const uint32_t ufbx_source_version; - - -// Practically always `true` (see below), if not you need to be careful with threads. -// -// Guaranteed to be `true` in _any_ of the following conditions: -// - ufbx.c has been compiled using: GCC / Clang / MSVC / ICC / EMCC / TCC -// - ufbx.c has been compiled as C++11 or later -// - ufbx.c has been compiled as C11 or later with `` support -// -// If `false` you can't call the following functions concurrently: -// ufbx_evaluate_scene() -// ufbx_free_scene() -// ufbx_subdivide_mesh() -// ufbx_tessellate_nurbs_surface() -// ufbx_free_mesh() -ufbx_abi bool ufbx_is_thread_safe(void); - -// Load a scene from a `size` byte memory buffer at `data` -ufbx_abi ufbx_scene *ufbx_load_memory( - const void *data, size_t data_size, - const ufbx_load_opts *opts, ufbx_error *error); - -// Load a scene by opening a file named `filename` -ufbx_abi ufbx_scene *ufbx_load_file( - const char *filename, - const ufbx_load_opts *opts, ufbx_error *error); -ufbx_abi ufbx_scene *ufbx_load_file_len( - const char *filename, size_t filename_len, - const ufbx_load_opts *opts, ufbx_error *error); - -// Load a scene by reading from an `FILE *file` stream -// NOTE: `file` is passed as a `void` pointer to avoid including -ufbx_abi ufbx_scene *ufbx_load_stdio( - void *file, - const ufbx_load_opts *opts, ufbx_error *error); - -// Load a scene by reading from an `FILE *file` stream with a prefix -// NOTE: `file` is passed as a `void` pointer to avoid including -ufbx_abi ufbx_scene *ufbx_load_stdio_prefix( - void *file, - const void *prefix, size_t prefix_size, - const ufbx_load_opts *opts, ufbx_error *error); - -// Load a scene from a user-specified stream -ufbx_abi ufbx_scene *ufbx_load_stream( - const ufbx_stream *stream, - const ufbx_load_opts *opts, ufbx_error *error); - -// Load a scene from a user-specified stream with a prefix -ufbx_abi ufbx_scene *ufbx_load_stream_prefix( - const ufbx_stream *stream, - const void *prefix, size_t prefix_size, - const ufbx_load_opts *opts, ufbx_error *error); - -// Free a previously loaded or evaluated scene -ufbx_abi void ufbx_free_scene(ufbx_scene *scene); - -// Increment `scene` refcount -ufbx_abi void ufbx_retain_scene(ufbx_scene *scene); - -// Format a textual description of `error`. -// Always produces a NULL-terminated string to `char dst[dst_size]`, truncating if -// necessary. Returns the number of characters written not including the NULL terminator. -ufbx_abi size_t ufbx_format_error(char *dst, size_t dst_size, const ufbx_error *error); - -// Query - -// Find a property `name` from `props`, returns `NULL` if not found. -// Searches through `ufbx_props.defaults` as well. -ufbx_abi ufbx_prop *ufbx_find_prop_len(const ufbx_props *props, const char *name, size_t name_len); -ufbx_abi ufbx_prop *ufbx_find_prop(const ufbx_props *props, const char *name); - -// Utility functions for finding the value of a property, returns `def` if not found. -// NOTE: For `ufbx_string` you need to ensure the lifetime of the default is -// sufficient as no copy is made. -ufbx_abi ufbx_real ufbx_find_real_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_real def); -ufbx_abi ufbx_real ufbx_find_real(const ufbx_props *props, const char *name, ufbx_real def); -ufbx_abi ufbx_vec3 ufbx_find_vec3_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_vec3 def); -ufbx_abi ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, const char *name, ufbx_vec3 def); -ufbx_abi int64_t ufbx_find_int_len(const ufbx_props *props, const char *name, size_t name_len, int64_t def); -ufbx_abi int64_t ufbx_find_int(const ufbx_props *props, const char *name, int64_t def); -ufbx_abi bool ufbx_find_bool_len(const ufbx_props *props, const char *name, size_t name_len, bool def); -ufbx_abi bool ufbx_find_bool(const ufbx_props *props, const char *name, bool def); -ufbx_abi ufbx_string ufbx_find_string_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_string def); -ufbx_abi ufbx_string ufbx_find_string(const ufbx_props *props, const char *name, ufbx_string def); -ufbx_abi ufbx_blob ufbx_find_blob_len(const ufbx_props *props, const char *name, size_t name_len, ufbx_blob def); -ufbx_abi ufbx_blob ufbx_find_blob(const ufbx_props *props, const char *name, ufbx_blob def); - -// Find property in `props` with concatenated `parts[num_parts]`. -ufbx_abi ufbx_prop *ufbx_find_prop_concat(const ufbx_props *props, const ufbx_string *parts, size_t num_parts); - -// Get an element connected to a property. -ufbx_abi ufbx_element *ufbx_get_prop_element(const ufbx_element *element, const ufbx_prop *prop, ufbx_element_type type); - -// Find an element connected to a property by name. -ufbx_abi ufbx_element *ufbx_find_prop_element_len(const ufbx_element *element, const char *name, size_t name_len, ufbx_element_type type); -ufbx_abi ufbx_element *ufbx_find_prop_element(const ufbx_element *element, const char *name, ufbx_element_type type); - -// Find any element of type `type` in `scene` by `name`. -// For example if you want to find `ufbx_material` named `Mat`: -// (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Mat"); -ufbx_abi ufbx_element *ufbx_find_element_len(const ufbx_scene *scene, ufbx_element_type type, const char *name, size_t name_len); -ufbx_abi ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, const char *name); - -// Find node in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_NODE)`). -ufbx_abi ufbx_node *ufbx_find_node_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_abi ufbx_node *ufbx_find_node(const ufbx_scene *scene, const char *name); - -// Find an animation stack in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_ANIM_STACK)`) -ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const char *name); - -// Find a material in `scene` by `name` (shorthand for `ufbx_find_element(UFBX_ELEMENT_MATERIAL)`). -ufbx_abi ufbx_material *ufbx_find_material_len(const ufbx_scene *scene, const char *name, size_t name_len); -ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name); - -// Find a single animated property `prop` of `element` in `layer`. -// Returns `NULL` if not found. -ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop_len(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop, size_t prop_len); -ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop); - -// Find all animated properties of `element` in `layer`. -ufbx_abi ufbx_anim_prop_list ufbx_find_anim_props(const ufbx_anim_layer *layer, const ufbx_element *element); - -// Get a matrix that transforms normals in the same way as Autodesk software. -// NOTE: The resulting normals are slightly incorrect as this function deliberately -// inverts geometric transformation wrong. For better results use -// `ufbx_matrix_for_normals(&node->geometry_to_world)`. -ufbx_abi ufbx_matrix ufbx_get_compatible_matrix_for_normals(const ufbx_node *node); - -// Utility - -// Decompress a DEFLATE compressed buffer. -// Returns the decompressed size or a negative error code (see source for details). -// NOTE: You must supply a valid `retain` with `ufbx_inflate_retain.initialized == false` -// but the rest can be uninitialized. -ufbx_abi ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain); - -// Same as `ufbx_open_file()` but compatible with the callback in `ufbx_open_file_fn`. -// The `user` parameter is actually not used here. -ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info); - -// Open a `ufbx_stream` from a file. -// Use `path_len == SIZE_MAX` for NULL terminated string. -ufbx_abi bool ufbx_open_file(ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); -ufbx_unsafe ufbx_abi bool ufbx_open_file_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const char *path, size_t path_len, const ufbx_open_file_opts *opts, ufbx_error *error); - -// NOTE: Uses the default ufbx allocator! -ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); -ufbx_unsafe ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_file_context ctx, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error); - -// Animation evaluation - -// Evaluate a single animation `curve` at a `time`. -// Returns `default_value` only if `curve == NULL` or it has no keyframes. -ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value); -ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags); - -// Evaluate a value from bundled animation curves. -ufbx_abi ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time); -ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time); -ufbx_abi ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); -ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); - -// Evaluate an animated property `name` from `element` at `time`. -// NOTE: If the property is not found it will have the flag `UFBX_PROP_FLAG_NOT_FOUND`. -ufbx_abi ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time); -ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time); -ufbx_abi ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags); -ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags); - -// Evaluate all _animated_ properties of `element`. -// HINT: This function returns an `ufbx_props` structure with the original properties as -// `ufbx_props.defaults`. This lets you use `ufbx_find_prop/value()` for the results. -ufbx_abi ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size); -ufbx_abi ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags); - -// Flags to control `ufbx_evaluate_transform_flags()`. -typedef enum ufbx_transform_flags UFBX_FLAG_REPR { - - // Ignore parent scale helper. - UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER = 0x1, - - // Ignore componentwise scale. - // Note that if you don't specify this, ufbx will have to potentially - // evaluate the entire parent chain in the worst case. - UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE = 0x2, - - // Require explicit components - UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES = 0x4, - - // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.translation`. - UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION = 0x10, - // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.rotation`. - UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION = 0x20, - // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.scale`. - UFBX_TRANSFORM_FLAG_INCLUDE_SCALE = 0x40, - - // Do not extrapolate keyframes. - // See `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. - UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION = 0x80, - - UFBX_FLAG_FORCE_WIDTH(UFBX_TRANSFORM_FLAGS) -} ufbx_transform_flags; - -// Evaluate the animated transform of a node given a time. -// The returned transform is the local transform of the node (ie. relative to the parent), -// comparable to `ufbx_node.local_transform`. -ufbx_abi ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time); -ufbx_abi ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags); - -// Evaluate the blend shape weight of a blend channel. -// NOTE: Return value uses `1.0` for full weight, instead of `100.0` that the internal property `UFBX_Weight` uses. -ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time); -ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags); - -// Evaluate the whole `scene` at a specific `time` in the animation `anim`. -// The returned scene behaves as if it had been exported at a specific time -// in the specified animation, except that animated elements' properties contain -// only the animated values, the original ones are in `props->defaults`. -// -// NOTE: The returned scene refers to the original `scene` so the original -// scene cannot be freed until all evaluated scenes are freed. -ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error); - -// Create a custom animation descriptor. -// `ufbx_anim_opts` is used to specify animation layers and weights. -// HINT: You can also leave `ufbx_anim_opts.layer_ids[]` empty and only specify -// overrides to evaluate the scene with different properties or local transforms. -ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error); - -// Free an animation returned by `ufbx_create_anim()`. -ufbx_abi void ufbx_free_anim(ufbx_anim *anim); - -// Increase the animation reference count. -ufbx_abi void ufbx_retain_anim(ufbx_anim *anim); - -// Animation baking - -// "Bake" an animation to linearly interpolated keyframes. -// Composites the FBX transformation chain into quaternion rotations. -ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_anim *anim, const ufbx_bake_opts *opts, ufbx_error *error); - -ufbx_abi void ufbx_retain_baked_anim(ufbx_baked_anim *bake); -ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake); - -ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id); -ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node); - -ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id); -ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element); - -// Evaluate baked animation `keyframes` at `time`. -// Internally linearly interpolates between two adjacent keyframes. -// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. -ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time); - -// Evaluate baked animation `keyframes` at `time`. -// Internally spherically interpolates (`ufbx_quat_slerp()`) between two adjacent keyframes. -// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. -ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time); - -// Poses - -// Retrieve the bone pose for `node`. -// Returns `NULL` if the pose does not contain `node`. -ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node); - -// Materials - -// Find a texture for a given material FBX property. -ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len); -ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name); - -// Find a texture for a given shader property. -ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len); -ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name); - -// Map from a shader property to material property. -ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len); -ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name); - -// Find an input in a shader texture. -ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len); -ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name); - -// Math - -// Returns `true` if `axes` forms a valid coordinate space. -ufbx_abi bool ufbx_coordinate_axes_valid(ufbx_coordinate_axes axes); - -// Vector math utility functions. -ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v); - -// Quaternion math utility functions. -ufbx_abi ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b); -ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b); -ufbx_abi ufbx_quat ufbx_quat_normalize(ufbx_quat q); -ufbx_abi ufbx_quat ufbx_quat_fix_antipodal(ufbx_quat q, ufbx_quat reference); -ufbx_abi ufbx_quat ufbx_quat_slerp(ufbx_quat a, ufbx_quat b, ufbx_real t); -ufbx_abi ufbx_vec3 ufbx_quat_rotate_vec3(ufbx_quat q, ufbx_vec3 v); -ufbx_abi ufbx_vec3 ufbx_quat_to_euler(ufbx_quat q, ufbx_rotation_order order); -ufbx_abi ufbx_quat ufbx_euler_to_quat(ufbx_vec3 v, ufbx_rotation_order order); - -// Matrix math utility functions. -ufbx_abi ufbx_matrix ufbx_matrix_mul(const ufbx_matrix *a, const ufbx_matrix *b); -ufbx_abi ufbx_real ufbx_matrix_determinant(const ufbx_matrix *m); -ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m); - -// Get a matrix that can be used to transform geometry normals. -// NOTE: You must normalize the normals after transforming them with this matrix, -// eg. using `ufbx_vec3_normalize()`. -// NOTE: This function flips the normals if the determinant is negative. -ufbx_abi ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m); - -// Matrix transformation utilities. -ufbx_abi ufbx_vec3 ufbx_transform_position(const ufbx_matrix *m, ufbx_vec3 v); -ufbx_abi ufbx_vec3 ufbx_transform_direction(const ufbx_matrix *m, ufbx_vec3 v); - -// Conversions between `ufbx_matrix` and `ufbx_transform`. -ufbx_abi ufbx_matrix ufbx_transform_to_matrix(const ufbx_transform *t); -ufbx_abi ufbx_transform ufbx_matrix_to_transform(const ufbx_matrix *m); - -// Skinning - -// Get a matrix representing the deformation for a single vertex. -// Returns `fallback` if the vertex is not skinned. -ufbx_abi ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic *panic, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback); -ufbx_inline ufbx_matrix ufbx_get_skin_vertex_matrix(const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) { - return ufbx_catch_get_skin_vertex_matrix(NULL, skin, vertex, fallback); -} - -// Resolve the index into `ufbx_blend_shape.position_offsets[]` given a vertex. -// Returns `UFBX_NO_INDEX` if the vertex is not included in the blend shape. -ufbx_abi uint32_t ufbx_get_blend_shape_offset_index(const ufbx_blend_shape *shape, size_t vertex); - -// Get the offset for a given vertex in the blend shape. -// Returns `ufbx_zero_vec3` if the vertex is not a included in the blend shape. -ufbx_abi ufbx_vec3 ufbx_get_blend_shape_vertex_offset(const ufbx_blend_shape *shape, size_t vertex); - -// Get the _current_ blend offset given a blend deformer. -// NOTE: This depends on the current animated blend weight of the deformer. -ufbx_abi ufbx_vec3 ufbx_get_blend_vertex_offset(const ufbx_blend_deformer *blend, size_t vertex); - -// Apply the blend shape with `weight` to given vertices. -ufbx_abi void ufbx_add_blend_shape_vertex_offsets(const ufbx_blend_shape *shape, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); - -// Apply the blend deformer with `weight` to given vertices. -// NOTE: This depends on the current animated blend weight of the deformer. -ufbx_abi void ufbx_add_blend_vertex_offsets(const ufbx_blend_deformer *blend, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); - -// Curves/surfaces - -// Low-level utility to evaluate NURBS the basis functions. -ufbx_abi size_t ufbx_evaluate_nurbs_basis(const ufbx_nurbs_basis *basis, ufbx_real u, ufbx_real *weights, size_t num_weights, ufbx_real *derivatives, size_t num_derivatives); - -// Evaluate a point on a NURBS curve given the parameter `u`. -ufbx_abi ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nurbs_curve *curve, ufbx_real u); - -// Evaluate a point on a NURBS surface given the parameter `u` and `v`. -ufbx_abi ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v); - -// Tessellate a NURBS curve into a polyline. -ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error); - -// Tessellate a NURBS surface into a mesh. -ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surface, const ufbx_tessellate_surface_opts *opts, ufbx_error *error); - -// Free a line returned by `ufbx_tessellate_nurbs_curve()`. -ufbx_abi void ufbx_free_line_curve(ufbx_line_curve *curve); - -// Increase the refcount of the line. -ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *curve); - -// Mesh Topology - -// Find the face that contains a given `index`. -// Returns `UFBX_NO_INDEX` if out of bounds. -ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index); - -// Triangulate a mesh face, returning the number of triangles. -// NOTE: You need to space for `(face.num_indices - 2) * 3 - 1` indices! -// HINT: Using `ufbx_mesh.max_face_triangles * 3` is always safe. -ufbx_abi uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); -ufbx_abi uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); - -// Generate the half-edge representation of `mesh` to `topo[mesh->num_indices]` -ufbx_abi void ufbx_catch_compute_topology(ufbx_panic *panic, const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); -ufbx_abi void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *topo, size_t num_topo); - -// Get the next/previous edge around a vertex -// NOTE: Does not return the half-edge on the opposite side (ie. `topo[index].twin`) - -// Get the next half-edge in `topo`. -ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); -ufbx_abi uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); - -// Get the previous half-edge in `topo`. -ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); -ufbx_abi uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); - -// Calculate a normal for a given face. -// The returned normal is weighted by face area. -ufbx_abi ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face); -ufbx_abi ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face); - -// Generate indices for normals from the topology. -// Respects smoothing groups. -ufbx_abi size_t ufbx_catch_generate_normal_mapping(ufbx_panic *panic, const ufbx_mesh *mesh, - const ufbx_topo_edge *topo, size_t num_topo, - uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); -ufbx_abi size_t ufbx_generate_normal_mapping(const ufbx_mesh *mesh, - const ufbx_topo_edge *topo, size_t num_topo, - uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); - -// Compute normals given normal indices. -// You can use `ufbx_generate_normal_mapping()` to generate the normal indices. -ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, - const uint32_t *normal_indices, size_t num_normal_indices, - ufbx_vec3 *normals, size_t num_normals); -ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, - const uint32_t *normal_indices, size_t num_normal_indices, - ufbx_vec3 *normals, size_t num_normals); - -// Subdivide a mesh using the Catmull-Clark subdivision `level` times. -ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error); - -// Free a mesh returned from `ufbx_subdivide_mesh()` or `ufbx_tessellate_nurbs_surface()`. -ufbx_abi void ufbx_free_mesh(ufbx_mesh *mesh); - -// Increase the mesh reference count. -ufbx_abi void ufbx_retain_mesh(ufbx_mesh *mesh); - -// Geometry caches - -// Load geometry cache information from a file. -// As geometry caches can be massive, this does not actually read the data, but -// only seeks through the files to form the metadata. -ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache( - const char *filename, - const ufbx_geometry_cache_opts *opts, ufbx_error *error); -ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( - const char *filename, size_t filename_len, - const ufbx_geometry_cache_opts *opts, ufbx_error *error); - -// Free a geometry cache returned from `ufbx_load_geometry_cache()`. -ufbx_abi void ufbx_free_geometry_cache(ufbx_geometry_cache *cache); -// Increase the geometry cache reference count. -ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache); - -// Read a frame from a geometry cache. -ufbx_abi size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); -ufbx_abi size_t ufbx_read_geometry_cache_vec3(const ufbx_cache_frame *frame, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); -// Sample the a geometry cache channel, linearly blending between adjacent frames. -ufbx_abi size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); -ufbx_abi size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channel, double time, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); - -// DOM - -// Find a DOM node given a name. -ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len); -ufbx_abi ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name); - -// Utility - -// Generate an index buffer for a flat vertex buffer. -// `streams` specifies one or more vertex data arrays, each stream must contain `num_indices` vertices. -// This function compacts the data within `streams` in-place, writing the deduplicated indices to `indices`. -ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error); - -// Thread pool - -// Run a single thread pool task. -// See `ufbx_thread_pool_run_fn` for more information. -ufbx_unsafe ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx, uint32_t index); - -// Get or set an arbitrary user pointer for the thread pool context. -// `ufbx_thread_pool_get_user_ptr()` returns `NULL` if unset. -ufbx_unsafe ufbx_abi void ufbx_thread_pool_set_user_ptr(ufbx_thread_pool_context ctx, void *user_ptr); -ufbx_unsafe ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx); - -// -- Inline API - -// Utility functions for reading geometry data for a single index. -ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index); -ufbx_abi ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index); -ufbx_abi ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); -ufbx_abi ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index); - -// Utility functions for reading geometry data for a single index. -ufbx_inline ufbx_real ufbx_get_vertex_real(const ufbx_vertex_real *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } -ufbx_inline ufbx_vec2 ufbx_get_vertex_vec2(const ufbx_vertex_vec2 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } -ufbx_inline ufbx_vec3 ufbx_get_vertex_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } -ufbx_inline ufbx_vec4 ufbx_get_vertex_vec4(const ufbx_vertex_vec4 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } - -ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); -ufbx_inline ufbx_real ufbx_get_vertex_w_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values_w.count > 0 ? v->values_w.data[(int32_t)v->indices.data[index]] : 0.0f; } - -// Functions for converting an untyped `ufbx_element` to a concrete type. -// Returns `NULL` if the element is not that type. -ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element); -ufbx_abi ufbx_node *ufbx_as_node(const ufbx_element *element); -ufbx_abi ufbx_mesh *ufbx_as_mesh(const ufbx_element *element); -ufbx_abi ufbx_light *ufbx_as_light(const ufbx_element *element); -ufbx_abi ufbx_camera *ufbx_as_camera(const ufbx_element *element); -ufbx_abi ufbx_bone *ufbx_as_bone(const ufbx_element *element); -ufbx_abi ufbx_empty *ufbx_as_empty(const ufbx_element *element); -ufbx_abi ufbx_line_curve *ufbx_as_line_curve(const ufbx_element *element); -ufbx_abi ufbx_nurbs_curve *ufbx_as_nurbs_curve(const ufbx_element *element); -ufbx_abi ufbx_nurbs_surface *ufbx_as_nurbs_surface(const ufbx_element *element); -ufbx_abi ufbx_nurbs_trim_surface *ufbx_as_nurbs_trim_surface(const ufbx_element *element); -ufbx_abi ufbx_nurbs_trim_boundary *ufbx_as_nurbs_trim_boundary(const ufbx_element *element); -ufbx_abi ufbx_procedural_geometry *ufbx_as_procedural_geometry(const ufbx_element *element); -ufbx_abi ufbx_stereo_camera *ufbx_as_stereo_camera(const ufbx_element *element); -ufbx_abi ufbx_camera_switcher *ufbx_as_camera_switcher(const ufbx_element *element); -ufbx_abi ufbx_marker *ufbx_as_marker(const ufbx_element *element); -ufbx_abi ufbx_lod_group *ufbx_as_lod_group(const ufbx_element *element); -ufbx_abi ufbx_skin_deformer *ufbx_as_skin_deformer(const ufbx_element *element); -ufbx_abi ufbx_skin_cluster *ufbx_as_skin_cluster(const ufbx_element *element); -ufbx_abi ufbx_blend_deformer *ufbx_as_blend_deformer(const ufbx_element *element); -ufbx_abi ufbx_blend_channel *ufbx_as_blend_channel(const ufbx_element *element); -ufbx_abi ufbx_blend_shape *ufbx_as_blend_shape(const ufbx_element *element); -ufbx_abi ufbx_cache_deformer *ufbx_as_cache_deformer(const ufbx_element *element); -ufbx_abi ufbx_cache_file *ufbx_as_cache_file(const ufbx_element *element); -ufbx_abi ufbx_material *ufbx_as_material(const ufbx_element *element); -ufbx_abi ufbx_texture *ufbx_as_texture(const ufbx_element *element); -ufbx_abi ufbx_video *ufbx_as_video(const ufbx_element *element); -ufbx_abi ufbx_shader *ufbx_as_shader(const ufbx_element *element); -ufbx_abi ufbx_shader_binding *ufbx_as_shader_binding(const ufbx_element *element); -ufbx_abi ufbx_anim_stack *ufbx_as_anim_stack(const ufbx_element *element); -ufbx_abi ufbx_anim_layer *ufbx_as_anim_layer(const ufbx_element *element); -ufbx_abi ufbx_anim_value *ufbx_as_anim_value(const ufbx_element *element); -ufbx_abi ufbx_anim_curve *ufbx_as_anim_curve(const ufbx_element *element); -ufbx_abi ufbx_display_layer *ufbx_as_display_layer(const ufbx_element *element); -ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element); -ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element); -ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element); -ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element); -ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element); -ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element); -ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element); -ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element); - -// Functions for interfacing with DOM lists -ufbx_abi bool ufbx_dom_is_array(const ufbx_dom_node *node); -ufbx_abi size_t ufbx_dom_array_size(const ufbx_dom_node *node); -ufbx_abi ufbx_int32_list ufbx_dom_as_int32_list(const ufbx_dom_node *node); -ufbx_abi ufbx_int64_list ufbx_dom_as_int64_list(const ufbx_dom_node *node); -ufbx_abi ufbx_float_list ufbx_dom_as_float_list(const ufbx_dom_node *node); -ufbx_abi ufbx_double_list ufbx_dom_as_double_list(const ufbx_dom_node *node); -ufbx_abi ufbx_real_list ufbx_dom_as_real_list(const ufbx_dom_node *node); -ufbx_abi ufbx_blob_list ufbx_dom_as_blob_list(const ufbx_dom_node *node); - -#ifdef __cplusplus -} -#endif - -// bindgen-disable - -#if UFBX_CPP11 - -struct ufbx_string_view { - const char *data; - size_t length; - - ufbx_string_view() : data(nullptr), length(0) { } - ufbx_string_view(const char *data_, size_t length_) : data(data_), length(length_) { } - UFBX_CONVERSION_TO_IMPL(ufbx_string_view) -}; - -ufbx_inline ufbx_scene *ufbx_load_file(ufbx_string_view filename, const ufbx_load_opts *opts, ufbx_error *error) { return ufbx_load_file_len(filename.data, filename.length, opts, error); } -ufbx_inline ufbx_prop *ufbx_find_prop(const ufbx_props *props, ufbx_string_view name) { return ufbx_find_prop_len(props, name.data, name.length); } -ufbx_inline ufbx_real ufbx_find_real(const ufbx_props *props, ufbx_string_view name, ufbx_real def) { return ufbx_find_real_len(props, name.data, name.length, def); } -ufbx_inline ufbx_vec3 ufbx_find_vec3(const ufbx_props *props, ufbx_string_view name, ufbx_vec3 def) { return ufbx_find_vec3_len(props, name.data, name.length, def); } -ufbx_inline int64_t ufbx_find_int(const ufbx_props *props, ufbx_string_view name, int64_t def) { return ufbx_find_int_len(props, name.data, name.length, def); } -ufbx_inline bool ufbx_find_bool(const ufbx_props *props, ufbx_string_view name, bool def) { return ufbx_find_bool_len(props, name.data, name.length, def); } -ufbx_inline ufbx_string ufbx_find_string(const ufbx_props *props, ufbx_string_view name, ufbx_string def) { return ufbx_find_string_len(props, name.data, name.length, def); } -ufbx_inline ufbx_blob ufbx_find_blob(const ufbx_props *props, ufbx_string_view name, ufbx_blob def) { return ufbx_find_blob_len(props, name.data, name.length, def); } -ufbx_inline ufbx_element *ufbx_find_prop_element(const ufbx_element *element, ufbx_string_view name, ufbx_element_type type) { return ufbx_find_prop_element_len(element, name.data, name.length, type); } -ufbx_inline ufbx_element *ufbx_find_element(const ufbx_scene *scene, ufbx_element_type type, ufbx_string_view name) { return ufbx_find_element_len(scene, type, name.data, name.length); } -ufbx_inline ufbx_node *ufbx_find_node(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_node_len(scene, name.data, name.length); } -ufbx_inline ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_anim_stack_len(scene, name.data, name.length); } -ufbx_inline ufbx_material *ufbx_find_material(const ufbx_scene *scene, ufbx_string_view name) { return ufbx_find_material_len(scene, name.data, name.length); } -ufbx_inline ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, ufbx_string_view prop) { return ufbx_find_anim_prop_len(layer, element, prop.data, prop.length); } -ufbx_inline ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, ufbx_string_view name, double time) { return ufbx_evaluate_prop_len(anim, element, name.data, name.length, time); } -ufbx_inline ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, ufbx_string_view name) { return ufbx_find_prop_texture_len(material, name.data, name.length); } -ufbx_inline ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, ufbx_string_view name) { return ufbx_find_shader_prop_len(shader, name.data, name.length); } -ufbx_inline ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, ufbx_string_view name) { return ufbx_find_shader_prop_bindings_len(shader, name.data, name.length); } -ufbx_inline ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, ufbx_string_view name) { return ufbx_find_shader_texture_input_len(shader, name.data, name.length); } -ufbx_inline ufbx_geometry_cache *ufbx_load_geometry_cache(ufbx_string_view filename, const ufbx_geometry_cache_opts *opts, ufbx_error *error) { return ufbx_load_geometry_cache_len(filename.data, filename.length, opts, error); } -ufbx_inline ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, ufbx_string_view name) { return ufbx_dom_find_len(parent, name.data, name.length); } - -#endif - -#if UFBX_CPP11 - -template -struct ufbx_type_traits { enum { valid = 0 }; }; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_scene *ptr) { ufbx_retain_scene(ptr); } - static void free(ufbx_scene *ptr) { ufbx_free_scene(ptr); } -}; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_mesh *ptr) { ufbx_retain_mesh(ptr); } - static void free(ufbx_mesh *ptr) { ufbx_free_mesh(ptr); } -}; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_line_curve *ptr) { ufbx_retain_line_curve(ptr); } - static void free(ufbx_line_curve *ptr) { ufbx_free_line_curve(ptr); } -}; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_geometry_cache *ptr) { ufbx_retain_geometry_cache(ptr); } - static void free(ufbx_geometry_cache *ptr) { ufbx_free_geometry_cache(ptr); } -}; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_anim *ptr) { ufbx_retain_anim(ptr); } - static void free(ufbx_anim *ptr) { ufbx_free_anim(ptr); } -}; - -template<> struct ufbx_type_traits { - enum { valid = 1 }; - static void retain(ufbx_baked_anim *ptr) { ufbx_retain_baked_anim(ptr); } - static void free(ufbx_baked_anim *ptr) { ufbx_free_baked_anim(ptr); } -}; - -class ufbx_deleter { -public: - template - void operator()(T *ptr) const { - static_assert(ufbx_type_traits::valid, "ufbx_deleter() unsupported for type"); - ufbx_type_traits::free(ptr); - } -}; - -// RAII wrapper over refcounted ufbx types. - -// Behaves like `std::unique_ptr`. -template -class ufbx_unique_ptr { - T *ptr; - using traits = ufbx_type_traits; - static_assert(ufbx_type_traits::valid, "ufbx_unique_ptr unsupported for type"); -public: - ufbx_unique_ptr() noexcept : ptr(nullptr) { } - explicit ufbx_unique_ptr(T *ptr_) noexcept : ptr(ptr_) { } - ufbx_unique_ptr(ufbx_unique_ptr &&ref) noexcept : ptr(ref.ptr) { ref.ptr = nullptr; } - ~ufbx_unique_ptr() { traits::free(ptr); } - - ufbx_unique_ptr &operator=(ufbx_unique_ptr &&ref) noexcept { - if (&ref == this) return *this; - ptr = ref.ptr; - ref.ptr = nullptr; - return *this; - } - - void reset(T *new_ptr=nullptr) noexcept { - traits::free(ptr); - ptr = new_ptr; - } - - void swap(ufbx_unique_ptr &ref) noexcept { - T *tmp = ptr; - ptr = ref.ptr; - ref.ptr = tmp; - } - - T &operator*() const noexcept { return *ptr; } - T *operator->() const noexcept { return ptr; } - T *get() const noexcept { return ptr; } - explicit operator bool() const noexcept { return ptr != nullptr; } -}; - -// Behaves like `std::shared_ptr` except uses ufbx's internal reference counting, -// so it is half the size of a standard `shared_ptr` but might be marginally slower. -template -class ufbx_shared_ptr { - T *ptr; - using traits = ufbx_type_traits; - static_assert(ufbx_type_traits::valid, "ufbx_shared_ptr unsupported for type"); -public: - - ufbx_shared_ptr() noexcept : ptr(nullptr) { } - explicit ufbx_shared_ptr(T *ptr_) noexcept : ptr(ptr_) { } - ufbx_shared_ptr(const ufbx_shared_ptr &ref) noexcept : ptr(ref.ptr) { traits::retain(ref.ptr); } - ufbx_shared_ptr(ufbx_shared_ptr &&ref) noexcept : ptr(ref.ptr) { ref.ptr = nullptr; } - ~ufbx_shared_ptr() { traits::free(ptr); } - - ufbx_shared_ptr &operator=(const ufbx_shared_ptr &ref) noexcept { - if (&ref == this) return *this; - traits::free(ptr); - traits::retain(ref.ptr); - ptr = ref.ptr; - return *this; - } - - ufbx_shared_ptr &operator=(ufbx_shared_ptr &&ref) noexcept { - if (&ref == this) return *this; - ptr = ref.ptr; - ref.ptr = nullptr; - return *this; - } - - void reset(T *new_ptr=nullptr) noexcept { - traits::free(ptr); - ptr = new_ptr; - } - - void swap(ufbx_shared_ptr &ref) noexcept { - T *tmp = ptr; - ptr = ref.ptr; - ref.ptr = tmp; - } - - T &operator*() const noexcept { return *ptr; } - T *operator->() const noexcept { return ptr; } - T *get() const noexcept { return ptr; } - explicit operator bool() const noexcept { return ptr != nullptr; } -}; - -#endif -// bindgen-enable - -// -- Properties - -// Names of common properties in `ufbx_props`. -// Some of these differ from ufbx interpretations. - -// Local translation. -// Used by: `ufbx_node` -#define UFBX_Lcl_Translation "Lcl Translation" - -// Local rotation expressed in Euler degrees. -// Used by: `ufbx_node` -// The rotation order is defined by the `UFBX_RotationOrder` property. -#define UFBX_Lcl_Rotation "Lcl Rotation" - -// Local scaling factor, 3D vector. -// Used by: `ufbx_node` -#define UFBX_Lcl_Scaling "Lcl Scaling" - -// Euler rotation interpretation, used by `UFBX_Lcl_Rotation`. -// Used by: `ufbx_node`, enum value `ufbx_rotation_order`. -#define UFBX_RotationOrder "RotationOrder" - -// Scaling pivot: point around which scaling is performed. -// Used by: `ufbx_node`. -#define UFBX_ScalingPivot "ScalingPivot" - -// Scaling pivot: point around which rotation is performed. -// Used by: `ufbx_node`. -#define UFBX_RotationPivot "RotationPivot" - -// Scaling offset: translation added after scaling is performed. -// Used by: `ufbx_node`. -#define UFBX_ScalingOffset "ScalingOffset" - -// Rotation offset: translation added after rotation is performed. -// Used by: `ufbx_node`. -#define UFBX_RotationOffset "RotationOffset" - -// Pre-rotation: Rotation applied _after_ `UFBX_Lcl_Rotation`. -// Used by: `ufbx_node`. -// Affected by `UFBX_RotationPivot` but not `UFBX_RotationOrder`. -#define UFBX_PreRotation "PreRotation" - -// Post-rotation: Rotation applied _before_ `UFBX_Lcl_Rotation`. -// Used by: `ufbx_node`. -// Affected by `UFBX_RotationPivot` but not `UFBX_RotationOrder`. -#define UFBX_PostRotation "PostRotation" - -// Controls whether the node should be displayed or not. -// Used by: `ufbx_node`. -#define UFBX_Visibility "Visibility" - -// Weight of an animation layer in percentage (100.0 being full). -// Used by: `ufbx_anim_layer`. -#define UFBX_Weight "Weight" - -// Blend shape deformation weight (100.0 being full). -// Used by: `ufbx_blend_channel`. -#define UFBX_DeformPercent "DeformPercent" - -#if defined(_MSC_VER) - #pragma warning(pop) -#elif defined(__clang__) - #pragma clang diagnostic pop -#elif defined(__GNUC__) - #pragma GCC diagnostic pop -#endif - -#endif diff --git a/thirdparty/ufbx.lua b/thirdparty/ufbx.lua index 9b7138900..2c57932b0 100644 --- a/thirdparty/ufbx.lua +++ b/thirdparty/ufbx.lua @@ -31,8 +31,8 @@ function ufbx:project() language "C++" files { - path.join(folder, "ufbx/ufbx.h"), - path.join(folder, "ufbx/ufbx.c") + path.join(folder, "ufbx/*.h"), + path.join(folder, "ufbx/*.c") } self:include(includes) From 8007bb54a0ba9c01ddde14a22540b85e49510883 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Tue, 4 Nov 2025 13:55:02 +0800 Subject: [PATCH 045/123] Fixed missing parameter in the ufbx premake script causing the build to fail. --- thirdparty/ufbx.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/ufbx.lua b/thirdparty/ufbx.lua index 2c57932b0..992cbda25 100644 --- a/thirdparty/ufbx.lua +++ b/thirdparty/ufbx.lua @@ -8,7 +8,7 @@ function ufbx:include(includes) end end -function ufbx:link() +function ufbx:link(links) links:add(self:name()) end From b7162d6290ad532f1d20ed830f7016206110cac5 Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Tue, 4 Nov 2025 14:05:19 +0800 Subject: [PATCH 046/123] Undo changes to JsonMaterialLoader as they are no longer needed. --- .../Material/JsonMaterialLoader.cpp.template | 88 +++++++++---------- .../Material/JsonMaterialLoader.h.template | 8 +- 2 files changed, 42 insertions(+), 54 deletions(-) diff --git a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template index cf8a6b495..1be82e990 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template @@ -52,39 +52,50 @@ namespace class JsonLoader { public: - JsonLoader(MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - : m_memory(memory), + JsonLoader(std::istream& stream, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + : m_stream(stream), + m_memory(memory), m_context(context), m_registration(registration) { } - bool Load(json& jRoot, Material& material) const + bool Load(Material& material) const { - std::string type; - unsigned version; - - jRoot.at("_type").get_to(type); - jRoot.at("_version").get_to(version); - - if (type != "material" || version != 1u) - { - con::error("Tried to load material \"{}\" but did not find expected type material of version 1", material.info.name); - return false; - } + try + { + const auto jRoot = json::parse(m_stream); + std::string type; + unsigned version; + + jRoot.at("_type").get_to(type); + jRoot.at("_version").get_to(version); + + if (type != "material" || version != 1u) + { + con::error("Tried to load material \"{}\" but did not find expected type material of version 1", material.info.name); + return false; + } #ifndef FEATURE_T6 // T6 did not have this check in version 1, so to stay backwards compatible, let it stay that way - std::string game; - jRoot.at("_game").get_to(game); - if (game != GAME_LOWER) + std::string game; + jRoot.at("_game").get_to(game); + if (game != GAME_LOWER) + { + con::error("Tried to load material \"{}\" but \"_game\" did not find expected type value {}", material.info.name, GAME_LOWER); + return false; + } +#endif + + const auto jMaterial = jRoot.get(); + return CreateMaterialFromJson(jMaterial, material); + } + catch (const json::exception& e) { - con::error("Tried to load material \"{}\" but \"_game\" did not find expected type value {}", material.info.name, GAME_LOWER); - return false; + con::error("Failed to parse json of material: {}", e.what()); } -#endif - const auto jMaterial = jRoot.get(); - return CreateMaterialFromJson(jMaterial, material); + return false; } private: @@ -508,6 +519,7 @@ namespace return true; } + std::istream& m_stream; MemoryManager& m_memory; AssetCreationContext& m_context; AssetRegistration& m_registration; @@ -517,28 +529,10 @@ namespace namespace GAME { bool LoadMaterialAsJson( - std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - { - const JsonLoader loader(memory, context, registration); - - try - { - auto jRoot = json::parse(stream); - - return loader.Load(jRoot, material); - } - catch (const json::exception& e) - { - con::error("Failed to parse json of material: {}", e.what()); - return false; - } - } - - bool LoadMaterialAsJson( - json& json, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) - { - const JsonLoader loader(memory, context, registration); - - return loader.Load(json, material); - } -} // namespace GAME + std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration) + { + const JsonLoader loader(stream, memory, context, registration); + + return loader.Load(material); + } +} // namespace GAME \ No newline at end of file diff --git a/src/ObjLoading/Material/JsonMaterialLoader.h.template b/src/ObjLoading/Material/JsonMaterialLoader.h.template index 3e3887e78..0de4f692f 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.h.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.h.template @@ -15,15 +15,9 @@ #include "Utils/MemoryManager.h" #include -#include - -using namespace nlohmann; namespace GAME { bool LoadMaterialAsJson( std::istream& stream, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration); - - bool LoadMaterialAsJson( - json& json, Material& material, MemoryManager& memory, AssetCreationContext& context, AssetRegistration& registration); -} // namespace GAME +} // namespace GAME \ No newline at end of file From e4f6e195f6f81adf5813b207165a7aadc159ec87 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:09:00 +0800 Subject: [PATCH 047/123] Moved coordinate conversion to BSP creator instead of the BSP linker, and added a console print of the maximum number of objects in a collision leaf. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 8 +++++--- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 7 ++++++- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h | 1 + src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 0a6e43938..92ea2e822 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -91,9 +91,11 @@ namespace uint32_t index = tempIndices[idxOfIndex]; ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex.pos.x = static_cast(transformedPos.x); - vertex.pos.y = static_cast(transformedPos.y); - vertex.pos.z = static_cast(transformedPos.z); + vec3_t blenderCoords; + blenderCoords.x = static_cast(transformedPos.x); + blenderCoords.y = static_cast(transformedPos.y); + blenderCoords.z = static_cast(transformedPos.z); + vertex.pos = BSPUtil::convertToBO2Coords(blenderCoords); if (surface.material.materialType == MATERIAL_TYPE_TEXTURE || surface.material.materialType == MATERIAL_TYPE_EMPTY) { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index b43b6e9a0..2cf1f7e68 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -218,6 +218,9 @@ namespace BSP assert(objectCount > 0); + if (objectCount > highestLeafObjectCount) + highestLeafObjectCount = objectCount; + // add the parent AABB node vec3_t parentMins; vec3_t parentMaxs; @@ -456,6 +459,8 @@ namespace BSP // The plane of each node have the same index for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; + + con::info("Highest leaf object count: {}", highestLeafObjectCount); } bool ClipMapLinker::loadPartitions(clipMap_t* clipMap, BSPData* bsp) @@ -471,7 +476,7 @@ namespace BSP clipMap->vertCount = static_cast(bsp->colWorld.vertices.size()); clipMap->verts = m_memory.Alloc(clipMap->vertCount); for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) - clipMap->verts[vertIdx] = BSPUtil::convertToBO2Coords(bsp->colWorld.vertices[vertIdx].pos); + clipMap->verts[vertIdx] = bsp->colWorld.vertices[vertIdx].pos; // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index ca5995577..3bfe65626 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -30,6 +30,7 @@ namespace BSP std::vector nodeVec; std::vector leafVec; std::vector AABBTreeVec; + size_t highestLeafObjectCount = 0; int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 9f7386a3b..e64be8fb5 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -23,7 +23,7 @@ namespace BSP BSPVertex& bspVertex = bsp->gfxWorld.vertices.at(vertIdx); GfxPackedWorldVertex* gfxVertex = &vertexBuffer[vertIdx]; - gfxVertex->xyz = BSPUtil::convertToBO2Coords(bspVertex.pos); + gfxVertex->xyz = bspVertex.pos; gfxVertex->color.packed = pack32::Vec4PackGfxColor(bspVertex.color.v); gfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(bspVertex.texCoord.v); gfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.normal.v); From 6cd89a31b21968e47c3abd5cea33f4bb23b82b84 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:13:55 +0800 Subject: [PATCH 048/123] Extended BSP creation to support X, Y and Z splitting and simplified clipmap BSP generation. --- src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp | 13 +++++- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 42 +++++-------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp index 61dccd929..c80920a23 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp @@ -86,8 +86,6 @@ namespace BSP splitTree(); } - // For simplicity, only split across the X and Z axis. - // It is unlikely that there are many layers to a map, and is instead mostly flat void BSPTree::splitTree() { std::unique_ptr front; @@ -105,6 +103,17 @@ namespace BSP node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); leaf = nullptr; } + else if (max.y - min.y > MAX_NODE_SIZE) + { + // split along the x axis + halfLength = (min.y + max.y) * 0.5f; + front = std::make_unique(min.x, halfLength, min.z, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, max.x, halfLength, max.z, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_Y, halfLength); + leaf = nullptr; + } else if (max.z - min.z > MAX_NODE_SIZE) { // split along the z axis diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 2cf1f7e68..3389a953a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -332,27 +332,20 @@ namespace BSP else { cplane_s plane; - + plane.dist = tree->node->distance; if (tree->node->axis == AXIS_X) { plane.normal = normalX; - plane.dist = tree->node->distance; + plane.type = 0; } - else + else if (tree->node->axis == AXIS_Y) { - // converting Z coords to BO2 negates the z coords and sets it to the y coord. - assert(tree->node->axis == AXIS_Z); plane.normal = normalY; - plane.dist = -tree->node->distance; - } - - if (plane.normal.x == 1.0f) - plane.type = 0; - else if (plane.normal.y == 1.0f) plane.type = 1; - else + } + else // tree->node->axis == AXIS_Z { - assert(plane.normal.z == 1.0f); + plane.normal = normalZ; plane.type = 2; } @@ -369,23 +362,14 @@ namespace BSP planeVec.emplace_back(plane); - // The recursion of adding the children means the node needs to be added before the chilren are loaded + // The recursion of adding the children through loadBSPNode means the parent node needs to be added before the chilren are loaded size_t nodeIndex = nodeVec.size(); nodeVec.emplace_back(); cNode_t node; node.plane = nullptr; // initalised after the BSP tree has been loaded - if (plane.type == 1) - { - // Due to the plane normal going from Z to Y, objects behind the plane are now in front and objects in front are now behind - node.children[1] = loadBSPNode(clipMap, tree->node->front.get()); - node.children[0] = loadBSPNode(clipMap, tree->node->back.get()); - } - else - { - node.children[0] = loadBSPNode(clipMap, tree->node->front.get()); - node.children[1] = loadBSPNode(clipMap, tree->node->back.get()); - } + node.children[0] = loadBSPNode(clipMap, tree->node->front.get()); + node.children[1] = loadBSPNode(clipMap, tree->node->back.get()); nodeVec.at(nodeIndex) = node; @@ -395,15 +379,11 @@ namespace BSP void ClipMapLinker::loadBSPTree(clipMap_t* clipMap, BSPData* bsp) { - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate before it is added into the BSP tree. - vec3_t worldMins; vec3_t worldMaxs; for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) { - vec3_t vertex = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIdx]); + vec3_t vertex = clipMap->verts[vertIdx]; // initalise AABB with the first vertex if (vertIdx == 0) { @@ -423,7 +403,7 @@ namespace BSP for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) { uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = BSPUtil::convertFromBO2Coords(clipMap->verts[uind]); + vec3_t vert = clipMap->verts[uind]; // initalise the AABB with the first vertex if (uindIdx == 0) { From 0f70c295324a20e9eeaf440e022eeaef8f973aff Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:13:31 +0800 Subject: [PATCH 049/123] Fixed a crash related to AABB Tree children counts exceeding the maximum limit --- src/ObjLoading/Game/T6/BSP/BSP.h | 2 + .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 151 ++++++++++-------- .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- 3 files changed, 91 insertions(+), 64 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index b4854134e..dc3ca43ea 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -63,6 +63,8 @@ namespace BSP { constexpr unsigned int MAX_COLLISION_VERTS = UINT16_MAX; + constexpr size_t MAX_AABB_TREE_CHILDREN = 128; + enum BSPDefaultLights { STATIC_LIGHT_INDEX = 0, diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 3389a953a..7e1a316b7 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -209,81 +209,103 @@ namespace BSP */ } - int ClipMapLinker::addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex) { - assert(node->isLeaf); - - size_t objectCount = node->leaf->getObjectCount(); - size_t parentAABBIndex = AABBTreeVec.size(); - - assert(objectCount > 0); - - if (objectCount > highestLeafObjectCount) - highestLeafObjectCount = objectCount; - - // add the parent AABB node - vec3_t parentMins; - vec3_t parentMaxs; - for (size_t objectIdx = 0; objectIdx < objectCount; objectIdx++) + assert(tree->isLeaf); + + size_t leafObjectCount = tree->leaf->getObjectCount(); + assert(leafObjectCount > 0); + if (leafObjectCount > highestLeafObjectCount) + highestLeafObjectCount = leafObjectCount; + + // BO2 has a maximum limit of 128 children per AABB tree (essentially), + // so this is fixed by adding multiple parent AABB trees that hold 128 children each + size_t result = leafObjectCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = leafObjectCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t parentCount = result; + if (remainder > 0) + parentCount++; + + size_t parentAABBArrayIndex = AABBTreeVec.size(); + AABBTreeVec.resize(AABBTreeVec.size() + parentCount); + size_t unaddedObjectCount = leafObjectCount; + size_t addedObjectCount = 0; + for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) { - int partitionIndex = node->leaf->getObject(objectIdx)->partitionIndex; - CollisionPartition* partition = &clipMap->partitions[partitionIndex]; - for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) - { - uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = clipMap->verts[uind]; + size_t childObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) + childObjectCount = unaddedObjectCount; + else + unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; - // initalise the parent AABB with the first vertex - if (objectIdx == 0 && uindIdx == 0) + // add the parent AABB + vec3_t parentMins; + vec3_t parentMaxs; + for (size_t objectIdx = 0; objectIdx < childObjectCount; objectIdx++) + { + int partitionIndex = tree->leaf->getObject(addedObjectCount + objectIdx)->partitionIndex; + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) { - parentMins = vert; - parentMaxs = vert; - } - - BSPUtil::updateAABBWithPoint(vert, parentMins, parentMaxs); - } - } + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = clipMap->verts[uind]; - CollisionAabbTree parentAABB; - parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); - parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); - parentAABB.materialIndex = 0; // always use the first material - parentAABB.childCount = static_cast(objectCount); - parentAABB.u.firstChildIndex = static_cast(parentAABBIndex + 1); - AABBTreeVec.emplace_back(parentAABB); + // initalise the parent AABB with the first vertex + if (objectIdx == 0 && uindIdx == 0) + { + parentMins = vert; + parentMaxs = vert; + } - // add child AABB nodes - for (size_t objectIdx = 0; objectIdx < objectCount; objectIdx++) - { - int partitionIndex = node->leaf->getObject(objectIdx)->partitionIndex; - CollisionPartition* partition = &clipMap->partitions[partitionIndex]; - vec3_t childMins; - vec3_t childMaxs; - for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) + BSPUtil::updateAABBWithPoint(vert, parentMins, parentMaxs); + } + } + size_t childObjectStartIndex = AABBTreeVec.size(); + + CollisionAabbTree parentAABB; + parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); + parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); + parentAABB.materialIndex = 0; // always use the first material + parentAABB.childCount = static_cast(childObjectCount); + parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); + AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; + + // add child AABBs + for (size_t objectIdx = 0; objectIdx < childObjectCount; objectIdx++) { - uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = clipMap->verts[uind]; - - // initalise the child AABB with the first vertex - if (uindIdx == 0) + int partitionIndex = tree->leaf->getObject(addedObjectCount + objectIdx)->partitionIndex; + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + vec3_t childMins; + vec3_t childMaxs; + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) { - childMins = vert; - childMaxs = vert; + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = clipMap->verts[uind]; + + // initalise the child AABB with the first vertex + if (uindIdx == 0) + { + childMins = vert; + childMaxs = vert; + } + + BSPUtil::updateAABBWithPoint(vert, childMins, childMaxs); } - BSPUtil::updateAABBWithPoint(vert, childMins, childMaxs); + CollisionAabbTree childAABBTree; + childAABBTree.materialIndex = 0; // always use the first material + childAABBTree.childCount = 0; + childAABBTree.u.partitionIndex = partitionIndex; + childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); + childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); + AABBTreeVec.emplace_back(childAABBTree); } - CollisionAabbTree childAABBTree; - childAABBTree.materialIndex = 0; // always use the first material - childAABBTree.childCount = 0; - childAABBTree.u.partitionIndex = partitionIndex; - childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); - childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); - AABBTreeVec.emplace_back(childAABBTree); + addedObjectCount += childObjectCount; } - return static_cast(parentAABBIndex); + *out_parentCount = parentCount; + *out_parentStartIndex = parentAABBArrayIndex; } constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; @@ -315,8 +337,11 @@ namespace BSP if (tree->leaf->getObjectCount() > 0) { - leaf.firstCollAabbIndex = addAABBTreeFromLeaf(tree, clipMap); - leaf.collAabbCount = 1; // 1 as it is the parent of all object AABBs + size_t parentCount = 0; + size_t parentStartIndex = 0; + addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex); + leaf.collAabbCount = static_cast(parentCount); + leaf.firstCollAabbIndex = static_cast(parentStartIndex); } else { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 3bfe65626..4b6dcd6e7 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -31,7 +31,7 @@ namespace BSP std::vector leafVec; std::vector AABBTreeVec; size_t highestLeafObjectCount = 0; - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap); + void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); From c9aa8e373b24a63aceb11147fe713e4726415eba Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:32:29 +0800 Subject: [PATCH 050/123] Moved the re-ordering of indices to the BSP creator instead of the BSP asset linker. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 7 +++++-- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 13 +++---------- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 7 ++----- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 92ea2e822..eab04426f 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -158,9 +158,12 @@ namespace vertexVec.insert(vertexVec.end(), tempVertices.begin(), tempVertices.end()); // T6 uses unsigned shorts as their index type so we have to loop and convert them from an unsigned int - for (size_t idx = 0; idx < outIndices.size(); idx++) + for (size_t idx = 0; idx < outIndices.size(); idx += 3) { - indexVec.emplace_back(static_cast(outIndices[idx])); + // BO2's index ordering is opposite to the FBX, so its converted here + indexVec.emplace_back(static_cast(outIndices[idx + 2])); + indexVec.emplace_back(static_cast(outIndices[idx + 1])); + indexVec.emplace_back(static_cast(outIndices[idx + 0])); } surfaceVec.emplace_back(surface); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 7e1a316b7..56be1b958 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -490,17 +490,10 @@ namespace BSP { int indexOfFirstIndex = surface.indexOfFirstIndex; int indexOfFirstVertex = surface.indexOfFirstVertex; - for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx += 3) + for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) { - int firstTriIndex = indexOfFirstIndex + indexIdx; - int triIndex0 = bsp->colWorld.indices[firstTriIndex + 0] + indexOfFirstVertex; - int triIndex1 = bsp->colWorld.indices[firstTriIndex + 1] + indexOfFirstVertex; - int triIndex2 = bsp->colWorld.indices[firstTriIndex + 2] + indexOfFirstVertex; - - // triangle index ordering is opposite to blenders, so its converted here - triIndexVec.emplace_back(triIndex2); - triIndexVec.emplace_back(triIndex1); - triIndexVec.emplace_back(triIndex0); + int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; + triIndexVec.emplace_back(triIndex); } } // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index e64be8fb5..a9ecae323 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -44,12 +44,9 @@ namespace BSP assert(indexCount % 3 == 0); gfxWorld->draw.indexCount = static_cast(indexCount); gfxWorld->draw.indices = m_memory.Alloc(indexCount); - for (size_t indexIdx = 0; indexIdx < indexCount; indexIdx += 3) + for (size_t indexIdx = 0; indexIdx < indexCount; indexIdx++) { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[indexIdx + 2] = bsp->gfxWorld.indices.at(indexIdx + 0); - gfxWorld->draw.indices[indexIdx + 1] = bsp->gfxWorld.indices.at(indexIdx + 1); - gfxWorld->draw.indices[indexIdx + 0] = bsp->gfxWorld.indices.at(indexIdx + 2); + gfxWorld->draw.indices[indexIdx] = bsp->gfxWorld.indices.at(indexIdx); } } From dac943894a54e6fd3dffe41664a31c1028394481 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:17:35 +0800 Subject: [PATCH 051/123] wip: map file format now uses gltf instead of fbx --- src/ObjLoading/Game/T6/BSP/BSP.h | 154 +++- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 696 +++++++++++++++--- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 2 +- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 2 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 3 +- 5 files changed, 768 insertions(+), 89 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index dc3ca43ea..840c4122d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -24,7 +24,6 @@ namespace BSP vec4_t color; vec2_t texCoord; vec3_t normal; - vec3_t tangent; }; struct BSPMaterial @@ -36,7 +35,7 @@ namespace BSP struct BSPSurface { BSPMaterial material; - int triCount; + uint16_t triCount; int indexOfFirstVertex; int indexOfFirstIndex; }; @@ -183,4 +182,155 @@ namespace BSP constexpr vec4_t SUNLIGHT_COLOR = {0.75f, 0.75f, 0.75f, 1.0f}; constexpr vec3_t SUNLIGHT_DIRECTION = {0.0f, 0.0f, 0.0f}; }; // namespace BSPEditableConstants + + namespace BSPFlags + { + // enum SurfaceType + //{ + // SURF_TYPE_BARK, + // SURF_TYPE_BRICK, + // SURF_TYPE_CARPET, + // SURF_TYPE_CLOTH, + // SURF_TYPE_CONCRETE, + // SURF_TYPE_DIRT, + // SURF_TYPE_FLESH, + // SURF_TYPE_FOLIAGE, + // SURF_TYPE_GLASS, + // SURF_TYPE_GRASS, + // SURF_TYPE_GRAVEL, + // SURF_TYPE_ICE, + // SURF_TYPE_METAL, + // SURF_TYPE_MUD, + // SURF_TYPE_PAPER, + // SURF_TYPE_PLASTER, + // SURF_TYPE_ROCK, + // SURF_TYPE_SAND, + // SURF_TYPE_SNOW, + // SURF_TYPE_WATER, + // SURF_TYPE_WOOD, + // SURF_TYPE_ASPHALT, + // SURF_TYPE_CERAMIC, + // SURF_TYPE_PLASTIC, + // SURF_TYPE_RUBBER, + // SURF_TYPE_CUSHION, + // SURF_TYPE_FRUIT, + // SURF_TYPE_PAINTEDMETAL, + // SURF_TYPE_PLAYER, + // SURF_TYPE_TALLGRASS, + // SURF_TYPE_RIOTSHIELD, + // SURF_TYPE_OPAQUEGLASS, + // SURF_TYPE_CLIPMISSILE, + // SURF_TYPE_AI_NOSIGHT, + // SURF_TYPE_CLIPSHOT, + // SURF_TYPE_PLAYERCLIP, + // SURF_TYPE_MONSTERCLIP, + // SURF_TYPE_VEHICLECLIP, + // SURF_TYPE_ITEMCLIP, + // SURF_TYPE_NODROP, + // SURF_TYPE_NONSOLID, + // SURF_TYPE_DETAIL, + // SURF_TYPE_STRUCTURAL, + // SURF_TYPE_PORTAL, + // SURF_TYPE_CANSHOOTCLIP, + // SURF_TYPE_ORIGIN, + // SURF_TYPE_SKY, + // SURF_TYPE_NOCASTSHADOW, + // SURF_TYPE_ONLYCASTSHADOW, + // SURF_TYPE_PHYSICSGEOM, + // SURF_TYPE_LIGHTPORTAL, + // SURF_TYPE_CAULK, + // SURF_TYPE_AREALIGHT, + // SURF_TYPE_SLICK, + // SURF_TYPE_NOIMPACT, + // SURF_TYPE_NOMARKS, + // SURF_TYPE_NOPENETRATE, + // SURF_TYPE_LADDER, + // SURF_TYPE_NODAMAGE, + // SURF_TYPE_MANTLEON, + // SURF_TYPE_MANTLEOVER, + // SURF_TYPE_MOUNT, + // SURF_TYPE_NOSTEPS, + // SURF_TYPE_NODRAW, + // SURF_TYPE_NORECEIVEDYNAMICSHADOW, + // SURF_TYPE_NODLIGHT, + // SURF_TYPE_COUNT + // }; + // + // struct s_SurfaceTypeFlags + //{ + // unsigned int surfaceFlags; + // unsigned int contentFlags; + // }; + // + // s_SurfaceTypeFlags surfaceTypeToFlagMap[SURF_TYPE_COUNT] = { + // {0x100000, 0 }, // bark + // {0x200000, 0 }, // brick + // {0x300000, 0 }, // carpet + // {0x400000, 0 }, // cloth + // {0x500000, 0 }, // concrete + // {0x600000, 0 }, // dirt + // {0x700000, 0 }, // flesh + // {0x800000, 2 }, // foliage + // {0x900000, 0x10 }, // glass + // {0x0A00000, 0 }, // grass + // {0x0B00000, 0 }, // gravel + // {0x0C00000, 0 }, // ice + // {0x0D00000, 0 }, // metal + // {0x0E00000, 0 }, // mud + // {0x0F00000, 0 }, // paper + // {0x1000000, 0 }, // plaster + // {0x1100000, 0 }, // rock + // {0x1200000, 0 }, // sand + // {0x1300000, 0 }, // snow + // {0x1400000, 0x20 }, // water + // {0x1500000, 0 }, // wood + // {0x1600000, 0 }, // asphalt + // {0x1700000, 0 }, // ceramic + // {0x1800000, 0 }, // plastic + // {0x1900000, 0 }, // rubber + // {0x1A00000, 0 }, // cushion + // {0x1B00000, 0 }, // fruit + // {0x1C00000, 0 }, // paintedmetal + // {0x1D00000, 0 }, // player + // {0x1E00000, 0 }, // tallgrass + // {0x1F00000, 0 }, // riotshield + // {0x900000, 0 }, // opaqueglass + // {0, 0x80 }, // clipmissile + // {0, 0x1000 }, // ai_nosight + // {0, 0x2000 }, // clipshot + // {0, 0x10000 }, // playerclip + // {0, 0x20000 }, // monsterclip + // {0, 0x200 }, // vehicleclip + // {0, 0x400 }, // itemclip + // {0, 0x80000000}, // noDrop + // {0x4000, 0 }, // nonSolid + // {0, 0x8000000 }, // detail + // {0, 0x10000000}, // structural + // {0x80000000, 0 }, // portal + // {0, 0x40 }, // canShootClip + // {0, 0 }, // origin + // {4, 0x800 }, // sky + // {0x40000, 0 }, // noCastShadow + // {0x80000, 0 }, // onlyCastShadow + // {0, 0 }, // physicsGeom + // {0, 0 }, // lightPortal + // {0x1000, 0 }, // caulk + // {0x8000, 0 }, // areaLight + // {2, 0 }, // slick + // {0x10, 0 }, // noImpact + // {0x20, 0 }, // noMarks + // {0x100, 0 }, // noPenetrate + // {8, 0 }, // ladder + // {1, 0 }, // noDamage + // {0x4000000, 0x1000000 }, // mantleOn + // {0x8000000, 0x1000000 }, // mantleOver + // {0x10000000, 0x1000000 }, // mount + // {0x2000, 0 }, // noSteps + // {0x80, 0 }, // noDraw + // {0x800, 0 }, // noReceiveDynamicShadow + // {0x20000, 0 } // noDlight + // }; + // + //// TODO: template/define to get the surface type from the surface flags + } // namespace BSPFlags } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index eab04426f..13e61567f 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1,12 +1,622 @@ #include "BSPCreator.h" #include "BSPUtil.h" +#include "XModel/Gltf/GltfBinInput.h" +#include "XModel/Gltf/GltfTextInput.h" +#include "XModel/Gltf/Internal/GltfAccessor.h" +#include "XModel/Gltf/Internal/GltfBuffer.h" +#include "XModel/Gltf/Internal/GltfBufferView.h" +#include "XModel/Gltf/JsonGltf.h" + +#include +#include +#include +#include +#include +#include +#include -#include +namespace +{ + struct AccessorsForVertex + { + unsigned m_position_accessor; + unsigned m_normal_accessor; + std::optional m_color_accessor; + unsigned m_uv_accessor; + unsigned m_index_accessor; + }; + + void RhcToLhcCoordinates(float (&coords)[3]) + { + const float two[3]{coords[0], coords[1], coords[2]}; + + coords[0] = two[0]; + coords[1] = -two[2]; + coords[2] = two[1]; + } + + void RhcToLhcScale(float (&coords)[3]) + { + const float two[3]{coords[0], coords[1], coords[2]}; + + coords[0] = two[0]; + coords[1] = two[2]; + coords[2] = two[1]; + } + + void RhcToLhcIndices(unsigned (&indices)[3]) + { + const unsigned two[3]{indices[0], indices[1], indices[2]}; + + indices[0] = two[2]; + indices[1] = two[1]; + indices[2] = two[0]; + } +} // namespace namespace { using namespace BSP; + using namespace gltf; + + class GltfLoadException final : std::exception + { + public: + explicit GltfLoadException(std::string message) + : m_message(std::move(message)) + { + } + + [[nodiscard]] const std::string& Str() const + { + return m_message; + } + + [[nodiscard]] const char* what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class BSPLoader + { + private: + const Input& m_input; + BSPData* m_bsp; + std::vector> m_accessors; + std::vector> m_buffer_views; + std::vector> m_buffers; + + std::optional GetAccessorForIndex(const char* attributeName, + const std::optional index, + std::initializer_list allowedAccessorTypes, + std::initializer_list allowedAccessorComponentTypes) const + { + if (!index) + return std::nullopt; + + if (*index > m_accessors.size()) + throw GltfLoadException(std::format("Index for {} accessor out of bounds", attributeName)); + + auto* accessor = m_accessors[*index].get(); + + const auto maybeType = accessor->GetType(); + if (maybeType) + { + if (std::ranges::find(allowedAccessorTypes, *maybeType) == allowedAccessorTypes.end()) + throw GltfLoadException(std::format("Accessor for {} has unsupported type {}", attributeName, static_cast(*maybeType))); + } + + const auto maybeComponentType = accessor->GetComponentType(); + if (maybeComponentType) + { + if (std::ranges::find(allowedAccessorComponentTypes, *maybeComponentType) == allowedAccessorComponentTypes.end()) + throw GltfLoadException( + std::format("Accessor for {} has unsupported component type {}", attributeName, static_cast(*maybeComponentType))); + } + + return accessor; + } + + static void VerifyAccessorVertexCount(const char* accessorType, const Accessor* accessor, const size_t vertexCount) + { + if (accessor->GetCount() != vertexCount) + throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount)); + } + + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, BSPSurface& surface) + { + // clang-format off + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + accessorsForVertex.m_position_accessor, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); + // clang-format on + assert(positionAccessor != nullptr); + + const auto vertexCount = positionAccessor->GetCount(); + OnesAccessor onesAccessor(vertexCount); + + // clang-format off + const auto* normalAccessor = GetAccessorForIndex( + "NORMAL", + accessorsForVertex.m_normal_accessor, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); + VerifyAccessorVertexCount("NORMAL", normalAccessor, vertexCount); + assert(normalAccessor != nullptr); + + const auto* uvAccessor = GetAccessorForIndex( + "TEXCOORD_0", + accessorsForVertex.m_uv_accessor, + { JsonAccessorType::VEC2 }, + { JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT } + ).value_or(nullptr); + VerifyAccessorVertexCount("TEXCOORD_0", uvAccessor, vertexCount); + assert(uvAccessor != nullptr); + + const auto* colorAccessor = GetAccessorForIndex( + "COLOR_0", + accessorsForVertex.m_color_accessor, + { JsonAccessorType::VEC3, JsonAccessorType::VEC4 }, + { JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT } + ).value_or(&onesAccessor); + VerifyAccessorVertexCount("COLOR_0", colorAccessor, vertexCount); + + const auto* indexAccessor = GetAccessorForIndex( + "INDICES", + accessorsForVertex.m_index_accessor, + { JsonAccessorType::SCALAR }, + { JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT, JsonAccessorComponentType::UNSIGNED_INT } + ).value_or(nullptr); + assert(indexAccessor != nullptr); + // clang-format on + + const auto indexCount = indexAccessor->GetCount(); + if (indexCount % 3 != 0) + throw GltfLoadException("Index count must be dividable by 3 for triangles"); + const auto faceCount = indexCount / 3u; + if (faceCount > UINT16_MAX) + throw GltfLoadException("Face count exceeded the UINT16_MAX"); + + surface.triCount = faceCount; + surface.indexOfFirstIndex = static_cast(m_bsp->gfxWorld.indices.size()); + surface.indexOfFirstVertex = static_cast(m_bsp->gfxWorld.vertices.size()); + + for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++) + { + unsigned indices[3]; + if (!indexAccessor->GetUnsigned(faceIndex * 3u + 0u, indices[0]) || !indexAccessor->GetUnsigned(faceIndex * 3u + 1u, indices[1]) + || !indexAccessor->GetUnsigned(faceIndex * 3u + 2u, indices[2])) + { + assert(false); + } + if (indices[0] > UINT16_MAX || indices[1] > UINT16_MAX || indices[2] > UINT16_MAX) + throw GltfLoadException("Index number exceeded the UINT16_MAX"); + + RhcToLhcIndices(indices); + m_bsp->gfxWorld.indices.emplace_back(indices[0]); + m_bsp->gfxWorld.indices.emplace_back(indices[1]); + m_bsp->gfxWorld.indices.emplace_back(indices[2]); + } + + const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); + m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); + for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) + { + BSPVertex vertex; + + if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.pos.v) || !normalAccessor->GetFloatVec3(vertexIndex, vertex.normal.v) + || !colorAccessor->GetFloatVec4(vertexIndex, vertex.color.v) || !uvAccessor->GetFloatVec2(vertexIndex, vertex.texCoord.v)) + { + assert(false); + } + + RhcToLhcCoordinates(vertex.pos.v); + RhcToLhcCoordinates(vertex.normal.v); + + m_bsp->gfxWorld.vertices.emplace_back(vertex); + } + + return vertexOffset; + } + + void loadSurfaceMaterialData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) + { + BSPMaterialType matType; + if (!primitive.material) + if (!primitive.attributes.COLOR_0) + { + // matType = MATERIAL_TYPE_EMPTY; + throw GltfLoadException("Primitive requires material or colour data."); + } + else + matType = MATERIAL_TYPE_COLOUR; + else + matType = MATERIAL_TYPE_TEXTURE; + surface.material.materialType = matType; + + if (matType == MATERIAL_TYPE_TEXTURE) + { + if (!jRoot.materials || *primitive.material >= jRoot.materials->size()) + throw GltfLoadException("Invalid material index"); + surface.material.materialName = jRoot.materials.value()[primitive.material.value()].name.value(); + } + else + surface.material.materialName = ""; + } + + bool CreateSurfaceFromPrimitive(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive) + { + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + if (!primitive.attributes.TEXCOORD_0) + throw GltfLoadException("Requires primitives attribute TEXCOORD_0"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = *primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + + loadSurfaceMaterialData(jRoot, primitive, surface); + CreateVertices(accessorsForVertex, surface); + + m_bsp->gfxWorld.surfaces.emplace_back(surface); + + return true; + } + + static std::vector GetRootNodes(const JsonRoot& jRoot) + { + if (!jRoot.nodes || jRoot.nodes->empty()) + return {}; + + const auto nodeCount = jRoot.nodes->size(); + std::vector rootNodes; + std::vector isChild(nodeCount); + + for (const auto& node : jRoot.nodes.value()) + { + if (!node.children) + continue; + + for (const auto childIndex : node.children.value()) + { + if (childIndex >= nodeCount) + throw GltfLoadException("Illegal child index"); + + if (isChild[childIndex]) + throw GltfLoadException("Node hierarchy is not a set of disjoint strict trees"); + + isChild[childIndex] = true; + } + } + + for (auto nodeIndex = 0u; nodeIndex < nodeCount; nodeIndex++) + { + if (!isChild[nodeIndex]) + rootNodes.emplace_back(nodeIndex); + } + + return rootNodes; + } + + void TraverseNodes(const JsonRoot& jRoot) + { + // Make sure there are any nodes to traverse + if (!jRoot.nodes || jRoot.nodes->empty()) + return; + + std::deque nodeQueue; + const std::vector rootNodes = GetRootNodes(jRoot); + + for (const auto rootNode : rootNodes) + nodeQueue.emplace_back(rootNode); + + while (!nodeQueue.empty()) + { + const auto& node = jRoot.nodes.value()[nodeQueue.front()]; + nodeQueue.pop_front(); + + if (node.children) + { + for (const auto childIndex : *node.children) + nodeQueue.emplace_back(childIndex); + } + + if (node.mesh) + { + con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); + + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + for (const auto& primitive : mesh.primitives) + { + CreateSurfaceFromPrimitive(jRoot, primitive); + } + } + } + } + + void CreateBuffers(const JsonRoot& jRoot) + { + if (!jRoot.buffers) + return; + + m_buffers.reserve(jRoot.buffers->size()); + for (const auto& jBuffer : *jRoot.buffers) + { + if (!jBuffer.uri) + { + const void* embeddedBufferPtr = nullptr; + size_t embeddedBufferSize = 0u; + if (!m_input.GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u) + throw GltfLoadException("Buffer tried to access embedded data when there is none"); + + m_buffers.emplace_back(std::make_unique(embeddedBufferPtr, embeddedBufferSize)); + } + else if (DataUriBuffer::IsDataUri(*jBuffer.uri)) + { + auto dataUriBuffer = std::make_unique(); + if (!dataUriBuffer->ReadDataFromUri(*jBuffer.uri)) + throw GltfLoadException("Buffer has invalid data uri"); + + m_buffers.emplace_back(std::move(dataUriBuffer)); + } + else + { + throw GltfLoadException("File buffers are not supported"); + } + } + } + + void CreateBufferViews(const JsonRoot& jRoot) + { + if (!jRoot.bufferViews) + return; + + m_buffer_views.reserve(jRoot.bufferViews->size()); + for (const auto& jBufferView : *jRoot.bufferViews) + { + if (jBufferView.buffer >= m_buffers.size()) + throw GltfLoadException("Buffer view references invalid buffer"); + + const auto* buffer = m_buffers[jBufferView.buffer].get(); + const auto offset = jBufferView.byteOffset.value_or(0u); + const auto length = jBufferView.byteLength; + const auto stride = jBufferView.byteStride.value_or(0u); + + if (offset + length > buffer->GetSize()) + throw GltfLoadException("Buffer view is defined larger as underlying buffer"); + + m_buffer_views.emplace_back(std::make_unique(buffer, offset, length, stride)); + } + } + + void CreateAccessors(const JsonRoot& jRoot) + { + if (!jRoot.accessors) + return; + + m_accessors.reserve(jRoot.accessors->size()); + for (const auto& jAccessor : *jRoot.accessors) + { + if (!jAccessor.bufferView) + { + m_accessors.emplace_back(std::make_unique(jAccessor.count)); + continue; + } + + if (*jAccessor.bufferView >= m_buffer_views.size()) + throw GltfLoadException("Accessor references invalid buffer view"); + + const auto* bufferView = m_buffer_views[*jAccessor.bufferView].get(); + const auto byteOffset = jAccessor.byteOffset.value_or(0u); + if (jAccessor.componentType == JsonAccessorComponentType::FLOAT) + m_accessors.emplace_back(std::make_unique(bufferView, jAccessor.type, byteOffset, jAccessor.count)); + else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_BYTE) + m_accessors.emplace_back(std::make_unique(bufferView, jAccessor.type, byteOffset, jAccessor.count)); + else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_SHORT) + m_accessors.emplace_back(std::make_unique(bufferView, jAccessor.type, byteOffset, jAccessor.count)); + else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_INT) + m_accessors.emplace_back(std::make_unique(bufferView, jAccessor.type, byteOffset, jAccessor.count)); + else + throw GltfLoadException(std::format("Accessor has unsupported component type {}", static_cast(jAccessor.componentType))); + } + } + + public: + bool addGLTFDataToBSP(bool isGfxWorld) + { + JsonRoot jRoot; + try + { + jRoot = m_input.GetJson().get(); + } + catch (const nlohmann::json::exception& e) + { + con::error("Failed to parse GLTF JSON: {}", e.what()); + return false; + } + + try + { + CreateBuffers(jRoot); + CreateBufferViews(jRoot); + CreateAccessors(jRoot); + + TraverseNodes(jRoot); + } + catch (const GltfLoadException& e) + { + con::error("Failed to load GLTF: {}", e.Str()); + return false; + } + } + + BSPLoader(const Input& input, BSPData* bsp) + : m_input(input), + m_bsp(bsp) {}; + }; +} // namespace + +namespace BSP +{ + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) + { + bool isGfxFileGltf = true; + std::string gfxFilePath = BSPUtil::getFileNameForBSPAsset("map_gfx.gltf"); + auto gfxFile = searchPath.Open(gfxFilePath); + if (!gfxFile.IsOpen()) + { + isGfxFileGltf = false; + gfxFilePath = BSPUtil::getFileNameForBSPAsset("map_gfx.glb"); + gfxFile = searchPath.Open(gfxFilePath); + if (!gfxFile.IsOpen()) + { + con::error("BSP Creator: Can't find map_gfx.gltf or map_gfx.glb."); + return nullptr; + } + } + + std::unique_ptr bsp = std::make_unique(); + + bsp->name = mapName; + bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + + if (isGfxFileGltf) + { + gltf::TextInput input; + if (!input.ReadGltfData(*gfxFile.m_stream)) + return nullptr; + + BSPLoader loader(input, bsp.get()); + if (!loader.addGLTFDataToBSP(true)) + return nullptr; + } + else + { + gltf::BinInput input; + if (!input.ReadGltfData(*gfxFile.m_stream)) + return nullptr; + + BSPLoader loader(input, bsp.get()); + if (!loader.addGLTFDataToBSP(true)) + return nullptr; + } + + return bsp; + } +} // namespace BSP + +/* +std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) + { + std::string gfxFbxFileName = "map_gfx.fbx"; + std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset(gfxFbxFileName); + auto gfxFile = searchPath.Open(gfxFbxPath); + if (!gfxFile.IsOpen()) + { + con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); + gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); + if (gfxFile.m_stream->gcount() != gfxFile.m_length) + { + con::error("Read error of gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + ufbx_error errorGfx; + ufbx_load_opts optsGfx{}; + optsGfx.target_axes = ufbx_axes_right_handed_y_up; + optsGfx.generate_missing_normals = true; + optsGfx.allow_missing_vertex_position = false; + ufbx_scene* gfxScene = ufbx_load_memory(gfxMapData.get(), static_cast(gfxFile.m_length), &optsGfx, &errorGfx); + if (!gfxScene) + { + con::error("Failed to load map gfx fbx file: {}", errorGfx.description.data); + return nullptr; + } + + ufbx_scene* colScene; + std::string colFbxFileName = "map_col.fbx"; + std::string colFbxPath = BSPUtil::getFileNameForBSPAsset(colFbxFileName); + auto colFile = searchPath.Open(colFbxPath); + if (!colFile.IsOpen()) + { + con::warn("Failed to open map collison fbx file: {}. map gfx will be used for collision instead.", colFbxPath); + colScene = gfxScene; + } + else + { + std::unique_ptr colMapData(new char[static_cast(colFile.m_length)]); + colFile.m_stream->seekg(0); + colFile.m_stream->read(colMapData.get(), colFile.m_length); + if (colFile.m_stream->gcount() != colFile.m_length) + { + con::error("Read error of collision fbx file: {}", colFbxPath); + return nullptr; + } + + ufbx_error errorCol; + ufbx_load_opts optsCol{}; + optsCol.target_axes = ufbx_axes_right_handed_y_up; + optsCol.generate_missing_normals = true; + optsCol.allow_missing_vertex_position = false; + colScene = ufbx_load_memory(colMapData.get(), static_cast(colFile.m_length), &optsCol, &errorCol); + if (!colScene) + { + con::error("Failed to load map collision fbx file: {}", errorCol.description.data); + return nullptr; + } + } + + std::unique_ptr bsp = std::make_unique(); + + bsp->name = mapName; + bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + + loadWorldData(gfxScene, bsp.get(), true); + loadWorldData(colScene, bsp.get(), false); + + ufbx_free_scene(gfxScene); + if (gfxScene != colScene) + ufbx_free_scene(colScene); + + return bsp; + } + + + + + + + + + + + + using namespace BSP; void addFBXMeshToWorld( ufbx_node* node, std::vector& surfaceVec, std::vector& vertexVec, std::vector& indexVec, bool& hasTangentSpace) @@ -191,86 +801,4 @@ namespace if (hasTangentSpace == false) con::warn("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX."); } -} // namespace - -namespace BSP -{ - std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) - { - std::string gfxFbxFileName = "map_gfx.fbx"; - std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset(gfxFbxFileName); - auto gfxFile = searchPath.Open(gfxFbxPath); - if (!gfxFile.IsOpen()) - { - con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); - return nullptr; - } - - std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); - gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); - if (gfxFile.m_stream->gcount() != gfxFile.m_length) - { - con::error("Read error of gfx fbx file: {}", gfxFbxPath); - return nullptr; - } - - ufbx_error errorGfx; - ufbx_load_opts optsGfx{}; - optsGfx.target_axes = ufbx_axes_right_handed_y_up; - optsGfx.generate_missing_normals = true; - optsGfx.allow_missing_vertex_position = false; - ufbx_scene* gfxScene = ufbx_load_memory(gfxMapData.get(), static_cast(gfxFile.m_length), &optsGfx, &errorGfx); - if (!gfxScene) - { - con::error("Failed to load map gfx fbx file: {}", errorGfx.description.data); - return nullptr; - } - - ufbx_scene* colScene; - std::string colFbxFileName = "map_col.fbx"; - std::string colFbxPath = BSPUtil::getFileNameForBSPAsset(colFbxFileName); - auto colFile = searchPath.Open(colFbxPath); - if (!colFile.IsOpen()) - { - con::warn("Failed to open map collison fbx file: {}. map gfx will be used for collision instead.", colFbxPath); - colScene = gfxScene; - } - else - { - std::unique_ptr colMapData(new char[static_cast(colFile.m_length)]); - colFile.m_stream->seekg(0); - colFile.m_stream->read(colMapData.get(), colFile.m_length); - if (colFile.m_stream->gcount() != colFile.m_length) - { - con::error("Read error of collision fbx file: {}", colFbxPath); - return nullptr; - } - - ufbx_error errorCol; - ufbx_load_opts optsCol{}; - optsCol.target_axes = ufbx_axes_right_handed_y_up; - optsCol.generate_missing_normals = true; - optsCol.allow_missing_vertex_position = false; - colScene = ufbx_load_memory(colMapData.get(), static_cast(colFile.m_length), &optsCol, &errorCol); - if (!colScene) - { - con::error("Failed to load map collision fbx file: {}", errorCol.description.data); - return nullptr; - } - } - - std::unique_ptr bsp = std::make_unique(); - - bsp->name = mapName; - bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; - - loadWorldData(gfxScene, bsp.get(), true); - loadWorldData(colScene, bsp.get(), false); - - ufbx_free_scene(gfxScene); - if (gfxScene != colScene) - ufbx_free_scene(colScene); - - return bsp; - } -} // namespace BSP +*/ diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index cd5ebe58b..8c56f95d6 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -7,7 +7,7 @@ namespace BSP { - std::string BSPUtil::getFileNameForBSPAsset(std::string& assetName) + std::string BSPUtil::getFileNameForBSPAsset(const std::string& assetName) { return std::format("BSP/{}", assetName); } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 54bdd0a59..8b7664f8f 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -7,7 +7,7 @@ namespace BSP class BSPUtil { public: - static std::string getFileNameForBSPAsset(std::string& assetName); + static std::string getFileNameForBSPAsset(const std::string& assetName); static vec3_t convertToBO2Coords(vec3_t& OGL_coordinate); static vec3_t convertFromBO2Coords(vec3_t& bo2_coordinate); static void updateAABB(vec3_t& newAABBMins, vec3_t& newAABBMaxs, vec3_t& AABBMins, vec3_t& AABBMaxs); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index a9ecae323..0ec1963e6 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -27,7 +27,8 @@ namespace BSP gfxVertex->color.packed = pack32::Vec4PackGfxColor(bspVertex.color.v); gfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(bspVertex.texCoord.v); gfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.normal.v); - gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); + // gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); + gfxVertex->tangent.packed = 0; // unimplemented variables gfxVertex->binormalSign = 0.0f; From 984f461e88f6e9e9a7e062cb29d4ba2bc1816eb7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:45:11 +0800 Subject: [PATCH 052/123] Vertex position is now transformed by node transform. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 161 ++++++++++++++++------ 1 file changed, 117 insertions(+), 44 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 13e61567f..210d1646a 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -8,6 +8,10 @@ #include "XModel/Gltf/Internal/GltfBufferView.h" #include "XModel/Gltf/JsonGltf.h" +#pragma warning(push, 0) +#include +#pragma warning(pop) + #include #include #include @@ -36,15 +40,6 @@ namespace coords[2] = two[1]; } - void RhcToLhcScale(float (&coords)[3]) - { - const float two[3]{coords[0], coords[1], coords[2]}; - - coords[0] = two[0]; - coords[1] = two[2]; - coords[2] = two[1]; - } - void RhcToLhcIndices(unsigned (&indices)[3]) { const unsigned two[3]{indices[0], indices[1], indices[2]}; @@ -128,7 +123,73 @@ namespace throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount)); } - unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, BSPSurface& surface) + Eigen::Matrix4d createNodeMatrix(const gltf::JsonNode& node) + { + if (node.matrix) + return Eigen::Matrix4d({ + {(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]}, + {(*node.matrix)[1], (*node.matrix)[5], (*node.matrix)[9], (*node.matrix)[13]}, + {(*node.matrix)[2], (*node.matrix)[6], (*node.matrix)[10], (*node.matrix)[14]}, + {(*node.matrix)[3], (*node.matrix)[7], (*node.matrix)[11], (*node.matrix)[15]} + }); + + float localTranslation[3]; + float localRotation[4]; + float localScale[3]; + if (node.translation) + { + localTranslation[0] = (*node.translation)[0]; + localTranslation[1] = (*node.translation)[1]; + localTranslation[2] = (*node.translation)[2]; + } + else + { + localTranslation[0] = 0.0f; + localTranslation[1] = 0.0f; + localTranslation[2] = 0.0f; + } + + if (node.rotation) + { + localRotation[0] = (*node.rotation)[0]; + localRotation[1] = (*node.rotation)[1]; + localRotation[2] = (*node.rotation)[2]; + localRotation[3] = (*node.rotation)[3]; + } + else + { + localRotation[0] = 0.0f; + localRotation[1] = 0.0f; + localRotation[2] = 0.0f; + localRotation[3] = 1.0f; + } + + if (node.scale) + { + localScale[0] = (*node.scale)[0]; + localScale[1] = (*node.scale)[1]; + localScale[2] = (*node.scale)[2]; + } + else + { + localScale[0] = 1.0f; + localScale[1] = 1.0f; + localScale[2] = 1.0f; + } + + Eigen::Vector3d translation(localTranslation[0], localTranslation[1], localTranslation[2]); + Eigen::Quaterniond rotation(localRotation[0], localRotation[1], localRotation[2], localRotation[3]); + Eigen::Vector3d scale(localScale[0], localScale[1], localScale[2]); + + Eigen::Affine3d transform = Eigen::Affine3d::Identity(); + transform.translate(translation); + transform.rotate(rotation); + transform.scale(scale); + + return transform.matrix(); + } + + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, BSPSurface& surface) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -207,6 +268,7 @@ namespace m_bsp->gfxWorld.indices.emplace_back(indices[2]); } + Eigen::Matrix4d nodeMatrix = createNodeMatrix(node); const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) @@ -219,6 +281,12 @@ namespace assert(false); } + Eigen::Vector4d position(vertex.pos.x, vertex.pos.y, vertex.pos.z, 1.0f); + Eigen::Vector4d transformedPosition = nodeMatrix * position; + vertex.pos.x = transformedPosition.x(); + vertex.pos.y = transformedPosition.y(); + vertex.pos.z = transformedPosition.z(); + RhcToLhcCoordinates(vertex.pos.v); RhcToLhcCoordinates(vertex.normal.v); @@ -253,33 +321,42 @@ namespace surface.material.materialName = ""; } - bool CreateSurfaceFromPrimitive(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive) + bool CreateSurfacesFromNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { - if (!primitive.indices) - throw GltfLoadException("Requires primitives indices"); - if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) - throw GltfLoadException("Only triangles are supported"); - if (!primitive.attributes.POSITION) - throw GltfLoadException("Requires primitives attribute POSITION"); - if (!primitive.attributes.NORMAL) - throw GltfLoadException("Requires primitives attribute NORMAL"); - if (!primitive.attributes.TEXCOORD_0) - throw GltfLoadException("Requires primitives attribute TEXCOORD_0"); - - const AccessorsForVertex accessorsForVertex{ - .m_position_accessor = *primitive.attributes.POSITION, - .m_normal_accessor = *primitive.attributes.NORMAL, - .m_color_accessor = primitive.attributes.COLOR_0, - .m_uv_accessor = *primitive.attributes.TEXCOORD_0, - .m_index_accessor = *primitive.indices, - }; - - BSPSurface surface; + if (!node.mesh) + return false; - loadSurfaceMaterialData(jRoot, primitive, surface); - CreateVertices(accessorsForVertex, surface); + con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); - m_bsp->gfxWorld.surfaces.emplace_back(surface); + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + for (const auto& primitive : mesh.primitives) + { + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + if (!primitive.attributes.TEXCOORD_0) + throw GltfLoadException("Requires primitives attribute TEXCOORD_0"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = *primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + + loadSurfaceMaterialData(jRoot, primitive, surface); + CreateVertices(accessorsForVertex, node, surface); + + m_bsp->gfxWorld.surfaces.emplace_back(surface); + } return true; } @@ -340,18 +417,12 @@ namespace { for (const auto childIndex : *node.children) nodeQueue.emplace_back(childIndex); - } - if (node.mesh) - { - con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); - - const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; - for (const auto& primitive : mesh.primitives) - { - CreateSurfaceFromPrimitive(jRoot, primitive); - } + if (node.matrix || node.translation || node.rotation || node.scale) + con::warn("Parent node has position data that won't be used"); } + + CreateSurfacesFromNode(jRoot, node); } } @@ -522,6 +593,8 @@ namespace BSP return nullptr; } + bsp->colWorld = bsp->gfxWorld; + return bsp; } } // namespace BSP From f600b35ffb0804fc5c77b6470239886898784f7b Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:19:28 +0800 Subject: [PATCH 053/123] Added tangent and binormal calculation, corrected scale, rotate and translate order of operations. --- src/ObjLoading/Game/T6/BSP/BSP.h | 2 ++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 21 +++++++++++++++++-- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 7 +++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 840c4122d..5a5718278 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -24,6 +24,8 @@ namespace BSP vec4_t color; vec2_t texCoord; vec3_t normal; + vec3_t tangent; + vec3_t binormal; }; struct BSPMaterial diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 210d1646a..377c148c2 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -7,6 +7,7 @@ #include "XModel/Gltf/Internal/GltfBuffer.h" #include "XModel/Gltf/Internal/GltfBufferView.h" #include "XModel/Gltf/JsonGltf.h" +#include "XModel/Tangentspace.h" #pragma warning(push, 0) #include @@ -182,9 +183,9 @@ namespace Eigen::Vector3d scale(localScale[0], localScale[1], localScale[2]); Eigen::Affine3d transform = Eigen::Affine3d::Identity(); - transform.translate(translation); - transform.rotate(rotation); transform.scale(scale); + transform.rotate(rotation); + transform.translate(translation); return transform.matrix(); } @@ -293,6 +294,22 @@ namespace m_bsp->gfxWorld.vertices.emplace_back(vertex); } + // generate tangent and binormal vectors + tangent_space::VertexData vertexData{ + &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].pos, + sizeof(BSPVertex), + &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].normal, + sizeof(BSPVertex), + &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].texCoord, + sizeof(BSPVertex), + &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].tangent, + sizeof(BSPVertex), + &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].binormal, + sizeof(BSPVertex), + &m_bsp->gfxWorld.indices[surface.indexOfFirstIndex], + }; + tangent_space::CalculateTangentSpace(vertexData, faceCount, vertexCount); + return vertexOffset; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 0ec1963e6..c42b525b8 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -27,11 +27,10 @@ namespace BSP gfxVertex->color.packed = pack32::Vec4PackGfxColor(bspVertex.color.v); gfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(bspVertex.texCoord.v); gfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.normal.v); - // gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); - gfxVertex->tangent.packed = 0; + gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); + gfxVertex->binormalSign = bspVertex.binormal.v[0] > 0.0f ? 1.0f : -1.0f; - // unimplemented variables - gfxVertex->binormalSign = 0.0f; + // lightmap isn't implemented currently gfxVertex->lmapCoord.packed = 0; } gfxWorld->draw.vd0.data = reinterpret_cast(vertexBuffer); From 869724bc25c53a73102fd15de883cbed39126db7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:33:36 +0800 Subject: [PATCH 054/123] Extended and fixed various GFXWorld linking code --- src/ObjLoading/Game/T6/BSP/BSP.h | 1 + src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 1 + .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 39 +++++++++---------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 5a5718278..d67d43480 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -37,6 +37,7 @@ namespace BSP struct BSPSurface { BSPMaterial material; + uint16_t vertexCount; uint16_t triCount; int indexOfFirstVertex; int indexOfFirstIndex; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 377c148c2..46f48da3e 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -248,6 +248,7 @@ namespace if (faceCount > UINT16_MAX) throw GltfLoadException("Face count exceeded the UINT16_MAX"); + surface.vertexCount = vertexCount; surface.triCount = faceCount; surface.indexOfFirstIndex = static_cast(m_bsp->gfxWorld.indices.size()); surface.indexOfFirstVertex = static_cast(m_bsp->gfxWorld.vertices.size()); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index c42b525b8..28307de3e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -68,11 +68,13 @@ namespace BSP gfxSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; gfxSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - gfxSurface->tris.triCount = static_cast(bspSurface.triCount); + gfxSurface->tris.triCount = bspSurface.triCount; gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex; + gfxSurface->tris.vertexCount = bspSurface.vertexCount; + gfxSurface->tris.firstVertex = bspSurface.indexOfFirstVertex; gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - gfxSurface->tris.vertexDataOffset1 = 0; + gfxSurface->tris.vertexDataOffset1 = 0; // vd1 is unused std::string surfMaterialName; if (bspSurface.material.materialType == MATERIAL_TYPE_TEXTURE) @@ -105,17 +107,15 @@ namespace BSP uint16_t vertIndex = gfxWorld->draw.indices[gfxSurface->tris.baseIndex + indexIdx]; BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, gfxSurface->bounds[0], gfxSurface->bounds[1]); } - - // unused values - gfxSurface->tris.mins.x = 0.0f; - gfxSurface->tris.mins.y = 0.0f; - gfxSurface->tris.mins.z = 0.0f; - gfxSurface->tris.maxs.x = 0.0f; - gfxSurface->tris.maxs.y = 0.0f; - gfxSurface->tris.maxs.z = 0.0f; + gfxSurface->tris.mins.x = gfxSurface->bounds[0].x; + gfxSurface->tris.mins.y = gfxSurface->bounds[0].y; + gfxSurface->tris.mins.z = gfxSurface->bounds[0].z; + gfxSurface->tris.maxs.x = gfxSurface->bounds[1].x; + gfxSurface->tris.maxs.y = gfxSurface->bounds[1].y; + gfxSurface->tris.maxs.z = gfxSurface->bounds[1].z; + + // unknown value gfxSurface->tris.himipRadiusInvSq = 0.0f; - gfxSurface->tris.vertexCount = 0; - gfxSurface->tris.firstVertex = 0; } // doesn't seem to matter what order the sorted surfs go in @@ -484,13 +484,12 @@ namespace BSP void GfxWorldLinker::loadWorldBounds(GfxWorld* gfxWorld) { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - + gfxWorld->mins.x = gfxWorld->dpvs.surfaces[0].bounds[0].x; + gfxWorld->mins.y = gfxWorld->dpvs.surfaces[0].bounds[0].y; + gfxWorld->mins.z = gfxWorld->dpvs.surfaces[0].bounds[0].z; + gfxWorld->maxs.x = gfxWorld->dpvs.surfaces[0].bounds[1].x; + gfxWorld->maxs.y = gfxWorld->dpvs.surfaces[0].bounds[1].y; + gfxWorld->maxs.z = gfxWorld->dpvs.surfaces[0].bounds[1].z; for (int surfIdx = 0; surfIdx < gfxWorld->surfaceCount; surfIdx++) { BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[surfIdx].bounds[0], gfxWorld->dpvs.surfaces[surfIdx].bounds[1], gfxWorld->mins, gfxWorld->maxs); @@ -664,7 +663,7 @@ namespace BSP gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // +100: there is a crash that happens when ragdolls are created, and dynEntClientWordCount[0] is the issue. // Making the value much larger than required fixes it, but unsure what the root cause is gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; From 55c515c0b22544076834719a6b1554095bd5b795 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:41:40 +0800 Subject: [PATCH 055/123] Minor clipmap improvements --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 13 ++++++++++--- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 56be1b958..7d1624290 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -402,7 +402,7 @@ namespace BSP } } - void ClipMapLinker::loadBSPTree(clipMap_t* clipMap, BSPData* bsp) + bool ClipMapLinker::loadBSPTree(clipMap_t* clipMap, BSPData* bsp) { vec3_t worldMins; vec3_t worldMaxs; @@ -418,7 +418,11 @@ namespace BSP BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - assert(!tree->isLeaf); + if (!tree->isLeaf) + { + con::error("Map size is too small for BSP generation!"); + return false; + } for (int partitionIdx = 0; partitionIdx < clipMap->partitionCount; partitionIdx++) { @@ -466,6 +470,8 @@ namespace BSP clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; con::info("Highest leaf object count: {}", highestLeafObjectCount); + + return true; } bool ClipMapLinker::loadPartitions(clipMap_t* clipMap, BSPData* bsp) @@ -600,7 +606,8 @@ namespace BSP if (!loadPartitions(clipMap, bsp)) return false; - loadBSPTree(clipMap, bsp); + if (!loadBSPTree(clipMap, bsp)) + return false; return true; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 4b6dcd6e7..459eac426 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -33,7 +33,7 @@ namespace BSP size_t highestLeafObjectCount = 0; void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); - void loadBSPTree(clipMap_t* clipMap, BSPData* bsp); + bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); }; From 07392a8442cd7e20052e2b8ee52ef5ce20fc42f1 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:20:41 +0800 Subject: [PATCH 056/123] Minor improvements to GFX linker --- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 28307de3e..510d84d33 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -44,10 +44,7 @@ namespace BSP assert(indexCount % 3 == 0); gfxWorld->draw.indexCount = static_cast(indexCount); gfxWorld->draw.indices = m_memory.Alloc(indexCount); - for (size_t indexIdx = 0; indexIdx < indexCount; indexIdx++) - { - gfxWorld->draw.indices[indexIdx] = bsp->gfxWorld.indices.at(indexIdx); - } + memcpy(gfxWorld->draw.indices, bsp->gfxWorld.indices.data(), sizeof(uint16_t) * indexCount); } bool GfxWorldLinker::loadMapSurfaces(BSPData* bsp, GfxWorld* gfxWorld) @@ -118,7 +115,7 @@ namespace BSP gfxSurface->tris.himipRadiusInvSq = 0.0f; } - // doesn't seem to matter what order the sorted surfs go in + // Some code uses Sorted surfs to index surfaces, so for simplicity keep the indexes sequential and from 0 gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(surfaceCount); for (size_t surfIdx = 0; surfIdx < surfaceCount; surfIdx++) gfxWorld->dpvs.sortedSurfIndex[surfIdx] = static_cast(surfIdx); @@ -128,13 +125,13 @@ namespace BSP // set all surface types to lit opaque gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount - 1); + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount - 1); + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount - 1); + gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount - 1); + gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount - 1); + gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount - 1); + gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount - 1); // visdata is written to by the game // all visdata is alligned by 128 @@ -340,10 +337,13 @@ namespace BSP gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) { + gfxWorld->shadowGeom[lightIdx].smodelCount = 0; - gfxWorld->shadowGeom[lightIdx].surfaceCount = 0; gfxWorld->shadowGeom[lightIdx].smodelIndex = nullptr; - gfxWorld->shadowGeom[lightIdx].sortedSurfIndex = nullptr; + + // sorted surfs is written to by the game + gfxWorld->shadowGeom[lightIdx].surfaceCount = gfxWorld->dpvs.staticSurfaceCount; + gfxWorld->shadowGeom[lightIdx].sortedSurfIndex = m_memory.Alloc(gfxWorld->dpvs.staticSurfaceCount); } gfxWorld->lightRegion = m_memory.Alloc(gfxWorld->primaryLightCount); @@ -362,7 +362,7 @@ namespace BSP void GfxWorldLinker::loadLightGrid(GfxWorld* gfxWorld) { - // there is almost no basis for the values in this code, they were chosen based on what looks correct when reverse engineering. + // the values in this code were chosen based on what looked correct when reverse engineering. // mins and maxs define the range that the lightgrid will work in. // unknown how these values are calculated, but the below values are larger From 382e19964e7cada5a1b7af310fdcd1d310987b1d Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:03:57 +0800 Subject: [PATCH 057/123] Bux fixes for the clipmap linker --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 7d1624290..afd118695 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -123,23 +123,18 @@ namespace BSP void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) { // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) - auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); - assert(gfxWorldAsset != nullptr); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - // Right now there is only one submodel, the world sub model - assert(gfxWorld->modelCount == 1); - clipMap->numSubModels = 1; clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); - - GfxBrushModel* gfxModel = &gfxWorld->models[0]; - clipMap->cmodels[0].mins.x = gfxModel->bounds[0].x; - clipMap->cmodels[0].mins.y = gfxModel->bounds[0].y; - clipMap->cmodels[0].mins.z = gfxModel->bounds[0].z; - clipMap->cmodels[0].maxs.x = gfxModel->bounds[1].x; - clipMap->cmodels[0].maxs.y = gfxModel->bounds[1].y; - clipMap->cmodels[0].maxs.z = gfxModel->bounds[1].z; + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) + { + vec3_t vertex = clipMap->verts[vertIdx]; + if (vertIdx == 0) + { + clipMap->cmodels[0].mins = vertex; + clipMap->cmodels[0].maxs = vertex; + } + BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); + } clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; // The world sub model has no leafs associated with it @@ -155,8 +150,7 @@ namespace BSP clipMap->cmodels[0].leaf.maxs.z = 0.0f; clipMap->cmodels[0].leaf.leafBrushNode = 0; clipMap->cmodels[0].leaf.cluster = 0; - - clipMap->cmodels[0].info = nullptr; // always set to 0 + clipMap->cmodels[0].info = nullptr; } void ClipMapLinker::loadXModelCollision(clipMap_t* clipMap) @@ -632,8 +626,6 @@ namespace BSP loadRopesAndConstraints(clipMap); - loadSubModelCollision(clipMap, bsp); - loadDynEnts(clipMap); loadXModelCollision(clipMap); @@ -646,15 +638,17 @@ namespace BSP clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + if (!loadWorldCollision(clipMap, bsp)) + return nullptr; + + loadSubModelCollision(clipMap, bsp); // requires tri verts + // set all edges to walkable // might do weird stuff on walls, but from testing doesnt seem to do anything int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + clipMap->triEdgeIsWalkable = m_memory.Alloc(walkableEdgeSize); memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - if (!loadWorldCollision(clipMap, bsp)) - return nullptr; - return clipMap; } } // namespace BSP From a9e5e6a8e975103896b6e0d5e39ad3e10383094f Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:24:04 +0800 Subject: [PATCH 058/123] Clipmap linking improvements and bug fix --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index afd118695..de120a2dc 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -316,11 +316,10 @@ namespace BSP { cLeaf_s leaf; - leaf.cluster = 0; // always use cluster 0 - leaf.brushContents = 0; // no brushes used so contents is 0 - leaf.terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; + leaf.cluster = 0; // always use cluster 0 - // unused when leafBrushNode == 0 + // no brushes used so data is set to 0 + leaf.brushContents = 0; leaf.mins.x = 0.0f; leaf.mins.y = 0.0f; leaf.mins.z = 0.0f; @@ -329,6 +328,8 @@ namespace BSP leaf.maxs.z = 0.0f; leaf.leafBrushNode = 0; + // -1 as it uses tris + leaf.terrainContents = -1; if (tree->leaf->getObjectCount() > 0) { size_t parentCount = 0; @@ -412,7 +413,7 @@ namespace BSP BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - if (!tree->isLeaf) + if (tree->isLeaf) { con::error("Map size is too small for BSP generation!"); return false; From 3d7adbc47c2ccb47f3b43ec37bf227b889f653ae Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:35:16 +0800 Subject: [PATCH 059/123] Materials now have their own vector and are acessed via indexes --- src/ObjLoading/Game/T6/BSP/BSP.h | 3 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 50 +++++++++++++------ .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 43 ++++++++++++---- .../Game/T6/BSP/Linker/ClipMapLinker.h | 3 ++ .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 25 +++++----- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index d67d43480..227c35a1c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -36,7 +36,7 @@ namespace BSP struct BSPSurface { - BSPMaterial material; + size_t materialIndex; uint16_t vertexCount; uint16_t triCount; int indexOfFirstVertex; @@ -48,6 +48,7 @@ namespace BSP std::vector surfaces; std::vector vertices; std::vector indices; + std::vector materials; }; struct BSPData diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 46f48da3e..54aa417b9 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -83,6 +83,7 @@ namespace private: const Input& m_input; BSPData* m_bsp; + size_t m_color_mat_idx; std::vector> m_accessors; std::vector> m_buffer_views; std::vector> m_buffers; @@ -316,27 +317,15 @@ namespace void loadSurfaceMaterialData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) { - BSPMaterialType matType; if (!primitive.material) + { if (!primitive.attributes.COLOR_0) - { - // matType = MATERIAL_TYPE_EMPTY; throw GltfLoadException("Primitive requires material or colour data."); - } - else - matType = MATERIAL_TYPE_COLOUR; - else - matType = MATERIAL_TYPE_TEXTURE; - surface.material.materialType = matType; - if (matType == MATERIAL_TYPE_TEXTURE) - { - if (!jRoot.materials || *primitive.material >= jRoot.materials->size()) - throw GltfLoadException("Invalid material index"); - surface.material.materialName = jRoot.materials.value()[primitive.material.value()].name.value(); + surface.materialIndex = m_color_mat_idx; } else - surface.material.materialName = ""; + surface.materialIndex = *primitive.material; } bool CreateSurfacesFromNode(const JsonRoot& jRoot, const gltf::JsonNode& node) @@ -414,6 +403,36 @@ namespace return rootNodes; } + void LoadMaterials(const JsonRoot& jRoot) + { + if (!jRoot.materials) + return; + m_bsp->gfxWorld.materials.reserve((*jRoot.materials).size()); + for (auto& jsMaterial : *jRoot.materials) + { + BSPMaterial material; + + if (jsMaterial.name && (*jsMaterial.name).length() != 0) + { + material.materialType = MATERIAL_TYPE_TEXTURE; + material.materialName = *jsMaterial.name; + } + else + { + material.materialType = MATERIAL_TYPE_EMPTY; + material.materialName = ""; + } + + m_bsp->gfxWorld.materials.emplace_back(material); + } + + m_color_mat_idx = m_bsp->gfxWorld.materials.size(); + BSPMaterial colorMaterial; + colorMaterial.materialType = MATERIAL_TYPE_COLOUR; + colorMaterial.materialName = ""; + m_bsp->gfxWorld.materials.emplace_back(colorMaterial); + } + void TraverseNodes(const JsonRoot& jRoot) { // Make sure there are any nodes to traverse @@ -551,6 +570,7 @@ namespace CreateBufferViews(jRoot); CreateAccessors(jRoot); + LoadMaterials(jRoot); TraverseNodes(jRoot); } catch (const GltfLoadException& e) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index de120a2dc..22d415304 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -220,6 +220,8 @@ namespace BSP if (remainder > 0) parentCount++; + // the material index of the AABB tree is only checked for the parent node, so + size_t parentAABBArrayIndex = AABBTreeVec.size(); AABBTreeVec.resize(AABBTreeVec.size() + parentCount); size_t unaddedObjectCount = leafObjectCount; @@ -232,7 +234,7 @@ namespace BSP else unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; - // add the parent AABB + // calculate parent AABB mins and maxs vec3_t parentMins; vec3_t parentMaxs; for (size_t objectIdx = 0; objectIdx < childObjectCount; objectIdx++) @@ -259,7 +261,7 @@ namespace BSP CollisionAabbTree parentAABB; parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); - parentAABB.materialIndex = 0; // always use the first material + parentAABB.materialIndex = 0; parentAABB.childCount = static_cast(childObjectCount); parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; @@ -505,8 +507,10 @@ namespace BSP // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. std::vector partitionVec; std::vector uniqueIndicesVec; - for (BSPSurface& surface : bsp->colWorld.surfaces) + for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) { + BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; + // partitions are made for each triangle, not one for each surface. // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower @@ -528,6 +532,8 @@ namespace BSP uniqueIndicesVec.emplace_back(tri[2]); partitionVec.emplace_back(partition); + + partitionToSurfaceMap.emplace_back(surfIdx); } } clipMap->partitionCount = static_cast(partitionVec.size()); @@ -607,6 +613,29 @@ namespace BSP return true; } + bool ClipMapLinker::loadMaterials(clipMap_t* clipMap, BSPData* bsp) + { + // Clipmap materials define the properties of a material (bullet penetration, no collision, water, etc) + + if (bsp->colWorld.materials.size() > UINT16_MAX) + { + con::error("Collision map exceeds 0xFFFF materials"); + return false; + } + + clipMap->info.numMaterials = static_cast(bsp->colWorld.materials.size()); + clipMap->info.materials = m_memory.Alloc(clipMap->info.numMaterials); + for (size_t matIdx = 0; matIdx < bsp->colWorld.materials.size(); matIdx++) + { + ClipMaterial* clipMat = &clipMap->info.materials[matIdx]; + BSPMaterial bspMat = bsp->colWorld.materials.at(matIdx); + + clipMat->name = m_memory.Dup(bspMat.materialName.c_str()); + clipMat->contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; + clipMat->surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + } + } + clipMap_t* ClipMapLinker::linkClipMap(BSPData* bsp) { clipMap_t* clipMap = m_memory.Alloc(); @@ -631,13 +660,7 @@ namespace BSP loadXModelCollision(clipMap); - // Clipmap materials define the properties of a material (bullet penetration, no collision, water, etc) - // Right now there is no way to define properties per material so only one material is used - clipMap->info.numMaterials = 1; - clipMap->info.materials = m_memory.Alloc(clipMap->info.numMaterials); - clipMap->info.materials[0].name = m_memory.Dup(BSPLinkingConstants::MISSING_IMAGE_NAME); - clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + loadMaterials(clipMap, bsp); if (!loadWorldCollision(clipMap, bsp)) return nullptr; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 459eac426..5597b7e40 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -31,10 +31,13 @@ namespace BSP std::vector leafVec; std::vector AABBTreeVec; size_t highestLeafObjectCount = 0; + std::vector partitionToSurfaceMap; + std::vector surfaceToMaterialMap; void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); + bool loadMaterials(clipMap_t* clipMap, BSPData* bsp); }; } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 510d84d33..e04a115c7 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -73,22 +73,21 @@ namespace BSP gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); gfxSurface->tris.vertexDataOffset1 = 0; // vd1 is unused - std::string surfMaterialName; - if (bspSurface.material.materialType == MATERIAL_TYPE_TEXTURE) - surfMaterialName = bspSurface.material.materialName; - else // MATERIAL_TYPE_COLOUR || MATERIAL_TYPE_EMPTY - surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + BSPMaterial bspMaterial = bsp->colWorld.materials.at(bspSurface.materialIndex); - auto surfMaterialAsset = m_context.LoadDependency(surfMaterialName); + std::string materialName; + if (bspMaterial.materialType == MATERIAL_TYPE_EMPTY) + materialName = BSPLinkingConstants::MISSING_IMAGE_NAME; + else if (bspMaterial.materialType == MATERIAL_TYPE_COLOUR) + materialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + else // MATERIAL_TYPE_TEXTURE + materialName = bspMaterial.materialName; + + auto surfMaterialAsset = m_context.LoadDependency(materialName); if (surfMaterialAsset == nullptr) { - std::string missingImageName = BSPLinkingConstants::MISSING_IMAGE_NAME; - surfMaterialAsset = m_context.LoadDependency(missingImageName); - if (surfMaterialAsset == nullptr) - { - con::error("unable to load the missing image texture {}!", missingImageName); - return false; - } + surfMaterialAsset = m_context.LoadDependency(BSPLinkingConstants::MISSING_IMAGE_NAME); + assert(surfMaterialAsset != nullptr); } gfxSurface->material = surfMaterialAsset->Asset(); From b7943dbd6d688c9fa1472a4f66ad81d6e45b1554 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:14:37 +0800 Subject: [PATCH 060/123] WIP unique tree for each material type --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 22 ++++++++++++++++++- .../Game/T6/BSP/Linker/ClipMapLinker.h | 3 +-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 22d415304..1d9fc1cd6 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -212,6 +212,26 @@ namespace BSP if (leafObjectCount > highestLeafObjectCount) highestLeafObjectCount = leafObjectCount; + std::vector uniqueMaterials; + for (size_t objIdx = 0; objIdx < leafObjectCount; objIdx++) + { + int partitionIdx = tree->leaf->getObject(objIdx)->partitionIndex; + unsigned materialIndex = partitionToMaterialMap[partitionIdx]; + bool foundIdx = false; + for (unsigned int uniqueMat : uniqueMaterials) + { + if (uniqueMat == materialIndex) + { + foundIdx = true; + break; + } + } + if (!foundIdx) + uniqueMaterials.emplace_back(materialIndex); + } + + con::info("{} {}", uniqueMaterials.size(), uniqueMaterials[0]); + // BO2 has a maximum limit of 128 children per AABB tree (essentially), // so this is fixed by adding multiple parent AABB trees that hold 128 children each size_t result = leafObjectCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; @@ -533,7 +553,7 @@ namespace BSP partitionVec.emplace_back(partition); - partitionToSurfaceMap.emplace_back(surfIdx); + partitionToMaterialMap.emplace_back(surface.materialIndex); } } clipMap->partitionCount = static_cast(partitionVec.size()); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 5597b7e40..a4f5f19c8 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -31,8 +31,7 @@ namespace BSP std::vector leafVec; std::vector AABBTreeVec; size_t highestLeafObjectCount = 0; - std::vector partitionToSurfaceMap; - std::vector surfaceToMaterialMap; + std::vector partitionToMaterialMap; void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); From 98a873a47aa3a1346eff4470ea3ba039b3923864 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:53:48 +0800 Subject: [PATCH 061/123] Fixed various warning messages --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 30 ++++++++++--------- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 2 ++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 54aa417b9..a7bbf7820 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -125,10 +125,10 @@ namespace throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount)); } - Eigen::Matrix4d createNodeMatrix(const gltf::JsonNode& node) + Eigen::Matrix4f createNodeMatrix(const gltf::JsonNode& node) { if (node.matrix) - return Eigen::Matrix4d({ + return Eigen::Matrix4f({ {(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]}, {(*node.matrix)[1], (*node.matrix)[5], (*node.matrix)[9], (*node.matrix)[13]}, {(*node.matrix)[2], (*node.matrix)[6], (*node.matrix)[10], (*node.matrix)[14]}, @@ -179,11 +179,11 @@ namespace localScale[2] = 1.0f; } - Eigen::Vector3d translation(localTranslation[0], localTranslation[1], localTranslation[2]); - Eigen::Quaterniond rotation(localRotation[0], localRotation[1], localRotation[2], localRotation[3]); - Eigen::Vector3d scale(localScale[0], localScale[1], localScale[2]); + Eigen::Vector3f translation(localTranslation[0], localTranslation[1], localTranslation[2]); + Eigen::Quaternionf rotation(localRotation[0], localRotation[1], localRotation[2], localRotation[3]); + Eigen::Vector3f scale(localScale[0], localScale[1], localScale[2]); - Eigen::Affine3d transform = Eigen::Affine3d::Identity(); + Eigen::Affine3f transform = Eigen::Affine3f::Identity(); transform.scale(scale); transform.rotate(rotation); transform.translate(translation); @@ -249,8 +249,8 @@ namespace if (faceCount > UINT16_MAX) throw GltfLoadException("Face count exceeded the UINT16_MAX"); - surface.vertexCount = vertexCount; - surface.triCount = faceCount; + surface.vertexCount = static_cast(vertexCount); + surface.triCount = static_cast(faceCount); surface.indexOfFirstIndex = static_cast(m_bsp->gfxWorld.indices.size()); surface.indexOfFirstVertex = static_cast(m_bsp->gfxWorld.vertices.size()); @@ -266,12 +266,12 @@ namespace throw GltfLoadException("Index number exceeded the UINT16_MAX"); RhcToLhcIndices(indices); - m_bsp->gfxWorld.indices.emplace_back(indices[0]); - m_bsp->gfxWorld.indices.emplace_back(indices[1]); - m_bsp->gfxWorld.indices.emplace_back(indices[2]); + m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[0])); + m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[1])); + m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[2])); } - Eigen::Matrix4d nodeMatrix = createNodeMatrix(node); + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) @@ -284,8 +284,8 @@ namespace assert(false); } - Eigen::Vector4d position(vertex.pos.x, vertex.pos.y, vertex.pos.z, 1.0f); - Eigen::Vector4d transformedPosition = nodeMatrix * position; + Eigen::Vector4f position(vertex.pos.x, vertex.pos.y, vertex.pos.z, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; vertex.pos.x = transformedPosition.x(); vertex.pos.y = transformedPosition.y(); vertex.pos.z = transformedPosition.z(); @@ -578,6 +578,8 @@ namespace con::error("Failed to load GLTF: {}", e.Str()); return false; } + + return true; } BSPLoader(const Input& input, BSPData* bsp) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 1d9fc1cd6..d6571b8e2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -654,6 +654,8 @@ namespace BSP clipMat->contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; clipMat->surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; } + + return true; } clipMap_t* ClipMapLinker::linkClipMap(BSPData* bsp) From 3387792da76edfe7435cbf19e8d1d9c3111e7946 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 12 Feb 2026 17:26:39 +0800 Subject: [PATCH 062/123] Minor fixes --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 2 +- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index d6571b8e2..deac5368a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -351,7 +351,7 @@ namespace BSP leaf.leafBrushNode = 0; // -1 as it uses tris - leaf.terrainContents = -1; + leaf.terrainContents = 1; if (tree->leaf->getObjectCount() > 0) { size_t parentCount = 0; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index e04a115c7..e4d0cdf07 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -44,6 +44,7 @@ namespace BSP assert(indexCount % 3 == 0); gfxWorld->draw.indexCount = static_cast(indexCount); gfxWorld->draw.indices = m_memory.Alloc(indexCount); + static_assert(sizeof(bsp->gfxWorld.indices.data()[0]) == sizeof(uint16_t)); memcpy(gfxWorld->draw.indices, bsp->gfxWorld.indices.data(), sizeof(uint16_t) * indexCount); } From cb9976c45e1e1ad18232dd6a7be05dff98ec11f3 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 12 Feb 2026 17:47:53 +0800 Subject: [PATCH 063/123] Fixed incorrect order of operations during vertex matrix creation --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index a7bbf7820..fe07b6492 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -184,9 +184,9 @@ namespace Eigen::Vector3f scale(localScale[0], localScale[1], localScale[2]); Eigen::Affine3f transform = Eigen::Affine3f::Identity(); - transform.scale(scale); - transform.rotate(rotation); transform.translate(translation); + transform.rotate(rotation); + transform.scale(scale); return transform.matrix(); } From 2904a40c2ba5a634f30242983d6dd84394b29c83 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 16 Feb 2026 18:32:20 +0800 Subject: [PATCH 064/123] Clipmap linker refactor, and AABB trees are seperated by material now. --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 207 +++++++++--------- 1 file changed, 109 insertions(+), 98 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index deac5368a..6536be8d5 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -133,7 +133,8 @@ namespace BSP clipMap->cmodels[0].mins = vertex; clipMap->cmodels[0].maxs = vertex; } - BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); + else + BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); } clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; @@ -203,125 +204,147 @@ namespace BSP */ } + // out_mins and out_maxs are initialised in the function + void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs) + { + for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) + { + uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; + vec3_t vert = clipMap->verts[uind]; + + // initalise the AABB with the first vertex + if (uindIdx == 0) + { + out_mins = vert; + out_maxs = vert; + } + else + BSPUtil::updateAABBWithPoint(vert, out_mins, out_maxs); + } + } + + struct uniqueMatData + { + unsigned int materialIndex; + std::vector objectIndexes; + }; + void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex) { assert(tree->isLeaf); + BSPLeaf* bspLeaf = tree->leaf.get(); - size_t leafObjectCount = tree->leaf->getObjectCount(); + size_t leafObjectCount = bspLeaf->getObjectCount(); assert(leafObjectCount > 0); if (leafObjectCount > highestLeafObjectCount) highestLeafObjectCount = leafObjectCount; - std::vector uniqueMaterials; + // the material index of the AABB tree is only checked for the parent node, so each parent has only children with the same material + std::vector uniqueMaterials; for (size_t objIdx = 0; objIdx < leafObjectCount; objIdx++) { - int partitionIdx = tree->leaf->getObject(objIdx)->partitionIndex; + int partitionIdx = bspLeaf->getObject(objIdx)->partitionIndex; unsigned materialIndex = partitionToMaterialMap[partitionIdx]; bool foundIdx = false; - for (unsigned int uniqueMat : uniqueMaterials) + for (auto& uniqueMat : uniqueMaterials) { - if (uniqueMat == materialIndex) + if (uniqueMat.materialIndex == materialIndex) { + uniqueMat.objectIndexes.emplace_back(objIdx); foundIdx = true; break; } } if (!foundIdx) - uniqueMaterials.emplace_back(materialIndex); + { + uniqueMatData data; + data.materialIndex = materialIndex; + data.objectIndexes = std::vector(); + uniqueMaterials.emplace_back(data); + } } - con::info("{} {}", uniqueMaterials.size(), uniqueMaterials[0]); - // BO2 has a maximum limit of 128 children per AABB tree (essentially), // so this is fixed by adding multiple parent AABB trees that hold 128 children each - size_t result = leafObjectCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t remainder = leafObjectCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t parentCount = result; - if (remainder > 0) - parentCount++; - - // the material index of the AABB tree is only checked for the parent node, so + size_t totalParentCount = 0; + for (auto& matData : uniqueMaterials) + { + size_t objCount = matData.objectIndexes.size(); + size_t result = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + result++; + totalParentCount += result; + } + // every parent node needs to be contiguous in memory size_t parentAABBArrayIndex = AABBTreeVec.size(); - AABBTreeVec.resize(AABBTreeVec.size() + parentCount); - size_t unaddedObjectCount = leafObjectCount; - size_t addedObjectCount = 0; - for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) - { - size_t childObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; - if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) - childObjectCount = unaddedObjectCount; - else - unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; + AABBTreeVec.resize(AABBTreeVec.size() + totalParentCount); + *out_parentCount = totalParentCount; + *out_parentStartIndex = parentAABBArrayIndex; - // calculate parent AABB mins and maxs - vec3_t parentMins; - vec3_t parentMaxs; - for (size_t objectIdx = 0; objectIdx < childObjectCount; objectIdx++) + for (auto& matData : uniqueMaterials) + { + size_t* objIndexes = matData.objectIndexes.data(); + size_t objCount = matData.objectIndexes.size(); + size_t parentCount = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + parentCount++; + + size_t unaddedObjectCount = objCount; + size_t addedObjectCount = 0; + for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) { - int partitionIndex = tree->leaf->getObject(addedObjectCount + objectIdx)->partitionIndex; - CollisionPartition* partition = &clipMap->partitions[partitionIndex]; - for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) + size_t currChildObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) + currChildObjectCount = unaddedObjectCount; + else + unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; + + vec3_t parentMins; + vec3_t parentMaxs; + size_t childObjectStartIndex = AABBTreeVec.size(); + for (size_t objectIdx = 0; objectIdx < currChildObjectCount; objectIdx++) { - uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = clipMap->verts[uind]; - - // initalise the parent AABB with the first vertex - if (objectIdx == 0 && uindIdx == 0) + // create a child AABBTree with the partition and add it to AABBTreeVec + int partitionIndex = bspLeaf->getObject(objIndexes[addedObjectCount + objectIdx])->partitionIndex; + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + vec3_t childMins; + vec3_t childMaxs; + calculatePartitionAABB(clipMap, partition, childMins, childMaxs); + + CollisionAabbTree childAABBTree; + childAABBTree.materialIndex = matData.materialIndex; + childAABBTree.childCount = 0; + childAABBTree.u.partitionIndex = partitionIndex; + childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); + childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); + AABBTreeVec.emplace_back(childAABBTree); + + // update the parent AABB with the child AABB + if (objectIdx == 0) { - parentMins = vert; - parentMaxs = vert; + parentMins = childMins; + parentMaxs = childMaxs; } - - BSPUtil::updateAABBWithPoint(vert, parentMins, parentMaxs); + else + BSPUtil::updateAABB(childMins, childMaxs, parentMins, parentMaxs); } - } - size_t childObjectStartIndex = AABBTreeVec.size(); - - CollisionAabbTree parentAABB; - parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); - parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); - parentAABB.materialIndex = 0; - parentAABB.childCount = static_cast(childObjectCount); - parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); - AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; - - // add child AABBs - for (size_t objectIdx = 0; objectIdx < childObjectCount; objectIdx++) - { - int partitionIndex = tree->leaf->getObject(addedObjectCount + objectIdx)->partitionIndex; - CollisionPartition* partition = &clipMap->partitions[partitionIndex]; - vec3_t childMins; - vec3_t childMaxs; - for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) - { - uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = clipMap->verts[uind]; - - // initalise the child AABB with the first vertex - if (uindIdx == 0) - { - childMins = vert; - childMaxs = vert; - } - BSPUtil::updateAABBWithPoint(vert, childMins, childMaxs); - } + CollisionAabbTree parentAABB; + parentAABB.materialIndex = matData.materialIndex; + parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); + parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); + parentAABB.childCount = static_cast(currChildObjectCount); + parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); + AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; - CollisionAabbTree childAABBTree; - childAABBTree.materialIndex = 0; // always use the first material - childAABBTree.childCount = 0; - childAABBTree.u.partitionIndex = partitionIndex; - childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); - childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); - AABBTreeVec.emplace_back(childAABBTree); + addedObjectCount += currChildObjectCount; } - addedObjectCount += childObjectCount; + parentAABBArrayIndex += parentCount; } - - *out_parentCount = parentCount; - *out_parentStartIndex = parentAABBArrayIndex; } constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; @@ -350,8 +373,7 @@ namespace BSP leaf.maxs.z = 0.0f; leaf.leafBrushNode = 0; - // -1 as it uses tris - leaf.terrainContents = 1; + leaf.terrainContents = 1; // todo: use correct contents if (tree->leaf->getObjectCount() > 0) { size_t parentCount = 0; @@ -446,18 +468,7 @@ namespace BSP vec3_t partitionMins; vec3_t partitionMaxs; CollisionPartition* partition = &clipMap->partitions[partitionIdx]; - for (int uindIdx = 0; uindIdx < partition->nuinds; uindIdx++) - { - uint16_t uind = clipMap->info.uinds[partition->fuind + uindIdx]; - vec3_t vert = clipMap->verts[uind]; - // initalise the AABB with the first vertex - if (uindIdx == 0) - { - partitionMins = vert; - partitionMaxs = vert; - } - BSPUtil::updateAABBWithPoint(vert, partitionMins, partitionMaxs); - } + calculatePartitionAABB(clipMap, partition, partitionMins, partitionMaxs); std::shared_ptr currObject = std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); tree->addObjectToTree(std::move(currObject)); From 5e5d5add89e29ba35c00596980dfc2b065af68aa Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:35:05 +0800 Subject: [PATCH 065/123] feat: WIP add lights to the scene --- src/ObjLoading/Game/T6/BSP/BSP.h | 29 ++- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 191 ++++++++++++++++-- .../Game/T6/BSP/Linker/ComWorldLinker.h | 1 + .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 8 +- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- 5 files changed, 207 insertions(+), 24 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 227c35a1c..ae080d6b4 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -51,6 +51,27 @@ namespace BSP std::vector materials; }; + enum BSPLightType + { + LIGHT_TYPE_DIRECTIONAL, + LIGHT_TYPE_POINT, + LIGHT_TYPE_SPOT + }; + + struct BSPLight + { + BSPLightType type; + vec3_t pos; + vec3_t direction; + vec3_t colour; + float range; + float intensity; + + // only used on spot and dir lights + float innerConeAngle; + float outerConeAngle; + }; + struct BSPData { std::string name; @@ -58,6 +79,8 @@ namespace BSP BSPWorld gfxWorld; BSPWorld colWorld; + + std::vector lights; }; // BSPGameConstants: @@ -70,7 +93,7 @@ namespace BSP enum BSPDefaultLights { - STATIC_LIGHT_INDEX = 0, + EMPTY_LIGHT_INDEX = 0, SUN_LIGHT_INDEX = 1, BSP_DEFAULT_LIGHT_COUNT = 2 }; @@ -161,10 +184,10 @@ namespace BSP constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; // Default surface values - constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; + constexpr int DEFAULT_SURFACE_LIGHT = 2; constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; - constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); + constexpr int DEFAULT_SURFACE_FLAGS = 0; // material flags determine the features of a surface // unsure which flag type changes what right now diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index c4c7c7ff4..8d6fa9aa3 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -1,5 +1,8 @@ #include "ComWorldLinker.h" +#define _USE_MATH_DEFINES +#include + namespace BSP { ComWorldLinker::ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -9,28 +12,184 @@ namespace BSP { } + bool ComWorldLinker::createLightDefs() + { + T6::GfxLightDef* lightDefCube = m_memory.Alloc(); + lightDefCube->name = m_memory.Dup("white_light_cube"); + lightDefCube->lmapLookupStart = 0; // always 0 + lightDefCube->attenuation.samplerState = 115; // always 115 + auto imageCubeAsset = m_context.LoadDependency("whitesquare_ft"); + if (imageCubeAsset == nullptr) + return false; + lightDefCube->attenuation.image = imageCubeAsset->Asset(); + m_context.AddAsset(lightDefCube->name, lightDefCube); + + T6::GfxLightDef* lightDef2d = m_memory.Alloc(); + lightDef2d->name = m_memory.Dup("white_light"); + lightDef2d->lmapLookupStart = 0; // always 0 + lightDef2d->attenuation.samplerState = 115; // always 115 + auto image2dAsset = m_context.LoadDependency("whitesquare"); + if (image2dAsset == nullptr) + return false; + lightDef2d->attenuation.image = image2dAsset->Asset(); + m_context.AddAsset(lightDef2d->name, lightDef2d); + + return true; + } + + // does not set: type, cosHalfFovOuter, cosHalfFovInner, cosHalfFovExpanded, roundness, defName + void setLightCommonValues(ComPrimaryLight* light, BSPLight* bspLight) + { + light->origin.x = bspLight->pos.x; + light->origin.y = bspLight->pos.y; + light->origin.z = bspLight->pos.z; + + light->color.x = bspLight->colour.x; + light->color.y = bspLight->colour.y; + light->color.z = bspLight->colour.z; + light->diffuseColor.x = bspLight->colour.x; + light->diffuseColor.y = bspLight->colour.y; + light->diffuseColor.z = bspLight->colour.z; + light->diffuseColor.w = 0.0f; + + light->dir.x = bspLight->direction.x; + light->dir.y = bspLight->direction.y; + light->dir.z = bspLight->direction.z; + + light->dAttenuation = bspLight->intensity; // not too sure if this is correct or not + + light->radius = bspLight->range; + + light->falloff.x = 0.0f; + light->falloff.y = bspLight->range; + light->falloff.z = 0.0f; + light->falloff.w = 0.0f; + + // allways 0 + light->angle.x = 0.0f; + light->angle.y = 0.0f; + light->angle.z = 0.0f; + light->angle.w = 0.0f; + + // 1.0f - light cannot rotate + // -1.0f - infinitely rotate + // between 1 and -1 - limit that it can rotate each game update + light->translationLimit = 1.0f; + + // 0 - light cannot move + // 1 <= x > 0 - limit that it can move each game update + light->rotationLimit = 0.0f; + + // default values from official map + light->cookieControl0.x = 0.0f; + light->cookieControl0.y = 0.0f; + light->cookieControl0.z = 1.0f; + light->cookieControl0.w = 1.0f; + light->cookieControl1.x = 0.0f; + light->cookieControl1.y = 0.0f; + light->cookieControl1.z = 0.0f; + light->cookieControl1.w = 0.0f; + light->cookieControl2.x = 0.0f; + light->cookieControl2.y = 0.0f; + light->cookieControl2.z = 0.0f; + light->cookieControl2.w = 0.0f; + + // values taken from an mp_overflow light + light->aAbB.x = 0.5303301215171814f; + light->aAbB.y = 0.7071067690849304f; + light->aAbB.z = 0.5303301215171814f; + light->aAbB.w = 0.7071067690849304f; + + light->canUseShadowMap = false; + light->shadowmapVolume = 0; + light->exponent = 0; + light->priority = 0; + light->cullDist = 10000; + light->useCookie = 0; + light->mipDistance = 0.0f; + } + ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { + BSPLight eeeee; + eeeee.type = LIGHT_TYPE_POINT; + eeeee.pos = vec3_t{22.35f, (-493.42f), 10.96f}; + eeeee.direction = vec3_t{0.0f, 0.0f, 0.0f}; + eeeee.colour = vec3_t{1.0f, 1.0f, 1.0f}; + eeeee.range = 1000.0f; + eeeee.intensity = 1515948.33f; + eeeee.innerConeAngle = 0.0f; + eeeee.outerConeAngle = 0.0f; + bsp->lights.emplace_back(eeeee); + // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = m_memory.Alloc(); comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->isInUse = 1; - comWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; - comWorld->primaryLights = m_memory.Alloc(comWorld->primaryLightCount); - - // first (static) light is always empty - - ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - const vec4_t sunLightColor = BSPEditableConstants::SUNLIGHT_COLOR; - const vec3_t sunLightDirection = BSPEditableConstants::SUNLIGHT_DIRECTION; - sunLight->type = GFX_LIGHT_TYPE_DIR; - sunLight->diffuseColor.r = sunLightColor.r; - sunLight->diffuseColor.g = sunLightColor.g; - sunLight->diffuseColor.b = sunLightColor.b; - sunLight->diffuseColor.a = sunLightColor.a; - sunLight->dir.x = sunLightDirection.x; - sunLight->dir.y = sunLightDirection.y; - sunLight->dir.z = sunLightDirection.z; + + size_t totalLightCount = bsp->lights.size() + BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; + comWorld->primaryLightCount = static_cast(totalLightCount); + comWorld->primaryLights = m_memory.Alloc(totalLightCount); + + if (!createLightDefs()) + { + con::error("Unable to create lightdef assets."); + return nullptr; + } + + for (size_t lightIdx = 0; lightIdx < totalLightCount; lightIdx++) + { + ComPrimaryLight* light = &comWorld->primaryLights[lightIdx]; + + if (lightIdx == BSPGameConstants::EMPTY_LIGHT_INDEX) + continue; // first (empty) light has no data + else if (lightIdx == BSPGameConstants::SUN_LIGHT_INDEX) + { + const vec4_t sunLightColor = BSPEditableConstants::SUNLIGHT_COLOR; + const vec3_t sunLightDirection = BSPEditableConstants::SUNLIGHT_DIRECTION; + light->type = GFX_LIGHT_TYPE_DIR; + light->diffuseColor.r = sunLightColor.r; + light->diffuseColor.g = sunLightColor.g; + light->diffuseColor.a = sunLightColor.a; + light->dir.x = sunLightDirection.x; + light->dir.y = sunLightDirection.y; + light->dir.z = sunLightDirection.z; + } + else + { + BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); + + // cosHalfFovOuter, cosHalfFovInner, cosHalfFovExpanded + setLightCommonValues(light, bspLight); + if (bspLight->type == LIGHT_TYPE_DIRECTIONAL) + { + light->type = GFX_LIGHT_TYPE_DIR; + light->defName = "white_light"; + light->roundness = 1.0f; + light->cosHalfFovInner = cosf(bspLight->innerConeAngle); + light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); + light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); + } + else if (bspLight->type == LIGHT_TYPE_SPOT) + { + light->type = GFX_LIGHT_TYPE_SPOT; + light->defName = "white_light"; + light->roundness = 1.0f; + light->cosHalfFovInner = cosf(bspLight->innerConeAngle); + light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); + light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); + } + else // LIGHT_TYPE_POINT + { + light->type = GFX_LIGHT_TYPE_OMNI; + light->defName = "white_light_cube"; + light->roundness = 0.0f; + light->cosHalfFovInner = cosf(30 * (M_PI / 180.0)); + light->cosHalfFovOuter = cosf(60 * (M_PI / 180.0)); + light->cosHalfFovExpanded = cosf(60 * (M_PI / 180.0)); + } + } + } return comWorld; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h index 2c688e38e..25ae1a037 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h @@ -12,6 +12,7 @@ namespace BSP public: ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); ComWorld* linkComWorld(BSPData* bsp); + bool createLightDefs(); private: MemoryManager& m_memory; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index e4d0cdf07..5c9927d15 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -328,10 +328,10 @@ namespace BSP gfxWorld->sunLight = m_memory.Alloc(); } - void GfxWorldLinker::loadGfxLights(GfxWorld* gfxWorld) + void GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld) { // there must be 2 or more lights, first is the static light and second is the sun light - gfxWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; + gfxWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT + static_cast(bsp->lights.size()); gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); @@ -401,7 +401,7 @@ namespace BSP for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; entryArray[i].visibility = 0; } gfxWorld->lightGrid.entries = entryArray; @@ -758,7 +758,7 @@ namespace BSP loadLightGrid(gfxWorld); - loadGfxLights(gfxWorld); + loadGfxLights(bsp, gfxWorld); loadModels(gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 230635905..1c3faad7c 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -22,7 +22,7 @@ namespace BSP bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); void loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld); - void loadGfxLights(GfxWorld* gfxWorld); + void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); void loadLightGrid(GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld); void loadModels(GfxWorld* gfxWorld); From 0398a654c927ae767d5e2863839b31474bf16d2c Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:53:19 +0800 Subject: [PATCH 066/123] GLTF loader now has support for the punctual lights extension --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 75 +++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 91315e675..8f1385255 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -18,6 +18,74 @@ namespace gltf NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAsset, version, generator); + class JsonPunctualSpotLightProperties + { + public: + std::optional innerConeAngle; + std::optional outerConeAngle; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPunctualSpotLightProperties, innerConeAngle, outerConeAngle); + + enum class JsonPunctualLightType + { + DIRECTIONAL, + POINT, + SPOT + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(JsonPunctualLightType, + { + {JsonPunctualLightType::DIRECTIONAL, "directional"}, + {JsonPunctualLightType::POINT, "point" }, + {JsonPunctualLightType::SPOT, "spot" }, + }); + + class JsonPunctualLight + { + public: + std::optional> color; + std::optional intensity; + std::optional name; + std::optional range; + std::optional spot; + JsonPunctualLightType type; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPunctualLight, color, intensity, name, range, spot, type); + + class JsonPunctualLightsExt + { + public: + std::optional> lights; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPunctualLightsExt, lights); + + class JsonExtension + { + public: + std::optional KHR_lights_punctual; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonExtension, KHR_lights_punctual); + + class JsonPunctualLightIndex + { + public: + int light; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPunctualLightIndex, light); + + class JsonNodeExt + { + public: + std::optional KHR_lights_punctual; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExt, KHR_lights_punctual); + class JsonNode { public: @@ -29,9 +97,11 @@ namespace gltf std::optional> children; std::optional skin; std::optional mesh; + + std::optional extensions; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, matrix, children, skin, mesh); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, matrix, children, skin, mesh, extensions); class JsonBuffer { @@ -338,8 +408,9 @@ namespace gltf std::optional scene; std::optional> scenes; std::optional> textures; + std::optional extensions; }; NLOHMANN_DEFINE_TYPE_EXTENSION( - JsonRoot, accessors, animations, asset, buffers, bufferViews, images, materials, meshes, nodes, skins, scene, scenes, textures); + JsonRoot, accessors, animations, asset, buffers, bufferViews, images, materials, meshes, nodes, skins, scene, scenes, textures, extensions); } // namespace gltf From 1cb764fbe4d3b13ea589467f686a92f75c60a5c0 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:52:10 +0800 Subject: [PATCH 067/123] Spot lights now are fully implemented, with omni and directional not working. --- src/ObjLoading/Game/T6/BSP/BSP.h | 8 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 147 ++++++++++++++++-- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 25 ++- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 75 ++++----- 4 files changed, 187 insertions(+), 68 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index ae080d6b4..cad8b6da3 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -61,13 +61,15 @@ namespace BSP struct BSPLight { BSPLightType type; - vec3_t pos; - vec3_t direction; vec3_t colour; float range; float intensity; - // only used on spot and dir lights + vec3_t pos; + vec3_t direction; + bool hasPosBeenSet; + + // angle is in radians. only used on spot/dir lights float innerConeAngle; float outerConeAngle; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index fe07b6492..c77e33329 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -125,6 +125,8 @@ namespace throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount)); } + using Transform3f = Eigen::Transform; + Eigen::Matrix4f createNodeMatrix(const gltf::JsonNode& node) { if (node.matrix) @@ -180,18 +182,15 @@ namespace } Eigen::Vector3f translation(localTranslation[0], localTranslation[1], localTranslation[2]); - Eigen::Quaternionf rotation(localRotation[0], localRotation[1], localRotation[2], localRotation[3]); + Eigen::Quaternionf rotation(localRotation[3], localRotation[0], localRotation[1], localRotation[2]); // GLTF is XYZW, Eigen is WXYZ Eigen::Vector3f scale(localScale[0], localScale[1], localScale[2]); - Eigen::Affine3f transform = Eigen::Affine3f::Identity(); - transform.translate(translation); - transform.rotate(rotation); - transform.scale(scale); - - return transform.matrix(); + Transform3f T; + T = T.fromPositionOrientationScale(translation, rotation, scale); + return T.matrix(); } - unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, BSPSurface& surface) + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -271,7 +270,6 @@ namespace m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[2])); } - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) @@ -315,7 +313,7 @@ namespace return vertexOffset; } - void loadSurfaceMaterialData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) + void loadSurfaceLightData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) { if (!primitive.material) { @@ -330,9 +328,53 @@ namespace bool CreateSurfacesFromNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { - if (!node.mesh) + if (!node.mesh && !node.extensions) return false; + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + if (node.extensions && node.extensions->KHR_lights_punctual) + { + int lightIndex = node.extensions->KHR_lights_punctual->light; + + assert(lightIndex >= 0); + if (m_bsp->lights[lightIndex].hasPosBeenSet == true) + con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + + // BO2 uses -Z up and the default light direction is straight down + Eigen::Vector3f defaultDirection(0.0f, 0.0f, 1.0f); + Eigen::Vector3f outputDirection; + + if (node.rotation) + { + Eigen::Quaternionf rotationQuat( + (*node.rotation)[3], (*node.rotation)[0], (*node.rotation)[1], (*node.rotation)[2]); // GLTF is XYZW, Eigen is WXYZ + outputDirection = rotationQuat * defaultDirection; + } + else if (node.matrix) + { + con::error("matrix rotation unimpelemted"); + assert(false); + // Eigen::Quaternionf rotationQuat; + // rotationQuat = nodeMatrix; + // outputDirection = rotationQuat * defaultDirection; + } + else + outputDirection = defaultDirection; + outputDirection.normalize(); + m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].direction.v); + + m_bsp->lights[lightIndex].hasPosBeenSet = true; + + return true; + } + con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; @@ -359,8 +401,14 @@ namespace BSPSurface surface; - loadSurfaceMaterialData(jRoot, primitive, surface); - CreateVertices(accessorsForVertex, node, surface); + if (primitive.material) + surface.materialIndex = *primitive.material; + else if (primitive.attributes.COLOR_0) + surface.materialIndex = m_color_mat_idx; + else + throw GltfLoadException("Primitive requires material or colour data."); + + CreateVertices(accessorsForVertex, node, nodeMatrix, surface); m_bsp->gfxWorld.surfaces.emplace_back(surface); } @@ -433,6 +481,78 @@ namespace m_bsp->gfxWorld.materials.emplace_back(colorMaterial); } + void LoadLights(const JsonRoot& jRoot) + { + if (!jRoot.extensions) + return; + if (!jRoot.extensions->KHR_lights_punctual) + return; + if (!jRoot.extensions->KHR_lights_punctual->lights) + return; + const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); + + m_bsp->lights.reserve(jsLightArray.size()); + for (const JsonPunctualLight& jsLight : jsLightArray) + { + if (jsLight.type == JsonPunctualLightType::POINT) + con::error("Any point lights will be converted to a spotlight as point lights are unsupported right now."); + + BSPLight light{}; + + // position and direction data will be set during node traversal + light.hasPosBeenSet = false; + + if (!jsLight.color) + { + light.colour.x = 1.0f; + light.colour.y = 1.0f; + light.colour.z = 1.0f; + } + else + { + light.colour.x = (*jsLight.color)[0]; + light.colour.y = (*jsLight.color)[1]; + light.colour.z = (*jsLight.color)[2]; + } + + if (!jsLight.intensity) + light.intensity = 100000.0f; // adjusted from GLTF spec to better match BO2 + else + light.intensity = *jsLight.intensity; + + if (!jsLight.range) + light.range = 1000.0f; // adjusted from GLTF spec to better match BO2 + else + light.range = *jsLight.range; + + if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) + { + light.type = LIGHT_TYPE_DIRECTIONAL; + } + else if (jsLight.type == JsonPunctualLightType::POINT) + { + light.type = LIGHT_TYPE_POINT; + } + else // JsonPunctualLightType::SPOT + { + light.type = LIGHT_TYPE_SPOT; + assert(jsLight.spot); + + if (!jsLight.spot->innerConeAngle) + light.innerConeAngle = 0.0f; + else + light.innerConeAngle = *jsLight.spot->innerConeAngle; + + if (!jsLight.spot->outerConeAngle) + light.outerConeAngle = 3.14159265359f / 4.0f; /// 45 degrees + else + light.outerConeAngle = *jsLight.spot->outerConeAngle; + } + + m_bsp->lights.emplace_back(light); + } + } + void TraverseNodes(const JsonRoot& jRoot) { // Make sure there are any nodes to traverse @@ -570,6 +690,7 @@ namespace CreateBufferViews(jRoot); CreateAccessors(jRoot); + LoadLights(jRoot); LoadMaterials(jRoot); TraverseNodes(jRoot); } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 8c56f95d6..795e7a56d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -103,28 +103,23 @@ namespace BSP return sqrtf((x * x) + (y * y) + (z * z)); } - // angles are in euler degrees void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) { - float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f - - float cosX = cos(xRadians); - float sinX = sin(xRadians); - float cosY = cos(yRadians); - float sinY = sin(yRadians); - float cosZ = cos(zRadians); - float sinZ = sin(zRadians); + float cosX = cos(angles->x); + float sinX = sin(angles->x); + float cosY = cos(angles->y); + float sinY = sin(angles->y); + float cosZ = cos(angles->z); + float sinZ = sin(angles->z); axis[0].x = cosX * cosY; axis[0].y = cosX * sinY; axis[0].z = -sinX; - axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); - axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); + axis[1].x = ((sinZ * sinX) * cosY) - (cosZ * sinY); + axis[1].y = ((sinZ * sinX) * sinY) + (cosZ * cosY); axis[1].z = sinZ * cosX; - axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); - axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); + axis[2].x = ((cosZ * sinX) * cosY) + (sinZ * sinY); + axis[2].y = ((cosZ * sinX) * sinY) - (sinZ * cosY); axis[2].z = cosZ * cosX; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 8d6fa9aa3..9bec483e3 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -1,5 +1,7 @@ #include "ComWorldLinker.h" +#include "../BSPUtil.h" + #define _USE_MATH_DEFINES #include @@ -44,24 +46,31 @@ namespace BSP light->origin.y = bspLight->pos.y; light->origin.z = bspLight->pos.z; - light->color.x = bspLight->colour.x; - light->color.y = bspLight->colour.y; - light->color.z = bspLight->colour.z; - light->diffuseColor.x = bspLight->colour.x; - light->diffuseColor.y = bspLight->colour.y; - light->diffuseColor.z = bspLight->colour.z; - light->diffuseColor.w = 0.0f; - + // colour also effects the brightness of the light, it's unclear what the min/max is + vec3_t BSPColor; + BSPColor.x = bspLight->colour.x; + BSPColor.y = bspLight->colour.y; + BSPColor.z = bspLight->colour.z; + light->color.x = BSPColor.x; + light->color.y = BSPColor.y; + light->color.z = BSPColor.z; + light->diffuseColor.x = BSPColor.x; + light->diffuseColor.y = BSPColor.y; + light->diffuseColor.z = BSPColor.z; + light->diffuseColor.w = 0.0f; // always 0 + + // the forward dir of the light, default is (0, 0, 1) to face down (-Z up) light->dir.x = bspLight->direction.x; light->dir.y = bspLight->direction.y; light->dir.z = bspLight->direction.z; - light->dAttenuation = bspLight->intensity; // not too sure if this is correct or not + light->dAttenuation = bspLight->intensity; // not 100% sure if these values are calculated the same + // the radius of the light's lens light->radius = bspLight->range; light->falloff.x = 0.0f; - light->falloff.y = bspLight->range; + light->falloff.y = bspLight->range; // only Y is set light->falloff.z = 0.0f; light->falloff.w = 0.0f; @@ -71,16 +80,17 @@ namespace BSP light->angle.z = 0.0f; light->angle.w = 0.0f; + // 0 - light cannot move + // 1 <= x > 0 - limit that it can move each game update + light->translationLimit = 0.0f; + // 1.0f - light cannot rotate // -1.0f - infinitely rotate // between 1 and -1 - limit that it can rotate each game update - light->translationLimit = 1.0f; - - // 0 - light cannot move - // 1 <= x > 0 - limit that it can move each game update - light->rotationLimit = 0.0f; + light->rotationLimit = 1.0f; // default values from official map + light->useCookie = 0; light->cookieControl0.x = 0.0f; light->cookieControl0.y = 0.0f; light->cookieControl0.z = 1.0f; @@ -94,39 +104,32 @@ namespace BSP light->cookieControl2.z = 0.0f; light->cookieControl2.w = 0.0f; - // values taken from an mp_overflow light + // these are possibly the result of cos(), unsure how the angle is generated though + // values taken from official maps light->aAbB.x = 0.5303301215171814f; light->aAbB.y = 0.7071067690849304f; light->aAbB.z = 0.5303301215171814f; light->aAbB.w = 0.7071067690849304f; - light->canUseShadowMap = false; - light->shadowmapVolume = 0; + // light won't light the surface unless canUseShadowMap is set to 1 + // may be to do with lights casting shadows on other lights + light->canUseShadowMap = 1; + light->shadowmapVolume = 0; // ignored if this is set to 0 + light->exponent = 0; light->priority = 0; - light->cullDist = 10000; - light->useCookie = 0; + light->cullDist = 1000; light->mipDistance = 0.0f; } ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { - BSPLight eeeee; - eeeee.type = LIGHT_TYPE_POINT; - eeeee.pos = vec3_t{22.35f, (-493.42f), 10.96f}; - eeeee.direction = vec3_t{0.0f, 0.0f, 0.0f}; - eeeee.colour = vec3_t{1.0f, 1.0f, 1.0f}; - eeeee.range = 1000.0f; - eeeee.intensity = 1515948.33f; - eeeee.innerConeAngle = 0.0f; - eeeee.outerConeAngle = 0.0f; - bsp->lights.emplace_back(eeeee); - // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = m_memory.Alloc(); comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->isInUse = 1; + // first two lights are the empty light and the sun light. size_t totalLightCount = bsp->lights.size() + BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; comWorld->primaryLightCount = static_cast(totalLightCount); comWorld->primaryLights = m_memory.Alloc(totalLightCount); @@ -158,9 +161,6 @@ namespace BSP else { BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); - - // cosHalfFovOuter, cosHalfFovInner, cosHalfFovExpanded - setLightCommonValues(light, bspLight); if (bspLight->type == LIGHT_TYPE_DIRECTIONAL) { light->type = GFX_LIGHT_TYPE_DIR; @@ -184,10 +184,11 @@ namespace BSP light->type = GFX_LIGHT_TYPE_OMNI; light->defName = "white_light_cube"; light->roundness = 0.0f; - light->cosHalfFovInner = cosf(30 * (M_PI / 180.0)); - light->cosHalfFovOuter = cosf(60 * (M_PI / 180.0)); - light->cosHalfFovExpanded = cosf(60 * (M_PI / 180.0)); + light->cosHalfFovInner = cosf(30.0f * (M_PI / 180.0f)); + light->cosHalfFovOuter = cosf(55.0f * (M_PI / 180.0f)); + light->cosHalfFovExpanded = cosf(55.0f * (M_PI / 180.0f)); } + setLightCommonValues(light, bspLight); } } From 75e35cff93b1c01194c2fab4238e72084b337398 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:14:35 +0800 Subject: [PATCH 068/123] Extended GLTF classes to support: material colours, and images buffer view index / image type. --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 8f1385255..a2bdeeb8a 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -277,10 +277,11 @@ namespace gltf { public: std::optional baseColorTexture; + std::optional> baseColorFactor; std::optional metallicFactor; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPbrMetallicRoughness, baseColorTexture, metallicFactor); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPbrMetallicRoughness, baseColorTexture, baseColorFactor, metallicFactor); class JsonNormalTextureInfo { @@ -388,9 +389,12 @@ namespace gltf { public: std::optional uri; + + std::optional bufferView; + std::optional mimeType; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonImage, uri); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonImage, uri, bufferView, mimeType); class JsonRoot { From cab2bccd41965d788936cc36b72bb9388d3c03a9 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:20:25 +0800 Subject: [PATCH 069/123] BSP creator now reads colour data from materials and applies them to the vertices. Minor GFXWorld fixups and extensions to accomodate the changes. --- src/ObjLoading/Game/T6/BSP/BSP.h | 10 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 171 ++++++++++-------- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 12 +- 3 files changed, 104 insertions(+), 89 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index cad8b6da3..a3bf5a1d2 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -14,8 +14,7 @@ namespace BSP enum BSPMaterialType { MATERIAL_TYPE_COLOUR, - MATERIAL_TYPE_TEXTURE, - MATERIAL_TYPE_EMPTY + MATERIAL_TYPE_TEXTURE }; struct BSPVertex @@ -30,8 +29,9 @@ namespace BSP struct BSPMaterial { - BSPMaterialType materialType; std::string materialName; + BSPMaterialType materialType; + vec4_t materialColour; }; struct BSPSurface @@ -132,7 +132,7 @@ namespace BSP namespace BSPLinkingConstants { constexpr const char* MISSING_IMAGE_NAME = ",mc/lambert1"; - constexpr const char* COLOR_ONLY_IMAGE_NAME = ",white"; + constexpr const char* COLOR_ONLY_IMAGE_NAME = ",mc/lambert1"; constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ "attackers": [ @@ -186,7 +186,7 @@ namespace BSP constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; // Default surface values - constexpr int DEFAULT_SURFACE_LIGHT = 2; + constexpr int DEFAULT_SURFACE_LIGHT = 1; constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; constexpr int DEFAULT_SURFACE_FLAGS = 0; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index c77e33329..01237ea79 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -28,7 +28,7 @@ namespace unsigned m_position_accessor; unsigned m_normal_accessor; std::optional m_color_accessor; - unsigned m_uv_accessor; + std::optional m_uv_accessor; unsigned m_index_accessor; }; @@ -83,7 +83,6 @@ namespace private: const Input& m_input; BSPData* m_bsp; - size_t m_color_mat_idx; std::vector> m_accessors; std::vector> m_buffer_views; std::vector> m_buffers; @@ -190,7 +189,8 @@ namespace return T.matrix(); } - unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface) + unsigned CreateVertices( + const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -203,6 +203,7 @@ namespace assert(positionAccessor != nullptr); const auto vertexCount = positionAccessor->GetCount(); + NullAccessor nullAccessor(vertexCount); OnesAccessor onesAccessor(vertexCount); // clang-format off @@ -220,9 +221,8 @@ namespace accessorsForVertex.m_uv_accessor, { JsonAccessorType::VEC2 }, { JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT } - ).value_or(nullptr); + ).value_or(&nullAccessor); VerifyAccessorVertexCount("TEXCOORD_0", uvAccessor, vertexCount); - assert(uvAccessor != nullptr); const auto* colorAccessor = GetAccessorForIndex( "COLOR_0", @@ -282,6 +282,11 @@ namespace assert(false); } + vertex.color.x *= vertexColor.x; + vertex.color.y *= vertexColor.y; + vertex.color.z *= vertexColor.z; + vertex.color.w *= vertexColor.w; + Eigen::Vector4f position(vertex.pos.x, vertex.pos.y, vertex.pos.z, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; vertex.pos.x = transformedPosition.x(); @@ -313,24 +318,8 @@ namespace return vertexOffset; } - void loadSurfaceLightData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) - { - if (!primitive.material) - { - if (!primitive.attributes.COLOR_0) - throw GltfLoadException("Primitive requires material or colour data."); - - surface.materialIndex = m_color_mat_idx; - } - else - surface.materialIndex = *primitive.material; - } - - bool CreateSurfacesFromNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) { - if (!node.mesh && !node.extensions) - return false; - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); if (node.extensions && node.extensions->KHR_lights_punctual) @@ -375,48 +364,45 @@ namespace return true; } - con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); - - const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; - for (const auto& primitive : mesh.primitives) + if (node.mesh) { - if (!primitive.indices) - throw GltfLoadException("Requires primitives indices"); - if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) - throw GltfLoadException("Only triangles are supported"); - if (!primitive.attributes.POSITION) - throw GltfLoadException("Requires primitives attribute POSITION"); - if (!primitive.attributes.NORMAL) - throw GltfLoadException("Requires primitives attribute NORMAL"); - if (!primitive.attributes.TEXCOORD_0) - throw GltfLoadException("Requires primitives attribute TEXCOORD_0"); - - const AccessorsForVertex accessorsForVertex{ - .m_position_accessor = *primitive.attributes.POSITION, - .m_normal_accessor = *primitive.attributes.NORMAL, - .m_color_accessor = primitive.attributes.COLOR_0, - .m_uv_accessor = *primitive.attributes.TEXCOORD_0, - .m_index_accessor = *primitive.indices, - }; - - BSPSurface surface; - - if (primitive.material) - surface.materialIndex = *primitive.material; - else if (primitive.attributes.COLOR_0) - surface.materialIndex = m_color_mat_idx; - else - throw GltfLoadException("Primitive requires material or colour data."); - - CreateVertices(accessorsForVertex, node, nodeMatrix, surface); + assert(jRoot.meshes); + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + for (const auto& primitive : mesh.primitives) + { + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + if (primitive.material) + surface.materialIndex = *primitive.material; + else + surface.materialIndex = m_bsp->gfxWorld.materials.size() - 1; // last material is used for colour only meshes + vec4_t vertexColour = m_bsp->gfxWorld.materials.at(surface.materialIndex).materialColour; + CreateVertices(accessorsForVertex, node, nodeMatrix, surface, vertexColour); - m_bsp->gfxWorld.surfaces.emplace_back(surface); + m_bsp->gfxWorld.surfaces.emplace_back(surface); + return true; + } } - - return true; } static std::vector GetRootNodes(const JsonRoot& jRoot) + { if (!jRoot.nodes || jRoot.nodes->empty()) return {}; @@ -453,31 +439,62 @@ namespace void LoadMaterials(const JsonRoot& jRoot) { - if (!jRoot.materials) - return; - m_bsp->gfxWorld.materials.reserve((*jRoot.materials).size()); - for (auto& jsMaterial : *jRoot.materials) + if (jRoot.materials) { - BSPMaterial material; - - if (jsMaterial.name && (*jsMaterial.name).length() != 0) + m_bsp->gfxWorld.materials.reserve((*jRoot.materials).size()); + for (auto& jsMaterial : *jRoot.materials) { - material.materialType = MATERIAL_TYPE_TEXTURE; - material.materialName = *jsMaterial.name; - } - else - { - material.materialType = MATERIAL_TYPE_EMPTY; - material.materialName = ""; - } + BSPMaterial material; - m_bsp->gfxWorld.materials.emplace_back(material); + if (jsMaterial.name && (*jsMaterial.name).length() != 0) + material.materialName = *jsMaterial.name; + else + material.materialName = ""; + + if (jsMaterial.pbrMetallicRoughness) + { + if (jsMaterial.pbrMetallicRoughness->baseColorFactor) + { + material.materialColour.x = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[0]; + material.materialColour.y = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[1]; + material.materialColour.z = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[2]; + material.materialColour.w = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[3]; + } + else + { + + material.materialColour.x = 1.0f; + material.materialColour.y = 1.0f; + material.materialColour.z = 1.0f; + material.materialColour.w = 1.0f; + } + + if (jsMaterial.pbrMetallicRoughness->baseColorTexture) + material.materialType = MATERIAL_TYPE_TEXTURE; + else + material.materialType = MATERIAL_TYPE_COLOUR; + } + else + { + material.materialType = MATERIAL_TYPE_COLOUR; + material.materialColour.x = 1.0f; + material.materialColour.y = 1.0f; + material.materialColour.z = 1.0f; + material.materialColour.w = 1.0f; + } + + m_bsp->gfxWorld.materials.emplace_back(material); + } } - m_color_mat_idx = m_bsp->gfxWorld.materials.size(); + // last material is used when a primitve has no material/colour data BSPMaterial colorMaterial; colorMaterial.materialType = MATERIAL_TYPE_COLOUR; colorMaterial.materialName = ""; + colorMaterial.materialColour.x = 1.0f; + colorMaterial.materialColour.y = 1.0f; + colorMaterial.materialColour.z = 1.0f; + colorMaterial.materialColour.w = 1.0f; m_bsp->gfxWorld.materials.emplace_back(colorMaterial); } @@ -579,7 +596,7 @@ namespace con::warn("Parent node has position data that won't be used"); } - CreateSurfacesFromNode(jRoot, node); + addNodeToBSP(jRoot, node); } } @@ -692,7 +709,7 @@ namespace LoadLights(jRoot); LoadMaterials(jRoot); - TraverseNodes(jRoot); + TraverseNodes(jRoot); // requires materials and lights } catch (const GltfLoadException& e) { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 5c9927d15..7b27cf83b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -74,15 +74,13 @@ namespace BSP gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); gfxSurface->tris.vertexDataOffset1 = 0; // vd1 is unused - BSPMaterial bspMaterial = bsp->colWorld.materials.at(bspSurface.materialIndex); + BSPMaterial bspMaterial = bsp->gfxWorld.materials.at(bspSurface.materialIndex); std::string materialName; - if (bspMaterial.materialType == MATERIAL_TYPE_EMPTY) - materialName = BSPLinkingConstants::MISSING_IMAGE_NAME; - else if (bspMaterial.materialType == MATERIAL_TYPE_COLOUR) - materialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - else // MATERIAL_TYPE_TEXTURE + if (bspMaterial.materialType == MATERIAL_TYPE_TEXTURE) materialName = bspMaterial.materialName; + else // MATERIAL_TYPE_COLOUR + materialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; auto surfMaterialAsset = m_context.LoadDependency(materialName); if (surfMaterialAsset == nullptr) @@ -401,7 +399,7 @@ namespace BSP for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; entryArray[i].visibility = 0; } gfxWorld->lightGrid.entries = entryArray; From e0afd24e291dbb3824a18f207d39965f2b7bb87e Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:25:27 +0800 Subject: [PATCH 070/123] Refactored light direction calculation. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 36 +++++++---------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 01237ea79..eb6e5c977 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -335,29 +335,13 @@ namespace m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); - // BO2 uses -Z up and the default light direction is straight down - Eigen::Vector3f defaultDirection(0.0f, 0.0f, 1.0f); - Eigen::Vector3f outputDirection; - - if (node.rotation) - { - Eigen::Quaternionf rotationQuat( - (*node.rotation)[3], (*node.rotation)[0], (*node.rotation)[1], (*node.rotation)[2]); // GLTF is XYZW, Eigen is WXYZ - outputDirection = rotationQuat * defaultDirection; - } - else if (node.matrix) - { - con::error("matrix rotation unimpelemted"); - assert(false); - // Eigen::Quaternionf rotationQuat; - // rotationQuat = nodeMatrix; - // outputDirection = rotationQuat * defaultDirection; - } - else - outputDirection = defaultDirection; + // GLTF spec uses +Y up and the default light direction is straight down + Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; outputDirection.normalize(); m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; - RhcToLhcCoordinates(m_bsp->lights[lightIndex].direction.v); m_bsp->lights[lightIndex].hasPosBeenSet = true; @@ -399,6 +383,8 @@ namespace return true; } } + + return false; } static std::vector GetRootNodes(const JsonRoot& jRoot) @@ -533,12 +519,12 @@ namespace } if (!jsLight.intensity) - light.intensity = 100000.0f; // adjusted from GLTF spec to better match BO2 + light.intensity = 100000.0f; // adjusted from spec to better match BO2 else light.intensity = *jsLight.intensity; if (!jsLight.range) - light.range = 1000.0f; // adjusted from GLTF spec to better match BO2 + light.range = 1000.0f; // adjusted from spec to better match BO2 else light.range = *jsLight.range; @@ -553,15 +539,15 @@ namespace else // JsonPunctualLightType::SPOT { light.type = LIGHT_TYPE_SPOT; - assert(jsLight.spot); + assert(jsLight.spot); if (!jsLight.spot->innerConeAngle) light.innerConeAngle = 0.0f; else light.innerConeAngle = *jsLight.spot->innerConeAngle; if (!jsLight.spot->outerConeAngle) - light.outerConeAngle = 3.14159265359f / 4.0f; /// 45 degrees + light.outerConeAngle = 3.14159265359f / 4.0f; /// spec of 45 degrees else light.outerConeAngle = *jsLight.spot->outerConeAngle; } From fc5dd21b083d4ad4ae52b13bc90be8d24aa27c3b Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:18:26 +0800 Subject: [PATCH 071/123] BSP creator now loads seperate graphics and collision data. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 394 +++++----------------- 1 file changed, 83 insertions(+), 311 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index eb6e5c977..f35e58092 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -81,8 +81,10 @@ namespace class BSPLoader { private: - const Input& m_input; BSPData* m_bsp; + BSPWorld* m_curr_bsp_world; + bool m_is_world_gfx; + std::vector> m_accessors; std::vector> m_buffer_views; std::vector> m_buffers; @@ -250,8 +252,8 @@ namespace surface.vertexCount = static_cast(vertexCount); surface.triCount = static_cast(faceCount); - surface.indexOfFirstIndex = static_cast(m_bsp->gfxWorld.indices.size()); - surface.indexOfFirstVertex = static_cast(m_bsp->gfxWorld.vertices.size()); + surface.indexOfFirstIndex = static_cast(m_curr_bsp_world->indices.size()); + surface.indexOfFirstVertex = static_cast(m_curr_bsp_world->vertices.size()); for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++) { @@ -265,13 +267,13 @@ namespace throw GltfLoadException("Index number exceeded the UINT16_MAX"); RhcToLhcIndices(indices); - m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[0])); - m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[1])); - m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[2])); + m_curr_bsp_world->indices.emplace_back(static_cast(indices[0])); + m_curr_bsp_world->indices.emplace_back(static_cast(indices[1])); + m_curr_bsp_world->indices.emplace_back(static_cast(indices[2])); } - const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); - m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); + const auto vertexOffset = static_cast(m_curr_bsp_world->vertices.size()); + m_curr_bsp_world->vertices.reserve(vertexOffset + vertexCount); for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) { BSPVertex vertex; @@ -296,22 +298,22 @@ namespace RhcToLhcCoordinates(vertex.pos.v); RhcToLhcCoordinates(vertex.normal.v); - m_bsp->gfxWorld.vertices.emplace_back(vertex); + m_curr_bsp_world->vertices.emplace_back(vertex); } // generate tangent and binormal vectors tangent_space::VertexData vertexData{ - &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].pos, + &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].pos, sizeof(BSPVertex), - &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].normal, + &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].normal, sizeof(BSPVertex), - &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].texCoord, + &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].texCoord, sizeof(BSPVertex), - &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].tangent, + &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].tangent, sizeof(BSPVertex), - &m_bsp->gfxWorld.vertices[surface.indexOfFirstVertex].binormal, + &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].binormal, sizeof(BSPVertex), - &m_bsp->gfxWorld.indices[surface.indexOfFirstIndex], + &m_curr_bsp_world->indices[surface.indexOfFirstIndex], }; tangent_space::CalculateTangentSpace(vertexData, faceCount, vertexCount); @@ -322,7 +324,7 @@ namespace { Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - if (node.extensions && node.extensions->KHR_lights_punctual) + if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) { int lightIndex = node.extensions->KHR_lights_punctual->light; @@ -375,11 +377,11 @@ namespace if (primitive.material) surface.materialIndex = *primitive.material; else - surface.materialIndex = m_bsp->gfxWorld.materials.size() - 1; // last material is used for colour only meshes - vec4_t vertexColour = m_bsp->gfxWorld.materials.at(surface.materialIndex).materialColour; + surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes + vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; CreateVertices(accessorsForVertex, node, nodeMatrix, surface, vertexColour); - m_bsp->gfxWorld.surfaces.emplace_back(surface); + m_curr_bsp_world->surfaces.emplace_back(surface); return true; } } @@ -427,7 +429,7 @@ namespace { if (jRoot.materials) { - m_bsp->gfxWorld.materials.reserve((*jRoot.materials).size()); + m_curr_bsp_world->materials.reserve((*jRoot.materials).size()); for (auto& jsMaterial : *jRoot.materials) { BSPMaterial material; @@ -469,7 +471,7 @@ namespace material.materialColour.w = 1.0f; } - m_bsp->gfxWorld.materials.emplace_back(material); + m_curr_bsp_world->materials.emplace_back(material); } } @@ -481,7 +483,7 @@ namespace colorMaterial.materialColour.y = 1.0f; colorMaterial.materialColour.z = 1.0f; colorMaterial.materialColour.w = 1.0f; - m_bsp->gfxWorld.materials.emplace_back(colorMaterial); + m_curr_bsp_world->materials.emplace_back(colorMaterial); } void LoadLights(const JsonRoot& jRoot) @@ -586,7 +588,7 @@ namespace } } - void CreateBuffers(const JsonRoot& jRoot) + void CreateBuffers(const JsonRoot& jRoot, Input& gltfInput) { if (!jRoot.buffers) return; @@ -598,7 +600,7 @@ namespace { const void* embeddedBufferPtr = nullptr; size_t embeddedBufferSize = 0u; - if (!m_input.GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u) + if (!gltfInput.GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u) throw GltfLoadException("Buffer tried to access embedded data when there is none"); m_buffers.emplace_back(std::make_unique(embeddedBufferPtr, embeddedBufferSize)); @@ -674,12 +676,12 @@ namespace } public: - bool addGLTFDataToBSP(bool isGfxWorld) + bool addGLTFDataToBSP(Input& gltfInput, bool isGfxWorld) { JsonRoot jRoot; try { - jRoot = m_input.GetJson().get(); + jRoot = gltfInput.GetJson().get(); } catch (const nlohmann::json::exception& e) { @@ -689,11 +691,22 @@ namespace try { - CreateBuffers(jRoot); + m_is_world_gfx = isGfxWorld; + if (isGfxWorld) + m_curr_bsp_world = &m_bsp->gfxWorld; + else + m_curr_bsp_world = &m_bsp->colWorld; + m_accessors.clear(); + m_buffer_views.clear(); + m_buffers.clear(); + + CreateBuffers(jRoot, gltfInput); CreateBufferViews(jRoot); CreateAccessors(jRoot); - LoadLights(jRoot); + if (isGfxWorld) // lights aren't needed in collision data + LoadLights(jRoot); + LoadMaterials(jRoot); TraverseNodes(jRoot); // requires materials and lights } @@ -706,9 +719,12 @@ namespace return true; } - BSPLoader(const Input& input, BSPData* bsp) - : m_input(input), - m_bsp(bsp) {}; + BSPLoader(BSPData* bsp) + : m_bsp(bsp) + { + m_curr_bsp_world = nullptr; + m_is_world_gfx = false; + }; }; } // namespace @@ -716,7 +732,10 @@ namespace BSP { std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) { + bool seperateColFile = true; bool isGfxFileGltf = true; + bool isColFileGltf = true; + std::string gfxFilePath = BSPUtil::getFileNameForBSPAsset("map_gfx.gltf"); auto gfxFile = searchPath.Open(gfxFilePath); if (!gfxFile.IsOpen()) @@ -731,19 +750,31 @@ namespace BSP } } - std::unique_ptr bsp = std::make_unique(); + std::string colFilePath = BSPUtil::getFileNameForBSPAsset("map_col.gltf"); + auto colFile = searchPath.Open(colFilePath); + if (!colFile.IsOpen()) + { + isColFileGltf = false; + colFilePath = BSPUtil::getFileNameForBSPAsset("map_col.glb"); + colFile = searchPath.Open(colFilePath); + if (!colFile.IsOpen()) + { + con::info("BSP Creator: generating colision data from GLTF graphics data."); + seperateColFile = false; + } + } + std::unique_ptr bsp = std::make_unique(); bsp->name = mapName; bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + BSPLoader loader(bsp.get()); if (isGfxFileGltf) { gltf::TextInput input; if (!input.ReadGltfData(*gfxFile.m_stream)) return nullptr; - - BSPLoader loader(input, bsp.get()); - if (!loader.addGLTFDataToBSP(true)) + if (!loader.addGLTFDataToBSP(input, true)) return nullptr; } else @@ -751,291 +782,32 @@ namespace BSP gltf::BinInput input; if (!input.ReadGltfData(*gfxFile.m_stream)) return nullptr; - - BSPLoader loader(input, bsp.get()); - if (!loader.addGLTFDataToBSP(true)) + if (!loader.addGLTFDataToBSP(input, true)) return nullptr; } - bsp->colWorld = bsp->gfxWorld; - - return bsp; - } -} // namespace BSP - -/* -std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) - { - std::string gfxFbxFileName = "map_gfx.fbx"; - std::string gfxFbxPath = BSPUtil::getFileNameForBSPAsset(gfxFbxFileName); - auto gfxFile = searchPath.Open(gfxFbxPath); - if (!gfxFile.IsOpen()) - { - con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); - return nullptr; - } - - std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); - gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); - if (gfxFile.m_stream->gcount() != gfxFile.m_length) - { - con::error("Read error of gfx fbx file: {}", gfxFbxPath); - return nullptr; - } - - ufbx_error errorGfx; - ufbx_load_opts optsGfx{}; - optsGfx.target_axes = ufbx_axes_right_handed_y_up; - optsGfx.generate_missing_normals = true; - optsGfx.allow_missing_vertex_position = false; - ufbx_scene* gfxScene = ufbx_load_memory(gfxMapData.get(), static_cast(gfxFile.m_length), &optsGfx, &errorGfx); - if (!gfxScene) - { - con::error("Failed to load map gfx fbx file: {}", errorGfx.description.data); - return nullptr; - } - - ufbx_scene* colScene; - std::string colFbxFileName = "map_col.fbx"; - std::string colFbxPath = BSPUtil::getFileNameForBSPAsset(colFbxFileName); - auto colFile = searchPath.Open(colFbxPath); - if (!colFile.IsOpen()) + if (seperateColFile) { - con::warn("Failed to open map collison fbx file: {}. map gfx will be used for collision instead.", colFbxPath); - colScene = gfxScene; - } - else - { - std::unique_ptr colMapData(new char[static_cast(colFile.m_length)]); - colFile.m_stream->seekg(0); - colFile.m_stream->read(colMapData.get(), colFile.m_length); - if (colFile.m_stream->gcount() != colFile.m_length) - { - con::error("Read error of collision fbx file: {}", colFbxPath); - return nullptr; - } - - ufbx_error errorCol; - ufbx_load_opts optsCol{}; - optsCol.target_axes = ufbx_axes_right_handed_y_up; - optsCol.generate_missing_normals = true; - optsCol.allow_missing_vertex_position = false; - colScene = ufbx_load_memory(colMapData.get(), static_cast(colFile.m_length), &optsCol, &errorCol); - if (!colScene) + if (isColFileGltf) { - con::error("Failed to load map collision fbx file: {}", errorCol.description.data); - return nullptr; - } - } - - std::unique_ptr bsp = std::make_unique(); - - bsp->name = mapName; - bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; - - loadWorldData(gfxScene, bsp.get(), true); - loadWorldData(colScene, bsp.get(), false); - - ufbx_free_scene(gfxScene); - if (gfxScene != colScene) - ufbx_free_scene(colScene); - - return bsp; - } - - - - - - - - - - - - using namespace BSP; - - void addFBXMeshToWorld( - ufbx_node* node, std::vector& surfaceVec, std::vector& vertexVec, std::vector& indexVec, bool& hasTangentSpace) - { - ufbx_mesh* mesh = node->mesh; - - assert(node->attrib_type == UFBX_ELEMENT_MESH); - - if (mesh->instances.count != 1) - con::warn("mesh {} has {} instances, only the 1st instace will be used.", node->name.data, mesh->instances.count); - - if (mesh->num_triangles == 0) - { - con::warn("ignoring mesh {}: triangle count is 0.", node->name.data); - return; - } - - if (mesh->num_indices % 3 != 0) - { - con::warn("ignoring mesh {}: it is not triangulated.", node->name.data); - return; - } - - for (size_t k = 0; k < mesh->num_indices; k++) - { - if (mesh->vertex_indices[k] > UINT16_MAX) - { - con::warn("ignoring mesh {}, it has more than {} indices.", node->name.data, UINT16_MAX); - return; - } - } - - if (mesh->vertex_tangent.exists == false) - hasTangentSpace = false; - - // Fix the target_unit_meters ufbx opt not working - // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back - ufbx_transform origTransform = node->local_transform; - origTransform.translation.x /= 100.0f; - origTransform.translation.y /= 100.0f; - origTransform.translation.z /= 100.0f; - origTransform.scale.x /= 100.0f; - origTransform.scale.y /= 100.0f; - origTransform.scale.z /= 100.0f; - ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - - for (ufbx_mesh_part& meshPart : mesh->material_parts) - { - if (meshPart.num_faces == 0) - continue; - - BSPSurface surface; - size_t partTriangleNum = meshPart.num_triangles; - surface.triCount = static_cast(partTriangleNum); - surface.indexOfFirstVertex = static_cast(vertexVec.size()); - surface.indexOfFirstIndex = static_cast(indexVec.size()); - - if (mesh->materials.count == 0) - { - surface.material.materialType = MATERIAL_TYPE_EMPTY; - surface.material.materialName = ""; + gltf::TextInput input; + if (!input.ReadGltfData(*colFile.m_stream)) + return nullptr; + if (!loader.addGLTFDataToBSP(input, false)) + return nullptr; } else { - surface.material.materialType = MATERIAL_TYPE_TEXTURE; - surface.material.materialName = mesh->materials.data[meshPart.index]->name.data; - } - - std::vector tempVertices; - std::vector tempIndices; - tempIndices.resize(mesh->max_face_triangles * 3); - for (uint32_t faceIndex : meshPart.face_indices) - { - ufbx_face* face = &mesh->faces.data[faceIndex]; - - // Triangulate the face into the indices vector - uint32_t triangluatedTriCount = ufbx_triangulate_face(tempIndices.data(), tempIndices.size(), mesh, *face); - - for (uint32_t idxOfIndex = 0; idxOfIndex < triangluatedTriCount * 3; idxOfIndex++) - { - BSPVertex vertex; - uint32_t index = tempIndices[idxOfIndex]; - - ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vec3_t blenderCoords; - blenderCoords.x = static_cast(transformedPos.x); - blenderCoords.y = static_cast(transformedPos.y); - blenderCoords.z = static_cast(transformedPos.z); - vertex.pos = BSPUtil::convertToBO2Coords(blenderCoords); - - if (surface.material.materialType == MATERIAL_TYPE_TEXTURE || surface.material.materialType == MATERIAL_TYPE_EMPTY) - { - vertex.color.x = 1.0f; - vertex.color.y = 1.0f; - vertex.color.z = 1.0f; - vertex.color.w = 1.0f; - } - else // surface->material.materialType == MATERIAL_TYPE_COLOUR - { - float factor = static_cast(mesh->materials.data[meshPart.index]->fbx.diffuse_factor.value_real); - ufbx_vec4 diffuse = mesh->materials.data[meshPart.index]->fbx.diffuse_color.value_vec4; - vertex.color.x = static_cast(diffuse.x * factor); - vertex.color.y = static_cast(diffuse.y * factor); - vertex.color.z = static_cast(diffuse.z * factor); - vertex.color.w = static_cast(diffuse.w * factor); - } - - // 1.0f - uv.y reason: https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange - ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); - vertex.texCoord.x = static_cast(uv.x); - vertex.texCoord.y = static_cast(1.0f - uv.y); - - ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); - vertex.normal.x = static_cast(normal.x); - vertex.normal.y = static_cast(normal.y); - vertex.normal.z = static_cast(normal.z); - - if (mesh->vertex_tangent.exists) - { - ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); - vertex.tangent.x = static_cast(tangent.x); - vertex.tangent.y = static_cast(tangent.y); - vertex.tangent.z = static_cast(tangent.z); - } - else - { - vertex.tangent.x = 0.0f; - vertex.tangent.y = 0.0f; - vertex.tangent.z = 0.0f; - } - - tempVertices.emplace_back(vertex); - } - } - - // Generate the index buffer. - // ufbx_generate_indices will deduplicate vertices, modifying the arrays passed in streams, - // indices are written to outIndices and the number of unique vertices is returned. - ufbx_vertex_stream streams[1] = { - {tempVertices.data(), tempVertices.size(), sizeof(BSPVertex)}, - }; - std::vector outIndices; - outIndices.resize(partTriangleNum * 3); - size_t numGeneratedVertices = ufbx_generate_indices(streams, 1, outIndices.data(), outIndices.size(), nullptr, nullptr); - assert(numGeneratedVertices != 0); - - // trim non-unique vertexes and add to the world vertex vector - tempVertices.resize(numGeneratedVertices); - vertexVec.insert(vertexVec.end(), tempVertices.begin(), tempVertices.end()); - - // T6 uses unsigned shorts as their index type so we have to loop and convert them from an unsigned int - for (size_t idx = 0; idx < outIndices.size(); idx += 3) - { - // BO2's index ordering is opposite to the FBX, so its converted here - indexVec.emplace_back(static_cast(outIndices[idx + 2])); - indexVec.emplace_back(static_cast(outIndices[idx + 1])); - indexVec.emplace_back(static_cast(outIndices[idx + 0])); - } - - surfaceVec.emplace_back(surface); - } - } - - void loadWorldData(ufbx_scene* scene, BSPData* bsp, bool isGfxData) - { - bool hasTangentSpace = true; - for (ufbx_node* node : scene->nodes) - { - if (node->attrib_type == UFBX_ELEMENT_MESH) - { - if (isGfxData) - addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); - else - addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); - } - else - { - con::debug("ignoring node type {}: {}", static_cast(node->attrib_type), node->name.data); + gltf::BinInput input; + if (!input.ReadGltfData(*colFile.m_stream)) + return nullptr; + if (!loader.addGLTFDataToBSP(input, false)) + return nullptr; } } + else + bsp->colWorld = bsp->gfxWorld; - if (hasTangentSpace == false) - con::warn("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX."); + return bsp; } -*/ +} // namespace BSP From 658ba2eda99d2c2ed18f3717dfb5f6b241eb6687 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:02:46 +0800 Subject: [PATCH 072/123] feat: add gltf extras to node schema --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index a2bdeeb8a..fab9f55c5 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -78,13 +78,22 @@ namespace gltf NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPunctualLightIndex, light); - class JsonNodeExt + class JsonNodeExtension { public: std::optional KHR_lights_punctual; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExt, KHR_lights_punctual); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtension, KHR_lights_punctual); + + class JsonNodeExtras + { + public: + std::optional xmodel; + std::optional spawnpoint; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint); class JsonNode { @@ -98,10 +107,11 @@ namespace gltf std::optional skin; std::optional mesh; - std::optional extensions; + std::optional extensions; + std::optional extras; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, matrix, children, skin, mesh, extensions); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, matrix, children, skin, mesh, extensions, extras); class JsonBuffer { From 44186a38bd7a013b153314cab09245c034d2d155 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 18 Mar 2026 22:49:20 +0800 Subject: [PATCH 073/123] feat: spawn points are now defined in the GLTF file using custom properties. Initial xmodel implementation also included. --- src/ObjLoading/Game/T6/BSP/BSP.h | 96 +++------ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 182 ++++++++++++------ src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 25 ++- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 1 + .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 123 ++++++++---- 5 files changed, 271 insertions(+), 156 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index a3bf5a1d2..3a2f1dfee 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -74,6 +74,35 @@ namespace BSP float outerConeAngle; }; + struct BSPXModel + { + std::string name; + + vec3_t origin; + float scale; + vec3_t forward; + vec3_t right; + vec3_t up; + + bool areBoundsValid; + vec3_t mins; + vec3_t maxs; + }; + + enum BSPSpawnPointType + { + SPAWNPOINT_TYPE_DEFENDER, + SPAWNPOINT_TYPE_ATTACKER, + SPAWNPOINT_TYPE_ALL, + }; + + struct BSPSpawnPoint + { + vec3_t origin; + vec3_t forward; + BSPSpawnPointType type; + }; + struct BSPData { std::string name; @@ -83,6 +112,8 @@ namespace BSP BSPWorld colWorld; std::vector lights; + std::vector xmodels; + std::vector spawnpoints; }; // BSPGameConstants: @@ -99,32 +130,6 @@ namespace BSP SUN_LIGHT_INDEX = 1, BSP_DEFAULT_LIGHT_COUNT = 2 }; - - inline const char* DEFENDER_SPAWN_POINT_NAMES[] = {"mp_ctf_spawn_allies", - "mp_ctf_spawn_allies_start", - "mp_sd_spawn_defender", - "mp_dom_spawn_allies_start", - "mp_dem_spawn_defender_start", - "mp_dem_spawn_defenderOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_allies_start", - "mp_tdm_spawn_team1_start", - "mp_tdm_spawn_team2_start", - "mp_tdm_spawn_team3_start"}; - - inline const char* ATTACKER_SPAWN_POINT_NAMES[] = {"mp_ctf_spawn_axis", - "mp_ctf_spawn_axis_start", - "mp_sd_spawn_attacker", - "mp_dom_spawn_axis_start", - "mp_dem_spawn_attacker_start", - "mp_dem_spawn_attackerOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_axis_start", - "mp_tdm_spawn_team4_start", - "mp_tdm_spawn_team5_start", - "mp_tdm_spawn_team6_start"}; - - inline const char* FFA_SPAWN_POINT_NAMES[] = {"mp_tdm_spawn", "mp_dm_spawn", "mp_dom_spawn"}; } // namespace BSPGameConstants // BSPLinkingConstants: @@ -133,45 +138,6 @@ namespace BSP { constexpr const char* MISSING_IMAGE_NAME = ",mc/lambert1"; constexpr const char* COLOR_ONLY_IMAGE_NAME = ",mc/lambert1"; - - constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ - "attackers": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "defenders": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "FFA": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ] - })"; - - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ - "entities": [ - { - "classname": "worldspawn" - }, - { - "angles": "0 0 0", - "classname": "info_player_start", - "origin": "0 0 0" - }, - { - "angles": "0 0 0", - "classname": "mp_global_intermission", - "origin": "0 0 0" - } - ] - })"; } // namespace BSPLinkingConstants // BSPEditableConstants: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index f35e58092..be01a7a14 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -191,8 +191,7 @@ namespace return T.matrix(); } - unsigned CreateVertices( - const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -320,77 +319,148 @@ namespace return vertexOffset; } - bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addLightNode(const gltf::JsonNode& node) { + assert(m_is_world_gfx); + assert(node.extensions); + assert(node.extensions->KHR_lights_punctual); + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) + int lightIndex = node.extensions->KHR_lights_punctual->light; + + assert(lightIndex >= 0); + if (m_bsp->lights[lightIndex].hasPosBeenSet == true) + con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + + // GLTF spec uses +Y up and the default light direction is straight down + Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + + m_bsp->lights[lightIndex].hasPosBeenSet = true; + + return true; + } + + bool addMeshNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.mesh); + assert(jRoot.meshes); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + for (const auto& primitive : mesh.primitives) { - int lightIndex = node.extensions->KHR_lights_punctual->light; + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + if (primitive.material) + surface.materialIndex = *primitive.material; + else + surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes + vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; + CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); - assert(lightIndex >= 0); - if (m_bsp->lights[lightIndex].hasPosBeenSet == true) - con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + m_curr_bsp_world->surfaces.emplace_back(surface); + } - Eigen::Vector4f position(0, 0, 0, 1.0f); - Eigen::Vector4f transformedPosition = nodeMatrix * position; - m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; - RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + return true; + } - // GLTF spec uses +Y up and the default light direction is straight down - Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); - Eigen::Affine3f affineTransform(nodeMatrix); - Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); - Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; - outputDirection.normalize(); - m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + bool addXModelNode(const gltf::JsonNode& node) + { + return true; + } - m_bsp->lights[lightIndex].hasPosBeenSet = true; + bool addSpawnPointNode(const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->spawnpoint); - return true; + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + BSPSpawnPoint spawnPoint; + + if (!node.extras->spawnpoint->compare("attacker")) + spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; + else if (!node.extras->spawnpoint->compare("defender")) + spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; + else if (!node.extras->spawnpoint->compare("all")) + spawnPoint.type = SPAWNPOINT_TYPE_ALL; + else + { + con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); + return false; } - if (node.mesh) + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + spawnPoint.origin.x = transformedPosition.x(); + spawnPoint.origin.y = transformedPosition.y(); + spawnPoint.origin.z = transformedPosition.z(); + RhcToLhcCoordinates(spawnPoint.origin.v); + + // GLTF default direction is +Y up + Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + spawnPoint.forward.x = outputDirection.x(); + spawnPoint.forward.y = outputDirection.y(); + spawnPoint.forward.z = outputDirection.z(); + RhcToLhcCoordinates(spawnPoint.forward.v); + + m_bsp->spawnpoints.emplace_back(spawnPoint); + + return true; + } + + bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) + return addLightNode(node); + + if (node.extras) { - assert(jRoot.meshes); - const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; - for (const auto& primitive : mesh.primitives) - { - if (!primitive.indices) - throw GltfLoadException("Requires primitives indices"); - if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) - throw GltfLoadException("Only triangles are supported"); - if (!primitive.attributes.POSITION) - throw GltfLoadException("Requires primitives attribute POSITION"); - if (!primitive.attributes.NORMAL) - throw GltfLoadException("Requires primitives attribute NORMAL"); - - const AccessorsForVertex accessorsForVertex{ - .m_position_accessor = *primitive.attributes.POSITION, - .m_normal_accessor = *primitive.attributes.NORMAL, - .m_color_accessor = primitive.attributes.COLOR_0, - .m_uv_accessor = primitive.attributes.TEXCOORD_0, - .m_index_accessor = *primitive.indices, - }; - - BSPSurface surface; - if (primitive.material) - surface.materialIndex = *primitive.material; - else - surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes - vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; - CreateVertices(accessorsForVertex, node, nodeMatrix, surface, vertexColour); + if (m_is_world_gfx && node.extras->xmodel) + return addXModelNode(node); - m_curr_bsp_world->surfaces.emplace_back(surface); - return true; - } + if (m_is_world_gfx && node.extras->spawnpoint) + return addSpawnPointNode(node); } + if (node.mesh) + return addMeshNode(jRoot, node); + return false; } static std::vector GetRootNodes(const JsonRoot& jRoot) - { if (!jRoot.nodes || jRoot.nodes->empty()) return {}; @@ -488,14 +558,16 @@ namespace void LoadLights(const JsonRoot& jRoot) { + if (!m_is_world_gfx) + return; if (!jRoot.extensions) return; if (!jRoot.extensions->KHR_lights_punctual) return; if (!jRoot.extensions->KHR_lights_punctual->lights) return; - const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); + const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); m_bsp->lights.reserve(jsLightArray.size()); for (const JsonPunctualLight& jsLight : jsLightArray) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 795e7a56d..28c170dba 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace BSP { @@ -123,6 +124,28 @@ namespace BSP axis[2].z = cosZ * cosX; } + vec3_t BSPUtil::convertForwardVectorToViewAngles(vec3_t& forwardVec) + { + vec3_t viewAngles; + + float xAndYDist = sqrtf((forwardVec.x * forwardVec.x) + (forwardVec.y * forwardVec.y)); + float atanXRadians = atan2f(forwardVec.z, xAndYDist); + float atanXDegrees = atanXRadians * (-180.0f / std::numbers::pi_v); + if (atanXDegrees < 0.0f) + atanXDegrees += 360.0f; + viewAngles.x = atanXDegrees; + + float atanYRadians = atan2f(forwardVec.y, forwardVec.x); + float atanYDegrees = atanYRadians * (180.0f / std::numbers::pi_v); + if (atanYDegrees < 0.0f) + atanYDegrees += 360.0f; + viewAngles.y = atanYDegrees; + + viewAngles.z = 0.0f; + + return viewAngles; + } + void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) { out[0].x = in[0].x; @@ -161,7 +184,7 @@ namespace BSP std::string BSPUtil::convertVec3ToString(vec3_t& vec) { - std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + std::string result = std::format("{} {} {}", roundf(vec.x), roundf(vec.y), roundf(vec.z)); return result; } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 8b7664f8f..1e3b2ba72 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -17,6 +17,7 @@ namespace BSP static size_t allignBy128(size_t size); static float distBetweenPoints(vec3_t& p1, vec3_t& p2); static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); + static vec3_t convertForwardVectorToViewAngles(vec3_t& forwardVec); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); static vec3_t convertStringToVec3(std::string& str); static std::string convertVec3ToString(vec3_t& vec); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 09c43af73..a752395b2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -7,7 +7,7 @@ using namespace nlohmann; namespace { - bool parseMapEntsJSON(json& entArrayJs, std::string& entityString) + bool addJSONToEntString(json& entArrayJs, std::string& entityString) { for (size_t entIdx = 0; entIdx < entArrayJs.size(); entIdx++) { @@ -39,28 +39,99 @@ namespace return true; } - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], size_t nameCount) + inline const std::vector DEFENDER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start"}; + + inline const std::vector ATTACKER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_attacker", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start"}; + + inline const std::vector FFA_SPAWN_POINT_NAMES = {"mp_tdm_spawn", "mp_dm_spawn", "mp_dom_spawn"}; + + void addSpawnsToEntString(BSP::BSPData* bsp, std::string& entityString) { - for (auto& element : entArrayJs.items()) + if (bsp->spawnpoints.size() == 0) { - std::string origin; - std::string angles; - auto& entity = element.value(); - entity.at("origin").get_to(origin); - entity.at("angles").get_to(angles); + BSP::BSPSpawnPoint defaultSpawnPoint; + defaultSpawnPoint.origin.x = 0.0f; + defaultSpawnPoint.origin.y = 0.0f; + defaultSpawnPoint.origin.z = 0.0f; + defaultSpawnPoint.forward.x = 1.0f; + defaultSpawnPoint.forward.y = 0.0f; + defaultSpawnPoint.forward.z = 0.0f; + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ALL; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + } + + size_t defenderNameCount = std::extent::value; + size_t attackerNameCount = std::extent::value; + size_t ffaNameCount = std::extent::value; + + for (auto& spawnPoint : bsp->spawnpoints) + { + vec3_t origin = spawnPoint.origin; + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); - for (size_t nameIdx = 0; nameIdx < nameCount; nameIdx++) + std::string originStr = std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(origin)); + std::string anglesStr = std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles)); + + const std::vector* spawnPointList; + if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER) + spawnPointList = &DEFENDER_SPAWN_POINT_NAMES; + else if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER) + spawnPointList = &ATTACKER_SPAWN_POINT_NAMES; + else // SPAWNPOINT_TYPE_ALL + spawnPointList = &FFA_SPAWN_POINT_NAMES; + + for (const char* spawnName : *spawnPointList) { entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", origin)); - entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[nameIdx])); + entityString.append(originStr); + entityString.append(anglesStr); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnName)); entityString.append("}\n"); } } } - std::string loadMapEnts() {} + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ + "entities": [ + { + "classname": "worldspawn" + }, + { + "angles": "0 0 0", + "classname": "info_player_start", + "origin": "0 0 0" + }, + { + "angles": "0 0 0", + "classname": "mp_global_intermission", + "origin": "0 0 0" + } + ] + })"; } // namespace namespace BSP @@ -83,35 +154,17 @@ namespace BSP if (!entFile.IsOpen()) { con::warn("Can't find entity file {}, using default entities instead", entityFilePath); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + entJs = json::parse(DEFAULT_MAP_ENTS_STRING); } else { entJs = json::parse(*entFile.m_stream); } std::string entityString; - if (!parseMapEntsJSON(entJs["entities"], entityString)) + if (!addJSONToEntString(entJs["entities"], entityString)) return nullptr; - json spawnJs; - std::string spawnFileName = "spawns.json"; - std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset(spawnFileName); - const auto spawnFile = m_search_path.Open(spawnFilePath); - if (!spawnFile.IsOpen()) - { - con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); - } - size_t defenderNameCount = std::extent::value; - size_t attackerNameCount = std::extent::value; - size_t ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + addSpawnsToEntString(bsp, entityString); MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); @@ -119,7 +172,7 @@ namespace BSP mapEnts->entityString = m_memory.Dup(entityString.c_str()); mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character - // don't need these + // don't need these, unused by the game mapEnts->trigger.count = 0; mapEnts->trigger.models = nullptr; mapEnts->trigger.hullCount = 0; From c7835deb1a4d4a19041890efe3a5a1b0a42e93bf Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:23:57 +0800 Subject: [PATCH 074/123] fix to bsp materials so they require a name --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index be01a7a14..1708e59a8 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -507,7 +507,7 @@ namespace if (jsMaterial.name && (*jsMaterial.name).length() != 0) material.materialName = *jsMaterial.name; else - material.materialName = ""; + throw GltfLoadException("Materials must have a name."); if (jsMaterial.pbrMetallicRoughness) { From 2782d0f2c7e37c3e80cf653a5401eea33fc39d93 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:54:30 +0800 Subject: [PATCH 075/123] feat: load xmodels from GLTF and link them, xmodel collision not working yet. --- src/ObjLoading/Game/T6/BSP/BSP.h | 30 ++-- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 118 +++++++++++++- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 70 ++++++++- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 4 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 103 +++++++----- .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 146 ++++++++++-------- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- 8 files changed, 346 insertions(+), 129 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 3a2f1dfee..2635981e1 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -43,12 +43,26 @@ namespace BSP int indexOfFirstIndex; }; + struct BSPXModel + { + std::string name; + + vec3_t origin; + vec4_t rotationQuaternion; + float scale; + + bool areBoundsValid; + vec3_t mins; + vec3_t maxs; + }; + struct BSPWorld { std::vector surfaces; std::vector vertices; std::vector indices; std::vector materials; + std::vector xmodels; }; enum BSPLightType @@ -74,21 +88,6 @@ namespace BSP float outerConeAngle; }; - struct BSPXModel - { - std::string name; - - vec3_t origin; - float scale; - vec3_t forward; - vec3_t right; - vec3_t up; - - bool areBoundsValid; - vec3_t mins; - vec3_t maxs; - }; - enum BSPSpawnPointType { SPAWNPOINT_TYPE_DEFENDER, @@ -112,7 +111,6 @@ namespace BSP BSPWorld colWorld; std::vector lights; - std::vector xmodels; std::vector spawnpoints; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 1708e59a8..49206de7e 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -32,6 +32,16 @@ namespace unsigned m_index_accessor; }; + void RhcToLhcQuaternion(float (&coords)[4]) + { + const float two[4]{coords[0], coords[1], coords[2], coords[3]}; + + coords[0] = two[0]; + coords[1] = -two[2]; + coords[2] = two[1]; + coords[3] = two[3]; + } + void RhcToLhcCoordinates(float (&coords)[3]) { const float two[3]{coords[0], coords[1], coords[2]}; @@ -392,8 +402,110 @@ namespace return true; } - bool addXModelNode(const gltf::JsonNode& node) + void calculateXmodelBounds(BSPXModel& xmodel, std::optional meshIndex, Eigen::Matrix4f& nodeMatrix, const JsonRoot& jRoot) + { + if (meshIndex) + { + xmodel.areBoundsValid = true; + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + xmodel.mins.x = transformedPosition.x(); + xmodel.mins.y = transformedPosition.y(); + xmodel.mins.z = transformedPosition.z(); + xmodel.maxs.x = transformedPosition.x(); + xmodel.maxs.y = transformedPosition.y(); + xmodel.maxs.z = transformedPosition.z(); + RhcToLhcCoordinates(xmodel.mins.v); + RhcToLhcCoordinates(xmodel.maxs.v); + + const auto& mesh = jRoot.meshes.value()[*meshIndex]; + for (size_t primIdx = 0; primIdx < mesh.primitives.size(); primIdx++) + { + const auto& primitive = mesh.primitives.at(primIdx); + + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + + // clang-format off + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + primitive.attributes.POSITION, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); + // clang-format on + assert(positionAccessor != nullptr); + + for (size_t vertexIndex = 0u; vertexIndex < positionAccessor->GetCount(); vertexIndex++) + { + vec3_t vertex; + if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.v)) + assert(false); + + Eigen::Vector4f position(vertex.x, vertex.y, vertex.z, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vertex.x = transformedPosition.x(); + vertex.y = transformedPosition.y(); + vertex.z = transformedPosition.z(); + RhcToLhcCoordinates(vertex.v); + + if (vertexIndex == 0 && primIdx == 0) + { + xmodel.mins = vertex; + xmodel.maxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, xmodel.mins, xmodel.maxs); + } + } + } + else + { + xmodel.areBoundsValid = false; + xmodel.mins.x = 0.0f; + xmodel.mins.y = 0.0f; + xmodel.mins.z = 0.0f; + xmodel.maxs.x = 0.0f; + xmodel.maxs.y = 0.0f; + xmodel.maxs.z = 0.0f; + } + } + + bool addXModelNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { + assert(node.extras); + assert(node.extras->xmodel); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + BSPXModel xmodel; + + if (node.extras->xmodel->size() == 0) + throw GltfLoadException("Xmodel has no name."); + xmodel.name = *node.extras->xmodel; + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + xmodel.origin.x = transformedPosition.x(); + xmodel.origin.y = transformedPosition.y(); + xmodel.origin.z = transformedPosition.z(); + RhcToLhcCoordinates(xmodel.origin.v); + + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Quaternionf rotationQuat(affineTransform.rotation()); + rotationQuat.normalize(); + xmodel.rotationQuaternion.x = rotationQuat.x(); + xmodel.rotationQuaternion.y = rotationQuat.y(); + xmodel.rotationQuaternion.z = rotationQuat.z(); + xmodel.rotationQuaternion.w = rotationQuat.w(); // Eigen is WXYZ, game is XYZW + RhcToLhcQuaternion(xmodel.rotationQuaternion.v); + + con::warn("XModels don't support scale currently, keep it at 1 in your editor"); + xmodel.scale = 1.0f; + + calculateXmodelBounds(xmodel, node.mesh, nodeMatrix, jRoot); + + m_curr_bsp_world->xmodels.emplace_back(xmodel); + return true; } @@ -447,8 +559,8 @@ namespace if (node.extras) { - if (m_is_world_gfx && node.extras->xmodel) - return addXModelNode(node); + if (node.extras->xmodel) + return addXModelNode(jRoot, node); if (m_is_world_gfx && node.extras->spawnpoint) return addSpawnPointNode(node); diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 28c170dba..000020254 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -104,7 +104,43 @@ namespace BSP return sqrtf((x * x) + (y * y) + (z * z)); } - void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) + void BSPUtil::calculateXmodelBounds(XModel* xmodel, vec3_t axis[3], vec3_t& out_mins, vec3_t& out_maxs) + { + out_mins.x = 0.0f; + out_mins.y = 0.0f; + out_mins.z = 0.0f; + out_maxs.x = 0.0f; + out_maxs.y = 0.0f; + out_maxs.z = 0.0f; + + for (auto surfaceIndex = 0u; surfaceIndex < xmodel->lodInfo[0].numsurfs; surfaceIndex++) + { + const auto& surface = xmodel->surfs[surfaceIndex + xmodel->lodInfo[0].surfIndex]; + + if (!surface.verts0) + continue; + + for (auto vertIndex = 0u; vertIndex < surface.vertCount; vertIndex++) + { + const auto& vertex = surface.verts0[vertIndex].xyz; + + vec3_t rotatedVert; + rotatedVert.x = (vertex.x * axis[0].x) + (vertex.y * axis[1].x) + (vertex.z * axis[2].x); + rotatedVert.y = (vertex.x * axis[0].y) + (vertex.y * axis[1].y) + (vertex.z * axis[2].y); + rotatedVert.z = (vertex.x * axis[0].z) + (vertex.y * axis[1].z) + (vertex.z * axis[2].z); + + if (vertIndex == 0 && surfaceIndex == 0) + { + out_mins = rotatedVert; + out_maxs = rotatedVert; + } + else + BSPUtil::updateAABBWithPoint(rotatedVert, out_mins, out_maxs); + } + } + } + + void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t axis[3]) { float cosX = cos(angles->x); float sinX = sin(angles->x); @@ -124,6 +160,38 @@ namespace BSP axis[2].z = cosZ * cosX; } + void BSPUtil::convertQuaternionToAxis(vec4_t* quat, vec3_t axis[3]) + { + float quatX = quat->v[0]; + float quatY = quat->v[1]; + float quatZ = quat->v[2]; + float quatW = quat->v[3]; + + float xx = (quatX * 2.0) * quatX; + float xy = (quatX * 2.0) * quatY; + float xz = (quatX * 2.0) * quatZ; + float xw = (quatX * 2.0) * quatW; + + float yy = (quatY * 2.0) * quatY; + float yz = (quatY * 2.0) * quatZ; + float yw = (quatY * 2.0) * quatW; + + float zz = (quatZ * 2.0) * quatZ; + float zw = (quatZ * 2.0) * quatW; + + axis->x = 1.0f - (zz + yy); + axis->y = zw + xy; + axis->z = xz - yw; + + axis[1].x = xy - zw; + axis[1].y = 1.0f - (zz + xx); + axis[1].z = yz + xw; + + axis[2].x = yw + xz; + axis[2].y = yz - xw; + axis[2].z = 1.0f - (yy + xx); + } + vec3_t BSPUtil::convertForwardVectorToViewAngles(vec3_t& forwardVec) { vec3_t viewAngles; diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 1e3b2ba72..96fd05061 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -16,7 +16,9 @@ namespace BSP static vec3_t calcHalfSizeOfAABB(vec3_t& mins, vec3_t& maxs); static size_t allignBy128(size_t size); static float distBetweenPoints(vec3_t& p1, vec3_t& p2); - static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); + static void calculateXmodelBounds(XModel* xmodel, vec3_t axis[3], vec3_t& out_mins, vec3_t& out_maxs); + static void convertAnglesToAxis(vec3_t* angles, vec3_t axis[3]); + static void convertQuaternionToAxis(vec4_t* quat, vec3_t axis[3]); static vec3_t convertForwardVectorToViewAngles(vec3_t& forwardVec); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); static vec3_t convertStringToVec3(std::string& str); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 6536be8d5..d5ac6fc34 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -154,54 +154,77 @@ namespace BSP clipMap->cmodels[0].info = nullptr; } - void ClipMapLinker::loadXModelCollision(clipMap_t* clipMap) + bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) { - // Right now XModels aren't supported - clipMap->numStaticModels = 0; - clipMap->staticModelList = nullptr; - - // WIP code left in for future support - /* - auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); - assert(gfxWorldAsset != nullptr); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; + clipMap->numStaticModels = bsp->colWorld.xmodels.size(); clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; for (unsigned int i = 0; i < clipMap->numStaticModels; i++) { - GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; - GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; cStaticModel_s* currModel = &clipMap->staticModelList[i]; + BSPXModel& bspModel = bsp->colWorld.xmodels.at(i); + + vec3_t xmodelAxis[3]; + BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, xmodelAxis); + + auto xModelAsset = m_context.LoadDependency(bspModel.name); + if (xModelAsset == nullptr) + { + con::error("Unable to load xmodel asset: \"{}\"", bspModel.name); + return false; + } + else + currModel->xmodel = (XModel*)xModelAsset->Asset(); + + currModel->origin.x = bspModel.origin.x; + currModel->origin.y = bspModel.origin.y; + currModel->origin.z = bspModel.origin.z; + + currModel->contents = 1; + // currModel->contents = currModel->xmodel->contents; + + if (!xModelAsset->IsReference()) + { + BSPUtil::calculateXmodelBounds(currModel->xmodel, xmodelAxis, currModel->absmin, currModel->absmax); + currModel->absmin.x = (currModel->absmin.x * bspModel.scale) + bspModel.origin.x; + currModel->absmin.y = (currModel->absmin.y * bspModel.scale) + bspModel.origin.y; + currModel->absmin.z = (currModel->absmin.z * bspModel.scale) + bspModel.origin.z; + currModel->absmax.x = (currModel->absmax.x * bspModel.scale) + bspModel.origin.x; + currModel->absmax.y = (currModel->absmax.y * bspModel.scale) + bspModel.origin.y; + currModel->absmax.z = (currModel->absmax.z * bspModel.scale) + bspModel.origin.z; + } + else + { + if (bspModel.areBoundsValid) + { + currModel->absmin = bspModel.mins; + currModel->absmax = bspModel.maxs; + } + else + { + con::warn("Unable to determine the bounds of xmodel: \"{}\"", bspModel.name); + currModel->absmin.x = bspModel.origin.x - 1.0f; + currModel->absmin.y = bspModel.origin.y - 1.0f; + currModel->absmin.z = bspModel.origin.z - 1.0f; + currModel->absmax.x = bspModel.origin.x + 1.0f; + currModel->absmax.y = bspModel.origin.y + 1.0f; + currModel->absmax.z = bspModel.origin.z + 1.0f; + } + } + + BSPUtil::matrixTranspose3x3(xmodelAxis, currModel->invScaledAxis); + currModel->invScaledAxis[0].x = (1.0f / bspModel.scale) * currModel->invScaledAxis[0].x; + currModel->invScaledAxis[0].y = (1.0f / bspModel.scale) * currModel->invScaledAxis[0].y; + currModel->invScaledAxis[0].z = (1.0f / bspModel.scale) * currModel->invScaledAxis[0].z; + currModel->invScaledAxis[1].x = (1.0f / bspModel.scale) * currModel->invScaledAxis[1].x; + currModel->invScaledAxis[1].y = (1.0f / bspModel.scale) * currModel->invScaledAxis[1].y; + currModel->invScaledAxis[1].z = (1.0f / bspModel.scale) * currModel->invScaledAxis[1].z; + currModel->invScaledAxis[2].x = (1.0f / bspModel.scale) * currModel->invScaledAxis[2].x; + currModel->invScaledAxis[2].y = (1.0f / bspModel.scale) * currModel->invScaledAxis[2].y; + currModel->invScaledAxis[2].z = (1.0f / bspModel.scale) * currModel->invScaledAxis[2].z; memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); - currModel->xmodel = gfxModelDrawInst->model; - currModel->contents = gfxModelDrawInst->model->contents; - currModel->origin.x = gfxModelDrawInst->placement.origin.x; - currModel->origin.y = gfxModelDrawInst->placement.origin.y; - currModel->origin.z = gfxModelDrawInst->placement.origin.z; - - // TODO: this does not account for model rotation or scale - currModel->absmin.x = gfxModelInst->mins.x; - currModel->absmin.y = gfxModelInst->mins.y; - currModel->absmin.z = gfxModelInst->mins.z; - currModel->absmax.x = gfxModelInst->maxs.x; - currModel->absmax.y = gfxModelInst->maxs.y; - currModel->absmax.z = gfxModelInst->maxs.z; - - BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); - currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; - currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; - currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; - currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; - currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; - currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; - currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; - currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; - currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; } - */ } // out_mins and out_maxs are initialised in the function @@ -691,7 +714,7 @@ namespace BSP loadDynEnts(clipMap); - loadXModelCollision(clipMap); + loadXModelCollision(clipMap, bsp); loadMaterials(clipMap, bsp); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index a4f5f19c8..385800758 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -24,7 +24,7 @@ namespace BSP void loadDynEnts(clipMap_t* clipMap); void loadRopesAndConstraints(clipMap_t* clipMap); void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); - void loadXModelCollision(clipMap_t* clipMap); + bool loadXModelCollision(clipMap_t* clipMap, BSPData* bsp); std::vector planeVec; std::vector nodeVec; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 7b27cf83b..c2a6710c0 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -145,79 +145,90 @@ namespace BSP return true; } - void GfxWorldLinker::loadXModels(BSPData* bsp, GfxWorld* gfxWorld) + bool GfxWorldLinker::loadXModels(BSPData* bsp, GfxWorld* gfxWorld) { - /* - Models are unsupported right now - Code is left in in case it is supported later on - - unsigned int modelCount = projInfo->modelCount; + size_t modelCount = bsp->gfxWorld.xmodels.size(); gfxWorld->dpvs.smodelCount = modelCount; gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - for (unsigned int i = 0; i < modelCount; i++) + for (size_t modelIdx = 0; modelIdx < modelCount; modelIdx++) { - auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - customMapModel* inModel = &projInfo->models[i]; + auto currModel = &gfxWorld->dpvs.smodelDrawInsts[modelIdx]; + auto currModelInst = &gfxWorld->dpvs.smodelInsts[modelIdx]; + BSPXModel& bspModel = bsp->gfxWorld.xmodels.at(modelIdx); - auto xModelAsset = m_context.LoadDependency(inModel->name); - if (xModelAsset == nullptr) + auto xModelAsset = m_context.LoadDependency(bspModel.name); + if (xModelAsset == nullptr) + { + con::error("Unable to load xmodel asset: \"{}\"", bspModel.name); + return false; + } + else + currModel->model = (XModel*)xModelAsset->Asset(); + + currModel->placement.origin.x = bspModel.origin.x; + currModel->placement.origin.y = bspModel.origin.y; + currModel->placement.origin.z = bspModel.origin.z; + BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, currModel->placement.axis); + currModel->placement.scale = bspModel.scale; + + currModel->cullDist = 10000.0f; + currModel->flags = 0; + currModel->primaryLightIndex = 0; + currModel->reflectionProbeIndex = 0; + currModel->smid = modelIdx; + + // unknown use / unused + memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + currModel->invScaleSq = 0.0f; + currModel->lightingHandle = 0; // overwritten to by the game + currModel->colorsIndex = 0; + currModel->visibility = 0; + + // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // TODO: calculate lighting and store it here + currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; + + if (!xModelAsset->IsReference()) + { + BSPUtil::calculateXmodelBounds(currModel->model, currModel->placement.axis, currModelInst->mins, currModelInst->maxs); + currModelInst->mins.x = (currModelInst->mins.x * bspModel.scale) + bspModel.origin.x; + currModelInst->mins.y = (currModelInst->mins.y * bspModel.scale) + bspModel.origin.y; + currModelInst->mins.z = (currModelInst->mins.z * bspModel.scale) + bspModel.origin.z; + currModelInst->maxs.x = (currModelInst->maxs.x * bspModel.scale) + bspModel.origin.x; + currModelInst->maxs.y = (currModelInst->maxs.y * bspModel.scale) + bspModel.origin.y; + currModelInst->maxs.z = (currModelInst->maxs.z * bspModel.scale) + bspModel.origin.z; + } + else + { + if (bspModel.areBoundsValid) { - printf("XModel %s not found!\n", inModel->name.c_str()); - currModel->model = nullptr; + currModelInst->mins = bspModel.mins; + currModelInst->maxs = bspModel.maxs; } else - currModel->model = (XModel*)xModelAsset->Asset(); - - currModel->placement.origin.x = inModel->origin.x; - currModel->placement.origin.y = inModel->origin.y; - currModel->placement.origin.z = inModel->origin.z; - currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - currModel->placement.scale = inModel->scale; - - BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - - // mins and maxs are calculated in world space not local space - // TODO: this does not account for model rotation or scale - currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - - currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - currModel->flags = DEFAULT_SMODEL_FLAGS; - currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - - // unknown use / unused - currModel->smid = i; - memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - currModel->invScaleSq = 0.0f; - currModel->lightingHandle = 0; - currModel->colorsIndex = 0; - currModel->visibility = 0; - - // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // TODO: calculate lighting and store it here - currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; + { + con::warn("Unable to determine the bounds of xmodel: \"{}\"", bspModel.name); + currModelInst->mins.x = bspModel.origin.x - 1.0f; + currModelInst->mins.y = bspModel.origin.y - 1.0f; + currModelInst->mins.z = bspModel.origin.z - 1.0f; + currModelInst->maxs.x = bspModel.origin.x + 1.0f; + currModelInst->maxs.y = bspModel.origin.y + 1.0f; + currModelInst->maxs.z = bspModel.origin.z + 1.0f; + } + } + currModelInst->lightingOrigin.x = 0.0f; + currModelInst->lightingOrigin.y = 0.0f; + currModelInst->lightingOrigin.z = 0.0f; } - */ - - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = m_memory.Alloc(modelCount); - gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc(modelCount); // visdata is written to by the game // all visdata is alligned by 128 @@ -228,7 +239,7 @@ namespace BSP gfxWorld->dpvs.smodelVisData[2] = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelVisDataCameraSaved = m_memory.Alloc(allignedModelCount); gfxWorld->dpvs.smodelCastsShadow = m_memory.Alloc(allignedModelCount); - for (unsigned int i = 0; i < modelCount; i++) + for (size_t i = 0; i < modelCount; i++) { if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) gfxWorld->dpvs.smodelCastsShadow[i] = 1; @@ -236,8 +247,7 @@ namespace BSP gfxWorld->dpvs.smodelCastsShadow[i] = 0; } - // official maps set this to 0 - gfxWorld->dpvs.usageCount = 0; + return true; } void GfxWorldLinker::cleanGfxWorld(GfxWorld* gfxWorld) @@ -245,6 +255,9 @@ namespace BSP // checksum is generated by the game gfxWorld->checksum = 0; + // official maps set this to 0 + gfxWorld->dpvs.usageCount = 0; + // Remove Coronas gfxWorld->coronaCount = 0; gfxWorld->coronas = nullptr; @@ -735,7 +748,8 @@ namespace BSP if (!loadMapSurfaces(bsp, gfxWorld)) return nullptr; - loadXModels(bsp, gfxWorld); + if (!loadXModels(bsp, gfxWorld)) + return nullptr; if (!loadLightmapData(gfxWorld)) return nullptr; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 1c3faad7c..6baf4f2ed 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -20,7 +20,7 @@ namespace BSP void loadDrawData(BSPData* projInfo, GfxWorld* gfxWorld); bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); - void loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); + bool loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld); void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); void loadLightGrid(GfxWorld* gfxWorld); From f32ff26b514e127ba01c3c082bedfb9e3171b273 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 13:11:41 +0800 Subject: [PATCH 076/123] fixed error handling for xmodels and materials, and updated xmodel linker to check if an xmodel has valid collision. --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index d5ac6fc34..fe5fe97c3 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -156,6 +156,10 @@ namespace BSP bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) { + // it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid. + // A lot of XModels don't implement collmaps (OAT also doesn't generate these collmaps), and + // even official maps instead use terrain or brushes to cover where the collision should be. + clipMap->numStaticModels = bsp->colWorld.xmodels.size(); clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; @@ -180,11 +184,9 @@ namespace BSP currModel->origin.y = bspModel.origin.y; currModel->origin.z = bspModel.origin.z; - currModel->contents = 1; - // currModel->contents = currModel->xmodel->contents; - if (!xModelAsset->IsReference()) { + currModel->contents = currModel->xmodel->contents; BSPUtil::calculateXmodelBounds(currModel->xmodel, xmodelAxis, currModel->absmin, currModel->absmax); currModel->absmin.x = (currModel->absmin.x * bspModel.scale) + bspModel.origin.x; currModel->absmin.y = (currModel->absmin.y * bspModel.scale) + bspModel.origin.y; @@ -192,9 +194,13 @@ namespace BSP currModel->absmax.x = (currModel->absmax.x * bspModel.scale) + bspModel.origin.x; currModel->absmax.y = (currModel->absmax.y * bspModel.scale) + bspModel.origin.y; currModel->absmax.z = (currModel->absmax.z * bspModel.scale) + bspModel.origin.z; + + if (currModel->xmodel->numCollmaps == 0) + con::warn("Xmodel \"{}\" has no colision data", bspModel.name); } else { + currModel->contents = 1; if (bspModel.areBoundsValid) { currModel->absmin = bspModel.mins; @@ -225,6 +231,8 @@ namespace BSP memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); } + + return true; } // out_mins and out_maxs are initialised in the function @@ -714,9 +722,11 @@ namespace BSP loadDynEnts(clipMap); - loadXModelCollision(clipMap, bsp); + if (!loadXModelCollision(clipMap, bsp)) + return nullptr; - loadMaterials(clipMap, bsp); + if (!loadMaterials(clipMap, bsp)) + return nullptr; if (!loadWorldCollision(clipMap, bsp)) return nullptr; From 57ded3d10e491ef4ecf038e709a458744370984a Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 21 Mar 2026 17:26:20 +0800 Subject: [PATCH 077/123] feat: surface type and flags can now be set through the GLTF file --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 13 +- src/ObjLoading/Game/T6/BSP/BSP.h | 155 +---------- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 77 +++++ src/ObjLoading/Game/T6/BSP/BSPFlags.h | 263 ++++++++++++++++++ .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 22 +- .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- 6 files changed, 371 insertions(+), 161 deletions(-) create mode 100644 src/ObjLoading/Game/T6/BSP/BSPFlags.h diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index fab9f55c5..d38fc0de0 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -301,6 +301,15 @@ namespace gltf NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNormalTextureInfo, index); + class JsonMaterialExtras + { + public: + std::optional type; + std::optional flags; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterialExtras, type, flags); + class JsonMaterial { public: @@ -308,9 +317,11 @@ namespace gltf std::optional pbrMetallicRoughness; std::optional normalTexture; std::optional doubleSided; + + std::optional extras; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial, name, pbrMetallicRoughness, normalTexture, doubleSided); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial, name, pbrMetallicRoughness, normalTexture, doubleSided, extras); enum class JsonMeshPrimitivesMode { diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 2635981e1..731435b6f 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -1,5 +1,6 @@ #pragma once +#include "BSPFlags.h" #include "Game/T6/T6.h" #include "Utils/Logging/Log.h" @@ -32,6 +33,9 @@ namespace BSP std::string materialName; BSPMaterialType materialType; vec4_t materialColour; + + int surfaceFlags; + int contentFlags; }; struct BSPSurface @@ -175,155 +179,4 @@ namespace BSP constexpr vec4_t SUNLIGHT_COLOR = {0.75f, 0.75f, 0.75f, 1.0f}; constexpr vec3_t SUNLIGHT_DIRECTION = {0.0f, 0.0f, 0.0f}; }; // namespace BSPEditableConstants - - namespace BSPFlags - { - // enum SurfaceType - //{ - // SURF_TYPE_BARK, - // SURF_TYPE_BRICK, - // SURF_TYPE_CARPET, - // SURF_TYPE_CLOTH, - // SURF_TYPE_CONCRETE, - // SURF_TYPE_DIRT, - // SURF_TYPE_FLESH, - // SURF_TYPE_FOLIAGE, - // SURF_TYPE_GLASS, - // SURF_TYPE_GRASS, - // SURF_TYPE_GRAVEL, - // SURF_TYPE_ICE, - // SURF_TYPE_METAL, - // SURF_TYPE_MUD, - // SURF_TYPE_PAPER, - // SURF_TYPE_PLASTER, - // SURF_TYPE_ROCK, - // SURF_TYPE_SAND, - // SURF_TYPE_SNOW, - // SURF_TYPE_WATER, - // SURF_TYPE_WOOD, - // SURF_TYPE_ASPHALT, - // SURF_TYPE_CERAMIC, - // SURF_TYPE_PLASTIC, - // SURF_TYPE_RUBBER, - // SURF_TYPE_CUSHION, - // SURF_TYPE_FRUIT, - // SURF_TYPE_PAINTEDMETAL, - // SURF_TYPE_PLAYER, - // SURF_TYPE_TALLGRASS, - // SURF_TYPE_RIOTSHIELD, - // SURF_TYPE_OPAQUEGLASS, - // SURF_TYPE_CLIPMISSILE, - // SURF_TYPE_AI_NOSIGHT, - // SURF_TYPE_CLIPSHOT, - // SURF_TYPE_PLAYERCLIP, - // SURF_TYPE_MONSTERCLIP, - // SURF_TYPE_VEHICLECLIP, - // SURF_TYPE_ITEMCLIP, - // SURF_TYPE_NODROP, - // SURF_TYPE_NONSOLID, - // SURF_TYPE_DETAIL, - // SURF_TYPE_STRUCTURAL, - // SURF_TYPE_PORTAL, - // SURF_TYPE_CANSHOOTCLIP, - // SURF_TYPE_ORIGIN, - // SURF_TYPE_SKY, - // SURF_TYPE_NOCASTSHADOW, - // SURF_TYPE_ONLYCASTSHADOW, - // SURF_TYPE_PHYSICSGEOM, - // SURF_TYPE_LIGHTPORTAL, - // SURF_TYPE_CAULK, - // SURF_TYPE_AREALIGHT, - // SURF_TYPE_SLICK, - // SURF_TYPE_NOIMPACT, - // SURF_TYPE_NOMARKS, - // SURF_TYPE_NOPENETRATE, - // SURF_TYPE_LADDER, - // SURF_TYPE_NODAMAGE, - // SURF_TYPE_MANTLEON, - // SURF_TYPE_MANTLEOVER, - // SURF_TYPE_MOUNT, - // SURF_TYPE_NOSTEPS, - // SURF_TYPE_NODRAW, - // SURF_TYPE_NORECEIVEDYNAMICSHADOW, - // SURF_TYPE_NODLIGHT, - // SURF_TYPE_COUNT - // }; - // - // struct s_SurfaceTypeFlags - //{ - // unsigned int surfaceFlags; - // unsigned int contentFlags; - // }; - // - // s_SurfaceTypeFlags surfaceTypeToFlagMap[SURF_TYPE_COUNT] = { - // {0x100000, 0 }, // bark - // {0x200000, 0 }, // brick - // {0x300000, 0 }, // carpet - // {0x400000, 0 }, // cloth - // {0x500000, 0 }, // concrete - // {0x600000, 0 }, // dirt - // {0x700000, 0 }, // flesh - // {0x800000, 2 }, // foliage - // {0x900000, 0x10 }, // glass - // {0x0A00000, 0 }, // grass - // {0x0B00000, 0 }, // gravel - // {0x0C00000, 0 }, // ice - // {0x0D00000, 0 }, // metal - // {0x0E00000, 0 }, // mud - // {0x0F00000, 0 }, // paper - // {0x1000000, 0 }, // plaster - // {0x1100000, 0 }, // rock - // {0x1200000, 0 }, // sand - // {0x1300000, 0 }, // snow - // {0x1400000, 0x20 }, // water - // {0x1500000, 0 }, // wood - // {0x1600000, 0 }, // asphalt - // {0x1700000, 0 }, // ceramic - // {0x1800000, 0 }, // plastic - // {0x1900000, 0 }, // rubber - // {0x1A00000, 0 }, // cushion - // {0x1B00000, 0 }, // fruit - // {0x1C00000, 0 }, // paintedmetal - // {0x1D00000, 0 }, // player - // {0x1E00000, 0 }, // tallgrass - // {0x1F00000, 0 }, // riotshield - // {0x900000, 0 }, // opaqueglass - // {0, 0x80 }, // clipmissile - // {0, 0x1000 }, // ai_nosight - // {0, 0x2000 }, // clipshot - // {0, 0x10000 }, // playerclip - // {0, 0x20000 }, // monsterclip - // {0, 0x200 }, // vehicleclip - // {0, 0x400 }, // itemclip - // {0, 0x80000000}, // noDrop - // {0x4000, 0 }, // nonSolid - // {0, 0x8000000 }, // detail - // {0, 0x10000000}, // structural - // {0x80000000, 0 }, // portal - // {0, 0x40 }, // canShootClip - // {0, 0 }, // origin - // {4, 0x800 }, // sky - // {0x40000, 0 }, // noCastShadow - // {0x80000, 0 }, // onlyCastShadow - // {0, 0 }, // physicsGeom - // {0, 0 }, // lightPortal - // {0x1000, 0 }, // caulk - // {0x8000, 0 }, // areaLight - // {2, 0 }, // slick - // {0x10, 0 }, // noImpact - // {0x20, 0 }, // noMarks - // {0x100, 0 }, // noPenetrate - // {8, 0 }, // ladder - // {1, 0 }, // noDamage - // {0x4000000, 0x1000000 }, // mantleOn - // {0x8000000, 0x1000000 }, // mantleOver - // {0x10000000, 0x1000000 }, // mount - // {0x2000, 0 }, // noSteps - // {0x80, 0 }, // noDraw - // {0x800, 0 }, // noReceiveDynamicShadow - // {0x20000, 0 } // noDlight - // }; - // - //// TODO: template/define to get the surface type from the surface flags - } // namespace BSPFlags } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 49206de7e..904d7b2cd 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1,6 +1,7 @@ #include "BSPCreator.h" #include "BSPUtil.h" +#include "Utils/StringUtils.h" #include "XModel/Gltf/GltfBinInput.h" #include "XModel/Gltf/GltfTextInput.h" #include "XModel/Gltf/Internal/GltfAccessor.h" @@ -607,6 +608,78 @@ namespace return rootNodes; } + void setMaterialFlags(BSPMaterial& material, const JsonMaterial& jsMaterial) + { + if (!jsMaterial.extras) + { + material.surfaceFlags = 1; + material.contentFlags = 1; + return; + } + if (!jsMaterial.extras->flags && !jsMaterial.extras->type) + { + material.surfaceFlags = 1; + material.contentFlags = 1; + return; + } + + material.surfaceFlags = 0; + material.contentFlags = 0; + if (jsMaterial.extras->type) + { + std::string typeStr = *jsMaterial.extras->type; + + size_t typeNameCount = std::extent::value; + for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) + { + BSPFlags::SurfaceType surfType = BSPFlags::materialTypes[typeIdx]; + if (!typeStr.compare(BSPFlags::surfaceTypeToNameMap[surfType])) + { + BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; + material.surfaceFlags = flags.surfaceFlags; + material.contentFlags = flags.contentFlags; + break; + } + } + } + if (material.surfaceFlags == 0 && material.contentFlags == 0) + con::warn("invalid material {} type name: {}", material.materialName, *jsMaterial.extras->type); + + if (jsMaterial.extras->flags) + { + std::string flagsStr = *jsMaterial.extras->flags; + + std::vector flagStrVec = utils::StringSplit(flagsStr, ','); + for (std::string& flag : flagStrVec) + { + bool foundMatchingName = false; + utils::MakeStringLowerCase(flag); + utils::StringTrim(flag); + size_t typeNameCount = std::extent::value; + for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) + { + BSPFlags::SurfaceType surfType = BSPFlags::materialFlags[typeIdx]; + if (!flag.compare(BSPFlags::surfaceTypeToNameMap[surfType])) + { + BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; + material.surfaceFlags |= flags.surfaceFlags; + material.contentFlags |= flags.contentFlags; + foundMatchingName = true; + break; + } + } + if (!foundMatchingName) + con::warn("invalid material {} flag name: {}", material.materialName, flag); + } + } + + // the first content flag bit must be set to 1 for the surface to have collision + if ((material.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NONSOLID].surfaceFlags) != 0) + material.contentFlags &= 0xFFFFFFFE; + else + material.contentFlags |= 1; + } + void LoadMaterials(const JsonRoot& jRoot) { if (jRoot.materials) @@ -653,6 +726,8 @@ namespace material.materialColour.w = 1.0f; } + setMaterialFlags(material, jsMaterial); + m_curr_bsp_world->materials.emplace_back(material); } } @@ -660,6 +735,8 @@ namespace // last material is used when a primitve has no material/colour data BSPMaterial colorMaterial; colorMaterial.materialType = MATERIAL_TYPE_COLOUR; + colorMaterial.surfaceFlags = 1; + colorMaterial.contentFlags = 1; colorMaterial.materialName = ""; colorMaterial.materialColour.x = 1.0f; colorMaterial.materialColour.y = 1.0f; diff --git a/src/ObjLoading/Game/T6/BSP/BSPFlags.h b/src/ObjLoading/Game/T6/BSP/BSPFlags.h new file mode 100644 index 000000000..936c18632 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPFlags.h @@ -0,0 +1,263 @@ +#pragma once + +namespace BSPFlags +{ + enum SurfaceType + { + SURF_TYPE_BARK, + SURF_TYPE_BRICK, + SURF_TYPE_CARPET, + SURF_TYPE_CLOTH, + SURF_TYPE_CONCRETE, + SURF_TYPE_DIRT, + SURF_TYPE_FLESH, + SURF_TYPE_FOLIAGE, + SURF_TYPE_GLASS, + SURF_TYPE_GRASS, + SURF_TYPE_GRAVEL, + SURF_TYPE_ICE, + SURF_TYPE_METAL, + SURF_TYPE_MUD, + SURF_TYPE_PAPER, + SURF_TYPE_PLASTER, + SURF_TYPE_ROCK, + SURF_TYPE_SAND, + SURF_TYPE_SNOW, + SURF_TYPE_WATER, + SURF_TYPE_WOOD, + SURF_TYPE_ASPHALT, + SURF_TYPE_CERAMIC, + SURF_TYPE_PLASTIC, + SURF_TYPE_RUBBER, + SURF_TYPE_CUSHION, + SURF_TYPE_FRUIT, + SURF_TYPE_PAINTEDMETAL, + SURF_TYPE_PLAYER, + SURF_TYPE_TALLGRASS, + SURF_TYPE_RIOTSHIELD, + SURF_TYPE_OPAQUEGLASS, + SURF_TYPE_CLIPMISSILE, + SURF_TYPE_AI_NOSIGHT, + SURF_TYPE_CLIPSHOT, + SURF_TYPE_PLAYERCLIP, + SURF_TYPE_MONSTERCLIP, + SURF_TYPE_VEHICLECLIP, + SURF_TYPE_ITEMCLIP, + SURF_TYPE_NODROP, + SURF_TYPE_NONSOLID, + SURF_TYPE_DETAIL, + SURF_TYPE_STRUCTURAL, + SURF_TYPE_PORTAL, + SURF_TYPE_CANSHOOTCLIP, + SURF_TYPE_ORIGIN, + SURF_TYPE_SKY, + SURF_TYPE_NOCASTSHADOW, + SURF_TYPE_ONLYCASTSHADOW, + SURF_TYPE_PHYSICSGEOM, + SURF_TYPE_LIGHTPORTAL, + SURF_TYPE_CAULK, + SURF_TYPE_AREALIGHT, + SURF_TYPE_SLICK, + SURF_TYPE_NOIMPACT, + SURF_TYPE_NOMARKS, + SURF_TYPE_NOPENETRATE, + SURF_TYPE_LADDER, + SURF_TYPE_NODAMAGE, + SURF_TYPE_MANTLEON, + SURF_TYPE_MANTLEOVER, + SURF_TYPE_MOUNT, + SURF_TYPE_NOSTEPS, + SURF_TYPE_NODRAW, + SURF_TYPE_NORECEIVEDYNAMICSHADOW, + SURF_TYPE_NODLIGHT, + SURF_TYPE_NONE, // Custom value + SURF_TYPE_COUNT + }; + + struct s_SurfaceTypeFlags + { + unsigned int surfaceFlags; + unsigned int contentFlags; + }; + + constexpr s_SurfaceTypeFlags surfaceTypeToFlagMap[SURF_TYPE_COUNT] = { + {0x100000, 0 }, // bark + {0x200000, 0 }, // brick + {0x300000, 0 }, // carpet + {0x400000, 0 }, // cloth + {0x500000, 0 }, // concrete + {0x600000, 0 }, // dirt + {0x700000, 0 }, // flesh + {0x800000, 2 }, // foliage + {0x900000, 0x10 }, // glass + {0x0A00000, 0 }, // grass + {0x0B00000, 0 }, // gravel + {0x0C00000, 0 }, // ice + {0x0D00000, 0 }, // metal + {0x0E00000, 0 }, // mud + {0x0F00000, 0 }, // paper + {0x1000000, 0 }, // plaster + {0x1100000, 0 }, // rock + {0x1200000, 0 }, // sand + {0x1300000, 0 }, // snow + {0x1400000, 0x20 }, // water + {0x1500000, 0 }, // wood + {0x1600000, 0 }, // asphalt + {0x1700000, 0 }, // ceramic + {0x1800000, 0 }, // plastic + {0x1900000, 0 }, // rubber + {0x1A00000, 0 }, // cushion + {0x1B00000, 0 }, // fruit + {0x1C00000, 0 }, // paintedmetal + {0x1D00000, 0 }, // player + {0x1E00000, 0 }, // tallgrass + {0x1F00000, 0 }, // riotshield + {0x900000, 0 }, // opaqueglass + {0, 0x80 }, // clipmissile + {0, 0x1000 }, // ai_nosight + {0, 0x2000 }, // clipshot + {0, 0x10000 }, // playerclip + {0, 0x20000 }, // monsterclip + {0, 0x200 }, // vehicleclip + {0, 0x400 }, // itemclip + {0, 0x80000000}, // noDrop + {0x4000, 0 }, // nonSolid + {0, 0x8000000 }, // detail + {0, 0x10000000}, // structural + {0x80000000, 0 }, // portal + {0, 0x40 }, // canShootClip + {0, 0 }, // origin + {4, 0x800 }, // sky + {0x40000, 0 }, // noCastShadow + {0x80000, 0 }, // onlyCastShadow + {0, 0 }, // physicsGeom + {0, 0 }, // lightPortal + {0x1000, 0 }, // caulk + {0x8000, 0 }, // areaLight + {2, 0 }, // slick + {0x10, 0 }, // noImpact + {0x20, 0 }, // noMarks + {0x100, 0 }, // noPenetrate + {8, 0 }, // ladder + {1, 0 }, // noDamage + {0x4000000, 0x1000000 }, // mantleOn + {0x8000000, 0x1000000 }, // mantleOver + {0x10000000, 0x1000000 }, // mount + {0x2000, 0 }, // noSteps + {0x80, 0 }, // noDraw + {0x800, 0 }, // noReceiveDynamicShadow + {0x20000, 0 }, // noDlight + {0, 0 } // none + }; + + constexpr SurfaceType materialTypes[] = { + SURF_TYPE_NONE, SURF_TYPE_BARK, SURF_TYPE_BRICK, SURF_TYPE_CARPET, SURF_TYPE_CLOTH, SURF_TYPE_CONCRETE, SURF_TYPE_DIRT, + SURF_TYPE_FLESH, SURF_TYPE_FOLIAGE, SURF_TYPE_GLASS, SURF_TYPE_GRASS, SURF_TYPE_GRAVEL, SURF_TYPE_ICE, SURF_TYPE_METAL, + SURF_TYPE_MUD, SURF_TYPE_PAPER, SURF_TYPE_PLASTER, SURF_TYPE_ROCK, SURF_TYPE_SAND, SURF_TYPE_SNOW, SURF_TYPE_WATER, + SURF_TYPE_WOOD, SURF_TYPE_ASPHALT, SURF_TYPE_CERAMIC, SURF_TYPE_PLASTIC, SURF_TYPE_RUBBER, SURF_TYPE_CUSHION, SURF_TYPE_FRUIT, + SURF_TYPE_PAINTEDMETAL, SURF_TYPE_PLAYER, SURF_TYPE_TALLGRASS, SURF_TYPE_RIOTSHIELD, SURF_TYPE_OPAQUEGLASS, + }; + + constexpr SurfaceType materialFlags[] = { + SURF_TYPE_NONE, + SURF_TYPE_CLIPMISSILE, + SURF_TYPE_AI_NOSIGHT, + SURF_TYPE_CLIPSHOT, + SURF_TYPE_PLAYERCLIP, + SURF_TYPE_MONSTERCLIP, + SURF_TYPE_VEHICLECLIP, + SURF_TYPE_ITEMCLIP, + SURF_TYPE_NODROP, + SURF_TYPE_NONSOLID, + SURF_TYPE_DETAIL, + SURF_TYPE_STRUCTURAL, + SURF_TYPE_PORTAL, + SURF_TYPE_CANSHOOTCLIP, + SURF_TYPE_SKY, + SURF_TYPE_NOCASTSHADOW, + SURF_TYPE_ONLYCASTSHADOW, + SURF_TYPE_CAULK, + SURF_TYPE_AREALIGHT, + SURF_TYPE_SLICK, + SURF_TYPE_NOIMPACT, + SURF_TYPE_NOMARKS, + SURF_TYPE_NOPENETRATE, + SURF_TYPE_LADDER, + SURF_TYPE_NODAMAGE, + SURF_TYPE_MANTLEON, + SURF_TYPE_MANTLEOVER, + SURF_TYPE_MOUNT, + SURF_TYPE_NOSTEPS, + SURF_TYPE_NODRAW, + SURF_TYPE_NORECEIVEDYNAMICSHADOW, + SURF_TYPE_NODLIGHT, + }; + + constexpr const char* surfaceTypeToNameMap[SURF_TYPE_COUNT] = {"bark", + "brick", + "carpet", + "cloth", + "concrete", + "dirt", + "flesh", + "foliage", + "glass", + "grass", + "gravel", + "ice", + "metal", + "mud", + "paper", + "plaster", + "rock", + "sand", + "snow", + "water", + "wood", + "asphalt", + "ceramic", + "plastic", + "rubber", + "cushion", + "fruit", + "paintedmetal", + "player", + "tallgrass", + "riotshield", + "opaqueglass", + "clipmissile", + "ai_nosight", + "clipshot", + "playerclip", + "monsterclip", + "vehicleclip", + "itemclip", + "nodrop", + "nonsolid", + "detail", + "structural", + "portal", + "canshootclip", + "origin", + "sky", + "nocastshadow", + "onlycastshadow", + "physicsgeom", + "lightportal", + "caulk", + "arealight", + "slick", + "noimpact", + "nomarks", + "nopenetrate", + "ladder", + "nodamage", + "mantleon", + "mantleover", + "mount", + "nosteps", + "nodraw", + "noreceivedynamicshadow", + "nodlight", + "none"}; +} // namespace BSPFlags diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index fe5fe97c3..1bc0d7905 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -184,9 +184,11 @@ namespace BSP currModel->origin.y = bspModel.origin.y; currModel->origin.z = bspModel.origin.z; + // tracing only checks if this is equal to 0 or not + currModel->contents = 1; + if (!xModelAsset->IsReference()) { - currModel->contents = currModel->xmodel->contents; BSPUtil::calculateXmodelBounds(currModel->xmodel, xmodelAxis, currModel->absmin, currModel->absmax); currModel->absmin.x = (currModel->absmin.x * bspModel.scale) + bspModel.origin.x; currModel->absmin.y = (currModel->absmin.y * bspModel.scale) + bspModel.origin.y; @@ -200,7 +202,6 @@ namespace BSP } else { - currModel->contents = 1; if (bspModel.areBoundsValid) { currModel->absmin = bspModel.mins; @@ -260,7 +261,7 @@ namespace BSP std::vector objectIndexes; }; - void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex) + void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) { assert(tree->isLeaf); BSPLeaf* bspLeaf = tree->leaf.get(); @@ -376,6 +377,9 @@ namespace BSP parentAABBArrayIndex += parentCount; } + + for (auto& matData : uniqueMaterials) + *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; } constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; @@ -404,19 +408,21 @@ namespace BSP leaf.maxs.z = 0.0f; leaf.leafBrushNode = 0; - leaf.terrainContents = 1; // todo: use correct contents if (tree->leaf->getObjectCount() > 0) { size_t parentCount = 0; size_t parentStartIndex = 0; - addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex); + int treeContents; + addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex, &treeContents); leaf.collAabbCount = static_cast(parentCount); leaf.firstCollAabbIndex = static_cast(parentStartIndex); + leaf.terrainContents = treeContents; } else { leaf.firstCollAabbIndex = 0; leaf.collAabbCount = 0; + leaf.terrainContents = 0; } uint16_t leafIndex = static_cast(leafVec.size()); @@ -693,8 +699,8 @@ namespace BSP BSPMaterial bspMat = bsp->colWorld.materials.at(matIdx); clipMat->name = m_memory.Dup(bspMat.materialName.c_str()); - clipMat->contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; - clipMat->surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + clipMat->contentFlags = bspMat.contentFlags; + clipMat->surfaceFlags = bspMat.surfaceFlags; } return true; @@ -728,7 +734,7 @@ namespace BSP if (!loadMaterials(clipMap, bsp)) return nullptr; - if (!loadWorldCollision(clipMap, bsp)) + if (!loadWorldCollision(clipMap, bsp)) // requires materials return nullptr; loadSubModelCollision(clipMap, bsp); // requires tri verts diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 385800758..65328a04a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -32,7 +32,7 @@ namespace BSP std::vector AABBTreeVec; size_t highestLeafObjectCount = 0; std::vector partitionToMaterialMap; - void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex); + void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); From 1870190861dfa0deb51ed9c7baab65f6e5d299cc Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:25:28 +0800 Subject: [PATCH 078/123] feat: moved surface type to material extras, and surface flags to the node extras --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 7 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 194 ++++++++++++---------- 2 files changed, 109 insertions(+), 92 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index d38fc0de0..6beae7182 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -91,9 +91,11 @@ namespace gltf public: std::optional xmodel; std::optional spawnpoint; + + std::optional flags; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags); class JsonNode { @@ -305,10 +307,9 @@ namespace gltf { public: std::optional type; - std::optional flags; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterialExtras, type, flags); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterialExtras, type); class JsonMaterial { diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 904d7b2cd..69043ab08 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -22,6 +22,8 @@ #include #include +using namespace BSPFlags; + namespace { struct AccessorsForVertex @@ -95,6 +97,7 @@ namespace BSPData* m_bsp; BSPWorld* m_curr_bsp_world; bool m_is_world_gfx; + size_t m_emptyMaterialIndex; std::vector> m_accessors; std::vector> m_buffer_views; @@ -362,6 +365,53 @@ namespace return true; } + size_t createMaterialWithFlags(size_t originalMaterialIdx, const std::string& flags, bool& isNoDrawFlagSet) + { + BSPMaterial newMaterial = m_curr_bsp_world->materials.at(originalMaterialIdx); + + bool matchedAnyFlag = false; + std::vector flagStrVec = utils::StringSplit(flags, ','); + for (std::string& flag : flagStrVec) + { + bool foundMatchingName = false; + utils::MakeStringLowerCase(flag); + utils::StringTrim(flag); + size_t typeNameCount = std::extent::value; + for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) + { + BSPFlags::SurfaceType surfType = BSPFlags::materialFlags[typeIdx]; + if (!flag.compare(BSPFlags::surfaceTypeToNameMap[surfType])) + { + BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; + newMaterial.surfaceFlags |= flags.surfaceFlags; + newMaterial.contentFlags |= flags.contentFlags; + foundMatchingName = true; + matchedAnyFlag = true; + break; + } + } + if (!foundMatchingName) + con::warn("invalid surface using material {} flag name: {}", newMaterial.materialName, flag); + } + if (!matchedAnyFlag) + return originalMaterialIdx; + + // the first content flag bit must be set to 1 for the surface to have collision + if ((newMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NONSOLID].surfaceFlags) != 0) + newMaterial.contentFlags &= 0xFFFFFFFE; + else + newMaterial.contentFlags |= 1; + + if ((newMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) != 0) + isNoDrawFlagSet = true; + else + isNoDrawFlagSet = false; + + size_t newMaterialIndex = m_curr_bsp_world->materials.size(); + m_curr_bsp_world->materials.emplace_back(newMaterial); + return newMaterialIndex; + } + bool addMeshNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { assert(node.mesh); @@ -389,11 +439,25 @@ namespace .m_index_accessor = *primitive.indices, }; - BSPSurface surface; + size_t materialIndex; if (primitive.material) - surface.materialIndex = *primitive.material; + { + size_t originalMaterialIdx = *primitive.material; + if (node.extras && node.extras->flags) + { + bool isNoDrawFlagSet = false; + materialIndex = createMaterialWithFlags(originalMaterialIdx, *node.extras->flags, isNoDrawFlagSet); + if (isNoDrawFlagSet && m_is_world_gfx) + continue; // noDraw flag doesn't work, so remove the surface from the graphics data instead + } + else + materialIndex = originalMaterialIdx; + } else - surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes + materialIndex = m_emptyMaterialIndex; + + BSPSurface surface; + surface.materialIndex = materialIndex; vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); @@ -608,78 +672,6 @@ namespace return rootNodes; } - void setMaterialFlags(BSPMaterial& material, const JsonMaterial& jsMaterial) - { - if (!jsMaterial.extras) - { - material.surfaceFlags = 1; - material.contentFlags = 1; - return; - } - if (!jsMaterial.extras->flags && !jsMaterial.extras->type) - { - material.surfaceFlags = 1; - material.contentFlags = 1; - return; - } - - material.surfaceFlags = 0; - material.contentFlags = 0; - if (jsMaterial.extras->type) - { - std::string typeStr = *jsMaterial.extras->type; - - size_t typeNameCount = std::extent::value; - for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) - { - BSPFlags::SurfaceType surfType = BSPFlags::materialTypes[typeIdx]; - if (!typeStr.compare(BSPFlags::surfaceTypeToNameMap[surfType])) - { - BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; - material.surfaceFlags = flags.surfaceFlags; - material.contentFlags = flags.contentFlags; - break; - } - } - } - if (material.surfaceFlags == 0 && material.contentFlags == 0) - con::warn("invalid material {} type name: {}", material.materialName, *jsMaterial.extras->type); - - if (jsMaterial.extras->flags) - { - std::string flagsStr = *jsMaterial.extras->flags; - - std::vector flagStrVec = utils::StringSplit(flagsStr, ','); - for (std::string& flag : flagStrVec) - { - bool foundMatchingName = false; - utils::MakeStringLowerCase(flag); - utils::StringTrim(flag); - size_t typeNameCount = std::extent::value; - for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) - { - BSPFlags::SurfaceType surfType = BSPFlags::materialFlags[typeIdx]; - if (!flag.compare(BSPFlags::surfaceTypeToNameMap[surfType])) - { - BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; - material.surfaceFlags |= flags.surfaceFlags; - material.contentFlags |= flags.contentFlags; - foundMatchingName = true; - break; - } - } - if (!foundMatchingName) - con::warn("invalid material {} flag name: {}", material.materialName, flag); - } - } - - // the first content flag bit must be set to 1 for the surface to have collision - if ((material.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NONSOLID].surfaceFlags) != 0) - material.contentFlags &= 0xFFFFFFFE; - else - material.contentFlags |= 1; - } - void LoadMaterials(const JsonRoot& jRoot) { if (jRoot.materials) @@ -726,23 +718,43 @@ namespace material.materialColour.w = 1.0f; } - setMaterialFlags(material, jsMaterial); + material.surfaceFlags = 0; + material.contentFlags = 1; + if (jsMaterial.extras && jsMaterial.extras->type) + { + bool foundType = false; + size_t typeNameCount = std::extent::value; + for (size_t typeIdx = 0; typeIdx < typeNameCount; typeIdx++) + { + BSPFlags::SurfaceType surfType = BSPFlags::materialTypes[typeIdx]; + if (!jsMaterial.extras->type->compare(BSPFlags::surfaceTypeToNameMap[surfType])) + { + BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[surfType]; + material.surfaceFlags |= flags.surfaceFlags; + material.contentFlags |= flags.contentFlags; + foundType = true; + break; + } + } + if (!foundType) + con::warn("invalid material {} type name: {}", material.materialName, *jsMaterial.extras->type); + } m_curr_bsp_world->materials.emplace_back(material); } } - // last material is used when a primitve has no material/colour data - BSPMaterial colorMaterial; - colorMaterial.materialType = MATERIAL_TYPE_COLOUR; - colorMaterial.surfaceFlags = 1; - colorMaterial.contentFlags = 1; - colorMaterial.materialName = ""; - colorMaterial.materialColour.x = 1.0f; - colorMaterial.materialColour.y = 1.0f; - colorMaterial.materialColour.z = 1.0f; - colorMaterial.materialColour.w = 1.0f; - m_curr_bsp_world->materials.emplace_back(colorMaterial); + m_emptyMaterialIndex = m_curr_bsp_world->materials.size(); + BSPMaterial emptyMaterial; + emptyMaterial.materialType = MATERIAL_TYPE_COLOUR; + emptyMaterial.surfaceFlags = 0; + emptyMaterial.contentFlags = 1; + emptyMaterial.materialName = ""; + emptyMaterial.materialColour.x = 1.0f; + emptyMaterial.materialColour.y = 1.0f; + emptyMaterial.materialColour.z = 1.0f; + emptyMaterial.materialColour.w = 1.0f; + m_curr_bsp_world->materials.emplace_back(emptyMaterial); } void LoadLights(const JsonRoot& jRoot) @@ -1037,6 +1049,9 @@ namespace BSP return nullptr; if (!loader.addGLTFDataToBSP(input, true)) return nullptr; + if (!seperateColFile) + if (!loader.addGLTFDataToBSP(input, false)) + return nullptr; } else { @@ -1045,6 +1060,9 @@ namespace BSP return nullptr; if (!loader.addGLTFDataToBSP(input, true)) return nullptr; + if (!seperateColFile) + if (!loader.addGLTFDataToBSP(input, false)) + return nullptr; } if (seperateColFile) @@ -1066,8 +1084,6 @@ namespace BSP return nullptr; } } - else - bsp->colWorld = bsp->gfxWorld; return bsp; } From 86fe4c643f3a623df7820112518e8cbb23bc54d1 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:39:06 +0800 Subject: [PATCH 079/123] fix: gfx linker was not drawing the last surface in the surface array --- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index c2a6710c0..60c775185 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -123,13 +123,13 @@ namespace BSP // set all surface types to lit opaque gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount - 1); - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount - 1); - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount - 1); - gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount - 1); - gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount - 1); - gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount - 1); - gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount - 1); + gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount); + gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount); // visdata is written to by the game // all visdata is alligned by 128 From dd0706a737f335f6445346358ad63e27c65fd7fe Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:08:30 +0800 Subject: [PATCH 080/123] feat: clipmap linker now has support for maps smaller than the minimum BSP node size --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 31 +++++++++++++------ .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 1bc0d7905..000448432 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -390,10 +390,28 @@ namespace BSP // Nodes are indexed by their index in the node array // Leafs are indexed by (-1 - ) // See https://developer.valvesoftware.com/wiki/BSP_(Source) - int16_t ClipMapLinker::loadBSPNode(clipMap_t* clipMap, BSPTree* tree) + int16_t ClipMapLinker::loadBSPNode(clipMap_t* clipMap, BSPTree* tree, bool isRoot) { if (tree->isLeaf) { + if (isRoot) + { + cplane_s plane; + plane.dist = 0; + plane.normal = normalX; + plane.type = 0; + plane.signbits = 0; + plane.pad[0] = 0; + plane.pad[1] = 0; + planeVec.emplace_back(plane); + + cNode_t node; + node.plane = nullptr; // initalised after the BSP tree has been loaded + node.children[0] = -1; + node.children[1] = -1; + nodeVec.emplace_back(node); + } + cLeaf_s leaf; leaf.cluster = 0; // always use cluster 0 @@ -469,8 +487,8 @@ namespace BSP cNode_t node; node.plane = nullptr; // initalised after the BSP tree has been loaded - node.children[0] = loadBSPNode(clipMap, tree->node->front.get()); - node.children[1] = loadBSPNode(clipMap, tree->node->back.get()); + node.children[0] = loadBSPNode(clipMap, tree->node->front.get(), false); + node.children[1] = loadBSPNode(clipMap, tree->node->back.get(), false); nodeVec.at(nodeIndex) = node; @@ -494,11 +512,6 @@ namespace BSP BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - if (tree->isLeaf) - { - con::error("Map size is too small for BSP generation!"); - return false; - } for (int partitionIdx = 0; partitionIdx < clipMap->partitionCount; partitionIdx++) { @@ -512,7 +525,7 @@ namespace BSP } // load planes, nodes, leafs, and AABB trees - loadBSPNode(clipMap, tree.get()); + loadBSPNode(clipMap, tree.get(), true); clipMap->info.planeCount = static_cast(planeVec.size()); clipMap->info.planes = m_memory.Alloc(planeVec.size()); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 65328a04a..5c8d48421 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -33,7 +33,7 @@ namespace BSP size_t highestLeafObjectCount = 0; std::vector partitionToMaterialMap; void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); - int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree); + int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree, bool isRoot); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); bool loadWorldCollision(clipMap_t* clipMap, BSPData* bsp); From 9d95d82f631ef7841a5dc70853230177448bb8d7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:22:16 +0800 Subject: [PATCH 081/123] fix: clipmap AABB generation skipped first object --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 000448432..7ce566805 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -291,7 +291,7 @@ namespace BSP { uniqueMatData data; data.materialIndex = materialIndex; - data.objectIndexes = std::vector(); + data.objectIndexes.emplace_back(objIdx); uniqueMaterials.emplace_back(data); } } @@ -378,6 +378,7 @@ namespace BSP parentAABBArrayIndex += parentCount; } + *out_treeContents = 0; for (auto& matData : uniqueMaterials) *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; } @@ -430,7 +431,7 @@ namespace BSP { size_t parentCount = 0; size_t parentStartIndex = 0; - int treeContents; + int treeContents = 0; addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex, &treeContents); leaf.collAabbCount = static_cast(parentCount); leaf.firstCollAabbIndex = static_cast(parentStartIndex); From 5f98ea43822bb616db1afc580ec5ba63623fc13f Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:22:48 +0800 Subject: [PATCH 082/123] feat: Added console print when no spawns were found --- src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index a752395b2..9ef1efeca 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -69,6 +69,7 @@ namespace { if (bsp->spawnpoints.size() == 0) { + con::info("No spawnpoints found, setting all spawns to (0, 0, 0)"); BSP::BSPSpawnPoint defaultSpawnPoint; defaultSpawnPoint.origin.x = 0.0f; defaultSpawnPoint.origin.y = 0.0f; From b9c1094c58fafa74848712e81f6d0fb3bc037472 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 18:45:05 +0800 Subject: [PATCH 083/123] feat: gfxworld now uses surface flags for surfaces --- src/ObjLoading/Game/T6/BSP/BSPFlags.h | 87 ++++--------------- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 19 ++-- 2 files changed, 31 insertions(+), 75 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPFlags.h b/src/ObjLoading/Game/T6/BSP/BSPFlags.h index 936c18632..0a5b8e417 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPFlags.h +++ b/src/ObjLoading/Game/T6/BSP/BSPFlags.h @@ -52,7 +52,7 @@ namespace BSPFlags SURF_TYPE_ORIGIN, SURF_TYPE_SKY, SURF_TYPE_NOCASTSHADOW, - SURF_TYPE_ONLYCASTSHADOW, + SURF_TYPE_CASTSUNSHADOW, SURF_TYPE_PHYSICSGEOM, SURF_TYPE_LIGHTPORTAL, SURF_TYPE_CAULK, @@ -129,7 +129,7 @@ namespace BSPFlags {0, 0 }, // origin {4, 0x800 }, // sky {0x40000, 0 }, // noCastShadow - {0x80000, 0 }, // onlyCastShadow + {0x80000, 0 }, // castSunShadow {0, 0 }, // physicsGeom {0, 0 }, // lightPortal {0x1000, 0 }, // caulk @@ -175,7 +175,7 @@ namespace BSPFlags SURF_TYPE_CANSHOOTCLIP, SURF_TYPE_SKY, SURF_TYPE_NOCASTSHADOW, - SURF_TYPE_ONLYCASTSHADOW, + SURF_TYPE_CASTSUNSHADOW, SURF_TYPE_CAULK, SURF_TYPE_AREALIGHT, SURF_TYPE_SLICK, @@ -193,71 +193,18 @@ namespace BSPFlags SURF_TYPE_NODLIGHT, }; - constexpr const char* surfaceTypeToNameMap[SURF_TYPE_COUNT] = {"bark", - "brick", - "carpet", - "cloth", - "concrete", - "dirt", - "flesh", - "foliage", - "glass", - "grass", - "gravel", - "ice", - "metal", - "mud", - "paper", - "plaster", - "rock", - "sand", - "snow", - "water", - "wood", - "asphalt", - "ceramic", - "plastic", - "rubber", - "cushion", - "fruit", - "paintedmetal", - "player", - "tallgrass", - "riotshield", - "opaqueglass", - "clipmissile", - "ai_nosight", - "clipshot", - "playerclip", - "monsterclip", - "vehicleclip", - "itemclip", - "nodrop", - "nonsolid", - "detail", - "structural", - "portal", - "canshootclip", - "origin", - "sky", - "nocastshadow", - "onlycastshadow", - "physicsgeom", - "lightportal", - "caulk", - "arealight", - "slick", - "noimpact", - "nomarks", - "nopenetrate", - "ladder", - "nodamage", - "mantleon", - "mantleover", - "mount", - "nosteps", - "nodraw", - "noreceivedynamicshadow", - "nodlight", - "none"}; + constexpr const char* surfaceTypeToNameMap[SURF_TYPE_COUNT] = {"bark", "brick", "carpet", "cloth", "concrete", + "dirt", "flesh", "foliage", "glass", "grass", + "gravel", "ice", "metal", "mud", "paper", + "plaster", "rock", "sand", "snow", "water", + "wood", "asphalt", "ceramic", "plastic", "rubber", + "cushion", "fruit", "paintedmetal", "player", "tallgrass", + "riotshield", "opaqueglass", "clipmissile", "ai_nosight", "clipshot", + "playerclip", "monsterclip", "vehicleclip", "itemclip", "nodrop", + "nonsolid", "detail", "structural", "portal", "canshootclip", + "origin", "sky", "nocastshadow", "castshadow", "physicsgeom", + "lightportal", "caulk", "arealight", "slick", "noimpact", + "nomarks", "nopenetrate", "ladder", "nodamage", "mantleon", + "mantleover", "mount", "nosteps", "nodraw", "noreceivedynamicshadow", + "nodlight", "none"}; } // namespace BSPFlags diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 60c775185..df7876553 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -61,11 +61,6 @@ namespace BSP BSPSurface& bspSurface = bsp->gfxWorld.surfaces.at(surfIdx); GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx]; - gfxSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - gfxSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - gfxSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - gfxSurface->tris.triCount = bspSurface.triCount; gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex; gfxSurface->tris.vertexCount = bspSurface.vertexCount; @@ -109,6 +104,20 @@ namespace BSP gfxSurface->tris.maxs.y = gfxSurface->bounds[1].y; gfxSurface->tris.maxs.z = gfxSurface->bounds[1].z; + gfxSurface->flags = 0; + if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_SKY].contentFlags) != 0) + gfxSurface->flags |= GFX_SURFACE_IS_SKY; + if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].contentFlags) != 0) + gfxSurface->flags |= GFX_SURFACE_NO_DRAW; + if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_CASTSUNSHADOW].surfaceFlags) != 0) + gfxSurface->flags |= GFX_SURFACE_CASTS_SUN_SHADOW; + if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0) + gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW; + + gfxSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + gfxSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + gfxSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + // unknown value gfxSurface->tris.himipRadiusInvSq = 0.0f; } From c6bb376a5c9194b6c65434d11e353a36434f76b5 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:22:19 +0800 Subject: [PATCH 084/123] feat: xmodles have toggleable shadows --- src/ObjLoading/Game/T6/BSP/BSP.h | 2 ++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 12 ++++++++++ .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 23 ++++++++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 731435b6f..ef4f5e7b8 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -58,6 +58,8 @@ namespace BSP bool areBoundsValid; vec3_t mins; vec3_t maxs; + + bool doesCastShadow; }; struct BSPWorld diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 69043ab08..93a05722b 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -548,6 +548,18 @@ namespace throw GltfLoadException("Xmodel has no name."); xmodel.name = *node.extras->xmodel; + xmodel.doesCastShadow = true; + if (node.extras->flags) + { + std::vector flagStrVec = utils::StringSplit(*node.extras->flags, ','); + for (std::string& flag : flagStrVec) + if (!flag.compare(surfaceTypeToNameMap[SURF_TYPE_NOCASTSHADOW])) + { + xmodel.doesCastShadow = false; + break; + } + } + Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; xmodel.origin.x = transformedPosition.x(); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index df7876553..57d276777 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -182,9 +182,12 @@ namespace BSP BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, currModel->placement.axis); currModel->placement.scale = bspModel.scale; - currModel->cullDist = 10000.0f; currModel->flags = 0; - currModel->primaryLightIndex = 0; + if (!bspModel.doesCastShadow) + currModel->flags |= STATIC_MODEL_FLAG_NO_SHADOW; + + currModel->cullDist = 10000.0f; + currModel->primaryLightIndex = 1; currModel->reflectionProbeIndex = 0; currModel->smid = modelIdx; @@ -357,14 +360,22 @@ namespace BSP gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) { - + // smodelCount and smodelIndex is filled next loop gfxWorld->shadowGeom[lightIdx].smodelCount = 0; - gfxWorld->shadowGeom[lightIdx].smodelIndex = nullptr; + gfxWorld->shadowGeom[lightIdx].smodelIndex = m_memory.Alloc(gfxWorld->dpvs.smodelCount); - // sorted surfs is written to by the game + // sorted surfs and surfaceCount is recalculated each frame gfxWorld->shadowGeom[lightIdx].surfaceCount = gfxWorld->dpvs.staticSurfaceCount; gfxWorld->shadowGeom[lightIdx].sortedSurfIndex = m_memory.Alloc(gfxWorld->dpvs.staticSurfaceCount); } + for (unsigned int modelIdx = 0; modelIdx < gfxWorld->dpvs.smodelCount; modelIdx++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[modelIdx].flags & STATIC_MODEL_FLAG_NO_SHADOW) != 0) + continue; + char lightIndex = gfxWorld->dpvs.smodelDrawInsts[modelIdx].primaryLightIndex; + gfxWorld->shadowGeom[lightIndex].smodelIndex[gfxWorld->shadowGeom[lightIndex].smodelCount] = modelIdx; + gfxWorld->shadowGeom[lightIndex].smodelCount++; + } gfxWorld->lightRegion = m_memory.Alloc(gfxWorld->primaryLightCount); for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) @@ -779,7 +790,7 @@ namespace BSP loadLightGrid(gfxWorld); - loadGfxLights(bsp, gfxWorld); + loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces loadModels(gfxWorld); From 45d3ea188d88ba5b3ef283137202ca9e07353285 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:24:14 +0800 Subject: [PATCH 085/123] feat: pathnodes are added through the GLTF file --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 3 ++- src/ObjLoading/Game/T6/BSP/BSP.h | 6 +++++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 23 +++++++++++++++++++ .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 13 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 6beae7182..e202b3a70 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -91,11 +91,12 @@ namespace gltf public: std::optional xmodel; std::optional spawnpoint; + std::optional pathnode; std::optional flags; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags, pathnode); class JsonNode { diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index ef4f5e7b8..c545c69ad 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -108,6 +108,11 @@ namespace BSP BSPSpawnPointType type; }; + struct BSPPathNode + { + vec3_t origin; + }; + struct BSPData { std::string name; @@ -118,6 +123,7 @@ namespace BSP std::vector lights; std::vector spawnpoints; + std::vector pathnodes; }; // BSPGameConstants: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 93a05722b..e8d82426c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -586,6 +586,26 @@ namespace return true; } + bool addPathNode_Node(const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->pathnode); + + BSPPathNode pathnode; + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + pathnode.origin.x = transformedPosition.x(); + pathnode.origin.y = transformedPosition.y(); + pathnode.origin.z = transformedPosition.z(); + RhcToLhcCoordinates(pathnode.origin.v); + + m_bsp->pathnodes.emplace_back(pathnode); + + return true; + } + bool addSpawnPointNode(const gltf::JsonNode& node) { assert(node.extras); @@ -641,6 +661,9 @@ namespace if (m_is_world_gfx && node.extras->spawnpoint) return addSpawnPointNode(node); + + if (m_is_world_gfx && node.extras->pathnode) + return addPathNode_Node(node); } if (node.mesh) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 9ef1efeca..062f2fb9e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -116,6 +116,17 @@ namespace } } + void addPathNodesToEntString(BSP::BSPData* bsp, std::string& entityString) + { + for (auto& pathnode : bsp->pathnodes) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(pathnode.origin))); + entityString.append(std::format("\"classname\" \"{}\"\n", "node_pathnode")); + entityString.append("}\n"); + } + } + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ "entities": [ { @@ -167,6 +178,8 @@ namespace BSP addSpawnsToEntString(bsp, entityString); + addPathNodesToEntString(bsp, entityString); + MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); From 6ec63b065569795acb2a9e5ca5d29a2dfdce2ee0 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:04:11 +0800 Subject: [PATCH 086/123] chore: replace "new" keyword with correct memory allocation --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 2 +- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 7ce566805..b471fd3bf 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -161,7 +161,7 @@ namespace BSP // even official maps instead use terrain or brushes to cover where the collision should be. clipMap->numStaticModels = bsp->colWorld.xmodels.size(); - clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; + clipMap->staticModelList = m_memory.Alloc(clipMap->numStaticModels); for (unsigned int i = 0; i < clipMap->numStaticModels; i++) { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 57d276777..672b0a6ce 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -158,8 +158,8 @@ namespace BSP { size_t modelCount = bsp->gfxWorld.xmodels.size(); gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + gfxWorld->dpvs.smodelInsts = m_memory.Alloc(modelCount); + gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc(modelCount); for (size_t modelIdx = 0; modelIdx < modelCount; modelIdx++) { From 73412f9b6173c325b8ffce7cb4c82b543c22aaa7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:04:46 +0800 Subject: [PATCH 087/123] feat: set basic lightmap for xmodels --- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 672b0a6ce..4139885e4 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -198,17 +198,6 @@ namespace BSP currModel->colorsIndex = 0; currModel->visibility = 0; - // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // TODO: calculate lighting and store it here - currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; - if (!xModelAsset->IsReference()) { BSPUtil::calculateXmodelBounds(currModel->model, currModel->placement.axis, currModelInst->mins, currModelInst->maxs); @@ -218,6 +207,21 @@ namespace BSP currModelInst->maxs.x = (currModelInst->maxs.x * bspModel.scale) + bspModel.origin.x; currModelInst->maxs.y = (currModelInst->maxs.y * bspModel.scale) + bspModel.origin.y; currModelInst->maxs.z = (currModelInst->maxs.z * bspModel.scale) + bspModel.origin.z; + + for (uint16_t lodIdx = 0; lodIdx < currModel->model->numLods; lodIdx++) + { + uint16_t vertCount = 0; + for (auto surfaceIndex = 0u; surfaceIndex < currModel->model->lodInfo[lodIdx].numsurfs; surfaceIndex++) + { + const auto& surface = currModel->model->surfs[surfaceIndex + currModel->model->lodInfo[lodIdx].surfIndex]; + vertCount += surface.vertCount; + } + + currModel->lmapVertexInfo[lodIdx].numLmapVertexColors = vertCount; + currModel->lmapVertexInfo[lodIdx].lmapVertexColors = m_memory.Alloc(vertCount); + // a value of 1 makes shadowed surfaces slightly easier to see + memset(currModel->lmapVertexInfo[lodIdx].lmapVertexColors, 1, sizeof(unsigned int) * vertCount); + } } else { @@ -236,6 +240,16 @@ namespace BSP currModelInst->maxs.y = bspModel.origin.y + 1.0f; currModelInst->maxs.z = bspModel.origin.z + 1.0f; } + + // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; + currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; } currModelInst->lightingOrigin.x = 0.0f; currModelInst->lightingOrigin.y = 0.0f; From 1eddd2276ce7818ceb3d2782a4c04996cfb70aac Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:28:28 +0800 Subject: [PATCH 088/123] chorse: groundwork for implementing zombies maps --- src/ObjLoading/Game/T6/BSP/BSP.h | 1 + src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 3 ++- src/ObjLoading/Game/T6/BSP/BSPCreator.h | 2 +- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp | 12 +++++++----- src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h | 3 ++- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index c545c69ad..8e49b62eb 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -117,6 +117,7 @@ namespace BSP { std::string name; std::string bspName; + bool isZombiesMap; BSPWorld gfxWorld; BSPWorld colWorld; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e8d82426c..e801fae67 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1038,7 +1038,7 @@ namespace namespace BSP { - std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath, bool isZombiesMap) { bool seperateColFile = true; bool isGfxFileGltf = true; @@ -1075,6 +1075,7 @@ namespace BSP std::unique_ptr bsp = std::make_unique(); bsp->name = mapName; bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + bsp->isZombiesMap = isZombiesMap; BSPLoader loader(bsp.get()); if (isGfxFileGltf) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.h b/src/ObjLoading/Game/T6/BSP/BSPCreator.h index 8ab85e61c..bdd0a20cc 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.h +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.h @@ -5,5 +5,5 @@ namespace BSP { - std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath); + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath, bool isZombiesMap); }; // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index a1399c8e3..ec5ac2715 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -11,10 +11,11 @@ namespace class BSPLoader final : public IAssetCreator { public: - BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, ZoneDefinitionMapType mapType) : m_memory(memory), m_search_path(searchPath), - m_zone(zone) + m_zone(zone), + m_mapType(mapType) { } @@ -32,7 +33,7 @@ namespace bool FinalizeZone(AssetCreationContext& context) override { - std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path); + std::unique_ptr bsp = BSP::createBSPData(m_zone.m_name, m_search_path, m_mapType == ZoneDefinitionMapType::ZM); if (bsp == nullptr) return false; @@ -48,13 +49,14 @@ namespace MemoryManager& m_memory; ISearchPath& m_search_path; Zone& m_zone; + ZoneDefinitionMapType m_mapType; }; } // namespace namespace BSP { - std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, ZoneDefinitionMapType mapType) { - return std::make_unique(memory, searchPath, zone); + return std::make_unique(memory, searchPath, zone, mapType); } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h index 25ca4f8ae..9d0d04059 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h @@ -4,10 +4,11 @@ #include "Game/T6/T6.h" #include "SearchPath/ISearchPath.h" #include "Utils/MemoryManager.h" +#include "Zone/Definition/ZoneDefinition.h" #include namespace BSP { - std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); + std::unique_ptr CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, ZoneDefinitionMapType mapType); } // namespace BSP diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 4a32d6b74..d48c7e732 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -443,7 +443,7 @@ namespace T6 collection.AddAssetCreator(z_barrier::CreateGdtLoaderT6(memory, searchPath, gdt, zone)); if (definition.m_map_type != ZoneDefinitionMapType::NONE) - collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone)); + collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone, definition.m_map_type)); } } // namespace From d024223f3f3523b24b7c9e435046fe6123b615b1 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:51:42 +0800 Subject: [PATCH 089/123] Addition of zombie spawns, player spawns and zones for zombies. Clipmap AABB generation largely modified. --- src/ObjLoading/Game/T6/BSP/BSP.h | 40 ++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 188 +++++++- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 18 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 428 ++++++++++++++---- .../Game/T6/BSP/Linker/ClipMapLinker.h | 17 +- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 9 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 61 ++- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 51 ++- 9 files changed, 669 insertions(+), 145 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 8e49b62eb..95e94ebc4 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -69,6 +69,9 @@ namespace BSP std::vector indices; std::vector materials; std::vector xmodels; + + size_t staticSurfaceCount; + std::vector internal_scriptSurfaces; }; enum BSPLightType @@ -113,6 +116,37 @@ namespace BSP vec3_t origin; }; + struct BSPZoneZM + { + vec3_t origin; + std::string zoneName; + std::string zSpawnerGroupName; + std::string spawnpointGroupName; + size_t modelIndex; + }; + + struct BSPSpawnPointZM + { + std::string spawnpointGroupName; + vec3_t origin; + vec3_t forward; + }; + + struct BSPZSpawnerZM + { + std::string zSpawnerGroupName; + vec3_t origin; + vec3_t forward; + }; + + struct BSPModel + { + bool isGfxModel; + + size_t surfaceIndex; + size_t surfaceCount; + }; + struct BSPData { std::string name; @@ -125,6 +159,12 @@ namespace BSP std::vector lights; std::vector spawnpoints; std::vector pathnodes; + + std::vector zSpawnPoints; + std::vector zSpawners; + std::vector zZones; + + std::vector models; }; // BSPGameConstants: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e801fae67..a5365d5ce 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -612,26 +612,149 @@ namespace assert(node.extras->spawnpoint); Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - BSPSpawnPoint spawnPoint; - - if (!node.extras->spawnpoint->compare("attacker")) - spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; - else if (!node.extras->spawnpoint->compare("defender")) - spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; - else if (!node.extras->spawnpoint->compare("all")) - spawnPoint.type = SPAWNPOINT_TYPE_ALL; + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + // GLTF default direction is +Y up + Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + vec3_t forward; + forward.x = outputDirection.x(); + forward.y = outputDirection.y(); + forward.z = outputDirection.z(); + RhcToLhcCoordinates(forward.v); + + if (m_bsp->isZombiesMap) + { + BSPSpawnPointZM spawnPoint; + spawnPoint.origin = origin; + spawnPoint.forward = forward; + spawnPoint.spawnpointGroupName = *node.extras->spawnpoint; + m_bsp->zSpawnPoints.emplace_back(spawnPoint); + } else { - con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); - return false; + BSPSpawnPoint spawnPoint; + spawnPoint.origin = origin; + spawnPoint.forward = forward; + + if (!node.extras->spawnpoint->compare("attacker")) + spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; + else if (!node.extras->spawnpoint->compare("defender")) + spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; + else if (!node.extras->spawnpoint->compare("all")) + spawnPoint.type = SPAWNPOINT_TYPE_ALL; + else + { + con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); + return false; + } + + m_bsp->spawnpoints.emplace_back(spawnPoint); } + return true; + } + + size_t addScriptModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + if (!node.mesh || !jRoot.meshes) + throw new GltfLoadException("Script model created with no mesh data"); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + + if (mesh.primitives.size() == 0) + throw new GltfLoadException("Script model created with no mesh data"); + + BSPModel model; + model.isGfxModel = m_is_world_gfx; + model.surfaceIndex = m_curr_bsp_world->internal_scriptSurfaces.size(); + model.surfaceCount = mesh.primitives.size(); + m_bsp->models.emplace_back(model); + + for (const auto& primitive : mesh.primitives) + { + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + if (primitive.material) + surface.materialIndex = *primitive.material; + else + surface.materialIndex = m_emptyMaterialIndex; + vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; + CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); + + m_curr_bsp_world->internal_scriptSurfaces.emplace_back(surface); + } + + return m_bsp->models.size(); // script model index starts at 1 + } + + bool addZoneNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->zone); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; - spawnPoint.origin.x = transformedPosition.x(); - spawnPoint.origin.y = transformedPosition.y(); - spawnPoint.origin.z = transformedPosition.z(); - RhcToLhcCoordinates(spawnPoint.origin.v); + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + BSPZoneZM zone; + zone.origin = origin; + zone.zoneName = *node.extras->zone; + zone.zSpawnerGroupName = node.extras->zspawner_group.value_or(""); + zone.spawnpointGroupName = node.extras->spawnpoint_group.value_or(""); + zone.modelIndex = addScriptModel(jRoot, node); + m_bsp->zZones.emplace_back(zone); + + return true; + } + + bool addZSpawnerNode(const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->zspawner); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); // GLTF default direction is +Y up Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); @@ -639,12 +762,17 @@ namespace Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; outputDirection.normalize(); - spawnPoint.forward.x = outputDirection.x(); - spawnPoint.forward.y = outputDirection.y(); - spawnPoint.forward.z = outputDirection.z(); - RhcToLhcCoordinates(spawnPoint.forward.v); - - m_bsp->spawnpoints.emplace_back(spawnPoint); + vec3_t forward; + forward.x = outputDirection.x(); + forward.y = outputDirection.y(); + forward.z = outputDirection.z(); + RhcToLhcCoordinates(forward.v); + + BSPZSpawnerZM spawner; + spawner.origin = origin; + spawner.forward = forward; + spawner.zSpawnerGroupName = *node.extras->zspawner; + m_bsp->zSpawners.emplace_back(spawner); return true; } @@ -659,11 +787,20 @@ namespace if (node.extras->xmodel) return addXModelNode(jRoot, node); - if (m_is_world_gfx && node.extras->spawnpoint) + if (!m_is_world_gfx && node.extras->spawnpoint) return addSpawnPointNode(node); - if (m_is_world_gfx && node.extras->pathnode) + if (!m_is_world_gfx && node.extras->pathnode) return addPathNode_Node(node); + + if (!m_is_world_gfx && m_bsp->isZombiesMap) + { + if (node.extras->zone) + return addZoneNode(jRoot, node); + + if (node.extras->zspawner) + return addZSpawnerNode(node); + } } if (node.mesh) @@ -1017,6 +1154,13 @@ namespace LoadMaterials(jRoot); TraverseNodes(jRoot); // requires materials and lights + + size_t staticSurfaceCount = m_curr_bsp_world->surfaces.size(); + for (auto& model : m_bsp->models) + model.surfaceIndex += staticSurfaceCount; + m_curr_bsp_world->staticSurfaceCount = staticSurfaceCount; + m_curr_bsp_world->surfaces.insert( + m_curr_bsp_world->surfaces.end(), m_curr_bsp_world->internal_scriptSurfaces.begin(), m_curr_bsp_world->internal_scriptSurfaces.end()); } catch (const GltfLoadException& e) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 000020254..feb8cb501 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -167,17 +167,17 @@ namespace BSP float quatZ = quat->v[2]; float quatW = quat->v[3]; - float xx = (quatX * 2.0) * quatX; - float xy = (quatX * 2.0) * quatY; - float xz = (quatX * 2.0) * quatZ; - float xw = (quatX * 2.0) * quatW; + float xx = (quatX * 2.0f) * quatX; + float xy = (quatX * 2.0f) * quatY; + float xz = (quatX * 2.0f) * quatZ; + float xw = (quatX * 2.0f) * quatW; - float yy = (quatY * 2.0) * quatY; - float yz = (quatY * 2.0) * quatZ; - float yw = (quatY * 2.0) * quatW; + float yy = (quatY * 2.0f) * quatY; + float yz = (quatY * 2.0f) * quatZ; + float yw = (quatY * 2.0f) * quatW; - float zz = (quatZ * 2.0) * quatZ; - float zw = (quatZ * 2.0) * quatW; + float zz = (quatZ * 2.0f) * quatZ; + float zw = (quatZ * 2.0f) * quatW; axis->x = 1.0f - (zz + yy); axis->y = zw + xy; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index b471fd3bf..d957d0f80 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -120,40 +120,6 @@ namespace BSP clipMap->ropes = m_memory.Alloc(clipMap->max_ropes); } - void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) - { - // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) - clipMap->numSubModels = 1; - clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); - for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) - { - vec3_t vertex = clipMap->verts[vertIdx]; - if (vertIdx == 0) - { - clipMap->cmodels[0].mins = vertex; - clipMap->cmodels[0].maxs = vertex; - } - else - BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); - } - clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - - // The world sub model has no leafs associated with it - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = 0; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = nullptr; - } - bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) { // it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid. @@ -236,6 +202,115 @@ namespace BSP return true; } + void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs); + void addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); + + void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) + { + // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) + clipMap->numSubModels = static_cast(bsp->models.size() + 1); + clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + + // first model is always the world model + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) + { + vec3_t vertex = clipMap->verts[vertIdx]; + if (vertIdx == 0) + { + clipMap->cmodels[0].mins = vertex; + clipMap->cmodels[0].maxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); + } + clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + // The world sub model has no data apart from the bounds + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = 0; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = nullptr; + + for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) + { + auto clipModel = &clipMap->cmodels[modelIdx + 1]; + auto& bspMpdel = bsp->models.at(modelIdx); + + if (bspMpdel.isGfxModel) + { + clipModel->mins.x = 0.0f; + clipModel->mins.y = 0.0f; + clipModel->mins.z = 0.0f; + clipModel->maxs.x = 0.0f; + clipModel->maxs.y = 0.0f; + clipModel->maxs.z = 0.0f; + clipModel->radius = 0.0f; + clipModel->leaf.brushContents = 0; + clipModel->leaf.mins.x = 0.0f; + clipModel->leaf.mins.y = 0.0f; + clipModel->leaf.mins.z = 0.0f; + clipModel->leaf.maxs.x = 0.0f; + clipModel->leaf.maxs.y = 0.0f; + clipModel->leaf.maxs.z = 0.0f; + clipModel->leaf.leafBrushNode = 0; + clipModel->leaf.cluster = 0; + clipModel->info = nullptr; + continue; + } + + std::vector partitionIndexes; + for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + { + ColSurface& surf = collisionSurfaceVec.at(bspMpdel.surfaceIndex + surfIdx); + for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) + { + size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; + partitionIndexes.emplace_back(clipMapPartitionIndex); + vec3_t mins; + vec3_t maxs; + calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); + if (surfIdx == 0 && partitionIdx == 0) + { + clipModel->mins = mins; + clipModel->maxs = maxs; + } + else + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); + } + } + clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; + + int terrainContents = 0; + size_t firstCollAabbIndex = 0; + size_t collAabbCount = 0; + addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); + clipModel->leaf.terrainContents = terrainContents; + clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); + clipModel->leaf.collAabbCount = static_cast(collAabbCount); + + // no brush + clipModel->leaf.brushContents = 0; + clipModel->leaf.mins.x = 0.0f; + clipModel->leaf.mins.y = 0.0f; + clipModel->leaf.mins.z = 0.0f; + clipModel->leaf.maxs.x = 0.0f; + clipModel->leaf.maxs.y = 0.0f; + clipModel->leaf.maxs.z = 0.0f; + clipModel->leaf.leafBrushNode = 0; + clipModel->leaf.cluster = 0; + clipModel->info = nullptr; + } + } + // out_mins and out_maxs are initialised in the function void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs) { @@ -257,10 +332,129 @@ namespace BSP struct uniqueMatData { - unsigned int materialIndex; - std::vector objectIndexes; + size_t materialIndex; + std::vector partitionIndexes; }; + void ClipMapLinker::addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) + { + size_t partitionCount = partitions.size(); + assert(partitionCount > 0); + if (partitionCount > highestPartitionCountForAABB) + highestPartitionCountForAABB = partitionCount; + + std::vector uniqueMaterials; + for (size_t partArrayIdx = 0; partArrayIdx < partitionCount; partArrayIdx++) + { + int partitionIdx = partitions.at(partArrayIdx); + size_t materialIndex = collisionSurfaceVec.at(partitionToColSurfaceMap.at(partitionIdx)).materialIndex; + bool foundIdx = false; + for (auto& uniqueMat : uniqueMaterials) + { + if (uniqueMat.materialIndex == materialIndex) + { + uniqueMat.partitionIndexes.emplace_back(partitionIdx); + foundIdx = true; + break; + } + } + if (!foundIdx) + { + uniqueMatData data; + data.materialIndex = materialIndex; + data.partitionIndexes.emplace_back(partitionIdx); + uniqueMaterials.emplace_back(data); + } + } + + // BO2 has a maximum limit of 128 children per AABB tree (essentially), + // so this is fixed by adding multiple parent AABB trees that hold 128 children each + size_t totalParentCount = 0; + for (auto& matData : uniqueMaterials) + { + size_t objCount = matData.partitionIndexes.size(); + size_t result = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + result++; + totalParentCount += result; + } + + // every parent node needs to be contiguous in memory + size_t parentAABBArrayIndex = AABBTreeVec.size(); + AABBTreeVec.resize(AABBTreeVec.size() + totalParentCount); + *out_parentCount = totalParentCount; + *out_parentStartIndex = parentAABBArrayIndex; + + for (auto& matData : uniqueMaterials) + { + size_t matPartCount = matData.partitionIndexes.size(); + size_t parentCount = matPartCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = matPartCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + parentCount++; + + size_t unaddedObjectCount = matPartCount; + size_t addedObjectCount = 0; + for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) + { + size_t currChildObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) + currChildObjectCount = unaddedObjectCount; + else + unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; + + vec3_t parentMins; + vec3_t parentMaxs; + size_t childObjectStartIndex = AABBTreeVec.size(); + for (size_t objectIdx = 0; objectIdx < currChildObjectCount; objectIdx++) + { + // create a child AABBTree with the partition and add it to AABBTreeVec + int partitionIndex = matData.partitionIndexes.at(addedObjectCount + objectIdx); + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + vec3_t childMins; + vec3_t childMaxs; + calculatePartitionAABB(clipMap, partition, childMins, childMaxs); + + CollisionAabbTree childAABBTree; + childAABBTree.materialIndex = static_cast(matData.materialIndex); + childAABBTree.childCount = 0; + childAABBTree.u.partitionIndex = partitionIndex; + childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); + childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); + AABBTreeVec.emplace_back(childAABBTree); + + // update the parent AABB with the child AABB + if (objectIdx == 0) + { + parentMins = childMins; + parentMaxs = childMaxs; + } + else + BSPUtil::updateAABB(childMins, childMaxs, parentMins, parentMaxs); + } + + CollisionAabbTree parentAABB; + parentAABB.materialIndex = static_cast(matData.materialIndex); + parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); + parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); + parentAABB.childCount = static_cast(currChildObjectCount); + parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); + AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; + + addedObjectCount += currChildObjectCount; + } + + parentAABBArrayIndex += parentCount; + } + + *out_treeContents = 0; + for (auto& matData : uniqueMaterials) + *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; + } + + /* void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) { assert(tree->isLeaf); @@ -268,15 +462,15 @@ namespace BSP size_t leafObjectCount = bspLeaf->getObjectCount(); assert(leafObjectCount > 0); - if (leafObjectCount > highestLeafObjectCount) - highestLeafObjectCount = leafObjectCount; + if (leafObjectCount > highestPartitionCountForAABB) + highestPartitionCountForAABB = leafObjectCount; // the material index of the AABB tree is only checked for the parent node, so each parent has only children with the same material std::vector uniqueMaterials; for (size_t objIdx = 0; objIdx < leafObjectCount; objIdx++) { int partitionIdx = bspLeaf->getObject(objIdx)->partitionIndex; - unsigned materialIndex = partitionToMaterialMap[partitionIdx]; + size_t materialIndex = collisionSurfaceVec.at(partitionToColSurfaceMap.at(partitionIdx)).materialIndex; bool foundIdx = false; for (auto& uniqueMat : uniqueMaterials) { @@ -382,6 +576,7 @@ namespace BSP for (auto& matData : uniqueMaterials) *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; } + */ constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; constexpr vec3_t normalY = {0.0f, 1.0f, 0.0f}; @@ -432,7 +627,10 @@ namespace BSP size_t parentCount = 0; size_t parentStartIndex = 0; int treeContents = 0; - addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex, &treeContents); + std::vector partitions; + for (size_t objIdx = 0; objIdx < tree->leaf->getObjectCount(); objIdx++) + partitions.emplace_back(tree->leaf->getObject(objIdx)->partitionIndex); + addAABBTreeFromPartitions(clipMap, partitions, &parentCount, &parentStartIndex, &treeContents); leaf.collAabbCount = static_cast(parentCount); leaf.firstCollAabbIndex = static_cast(parentStartIndex); leaf.terrainContents = treeContents; @@ -514,15 +712,20 @@ namespace BSP } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - for (int partitionIdx = 0; partitionIdx < clipMap->partitionCount; partitionIdx++) + for (size_t surfIdx = 0; surfIdx < bsp->colWorld.staticSurfaceCount; surfIdx++) // only add the surfaces the player will collide with { - vec3_t partitionMins; - vec3_t partitionMaxs; - CollisionPartition* partition = &clipMap->partitions[partitionIdx]; - calculatePartitionAABB(clipMap, partition, partitionMins, partitionMaxs); - std::shared_ptr currObject = - std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); - tree->addObjectToTree(std::move(currObject)); + ColSurface& colSurface = collisionSurfaceVec.at(surfIdx); + for (size_t partitionIdx = 0; partitionIdx < colSurface.partitionCount; partitionIdx++) + { + vec3_t partitionMins; + vec3_t partitionMaxs; + size_t clipMapPartitionIdx = colSurface.partitionStartIndex + partitionIdx; + CollisionPartition* partition = &clipMap->partitions[clipMapPartitionIdx]; + calculatePartitionAABB(clipMap, partition, partitionMins, partitionMaxs); + std::shared_ptr currObject = std::make_shared( + partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, clipMapPartitionIdx); + tree->addObjectToTree(std::move(currObject)); + } } // load planes, nodes, leafs, and AABB trees @@ -540,15 +743,11 @@ namespace BSP clipMap->leafs = m_memory.Alloc(leafVec.size()); memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); - clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); - clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); - memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); - // The plane of each node have the same index for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; - con::info("Highest leaf object count: {}", highestLeafObjectCount); + con::info("Highest AABB tree partition count: {}", highestPartitionCountForAABB); return true; } @@ -568,38 +767,36 @@ namespace BSP for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) clipMap->verts[vertIdx] = bsp->colWorld.vertices[vertIdx].pos; - // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's - // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. std::vector triIndexVec; + std::vector partitionVec; + std::vector uniqueIndicesVec; for (BSPSurface& surface : bsp->colWorld.surfaces) { int indexOfFirstIndex = surface.indexOfFirstIndex; + int indexOfFirstTri = surface.indexOfFirstIndex / 3; int indexOfFirstVertex = surface.indexOfFirstVertex; - for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) - { - int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; - triIndexVec.emplace_back(triIndex); - } - } - // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion - clipMap->triCount = static_cast(triIndexVec.size() / 3); - clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); - memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. - std::vector partitionVec; - std::vector uniqueIndicesVec; - for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) - { - BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; + ColSurface colSurface; + colSurface.materialIndex = surface.materialIndex; + colSurface.partitionCount = surface.triCount; + colSurface.partitionStartIndex = partitionVec.size(); + collisionSurfaceVec.emplace_back(colSurface); - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower - int indexOfFirstTri = surface.indexOfFirstIndex / 3; - int indexOfFirstVertex = surface.indexOfFirstVertex; for (int triIdx = 0; triIdx < surface.triCount; triIdx++) { + // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's + // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. + int triIndex0 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 0] + indexOfFirstVertex; + int triIndex1 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 1] + indexOfFirstVertex; + int triIndex2 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 2] + indexOfFirstVertex; + triIndexVec.emplace_back(triIndex0); + triIndexVec.emplace_back(triIndex1); + triIndexVec.emplace_back(triIndex2); + + // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower CollisionPartition partition; partition.triCount = 1; partition.firstTri = indexOfFirstTri + triIdx; @@ -608,16 +805,20 @@ namespace BSP partition.fuind = static_cast(uniqueIndicesVec.size()); // All tri indices are unique since there is only one tri per partition - uint16_t* tri = clipMap->triIndices[partition.firstTri]; - uniqueIndicesVec.emplace_back(tri[0]); - uniqueIndicesVec.emplace_back(tri[1]); - uniqueIndicesVec.emplace_back(tri[2]); + int indexOfTriIndex = partition.firstTri * 3; + uniqueIndicesVec.emplace_back(triIndex0); + uniqueIndicesVec.emplace_back(triIndex1); + uniqueIndicesVec.emplace_back(triIndex2); partitionVec.emplace_back(partition); - - partitionToMaterialMap.emplace_back(surface.materialIndex); + partitionToColSurfaceMap.emplace_back(collisionSurfaceVec.size() - 1); // -1 as the colSurface has already been added } } + + clipMap->triCount = static_cast(triIndexVec.size() / 3); + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + clipMap->partitionCount = static_cast(partitionVec.size()); clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); @@ -628,6 +829,64 @@ namespace BSP return true; + //// The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's + //// index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. + // std::vector triIndexVec; + // for (BSPSurface& surface : bsp->colWorld.surfaces) + //{ + // int indexOfFirstIndex = surface.indexOfFirstIndex; + // int indexOfFirstVertex = surface.indexOfFirstVertex; + // for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) + // { + // int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; + // triIndexVec.emplace_back(triIndex); + // } + // } + //// the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion + // clipMap->triCount = static_cast(triIndexVec.size() / 3); + // clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + // memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + // + //// partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. + // std::vector partitionVec; + // std::vector uniqueIndicesVec; + // for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) + //{ + // BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; + // + // // partitions are made for each triangle, not one for each surface. + // // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + // int indexOfFirstTri = surface.indexOfFirstIndex / 3; + // int indexOfFirstVertex = surface.indexOfFirstVertex; + // for (int triIdx = 0; triIdx < surface.triCount; triIdx++) + // { + // CollisionPartition partition; + // partition.triCount = 1; + // partition.firstTri = indexOfFirstTri + triIdx; + // + // partition.nuinds = 3; + // partition.fuind = static_cast(uniqueIndicesVec.size()); + // + // // All tri indices are unique since there is only one tri per partition + // uint16_t* tri = clipMap->triIndices[partition.firstTri]; + // uniqueIndicesVec.emplace_back(tri[0]); + // uniqueIndicesVec.emplace_back(tri[1]); + // uniqueIndicesVec.emplace_back(tri[2]); + // + // partitionVec.emplace_back(partition); + // + // partitionToMaterialMap.emplace_back(surface.materialIndex); + // } + // } + // clipMap->partitionCount = static_cast(partitionVec.size()); + // clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); + // memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); + // + // clipMap->info.nuinds = static_cast(uniqueIndicesVec.size()); + // clipMap->info.uinds = m_memory.Alloc(uniqueIndicesVec.size()); + // memcpy(clipMap->info.uinds, uniqueIndicesVec.data(), sizeof(uint16_t) * uniqueIndicesVec.size()); + /* // Proper unique index creation code kept for future use int totalUindCount = 0; @@ -759,6 +1018,11 @@ namespace BSP clipMap->triEdgeIsWalkable = m_memory.Alloc(walkableEdgeSize); memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + // multiple functions add to AABBTreeVec, so it is added to the clipmap last + clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); + clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); + memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); + return clipMap; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 5c8d48421..744988868 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -8,6 +8,13 @@ namespace BSP { + struct ColSurface + { + size_t materialIndex; + size_t partitionCount; + size_t partitionStartIndex; + }; + class ClipMapLinker { public: @@ -30,9 +37,13 @@ namespace BSP std::vector nodeVec; std::vector leafVec; std::vector AABBTreeVec; - size_t highestLeafObjectCount = 0; - std::vector partitionToMaterialMap; - void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); + size_t highestPartitionCountForAABB = 0; + + std::vector collisionSurfaceVec; + std::vector partitionToColSurfaceMap; + + void addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree, bool isRoot); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 9bec483e3..c52de71e1 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -2,8 +2,7 @@ #include "../BSPUtil.h" -#define _USE_MATH_DEFINES -#include +#include namespace BSP { @@ -184,9 +183,9 @@ namespace BSP light->type = GFX_LIGHT_TYPE_OMNI; light->defName = "white_light_cube"; light->roundness = 0.0f; - light->cosHalfFovInner = cosf(30.0f * (M_PI / 180.0f)); - light->cosHalfFovOuter = cosf(55.0f * (M_PI / 180.0f)); - light->cosHalfFovExpanded = cosf(55.0f * (M_PI / 180.0f)); + light->cosHalfFovInner = cosf(30.0f * (std::numbers::pi_v / 180.0f)); + light->cosHalfFovOuter = cosf(55.0f * (std::numbers::pi_v / 180.0f)); + light->cosHalfFovExpanded = cosf(55.0f * (std::numbers::pi_v / 180.0f)); } setLightCommonValues(light, bspLight); } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 4139885e4..03e10c028 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -541,11 +541,10 @@ namespace BSP } } - void GfxWorldLinker::loadModels(GfxWorld* gfxWorld) + void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld) { // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) - // Right now there is only one submodel, the world sub model - gfxWorld->modelCount = 1; + gfxWorld->modelCount = static_cast(bsp->models.size() + 1); gfxWorld->models = m_memory.Alloc(gfxWorld->modelCount); // first model is always the world model @@ -559,23 +558,40 @@ namespace BSP gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - // Other models aren't implemented yet - // Code kept for future use - // for (size_t i = 0; i < entityModelList.size(); i++) - //{ - // auto currEntModel = &gfxWorld->models[i + 1]; - // entModelBounds currEntModelBounds = entityModelList[i]; - // - // currEntModel->startSurfIndex = 0; - // currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces - // currEntModel->bounds[0].x = currEntModelBounds.mins.x; - // currEntModel->bounds[0].y = currEntModelBounds.mins.y; - // currEntModel->bounds[0].z = currEntModelBounds.mins.z; - // currEntModel->bounds[1].x = currEntModelBounds.maxs.x; - // currEntModel->bounds[1].y = currEntModelBounds.maxs.y; - // currEntModel->bounds[1].z = currEntModelBounds.maxs.z; - // memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - //} + for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) + { + auto currEntModel = &gfxWorld->models[modelIdx + 1]; + auto& bspMpdel = bsp->models.at(modelIdx); + + if (bspMpdel.isGfxModel) + { + currEntModel->startSurfIndex = bspMpdel.surfaceIndex; + currEntModel->surfaceCount = bspMpdel.surfaceCount; + for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + { + GfxSurface* surf = &gfxWorld->dpvs.surfaces[bspMpdel.surfaceIndex + surfIdx]; + if (surfIdx == 0) + { + currEntModel->bounds[0] = surf->bounds[0]; + currEntModel->bounds[1] = surf->bounds[1]; + } + else + BSPUtil::updateAABB(surf->bounds[0], surf->bounds[1], currEntModel->bounds[0], currEntModel->bounds[1]); + } + } + else + { + currEntModel->startSurfIndex = 0; + currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->bounds[0].x = 0.0f; + currEntModel->bounds[0].y = 0.0f; + currEntModel->bounds[0].z = 0.0f; + currEntModel->bounds[1].x = 0.0f; + currEntModel->bounds[1].y = 0.0f; + currEntModel->bounds[1].z = 0.0f; + } + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + } } void GfxWorldLinker::loadSunData(GfxWorld* gfxWorld) @@ -687,7 +703,8 @@ namespace BSP void GfxWorldLinker::loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) { - std::string skyBoxName = "skybox_" + projInfo->name; + // std::string skyBoxName = "skybox_" + projInfo->name; + std::string skyBoxName = "skybox_zm_transit"; gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); if (m_context.LoadDependency(skyBoxName) == nullptr) @@ -806,7 +823,7 @@ namespace BSP loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces - loadModels(gfxWorld); + loadModels(bsp, gfxWorld); // requires surfaces loadSunData(gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 6baf4f2ed..e594cd019 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -25,7 +25,7 @@ namespace BSP void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); void loadLightGrid(GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld); - void loadModels(GfxWorld* gfxWorld); + void loadModels(BSPData* bsp, GfxWorld* gfxWorld); bool loadReflectionProbeData(GfxWorld* gfxWorld); bool loadLightmapData(GfxWorld* gfxWorld); void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 062f2fb9e..3b90c6f9a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -67,7 +67,7 @@ namespace void addSpawnsToEntString(BSP::BSPData* bsp, std::string& entityString) { - if (bsp->spawnpoints.size() == 0) + if (!bsp->isZombiesMap && bsp->spawnpoints.size() == 0) { con::info("No spawnpoints found, setting all spawns to (0, 0, 0)"); BSP::BSPSpawnPoint defaultSpawnPoint; @@ -127,6 +127,52 @@ namespace } } + void addZombiesEntitiesToEntString(BSP::BSPData* bsp, std::string& entityString) + { + for (auto& zone : bsp->zZones) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"info_volume\"\n"); + entityString.append("\"script_noteworthy\" \"player_volume\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", zone.zoneName)); + entityString.append(std::format("\"target\" \"{}\"\n", zone.zSpawnerGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zone.origin))); + entityString.append(std::format("\"model\" \"*{}\"\n", zone.modelIndex)); + entityString.append("}\n"); + + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append(std::format("\"targetname\" \"player_respawn_point\"\n")); + entityString.append(std::format("\"script_noteworthy\" \"{}\"\n", zone.zoneName)); + entityString.append(std::format("\"target\" \"{}\"\n", zone.spawnpointGroupName)); + entityString.append("}\n"); + } + + for (auto& zSpawner : bsp->zSpawners) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append("\"script_noteworthy\" \"riser_location\"\n"); + entityString.append("\"script_string\" \"find_flesh\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", zSpawner.zSpawnerGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zSpawner.origin))); + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(zSpawner.forward); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + entityString.append("}\n"); + } + + for (auto& spawnPoint : bsp->zSpawnPoints) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", spawnPoint.spawnpointGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(spawnPoint.origin))); + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + entityString.append("}\n"); + } + } + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ "entities": [ { @@ -180,6 +226,9 @@ namespace BSP addPathNodesToEntString(bsp, entityString); + if (bsp->isZombiesMap) + addZombiesEntitiesToEntString(bsp, entityString); + MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); From 30b14b6621a1026bd4d115da1daca44a376e83c8 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:48:12 +0800 Subject: [PATCH 090/123] Zombies GLTF --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index e202b3a70..70c142faa 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -89,14 +89,23 @@ namespace gltf class JsonNodeExtras { public: - std::optional xmodel; - std::optional spawnpoint; - std::optional pathnode; + // global + std::optional flags; // value: surface flags + std::optional pathnode; // value: n/a - std::optional flags; + // Multiplayer only + std::optional xmodel; // value: xmodel name + std::optional spawnpoint; // value: (MP)team, (ZM)spawnpoint group name + + // Zombies only + std::optional zone; // value: zone name + std::optional zspawner_group; // value: zone zspawner group name + std::optional spawnpoint_group; // value: zone zspawner group name + + std::optional zspawner; // value: zspawner group name }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags, pathnode); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags, pathnode, zone, zspawner_group, spawnpoint_group, zspawner); class JsonNode { From 1660e9264c0954ae42664926cc7d71f23de4fd20 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:48:44 +0800 Subject: [PATCH 091/123] chore: various minor additions --- src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp | 3 ++- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index d957d0f80..43390e000 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -758,7 +758,8 @@ namespace BSP // any vertex count over the uint16_t max means the vertices above the uint16_t max can't be indexed if (static_cast(bsp->colWorld.vertices.size()) > BSPGameConstants::MAX_COLLISION_VERTS) { - con::error("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", clipMap->vertCount, BSPGameConstants::MAX_COLLISION_VERTS); + con::error( + "ERROR: collision vertex count {} exceeds the maximum number: {}!\n", bsp->colWorld.vertices.size(), BSPGameConstants::MAX_COLLISION_VERTS); return false; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 03e10c028..51b00f782 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -1,5 +1,6 @@ #include "GfxWorldLinker.h" +#include "../BSPCalculation.h" #include "../BSPUtil.h" #include "Utils/Pack.h" @@ -463,6 +464,12 @@ namespace BSP gfxWorld->lightGrid.skyGridVolumes = nullptr; } + struct mnode_t + { + unsigned __int16 cellIndex; + unsigned __int16 rightChildOffset; + }; + void GfxWorldLinker::loadGfxCells(GfxWorld* gfxWorld) { // Cells are basically data used to determine what can be seen and what cant be seen @@ -518,6 +525,7 @@ namespace BSP // Nodes mnode_t.cellIndex indexes gfxWorld->cells // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes // Use only one node as there is no optimisation in custom maps + gfxWorld->nodeCount = 1; gfxWorld->dpvsPlanes.nodes = m_memory.Alloc(gfxWorld->nodeCount); gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 @@ -703,8 +711,7 @@ namespace BSP void GfxWorldLinker::loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) { - // std::string skyBoxName = "skybox_" + projInfo->name; - std::string skyBoxName = "skybox_zm_transit"; + std::string skyBoxName = "skybox_" + projInfo->name; gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); if (m_context.LoadDependency(skyBoxName) == nullptr) From 8cd7ef35dc017527942e0ff090f0188254804624 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:20:43 +0800 Subject: [PATCH 092/123] Minor additions to BSP Creator --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index a5365d5ce..d8129f427 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -391,7 +391,7 @@ namespace } } if (!foundMatchingName) - con::warn("invalid surface using material {} flag name: {}", newMaterial.materialName, flag); + con::warn("material {} has invalid flag name: {}", newMaterial.materialName, flag); } if (!matchedAnyFlag) return originalMaterialIdx; @@ -909,7 +909,7 @@ namespace } } if (!foundType) - con::warn("invalid material {} type name: {}", material.materialName, *jsMaterial.extras->type); + con::warn("material {} with invalid type name: {}", material.materialName, *jsMaterial.extras->type); } m_curr_bsp_world->materials.emplace_back(material); From e97917bd0a627c414d6e57ce36a2eb8ec2251033 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:05:43 +0800 Subject: [PATCH 093/123] rename castSunShadow flag back to onlyCastShadow --- src/ObjLoading/Game/T6/BSP/BSPFlags.h | 87 +++++++++++++++++++++------ 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPFlags.h b/src/ObjLoading/Game/T6/BSP/BSPFlags.h index 0a5b8e417..48116a0eb 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPFlags.h +++ b/src/ObjLoading/Game/T6/BSP/BSPFlags.h @@ -52,7 +52,7 @@ namespace BSPFlags SURF_TYPE_ORIGIN, SURF_TYPE_SKY, SURF_TYPE_NOCASTSHADOW, - SURF_TYPE_CASTSUNSHADOW, + SURF_TYPE_ONLYCASTSHADOW, SURF_TYPE_PHYSICSGEOM, SURF_TYPE_LIGHTPORTAL, SURF_TYPE_CAULK, @@ -129,7 +129,7 @@ namespace BSPFlags {0, 0 }, // origin {4, 0x800 }, // sky {0x40000, 0 }, // noCastShadow - {0x80000, 0 }, // castSunShadow + {0x80000, 0 }, // onlyCastShadow {0, 0 }, // physicsGeom {0, 0 }, // lightPortal {0x1000, 0 }, // caulk @@ -175,7 +175,7 @@ namespace BSPFlags SURF_TYPE_CANSHOOTCLIP, SURF_TYPE_SKY, SURF_TYPE_NOCASTSHADOW, - SURF_TYPE_CASTSUNSHADOW, + SURF_TYPE_ONLYCASTSHADOW, SURF_TYPE_CAULK, SURF_TYPE_AREALIGHT, SURF_TYPE_SLICK, @@ -193,18 +193,71 @@ namespace BSPFlags SURF_TYPE_NODLIGHT, }; - constexpr const char* surfaceTypeToNameMap[SURF_TYPE_COUNT] = {"bark", "brick", "carpet", "cloth", "concrete", - "dirt", "flesh", "foliage", "glass", "grass", - "gravel", "ice", "metal", "mud", "paper", - "plaster", "rock", "sand", "snow", "water", - "wood", "asphalt", "ceramic", "plastic", "rubber", - "cushion", "fruit", "paintedmetal", "player", "tallgrass", - "riotshield", "opaqueglass", "clipmissile", "ai_nosight", "clipshot", - "playerclip", "monsterclip", "vehicleclip", "itemclip", "nodrop", - "nonsolid", "detail", "structural", "portal", "canshootclip", - "origin", "sky", "nocastshadow", "castshadow", "physicsgeom", - "lightportal", "caulk", "arealight", "slick", "noimpact", - "nomarks", "nopenetrate", "ladder", "nodamage", "mantleon", - "mantleover", "mount", "nosteps", "nodraw", "noreceivedynamicshadow", - "nodlight", "none"}; + constexpr const char* surfaceTypeToNameMap[SURF_TYPE_COUNT] = {"bark", + "brick", + "carpet", + "cloth", + "concrete", + "dirt", + "flesh", + "foliage", + "glass", + "grass", + "gravel", + "ice", + "metal", + "mud", + "paper", + "plaster", + "rock", + "sand", + "snow", + "water", + "wood", + "asphalt", + "ceramic", + "plastic", + "rubber", + "cushion", + "fruit", + "paintedmetal", + "player", + "tallgrass", + "riotshield", + "opaqueglass", + "clipmissile", + "ai_nosight", + "clipshot", + "playerclip", + "monsterclip", + "vehicleclip", + "itemclip", + "nodrop", + "nonsolid", + "detail", + "structural", + "portal", + "canshootclip", + "origin", + "sky", + "nocastshadow", + "onlyCastShadow", + "physicsgeom", + "lightportal", + "caulk", + "arealight", + "slick", + "noimpact", + "nomarks", + "nopenetrate", + "ladder", + "nodamage", + "mantleon", + "mantleover", + "mount", + "nosteps", + "nodraw", + "noreceivedynamicshadow", + "nodlight", + "none"}; } // namespace BSPFlags From 4eb56f61dd378552c7729dd40f706570b14b7e7b Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:16:52 +0800 Subject: [PATCH 094/123] chore: Remove unused dependency ufbx --- .gitmodules | 3 --- premake5.lua | 2 -- src/ObjLoading.lua | 2 -- thirdparty/ufbx | 1 - thirdparty/ufbx.lua | 42 ------------------------------------------ 5 files changed, 50 deletions(-) delete mode 160000 thirdparty/ufbx delete mode 100644 thirdparty/ufbx.lua diff --git a/.gitmodules b/.gitmodules index 58bcab4af..ffaaa9753 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ [submodule "thirdparty/webview"] path = thirdparty/webview url = https://github.com/Laupetin/webview.git -[submodule "thirdparty/ufbx"] - path = thirdparty/ufbx - url = https://github.com/ufbx/ufbx diff --git a/premake5.lua b/premake5.lua index c196a771b..e1c1575a9 100644 --- a/premake5.lua +++ b/premake5.lua @@ -103,7 +103,6 @@ include "thirdparty/minizip.lua" include "thirdparty/salsa20.lua" include "thirdparty/webview.lua" include "thirdparty/zlib.lua" -include "thirdparty/ufbx.lua" -- ThirdParty group: All projects that are external dependencies group "ThirdParty" @@ -118,7 +117,6 @@ group "ThirdParty" minizip:project() salsa20:project() zlib:project() - ufbx:project() if _OPTIONS["modman"] then webview:project() diff --git a/src/ObjLoading.lua b/src/ObjLoading.lua index 8db8f6c55..d9994a1f0 100644 --- a/src/ObjLoading.lua +++ b/src/ObjLoading.lua @@ -21,7 +21,6 @@ function ObjLoading:link(links) links:linkto(minizip) links:linkto(zlib) links:linkto(libtomcrypt) - links:linkto(ufbx) end function ObjLoading:use() @@ -65,5 +64,4 @@ function ObjLoading:project() eigen:include(includes) json:include(includes) libtomcrypt:include(includes) - ufbx:include(includes) end \ No newline at end of file diff --git a/thirdparty/ufbx b/thirdparty/ufbx deleted file mode 160000 index 13b5df8e8..000000000 --- a/thirdparty/ufbx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 13b5df8e89eff0ce898f7c37fa0716e3bc186573 diff --git a/thirdparty/ufbx.lua b/thirdparty/ufbx.lua deleted file mode 100644 index 992cbda25..000000000 --- a/thirdparty/ufbx.lua +++ /dev/null @@ -1,42 +0,0 @@ -ufbx = {} - -function ufbx:include(includes) - if includes:handle(self:name()) then - includedirs { - path.join(ThirdPartyFolder(), "ufbx") - } - end -end - -function ufbx:link(links) - links:add(self:name()) -end - -function ufbx:use() - -end - -function ufbx:name() - return "ufbx" -end - -function ufbx:project() - local folder = ThirdPartyFolder() - local includes = Includes:create() - - project(self:name()) - targetdir(TargetDirectoryLib) - location "%{wks.location}/thirdparty/%{prj.name}" - kind "StaticLib" - language "C++" - - files { - path.join(folder, "ufbx/*.h"), - path.join(folder, "ufbx/*.c") - } - - self:include(includes) - - -- Disable warnings. They do not have any value to us since it is not our code. - warnings "off" -end From 6591a6218e1d5a4c05c8156d8adcef04b791a109 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:42:51 +0800 Subject: [PATCH 095/123] fix: incorrect flag name --- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 51b00f782..83320f527 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -110,7 +110,7 @@ namespace BSP gfxSurface->flags |= GFX_SURFACE_IS_SKY; if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].contentFlags) != 0) gfxSurface->flags |= GFX_SURFACE_NO_DRAW; - if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_CASTSUNSHADOW].surfaceFlags) != 0) + if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_ONLYCASTSHADOW].surfaceFlags) != 0) gfxSurface->flags |= GFX_SURFACE_CASTS_SUN_SHADOW; if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0) gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW; From a8d7c42d6b18d16d445b7ec364169c484762053f Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:07:43 +0800 Subject: [PATCH 096/123] refactor: better implementation of static and script surfaces --- src/ObjLoading/Game/T6/BSP/BSP.h | 8 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 13 +-- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 18 +++- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 92 +++++++++++-------- 4 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 95e94ebc4..670df6a7a 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -64,14 +64,14 @@ namespace BSP struct BSPWorld { - std::vector surfaces; + + std::vector staticSurfaces; + std::vector scriptSurfaces; + std::vector vertices; std::vector indices; std::vector materials; std::vector xmodels; - - size_t staticSurfaceCount; - std::vector internal_scriptSurfaces; }; enum BSPLightType diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index d8129f427..e14bcb902 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -461,7 +461,7 @@ namespace vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); - m_curr_bsp_world->surfaces.emplace_back(surface); + m_curr_bsp_world->staticSurfaces.emplace_back(surface); } return true; @@ -678,7 +678,7 @@ namespace BSPModel model; model.isGfxModel = m_is_world_gfx; - model.surfaceIndex = m_curr_bsp_world->internal_scriptSurfaces.size(); + model.surfaceIndex = m_curr_bsp_world->scriptSurfaces.size(); model.surfaceCount = mesh.primitives.size(); m_bsp->models.emplace_back(model); @@ -709,7 +709,7 @@ namespace vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); - m_curr_bsp_world->internal_scriptSurfaces.emplace_back(surface); + m_curr_bsp_world->scriptSurfaces.emplace_back(surface); } return m_bsp->models.size(); // script model index starts at 1 @@ -1154,13 +1154,6 @@ namespace LoadMaterials(jRoot); TraverseNodes(jRoot); // requires materials and lights - - size_t staticSurfaceCount = m_curr_bsp_world->surfaces.size(); - for (auto& model : m_bsp->models) - model.surfaceIndex += staticSurfaceCount; - m_curr_bsp_world->staticSurfaceCount = staticSurfaceCount; - m_curr_bsp_world->surfaces.insert( - m_curr_bsp_world->surfaces.end(), m_curr_bsp_world->internal_scriptSurfaces.begin(), m_curr_bsp_world->internal_scriptSurfaces.end()); } catch (const GltfLoadException& e) { diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 43390e000..ff348e19e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -270,7 +270,7 @@ namespace BSP std::vector partitionIndexes; for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) { - ColSurface& surf = collisionSurfaceVec.at(bspMpdel.surfaceIndex + surfIdx); + ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspMpdel.surfaceIndex + surfIdx); for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) { size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; @@ -712,7 +712,7 @@ namespace BSP } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - for (size_t surfIdx = 0; surfIdx < bsp->colWorld.staticSurfaceCount; surfIdx++) // only add the surfaces the player will collide with + for (size_t surfIdx = 0; surfIdx < bsp->colWorld.staticSurfaces.size(); surfIdx++) // only add static surfaces { ColSurface& colSurface = collisionSurfaceVec.at(surfIdx); for (size_t partitionIdx = 0; partitionIdx < colSurface.partitionCount; partitionIdx++) @@ -768,11 +768,23 @@ namespace BSP for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) clipMap->verts[vertIdx] = bsp->colWorld.vertices[vertIdx].pos; + size_t staticSurfaceCount = bsp->colWorld.staticSurfaces.size(); + size_t scriptSurfaceCount = bsp->colWorld.scriptSurfaces.size(); + size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; + + // TODO: figure out SV_EntityContact and see how it interacts with partitions + std::vector triIndexVec; std::vector partitionVec; std::vector uniqueIndicesVec; - for (BSPSurface& surface : bsp->colWorld.surfaces) + for (size_t surfIdx = 0; surfIdx < totalSurfaceCount; surfIdx++) { + BSPSurface surface; + if (surfIdx < staticSurfaceCount) + surface = bsp->colWorld.staticSurfaces.at(surfIdx); + else + surface = bsp->colWorld.scriptSurfaces.at(surfIdx - staticSurfaceCount); + int indexOfFirstIndex = surface.indexOfFirstIndex; int indexOfFirstTri = surface.indexOfFirstIndex / 3; int indexOfFirstVertex = surface.indexOfFirstVertex; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 83320f527..43ff731f2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -53,13 +53,21 @@ namespace BSP { loadDrawData(bsp, gfxWorld); - size_t surfaceCount = bsp->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = static_cast(surfaceCount); - gfxWorld->dpvs.staticSurfaceCount = static_cast(surfaceCount); - gfxWorld->dpvs.surfaces = m_memory.Alloc(surfaceCount); - for (size_t surfIdx = 0; surfIdx < surfaceCount; surfIdx++) + size_t staticSurfaceCount = bsp->gfxWorld.staticSurfaces.size(); + size_t scriptSurfaceCount = bsp->gfxWorld.scriptSurfaces.size(); + size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; + + // Static surfaces go first in the array then script surfaces after + gfxWorld->surfaceCount = static_cast(totalSurfaceCount); + gfxWorld->dpvs.staticSurfaceCount = static_cast(staticSurfaceCount); + gfxWorld->dpvs.surfaces = m_memory.Alloc(totalSurfaceCount); + for (size_t surfIdx = 0; surfIdx < totalSurfaceCount; surfIdx++) { - BSPSurface& bspSurface = bsp->gfxWorld.surfaces.at(surfIdx); + BSPSurface bspSurface; + if (surfIdx < staticSurfaceCount) + bspSurface = bsp->gfxWorld.staticSurfaces.at(surfIdx); + else + bspSurface = bsp->gfxWorld.scriptSurfaces.at(surfIdx - staticSurfaceCount); GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx]; gfxSurface->tris.triCount = bspSurface.triCount; @@ -124,26 +132,26 @@ namespace BSP } // Some code uses Sorted surfs to index surfaces, so for simplicity keep the indexes sequential and from 0 - gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(surfaceCount); - for (size_t surfIdx = 0; surfIdx < surfaceCount; surfIdx++) + gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(staticSurfaceCount); + for (size_t surfIdx = 0; surfIdx < staticSurfaceCount; surfIdx++) gfxWorld->dpvs.sortedSurfIndex[surfIdx] = static_cast(surfIdx); // surface materials are written to by the game - gfxWorld->dpvs.surfaceMaterials = m_memory.Alloc(surfaceCount); + gfxWorld->dpvs.surfaceMaterials = m_memory.Alloc(staticSurfaceCount); // set all surface types to lit opaque gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(surfaceCount); - gfxWorld->dpvs.litTransSurfsBegin = static_cast(surfaceCount); - gfxWorld->dpvs.litTransSurfsEnd = static_cast(surfaceCount); + gfxWorld->dpvs.litSurfsEnd = static_cast(staticSurfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = static_cast(staticSurfaceCount); + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = static_cast(staticSurfaceCount); + gfxWorld->dpvs.emissiveTransSurfsBegin = static_cast(staticSurfaceCount); + gfxWorld->dpvs.emissiveTransSurfsEnd = static_cast(staticSurfaceCount); + gfxWorld->dpvs.litTransSurfsBegin = static_cast(staticSurfaceCount); + gfxWorld->dpvs.litTransSurfsEnd = static_cast(staticSurfaceCount); // visdata is written to by the game // all visdata is alligned by 128 - size_t allignedSurfaceCount = BSPUtil::allignBy128(surfaceCount); + size_t allignedSurfaceCount = BSPUtil::allignBy128(staticSurfaceCount); gfxWorld->dpvs.surfaceVisDataCount = static_cast(allignedSurfaceCount); gfxWorld->dpvs.surfaceVisData[0] = m_memory.Alloc(allignedSurfaceCount); gfxWorld->dpvs.surfaceVisData[1] = m_memory.Alloc(allignedSurfaceCount); @@ -507,7 +515,7 @@ namespace BSP gfxWorld->cells[0].aabbTree[0].childCount = 0; gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = static_cast(gfxWorld->surfaceCount); + gfxWorld->cells[0].aabbTree[0].surfaceCount = static_cast(gfxWorld->dpvs.staticSurfaceCount); gfxWorld->cells[0].aabbTree[0].smodelIndexCount = static_cast(gfxWorld->dpvs.smodelCount); gfxWorld->cells[0].aabbTree[0].smodelIndexes = m_memory.Alloc(gfxWorld->dpvs.smodelCount); for (unsigned short smodelIdx = 0; smodelIdx < gfxWorld->dpvs.smodelCount; smodelIdx++) @@ -537,16 +545,17 @@ namespace BSP void GfxWorldLinker::loadWorldBounds(GfxWorld* gfxWorld) { - gfxWorld->mins.x = gfxWorld->dpvs.surfaces[0].bounds[0].x; - gfxWorld->mins.y = gfxWorld->dpvs.surfaces[0].bounds[0].y; - gfxWorld->mins.z = gfxWorld->dpvs.surfaces[0].bounds[0].z; - gfxWorld->maxs.x = gfxWorld->dpvs.surfaces[0].bounds[1].x; - gfxWorld->maxs.y = gfxWorld->dpvs.surfaces[0].bounds[1].y; - gfxWorld->maxs.z = gfxWorld->dpvs.surfaces[0].bounds[1].z; + gfxWorld->mins = gfxWorld->dpvs.surfaces[0].bounds[0]; + gfxWorld->maxs = gfxWorld->dpvs.surfaces[0].bounds[1]; for (int surfIdx = 0; surfIdx < gfxWorld->surfaceCount; surfIdx++) { BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[surfIdx].bounds[0], gfxWorld->dpvs.surfaces[surfIdx].bounds[1], gfxWorld->mins, gfxWorld->maxs); } + + for (unsigned int smodeldx = 0; smodeldx < gfxWorld->dpvs.smodelCount; smodeldx++) + { + BSPUtil::updateAABB(gfxWorld->dpvs.smodelInsts[smodeldx].mins, gfxWorld->dpvs.smodelInsts[smodeldx].maxs, gfxWorld->mins, gfxWorld->maxs); + } } void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld) @@ -557,27 +566,34 @@ namespace BSP // first model is always the world model gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = static_cast(gfxWorld->surfaceCount); - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + gfxWorld->models[0].surfaceCount = static_cast(gfxWorld->dpvs.staticSurfaceCount); + for (unsigned int surfIdx = 0; surfIdx < gfxWorld->dpvs.staticSurfaceCount; surfIdx++) + { + if (surfIdx == 0) + { + gfxWorld->models[0].bounds[0] = gfxWorld->dpvs.surfaces[surfIdx].bounds[0]; + gfxWorld->models[0].bounds[1] = gfxWorld->dpvs.surfaces[surfIdx].bounds[1]; + } + else + BSPUtil::updateAABB(gfxWorld->dpvs.surfaces[surfIdx].bounds[0], + gfxWorld->dpvs.surfaces[surfIdx].bounds[1], + gfxWorld->models[0].bounds[0], + gfxWorld->models[0].bounds[1]); + } memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) { auto currEntModel = &gfxWorld->models[modelIdx + 1]; - auto& bspMpdel = bsp->models.at(modelIdx); + auto& bspModel = bsp->models.at(modelIdx); - if (bspMpdel.isGfxModel) + if (bspModel.isGfxModel) { - currEntModel->startSurfIndex = bspMpdel.surfaceIndex; - currEntModel->surfaceCount = bspMpdel.surfaceCount; - for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + currEntModel->startSurfIndex = bsp->gfxWorld.staticSurfaces.size() + bspModel.surfaceIndex; + currEntModel->surfaceCount = bspModel.surfaceCount; + for (size_t surfIdx = 0; surfIdx < bspModel.surfaceCount; surfIdx++) { - GfxSurface* surf = &gfxWorld->dpvs.surfaces[bspMpdel.surfaceIndex + surfIdx]; + GfxSurface* surf = &gfxWorld->dpvs.surfaces[currEntModel->startSurfIndex + surfIdx]; if (surfIdx == 0) { currEntModel->bounds[0] = surf->bounds[0]; @@ -817,7 +833,7 @@ namespace BSP if (!loadReflectionProbeData(gfxWorld)) return nullptr; - // world bounds are based on loaded surface mins/maxs + // world bounds are based on loaded surface mins/maxs and xmodels loadWorldBounds(gfxWorld); if (!loadOutdoors(gfxWorld)) From e96f048bb4d8a78a09d70f645f94cede3179a900 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:42:22 +0800 Subject: [PATCH 097/123] chore: removed unused code comments --- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 222 ------------------ 1 file changed, 222 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index ff348e19e..7d2f3075e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -454,130 +454,6 @@ namespace BSP *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; } - /* - void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) - { - assert(tree->isLeaf); - BSPLeaf* bspLeaf = tree->leaf.get(); - - size_t leafObjectCount = bspLeaf->getObjectCount(); - assert(leafObjectCount > 0); - if (leafObjectCount > highestPartitionCountForAABB) - highestPartitionCountForAABB = leafObjectCount; - - // the material index of the AABB tree is only checked for the parent node, so each parent has only children with the same material - std::vector uniqueMaterials; - for (size_t objIdx = 0; objIdx < leafObjectCount; objIdx++) - { - int partitionIdx = bspLeaf->getObject(objIdx)->partitionIndex; - size_t materialIndex = collisionSurfaceVec.at(partitionToColSurfaceMap.at(partitionIdx)).materialIndex; - bool foundIdx = false; - for (auto& uniqueMat : uniqueMaterials) - { - if (uniqueMat.materialIndex == materialIndex) - { - uniqueMat.objectIndexes.emplace_back(objIdx); - foundIdx = true; - break; - } - } - if (!foundIdx) - { - uniqueMatData data; - data.materialIndex = materialIndex; - data.objectIndexes.emplace_back(objIdx); - uniqueMaterials.emplace_back(data); - } - } - - // BO2 has a maximum limit of 128 children per AABB tree (essentially), - // so this is fixed by adding multiple parent AABB trees that hold 128 children each - size_t totalParentCount = 0; - for (auto& matData : uniqueMaterials) - { - size_t objCount = matData.objectIndexes.size(); - size_t result = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; - if (remainder > 0) - result++; - totalParentCount += result; - } - - // every parent node needs to be contiguous in memory - size_t parentAABBArrayIndex = AABBTreeVec.size(); - AABBTreeVec.resize(AABBTreeVec.size() + totalParentCount); - *out_parentCount = totalParentCount; - *out_parentStartIndex = parentAABBArrayIndex; - - for (auto& matData : uniqueMaterials) - { - size_t* objIndexes = matData.objectIndexes.data(); - size_t objCount = matData.objectIndexes.size(); - size_t parentCount = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; - if (remainder > 0) - parentCount++; - - size_t unaddedObjectCount = objCount; - size_t addedObjectCount = 0; - for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) - { - size_t currChildObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; - if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) - currChildObjectCount = unaddedObjectCount; - else - unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; - - vec3_t parentMins; - vec3_t parentMaxs; - size_t childObjectStartIndex = AABBTreeVec.size(); - for (size_t objectIdx = 0; objectIdx < currChildObjectCount; objectIdx++) - { - // create a child AABBTree with the partition and add it to AABBTreeVec - int partitionIndex = bspLeaf->getObject(objIndexes[addedObjectCount + objectIdx])->partitionIndex; - CollisionPartition* partition = &clipMap->partitions[partitionIndex]; - vec3_t childMins; - vec3_t childMaxs; - calculatePartitionAABB(clipMap, partition, childMins, childMaxs); - - CollisionAabbTree childAABBTree; - childAABBTree.materialIndex = matData.materialIndex; - childAABBTree.childCount = 0; - childAABBTree.u.partitionIndex = partitionIndex; - childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); - childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); - AABBTreeVec.emplace_back(childAABBTree); - - // update the parent AABB with the child AABB - if (objectIdx == 0) - { - parentMins = childMins; - parentMaxs = childMaxs; - } - else - BSPUtil::updateAABB(childMins, childMaxs, parentMins, parentMaxs); - } - - CollisionAabbTree parentAABB; - parentAABB.materialIndex = matData.materialIndex; - parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); - parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); - parentAABB.childCount = static_cast(currChildObjectCount); - parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); - AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; - - addedObjectCount += currChildObjectCount; - } - - parentAABBArrayIndex += parentCount; - } - - *out_treeContents = 0; - for (auto& matData : uniqueMaterials) - *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; - } - */ - constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; constexpr vec3_t normalY = {0.0f, 1.0f, 0.0f}; constexpr vec3_t normalZ = {0.0f, 0.0f, 1.0f}; @@ -841,104 +717,6 @@ namespace BSP memcpy(clipMap->info.uinds, uniqueIndicesVec.data(), sizeof(uint16_t) * uniqueIndicesVec.size()); return true; - - //// The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's - //// index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. - // std::vector triIndexVec; - // for (BSPSurface& surface : bsp->colWorld.surfaces) - //{ - // int indexOfFirstIndex = surface.indexOfFirstIndex; - // int indexOfFirstVertex = surface.indexOfFirstVertex; - // for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) - // { - // int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; - // triIndexVec.emplace_back(triIndex); - // } - // } - //// the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion - // clipMap->triCount = static_cast(triIndexVec.size() / 3); - // clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); - // memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - // - //// partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. - // std::vector partitionVec; - // std::vector uniqueIndicesVec; - // for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) - //{ - // BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; - // - // // partitions are made for each triangle, not one for each surface. - // // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower - // int indexOfFirstTri = surface.indexOfFirstIndex / 3; - // int indexOfFirstVertex = surface.indexOfFirstVertex; - // for (int triIdx = 0; triIdx < surface.triCount; triIdx++) - // { - // CollisionPartition partition; - // partition.triCount = 1; - // partition.firstTri = indexOfFirstTri + triIdx; - // - // partition.nuinds = 3; - // partition.fuind = static_cast(uniqueIndicesVec.size()); - // - // // All tri indices are unique since there is only one tri per partition - // uint16_t* tri = clipMap->triIndices[partition.firstTri]; - // uniqueIndicesVec.emplace_back(tri[0]); - // uniqueIndicesVec.emplace_back(tri[1]); - // uniqueIndicesVec.emplace_back(tri[2]); - // - // partitionVec.emplace_back(partition); - // - // partitionToMaterialMap.emplace_back(surface.materialIndex); - // } - // } - // clipMap->partitionCount = static_cast(partitionVec.size()); - // clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); - // memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); - // - // clipMap->info.nuinds = static_cast(uniqueIndicesVec.size()); - // clipMap->info.uinds = m_memory.Alloc(uniqueIndicesVec.size()); - // memcpy(clipMap->info.uinds, uniqueIndicesVec.data(), sizeof(uint16_t) * uniqueIndicesVec.size()); - - /* - // Proper unique index creation code kept for future use - int totalUindCount = 0; - std::vector uindVec; - for (int i = 0; i < clipMap->partitionCount; i++) - { - CollisionPartition* currPartition = &clipMap->partitions[i]; - std::vector uniqueVertVec; - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (size_t m = 0; m < uniqueVertVec.size(); m++) - { - if (uniqueVertVec[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - uniqueVertVec.emplace_back(vertIndex); - } - } - - currPartition->fuind = totalUindCount; - currPartition->nuinds = (int)uniqueVertVec.size(); - uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); - totalUindCount += currPartition->nuinds; - } - clipMap->info.nuinds = totalUindCount; - clipMap->info.uinds = m_memory.Alloc(totalUindCount); - memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - */ } bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp) From 3f87af7d21c13e36c55cfe616a8dcaa6504fa86e Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:21:05 +0800 Subject: [PATCH 098/123] feat: zone volumes now use brushes instead of terrain to define their boundaries --- src/ObjLoading/Game/T6/BSP/BSP.h | 15 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 93 +++++++- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 210 +++++++++++------- .../Game/T6/BSP/Linker/ClipMapLinker.h | 3 + .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 2 +- 5 files changed, 243 insertions(+), 80 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 670df6a7a..6c1c66122 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -62,9 +62,17 @@ namespace BSP bool doesCastShadow; }; - struct BSPWorld + struct BSPBoxBrush { + vec3_t localMins; + vec3_t localMaxs; + + int surfaceFlags; + int contentFlags; + }; + struct BSPWorld + { std::vector staticSurfaces; std::vector scriptSurfaces; @@ -72,6 +80,8 @@ namespace BSP std::vector indices; std::vector materials; std::vector xmodels; + + std::vector scriptBoxBrushes; }; enum BSPLightType @@ -145,6 +155,9 @@ namespace BSP size_t surfaceIndex; size_t surfaceCount; + + bool hasBrush; + size_t brushIndex; }; struct BSPData diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e14bcb902..6fe760e8d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -665,7 +665,94 @@ namespace return true; } - size_t addScriptModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + size_t addScriptBrushModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + if (!node.mesh || !jRoot.meshes) + throw new GltfLoadException("Script model created with no mesh data"); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + + if (mesh.primitives.size() == 0) + throw new GltfLoadException("Script model created with no mesh data"); + + BSPModel model; + model.isGfxModel = m_is_world_gfx; + model.surfaceIndex = 0; + model.surfaceCount = 0; + model.hasBrush = true; + model.brushIndex = m_curr_bsp_world->scriptBoxBrushes.size(); + m_bsp->models.emplace_back(model); + + BSPBoxBrush boxBrush; + boxBrush.contentFlags = 1; + boxBrush.surfaceFlags = 1; + vec3_t worldMins; + vec3_t worldMaxs; + for (size_t primIdx = 0; primIdx < mesh.primitives.size(); primIdx++) + { + const auto& primitive = mesh.primitives.at(primIdx); + + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + + // clang-format off + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + primitive.attributes.POSITION, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); + // clang-format on + assert(positionAccessor != nullptr); + + if (positionAccessor->GetCount() == 0) + throw GltfLoadException("positionAccessor has count of 0"); + + for (size_t vertexIndex = 0u; vertexIndex < positionAccessor->GetCount(); vertexIndex++) + { + vec3_t vertex; + if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.v)) + assert(false); + + Eigen::Vector4f position(vertex.x, vertex.y, vertex.z, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vertex.x = transformedPosition.x(); + vertex.y = transformedPosition.y(); + vertex.z = transformedPosition.z(); + RhcToLhcCoordinates(vertex.v); + + if (vertexIndex == 0 && primIdx == 0) + { + worldMins = vertex; + worldMaxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); + } + } + + // convert world position to local position + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + boxBrush.localMins.x = worldMins.x - origin.x; + boxBrush.localMins.y = worldMins.y - origin.y; + boxBrush.localMins.z = worldMins.z - origin.z; + boxBrush.localMaxs.x = worldMaxs.x - origin.x; + boxBrush.localMaxs.y = worldMaxs.y - origin.y; + boxBrush.localMaxs.z = worldMaxs.z - origin.z; + + m_curr_bsp_world->scriptBoxBrushes.emplace_back(boxBrush); + + return m_bsp->models.size(); // script model index starts at 1 + } + + size_t addScriptTerrainModel(const JsonRoot& jRoot, const gltf::JsonNode& node) { if (!node.mesh || !jRoot.meshes) throw new GltfLoadException("Script model created with no mesh data"); @@ -680,6 +767,8 @@ namespace model.isGfxModel = m_is_world_gfx; model.surfaceIndex = m_curr_bsp_world->scriptSurfaces.size(); model.surfaceCount = mesh.primitives.size(); + model.hasBrush = false; + model.brushIndex = 0; m_bsp->models.emplace_back(model); for (const auto& primitive : mesh.primitives) @@ -735,7 +824,7 @@ namespace zone.zoneName = *node.extras->zone; zone.zSpawnerGroupName = node.extras->zspawner_group.value_or(""); zone.spawnpointGroupName = node.extras->spawnpoint_group.value_or(""); - zone.modelIndex = addScriptModel(jRoot, node); + zone.modelIndex = addScriptBrushModel(jRoot, node); m_bsp->zZones.emplace_back(zone); return true; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 7d2f3075e..4b8678698 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -243,17 +243,98 @@ namespace BSP for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) { auto clipModel = &clipMap->cmodels[modelIdx + 1]; - auto& bspMpdel = bsp->models.at(modelIdx); + auto& bspModel = bsp->models.at(modelIdx); - if (bspMpdel.isGfxModel) + if (bspModel.isGfxModel) + { + memset(clipModel, 0, sizeof(cmodel_t)); + continue; + } + + if (bspModel.surfaceCount != 0) + { + std::vector partitionIndexes; + for (size_t surfIdx = 0; surfIdx < bspModel.surfaceCount; surfIdx++) + { + ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspModel.surfaceIndex + surfIdx); + for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) + { + size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; + partitionIndexes.emplace_back(clipMapPartitionIndex); + vec3_t mins; + vec3_t maxs; + calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); + if (surfIdx == 0 && partitionIdx == 0) + { + clipModel->mins = mins; + clipModel->maxs = maxs; + } + else + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); + } + } + + int terrainContents = 0; + size_t firstCollAabbIndex = 0; + size_t collAabbCount = 0; + addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); + clipModel->leaf.terrainContents = terrainContents; + clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); + clipModel->leaf.collAabbCount = static_cast(collAabbCount); + } + else + { + clipModel->leaf.terrainContents = 0; + clipModel->leaf.firstCollAabbIndex = 0; + clipModel->leaf.collAabbCount = 0; + } + + if (bspModel.hasBrush) + { + BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); + clipModel->leaf.mins = bspBrush.localMins; + clipModel->leaf.maxs = bspBrush.localMaxs; + clipModel->leaf.brushContents = bspBrush.contentFlags; + clipModel->leaf.leafBrushNode = static_cast(brushNodeVec.size()); + assert(clipModel->leaf.leafBrushNode != 0); + + cLeafBrushNode_s brushNode; + brushNode.axis = 0; + brushNode.contents = bspBrush.contentFlags; + brushNode.leafBrushCount = 1; + brushNode.data.leaf.brushes = m_memory.Alloc(1); + brushNode.data.leaf.brushes[0] = static_cast(brushVec.size()); + brushNodeVec.emplace_back(brushNode); + + cbrush_array_t brush; + memset(&brush, 0, sizeof(cbrush_array_t)); // if not sides or verts are given, the mins/maxs are used instead + brush.contents = bspBrush.contentFlags; + brush.mins = bspBrush.localMins; + brush.maxs = bspBrush.localMaxs; + brush.axial_cflags[0][0] = bspBrush.contentFlags; + brush.axial_cflags[0][1] = bspBrush.contentFlags; + brush.axial_cflags[0][2] = bspBrush.contentFlags; + brush.axial_cflags[1][0] = bspBrush.contentFlags; + brush.axial_cflags[1][1] = bspBrush.contentFlags; + brush.axial_cflags[1][2] = bspBrush.contentFlags; + brush.axial_sflags[0][0] = bspBrush.surfaceFlags; + brush.axial_sflags[0][1] = bspBrush.surfaceFlags; + brush.axial_sflags[0][2] = bspBrush.surfaceFlags; + brush.axial_sflags[1][0] = bspBrush.surfaceFlags; + brush.axial_sflags[1][1] = bspBrush.surfaceFlags; + brush.axial_sflags[1][2] = bspBrush.surfaceFlags; + brushVec.emplace_back(brush); + + if (bspModel.surfaceCount != 0) + BSPUtil::updateAABB(bspBrush.localMins, bspBrush.localMaxs, clipModel->mins, clipModel->maxs); + else + { + clipModel->mins = bspBrush.localMins; + clipModel->maxs = bspBrush.localMaxs; + } + } + else { - clipModel->mins.x = 0.0f; - clipModel->mins.y = 0.0f; - clipModel->mins.z = 0.0f; - clipModel->maxs.x = 0.0f; - clipModel->maxs.y = 0.0f; - clipModel->maxs.z = 0.0f; - clipModel->radius = 0.0f; clipModel->leaf.brushContents = 0; clipModel->leaf.mins.x = 0.0f; clipModel->leaf.mins.y = 0.0f; @@ -262,50 +343,16 @@ namespace BSP clipModel->leaf.maxs.y = 0.0f; clipModel->leaf.maxs.z = 0.0f; clipModel->leaf.leafBrushNode = 0; - clipModel->leaf.cluster = 0; - clipModel->info = nullptr; - continue; } - std::vector partitionIndexes; - for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + if (bspModel.surfaceCount == 0 && !bspModel.hasBrush) { - ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspMpdel.surfaceIndex + surfIdx); - for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) - { - size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; - partitionIndexes.emplace_back(clipMapPartitionIndex); - vec3_t mins; - vec3_t maxs; - calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); - if (surfIdx == 0 && partitionIdx == 0) - { - clipModel->mins = mins; - clipModel->maxs = maxs; - } - else - BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); - } + clipModel->mins = {0.0f, 0.0f, 0.0f}; + clipModel->maxs = {0.0f, 0.0f, 0.0f}; + clipModel->radius = 0.0f; } - clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; - - int terrainContents = 0; - size_t firstCollAabbIndex = 0; - size_t collAabbCount = 0; - addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); - clipModel->leaf.terrainContents = terrainContents; - clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); - clipModel->leaf.collAabbCount = static_cast(collAabbCount); - - // no brush - clipModel->leaf.brushContents = 0; - clipModel->leaf.mins.x = 0.0f; - clipModel->leaf.mins.y = 0.0f; - clipModel->leaf.mins.z = 0.0f; - clipModel->leaf.maxs.x = 0.0f; - clipModel->leaf.maxs.y = 0.0f; - clipModel->leaf.maxs.z = 0.0f; - clipModel->leaf.leafBrushNode = 0; + else + clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; clipModel->leaf.cluster = 0; clipModel->info = nullptr; } @@ -607,22 +654,6 @@ namespace BSP // load planes, nodes, leafs, and AABB trees loadBSPNode(clipMap, tree.get(), true); - clipMap->info.planeCount = static_cast(planeVec.size()); - clipMap->info.planes = m_memory.Alloc(planeVec.size()); - memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); - - clipMap->numNodes = static_cast(nodeVec.size()); - clipMap->nodes = m_memory.Alloc(nodeVec.size()); - memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); - - clipMap->numLeafs = static_cast(leafVec.size()); - clipMap->leafs = m_memory.Alloc(leafVec.size()); - memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); - - // The plane of each node have the same index - for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) - clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; - con::info("Highest AABB tree partition count: {}", highestPartitionCountForAABB); return true; @@ -724,17 +755,21 @@ namespace BSP // No support for brushes, only tris right now clipMap->info.numBrushSides = 0; clipMap->info.brushsides = nullptr; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = nullptr; clipMap->info.numLeafBrushes = 0; clipMap->info.leafbrushes = nullptr; clipMap->info.numBrushVerts = 0; clipMap->info.brushVerts = nullptr; - clipMap->info.numBrushes = 0; - clipMap->info.brushes = nullptr; clipMap->info.brushBounds = nullptr; clipMap->info.brushContents = nullptr; + // first brush node is always empty + cLeafBrushNode_s tempNode; + memset(&tempNode, 0, sizeof(cLeafBrushNode_s)); + brushNodeVec.emplace_back(tempNode); + + clipMap->info.numBrushes = 0; + clipMap->info.brushes = nullptr; + // load verts, tris, uinds and partitions if (!loadPartitions(clipMap, bsp)) return false; @@ -742,6 +777,36 @@ namespace BSP if (!loadBSPTree(clipMap, bsp)) return false; + loadSubModelCollision(clipMap, bsp); // requires tri verts + + clipMap->info.planeCount = static_cast(planeVec.size()); + clipMap->info.planes = m_memory.Alloc(planeVec.size()); + memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); + + clipMap->numNodes = static_cast(nodeVec.size()); + clipMap->nodes = m_memory.Alloc(nodeVec.size()); + memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); + + clipMap->numLeafs = static_cast(leafVec.size()); + clipMap->leafs = m_memory.Alloc(leafVec.size()); + memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); + + clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); + clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); + memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); + + clipMap->info.leafbrushNodesCount = static_cast(brushNodeVec.size()); + clipMap->info.leafbrushNodes = m_memory.Alloc(brushNodeVec.size()); + memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size()); + + clipMap->info.numBrushes = static_cast(brushVec.size()); + clipMap->info.brushes = m_memory.Alloc(brushVec.size()); + memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.size()); + + // The plane of each node have the same index + for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) + clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; + return true; } @@ -801,19 +866,12 @@ namespace BSP if (!loadWorldCollision(clipMap, bsp)) // requires materials return nullptr; - loadSubModelCollision(clipMap, bsp); // requires tri verts - // set all edges to walkable // might do weird stuff on walls, but from testing doesnt seem to do anything int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; clipMap->triEdgeIsWalkable = m_memory.Alloc(walkableEdgeSize); memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - // multiple functions add to AABBTreeVec, so it is added to the clipmap last - clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); - clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); - memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); - return clipMap; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 744988868..53feb5fd2 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -39,6 +39,9 @@ namespace BSP std::vector AABBTreeVec; size_t highestPartitionCountForAABB = 0; + std::vector brushNodeVec; + std::vector brushVec; + std::vector collisionSurfaceVec; std::vector partitionToColSurfaceMap; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 43ff731f2..259a61b7b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -587,7 +587,7 @@ namespace BSP auto currEntModel = &gfxWorld->models[modelIdx + 1]; auto& bspModel = bsp->models.at(modelIdx); - if (bspModel.isGfxModel) + if (bspModel.isGfxModel && bspModel.surfaceCount != 0) { currEntModel->startSurfIndex = bsp->gfxWorld.staticSurfaces.size() + bspModel.surfaceIndex; currEntModel->surfaceCount = bspModel.surfaceCount; From 1d0d35138cb16f5ce2989d407d579241a05cd070 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:22:51 +0800 Subject: [PATCH 099/123] feat: trigger_use and trigger_multiple can now be loaded through GLTF --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 7 ++- src/ObjLoading/Game/T6/BSP/BSP.h | 10 ++++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 54 +++++++++++++++++++ .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 25 +++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 70c142faa..0fa9c3afe 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -103,9 +103,14 @@ namespace gltf std::optional spawnpoint_group; // value: zone zspawner group name std::optional zspawner; // value: zspawner group name + + // scripting + std::optional trigger_use; + std::optional trigger_multiple; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtras, xmodel, spawnpoint, flags, pathnode, zone, zspawner_group, spawnpoint_group, zspawner); + NLOHMANN_DEFINE_TYPE_EXTENSION( + JsonNodeExtras, xmodel, spawnpoint, flags, pathnode, zone, zspawner_group, spawnpoint_group, zspawner, trigger_use, trigger_multiple); class JsonNode { diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 6c1c66122..80ef1d4cc 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -160,6 +160,13 @@ namespace BSP size_t brushIndex; }; + struct BSPTriggerBox + { + vec3_t origin; + size_t modelIndex; + std::string triggerName; + }; + struct BSPData { std::string name; @@ -177,6 +184,9 @@ namespace BSP std::vector zSpawners; std::vector zZones; + std::vector useTriggers; + std::vector triggerMultiples; + std::vector models; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 6fe760e8d..402375156 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -866,6 +866,54 @@ namespace return true; } + bool addTriggerUseNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->trigger_use); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + BSPTriggerBox trigger; + trigger.origin = origin; + trigger.triggerName = *node.extras->trigger_use; + trigger.modelIndex = addScriptBrushModel(jRoot, node); + m_bsp->useTriggers.emplace_back(trigger); + + return true; + } + + bool addTriggerMultipleNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->trigger_multiple); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + BSPTriggerBox trigger; + trigger.origin = origin; + trigger.triggerName = *node.extras->trigger_multiple; + trigger.modelIndex = addScriptBrushModel(jRoot, node); + m_bsp->triggerMultiples.emplace_back(trigger); + + return true; + } + bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) { if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) @@ -882,6 +930,12 @@ namespace if (!m_is_world_gfx && node.extras->pathnode) return addPathNode_Node(node); + if (!m_is_world_gfx && node.extras->trigger_use) + return addTriggerUseNode(jRoot, node); + + if (!m_is_world_gfx && node.extras->trigger_multiple) + return addTriggerMultipleNode(jRoot, node); + if (!m_is_world_gfx && m_bsp->isZombiesMap) { if (node.extras->zone) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 3b90c6f9a..3c76e6d86 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -173,6 +173,29 @@ namespace } } + void addScriptingToEntString(BSP::BSPData* bsp, std::string& entityString) + { + for (auto& useTrigger : bsp->useTriggers) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", useTrigger.triggerName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(useTrigger.origin))); + entityString.append(std::format("\"model\" \"*{}\"\n", useTrigger.modelIndex)); + entityString.append("}\n"); + } + + for (auto& useTrigger : bsp->triggerMultiples) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_multiple\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", useTrigger.triggerName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(useTrigger.origin))); + entityString.append(std::format("\"model\" \"*{}\"\n", useTrigger.modelIndex)); + entityString.append("}\n"); + } + } + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ "entities": [ { @@ -229,6 +252,8 @@ namespace BSP if (bsp->isZombiesMap) addZombiesEntitiesToEntString(bsp, entityString); + addScriptingToEntString(bsp, entityString); + MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); From 501ac56eb62010e9537456e6a27fa25efe2b24d0 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:41:13 +0800 Subject: [PATCH 100/123] feat: fully customisable entities through gltf --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 2 +- src/ObjLoading/Game/T6/BSP/BSP.h | 48 ++-- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 223 ++++++++---------- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 52 ++++ src/ObjLoading/Game/T6/BSP/BSPUtil.h | 2 + .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 93 +++----- 6 files changed, 196 insertions(+), 224 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 0fa9c3afe..d07e2bbb6 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -125,7 +125,7 @@ namespace gltf std::optional mesh; std::optional extensions; - std::optional extras; + std::optional extras; }; NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, matrix, children, skin, mesh, extensions, extras); diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 80ef1d4cc..ca3b98560 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -107,44 +107,25 @@ namespace BSP float outerConeAngle; }; - enum BSPSpawnPointType - { - SPAWNPOINT_TYPE_DEFENDER, - SPAWNPOINT_TYPE_ATTACKER, - SPAWNPOINT_TYPE_ALL, - }; - struct BSPSpawnPoint { vec3_t origin; vec3_t forward; - BSPSpawnPointType type; - }; - - struct BSPPathNode - { - vec3_t origin; + std::string spawnpointGroupName; }; struct BSPZoneZM { vec3_t origin; std::string zoneName; - std::string zSpawnerGroupName; + std::string spawnerGroupName; std::string spawnpointGroupName; size_t modelIndex; }; - struct BSPSpawnPointZM - { - std::string spawnpointGroupName; - vec3_t origin; - vec3_t forward; - }; - struct BSPZSpawnerZM { - std::string zSpawnerGroupName; + std::string spawnerGroupName; vec3_t origin; vec3_t forward; }; @@ -160,11 +141,19 @@ namespace BSP size_t brushIndex; }; - struct BSPTriggerBox + struct BSPEntityEntry + { + std::string key; + std::string value; + }; + + struct BSPEntity { vec3_t origin; + vec4_t rotationQuaternion; size_t modelIndex; - std::string triggerName; + + std::vector entries; }; struct BSPData @@ -178,14 +167,9 @@ namespace BSP std::vector lights; std::vector spawnpoints; - std::vector pathnodes; - - std::vector zSpawnPoints; - std::vector zSpawners; - std::vector zZones; - - std::vector useTriggers; - std::vector triggerMultiples; + std::vector zm_zones; + std::vector zm_spawners; + std::vector entities; std::vector models; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 402375156..225338927 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -443,10 +443,10 @@ namespace if (primitive.material) { size_t originalMaterialIdx = *primitive.material; - if (node.extras && node.extras->flags) + if (node.extras && node.extras->contains("flags")) { bool isNoDrawFlagSet = false; - materialIndex = createMaterialWithFlags(originalMaterialIdx, *node.extras->flags, isNoDrawFlagSet); + materialIndex = createMaterialWithFlags(originalMaterialIdx, node.extras->at("flags"), isNoDrawFlagSet); if (isNoDrawFlagSet && m_is_world_gfx) continue; // noDraw flag doesn't work, so remove the surface from the graphics data instead } @@ -492,12 +492,12 @@ namespace throw GltfLoadException("Requires primitives attribute POSITION"); // clang-format off - const auto* positionAccessor = GetAccessorForIndex( - "POSITION", - primitive.attributes.POSITION, - { JsonAccessorType::VEC3 }, - { JsonAccessorComponentType::FLOAT } - ).value_or(nullptr); + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + primitive.attributes.POSITION, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); // clang-format on assert(positionAccessor != nullptr); @@ -539,19 +539,17 @@ namespace bool addXModelNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { assert(node.extras); - assert(node.extras->xmodel); + assert(node.extras->contains("xmodel")); Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); BSPXModel xmodel; - if (node.extras->xmodel->size() == 0) - throw GltfLoadException("Xmodel has no name."); - xmodel.name = *node.extras->xmodel; + xmodel.name = node.extras->at("xmodel"); xmodel.doesCastShadow = true; - if (node.extras->flags) + if (node.extras->contains("flags")) { - std::vector flagStrVec = utils::StringSplit(*node.extras->flags, ','); + std::vector flagStrVec = utils::StringSplit(node.extras->at("flags"), ','); for (std::string& flag : flagStrVec) if (!flag.compare(surfaceTypeToNameMap[SURF_TYPE_NOCASTSHADOW])) { @@ -573,7 +571,7 @@ namespace xmodel.rotationQuaternion.x = rotationQuat.x(); xmodel.rotationQuaternion.y = rotationQuat.y(); xmodel.rotationQuaternion.z = rotationQuat.z(); - xmodel.rotationQuaternion.w = rotationQuat.w(); // Eigen is WXYZ, game is XYZW + xmodel.rotationQuaternion.w = rotationQuat.w(); RhcToLhcQuaternion(xmodel.rotationQuaternion.v); con::warn("XModels don't support scale currently, keep it at 1 in your editor"); @@ -586,30 +584,10 @@ namespace return true; } - bool addPathNode_Node(const gltf::JsonNode& node) - { - assert(node.extras); - assert(node.extras->pathnode); - - BSPPathNode pathnode; - - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - Eigen::Vector4f position(0, 0, 0, 1.0f); - Eigen::Vector4f transformedPosition = nodeMatrix * position; - pathnode.origin.x = transformedPosition.x(); - pathnode.origin.y = transformedPosition.y(); - pathnode.origin.z = transformedPosition.z(); - RhcToLhcCoordinates(pathnode.origin.v); - - m_bsp->pathnodes.emplace_back(pathnode); - - return true; - } - bool addSpawnPointNode(const gltf::JsonNode& node) { assert(node.extras); - assert(node.extras->spawnpoint); + assert(node.extras->contains("spawnpoint")); Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); @@ -633,34 +611,18 @@ namespace forward.z = outputDirection.z(); RhcToLhcCoordinates(forward.v); - if (m_bsp->isZombiesMap) + std::string team = node.extras->at("spawnpoint"); + if (!m_bsp->isZombiesMap && team.compare("attacker") && team.compare("defender") && team.compare("all")) { - BSPSpawnPointZM spawnPoint; - spawnPoint.origin = origin; - spawnPoint.forward = forward; - spawnPoint.spawnpointGroupName = *node.extras->spawnpoint; - m_bsp->zSpawnPoints.emplace_back(spawnPoint); + con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); + return false; } - else - { - BSPSpawnPoint spawnPoint; - spawnPoint.origin = origin; - spawnPoint.forward = forward; - - if (!node.extras->spawnpoint->compare("attacker")) - spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; - else if (!node.extras->spawnpoint->compare("defender")) - spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; - else if (!node.extras->spawnpoint->compare("all")) - spawnPoint.type = SPAWNPOINT_TYPE_ALL; - else - { - con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); - return false; - } - m_bsp->spawnpoints.emplace_back(spawnPoint); - } + BSPSpawnPoint spawnPoint; + spawnPoint.origin = origin; + spawnPoint.forward = forward; + spawnPoint.spawnpointGroupName = team; + m_bsp->spawnpoints.emplace_back(spawnPoint); return true; } @@ -697,12 +659,12 @@ namespace throw GltfLoadException("Requires primitives attribute POSITION"); // clang-format off - const auto* positionAccessor = GetAccessorForIndex( - "POSITION", - primitive.attributes.POSITION, - { JsonAccessorType::VEC3 }, - { JsonAccessorComponentType::FLOAT } - ).value_or(nullptr); + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + primitive.attributes.POSITION, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); // clang-format on assert(positionAccessor != nullptr); @@ -807,7 +769,13 @@ namespace bool addZoneNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { assert(node.extras); - assert(node.extras->zone); + assert(node.extras->contains("zone")); + + if (!node.extras->contains("spawner_group") || !node.extras->contains("spawnpoint_group")) + { + con::error("ignoring zone: Zone object must have a valid spawner_group and spawnpoint_group property"); + return false; + } Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); @@ -821,11 +789,11 @@ namespace BSPZoneZM zone; zone.origin = origin; - zone.zoneName = *node.extras->zone; - zone.zSpawnerGroupName = node.extras->zspawner_group.value_or(""); - zone.spawnpointGroupName = node.extras->spawnpoint_group.value_or(""); + zone.zoneName = node.extras->at("zone"); + zone.spawnerGroupName = node.extras->at("spawner_group"); + zone.spawnpointGroupName = node.extras->at("spawnpoint_group"); zone.modelIndex = addScriptBrushModel(jRoot, node); - m_bsp->zZones.emplace_back(zone); + m_bsp->zm_zones.emplace_back(zone); return true; } @@ -833,7 +801,7 @@ namespace bool addZSpawnerNode(const gltf::JsonNode& node) { assert(node.extras); - assert(node.extras->zspawner); + assert(node.extras->contains("spawner")); Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); @@ -860,57 +828,55 @@ namespace BSPZSpawnerZM spawner; spawner.origin = origin; spawner.forward = forward; - spawner.zSpawnerGroupName = *node.extras->zspawner; - m_bsp->zSpawners.emplace_back(spawner); + spawner.spawnerGroupName = node.extras->at("spawner"); + m_bsp->zm_spawners.emplace_back(spawner); return true; } - bool addTriggerUseNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addClassNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { - assert(node.extras); - assert(node.extras->trigger_use); - - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - - Eigen::Vector4f position(0, 0, 0, 1.0f); - Eigen::Vector4f transformedPosition = nodeMatrix * position; - vec3_t origin; - origin.x = transformedPosition.x(); - origin.y = transformedPosition.y(); - origin.z = transformedPosition.z(); - RhcToLhcCoordinates(origin.v); + BSPEntity entity; + bool doesClassContainModel = false; + for (auto& element : node.extras->items()) + { + std::string key = element.key(); + if (!key.compare("origin") || !key.compare("angles")) + continue; - BSPTriggerBox trigger; - trigger.origin = origin; - trigger.triggerName = *node.extras->trigger_use; - trigger.modelIndex = addScriptBrushModel(jRoot, node); - m_bsp->useTriggers.emplace_back(trigger); + if (!key.compare("model")) + doesClassContainModel = true; - return true; - } + BSPEntityEntry entry; + entry.key = key; + entry.value = element.value(); + entity.entries.emplace_back(entry); + } - bool addTriggerMultipleNode(const JsonRoot& jRoot, const gltf::JsonNode& node) - { - assert(node.extras); - assert(node.extras->trigger_multiple); + if (node.mesh && !doesClassContainModel) + entity.modelIndex = addScriptBrushModel(jRoot, node); + else + entity.modelIndex = 0; Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; - vec3_t origin; - origin.x = transformedPosition.x(); - origin.y = transformedPosition.y(); - origin.z = transformedPosition.z(); - RhcToLhcCoordinates(origin.v); + entity.origin.x = transformedPosition.x(); + entity.origin.y = transformedPosition.y(); + entity.origin.z = transformedPosition.z(); + RhcToLhcCoordinates(entity.origin.v); - BSPTriggerBox trigger; - trigger.origin = origin; - trigger.triggerName = *node.extras->trigger_multiple; - trigger.modelIndex = addScriptBrushModel(jRoot, node); - m_bsp->triggerMultiples.emplace_back(trigger); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Quaternionf rotationQuat(affineTransform.rotation()); + rotationQuat.normalize(); + entity.rotationQuaternion.x = rotationQuat.x(); + entity.rotationQuaternion.y = rotationQuat.y(); + entity.rotationQuaternion.z = rotationQuat.z(); + entity.rotationQuaternion.w = rotationQuat.w(); + RhcToLhcQuaternion(entity.rotationQuaternion.v); + m_bsp->entities.emplace_back(entity); return true; } @@ -921,34 +887,31 @@ namespace if (node.extras) { - if (node.extras->xmodel) - return addXModelNode(jRoot, node); - - if (!m_is_world_gfx && node.extras->spawnpoint) - return addSpawnPointNode(node); + if (!m_is_world_gfx) + { + if (node.extras->contains("classname")) + return addClassNode(jRoot, node); - if (!m_is_world_gfx && node.extras->pathnode) - return addPathNode_Node(node); + if (node.extras->contains("spawnpoint")) + return addSpawnPointNode(node); - if (!m_is_world_gfx && node.extras->trigger_use) - return addTriggerUseNode(jRoot, node); + if (m_bsp->isZombiesMap) + { + if (node.extras->contains("zone")) + return addZoneNode(jRoot, node); - if (!m_is_world_gfx && node.extras->trigger_multiple) - return addTriggerMultipleNode(jRoot, node); + if (node.extras->contains("spawner")) + return addZSpawnerNode(node); + } + } - if (!m_is_world_gfx && m_bsp->isZombiesMap) - { - if (node.extras->zone) - return addZoneNode(jRoot, node); + if (node.extras->contains("xmodel")) + return addXModelNode(jRoot, node); - if (node.extras->zspawner) - return addZSpawnerNode(node); - } + if (node.mesh) + return addMeshNode(jRoot, node); } - if (node.mesh) - return addMeshNode(jRoot, node); - return false; } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index feb8cb501..20ef6458b 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -196,6 +196,17 @@ namespace BSP { vec3_t viewAngles; + if (forwardVec.x == 0.0f && forwardVec.y == 0.0f) + { + if (-forwardVec.z < 0.0f) + viewAngles.x = 270.0f; + else + viewAngles.x = 90.0f; + viewAngles.y = 0.0f; + viewAngles.z = 0.0f; + return viewAngles; + } + float xAndYDist = sqrtf((forwardVec.x * forwardVec.x) + (forwardVec.y * forwardVec.y)); float atanXRadians = atan2f(forwardVec.z, xAndYDist); float atanXDegrees = atanXRadians * (-180.0f / std::numbers::pi_v); @@ -214,6 +225,47 @@ namespace BSP return viewAngles; } + float BSPUtil::getPitchFromVector(vec3_t& vector) + { + if (vector.x == 0.0f && vector.y == 0.0f) + { + if (-vector.z < 0.0f) + return -90.0f; + else + return 90.0f; + } + + float xAndYDist = sqrtf((vector.x * vector.x) + (vector.y * vector.y)); + float pitchRadians = atan2f(vector.z, xAndYDist); + float pitchDegrees = pitchRadians * (-180.0f / std::numbers::pi_v); + return pitchDegrees; + } + + vec3_t BSPUtil::convertAxisToAngles(vec3_t axis[3]) + { + vec3_t tempAngles = convertForwardVectorToViewAngles(axis[0]); + float xRadiansNeg = -tempAngles.x * (std::numbers::pi_v / 180.0f); + float yRadiansNeg = -tempAngles.y * (std::numbers::pi_v / 180.0f); + float cosX = cos(xRadiansNeg); + float sinX = sin(xRadiansNeg); + float cosY = cos(yRadiansNeg); + float sinY = sin(yRadiansNeg); + + vec3_t tempVec; + float tempFloat = (axis[1].x * cosY) - (axis[1].y * sinY); + tempVec.x = (axis[1].z * sinX) + (tempFloat * cosX); + tempVec.y = (axis[1].x * sinY) + (axis[1].y * cosY); + tempVec.z = (axis[1].z * cosX) - (tempFloat * sinX); + float pitch = getPitchFromVector(tempVec); + if (tempVec.y >= 0.0f) + tempAngles.z = -pitch; + else if (pitch >= 0.0f) + tempAngles.z = pitch - 180.0f; + else + tempAngles.z = pitch + 180.0f; + return tempAngles; + } + void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) { out[0].x = in[0].x; diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 96fd05061..d319748b9 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -20,6 +20,8 @@ namespace BSP static void convertAnglesToAxis(vec3_t* angles, vec3_t axis[3]); static void convertQuaternionToAxis(vec4_t* quat, vec3_t axis[3]); static vec3_t convertForwardVectorToViewAngles(vec3_t& forwardVec); + static float getPitchFromVector(vec3_t& vector); + static vec3_t convertAxisToAngles(vec3_t axis[3]); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); static vec3_t convertStringToVec3(std::string& str); static std::string convertVec3ToString(vec3_t& vec); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 3c76e6d86..548cff22d 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -67,22 +67,19 @@ namespace void addSpawnsToEntString(BSP::BSPData* bsp, std::string& entityString) { - if (!bsp->isZombiesMap && bsp->spawnpoints.size() == 0) + if (bsp->isZombiesMap) { - con::info("No spawnpoints found, setting all spawns to (0, 0, 0)"); - BSP::BSPSpawnPoint defaultSpawnPoint; - defaultSpawnPoint.origin.x = 0.0f; - defaultSpawnPoint.origin.y = 0.0f; - defaultSpawnPoint.origin.z = 0.0f; - defaultSpawnPoint.forward.x = 1.0f; - defaultSpawnPoint.forward.y = 0.0f; - defaultSpawnPoint.forward.z = 0.0f; - defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER; - bsp->spawnpoints.emplace_back(defaultSpawnPoint); - defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER; - bsp->spawnpoints.emplace_back(defaultSpawnPoint); - defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ALL; - bsp->spawnpoints.emplace_back(defaultSpawnPoint); + for (auto& spawnPoint : bsp->spawnpoints) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", spawnPoint.spawnpointGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(spawnPoint.origin))); + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + entityString.append("}\n"); + } + return; } size_t defenderNameCount = std::extent::value; @@ -98,9 +95,9 @@ namespace std::string anglesStr = std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles)); const std::vector* spawnPointList; - if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER) + if (!spawnPoint.spawnpointGroupName.compare("defender")) spawnPointList = &DEFENDER_SPAWN_POINT_NAMES; - else if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER) + else if (!spawnPoint.spawnpointGroupName.compare("attacker")) spawnPointList = &ATTACKER_SPAWN_POINT_NAMES; else // SPAWNPOINT_TYPE_ALL spawnPointList = &FFA_SPAWN_POINT_NAMES; @@ -116,26 +113,15 @@ namespace } } - void addPathNodesToEntString(BSP::BSPData* bsp, std::string& entityString) - { - for (auto& pathnode : bsp->pathnodes) - { - entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(pathnode.origin))); - entityString.append(std::format("\"classname\" \"{}\"\n", "node_pathnode")); - entityString.append("}\n"); - } - } - void addZombiesEntitiesToEntString(BSP::BSPData* bsp, std::string& entityString) { - for (auto& zone : bsp->zZones) + for (auto& zone : bsp->zm_zones) { entityString.append("{\n"); entityString.append("\"classname\" \"info_volume\"\n"); entityString.append("\"script_noteworthy\" \"player_volume\"\n"); entityString.append(std::format("\"targetname\" \"{}\"\n", zone.zoneName)); - entityString.append(std::format("\"target\" \"{}\"\n", zone.zSpawnerGroupName)); + entityString.append(std::format("\"target\" \"{}\"\n", zone.spawnerGroupName)); entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zone.origin))); entityString.append(std::format("\"model\" \"*{}\"\n", zone.modelIndex)); entityString.append("}\n"); @@ -148,50 +134,37 @@ namespace entityString.append("}\n"); } - for (auto& zSpawner : bsp->zSpawners) + for (auto& zSpawner : bsp->zm_spawners) { entityString.append("{\n"); entityString.append("\"classname\" \"script_struct\"\n"); entityString.append("\"script_noteworthy\" \"riser_location\"\n"); entityString.append("\"script_string\" \"find_flesh\"\n"); - entityString.append(std::format("\"targetname\" \"{}\"\n", zSpawner.zSpawnerGroupName)); + entityString.append(std::format("\"targetname\" \"{}\"\n", zSpawner.spawnerGroupName)); entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zSpawner.origin))); vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(zSpawner.forward); entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); entityString.append("}\n"); } - - for (auto& spawnPoint : bsp->zSpawnPoints) - { - entityString.append("{\n"); - entityString.append("\"classname\" \"script_struct\"\n"); - entityString.append(std::format("\"targetname\" \"{}\"\n", spawnPoint.spawnpointGroupName)); - entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(spawnPoint.origin))); - vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); - entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); - entityString.append("}\n"); - } } - void addScriptingToEntString(BSP::BSPData* bsp, std::string& entityString) + void addClassEntitiesToEntString(BSP::BSPData* bsp, std::string& entityString) { - for (auto& useTrigger : bsp->useTriggers) + for (auto& entity : bsp->entities) { - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use\"\n"); - entityString.append(std::format("\"targetname\" \"{}\"\n", useTrigger.triggerName)); - entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(useTrigger.origin))); - entityString.append(std::format("\"model\" \"*{}\"\n", useTrigger.modelIndex)); - entityString.append("}\n"); - } + vec3_t origin = entity.origin; + vec3_t axis[3]; + BSP::BSPUtil::convertQuaternionToAxis(&entity.rotationQuaternion, axis); + vec3_t angles = BSP::BSPUtil::convertAxisToAngles(axis); - for (auto& useTrigger : bsp->triggerMultiples) - { entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_multiple\"\n"); - entityString.append(std::format("\"targetname\" \"{}\"\n", useTrigger.triggerName)); - entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(useTrigger.origin))); - entityString.append(std::format("\"model\" \"*{}\"\n", useTrigger.modelIndex)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(origin))); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + for (auto& entry : entity.entries) + entityString.append(std::format("\"{}\" \"{}\"\n", entry.key, entry.value)); + if (entity.modelIndex != 0) + entityString.append(std::format("\"model\" \"*{}\"\n", entity.modelIndex)); + entityString.append("}\n"); } } @@ -247,12 +220,10 @@ namespace BSP addSpawnsToEntString(bsp, entityString); - addPathNodesToEntString(bsp, entityString); - if (bsp->isZombiesMap) addZombiesEntitiesToEntString(bsp, entityString); - addScriptingToEntString(bsp, entityString); + addClassEntitiesToEntString(bsp, entityString); MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); From bd0f645f6f44cdf1feb3ddbdfc9ca6d14e68ce91 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:15:51 +0800 Subject: [PATCH 101/123] fix: uppercase flag name --- src/ObjLoading/Game/T6/BSP/BSPFlags.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPFlags.h b/src/ObjLoading/Game/T6/BSP/BSPFlags.h index 48116a0eb..936c18632 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPFlags.h +++ b/src/ObjLoading/Game/T6/BSP/BSPFlags.h @@ -241,7 +241,7 @@ namespace BSPFlags "origin", "sky", "nocastshadow", - "onlyCastShadow", + "onlycastshadow", "physicsgeom", "lightportal", "caulk", From 63af3c8da873f49c28166bb14208a12ad01a4cef Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:21:36 +0800 Subject: [PATCH 102/123] refactor: minor improvements to bsp creation --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 101 +++++++++++++++------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 225338927..2109cfc65 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -103,6 +103,11 @@ namespace std::vector> m_buffer_views; std::vector> m_buffers; + std::string getWorldTypeName() + { + return m_is_world_gfx ? "gfx" : "col"; + } + std::optional GetAccessorForIndex(const char* attributeName, const std::optional index, std::initializer_list allowedAccessorTypes, @@ -205,7 +210,11 @@ namespace return T.matrix(); } - unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) + unsigned CreateSurface(const AccessorsForVertex& accessorsForVertex, + Eigen::Matrix4f& nodeMatrix, + size_t materialIndex, + bool convertWorldToLocalPos, + BSPSurface& out_surface) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -263,10 +272,20 @@ namespace if (faceCount > UINT16_MAX) throw GltfLoadException("Face count exceeded the UINT16_MAX"); - surface.vertexCount = static_cast(vertexCount); - surface.triCount = static_cast(faceCount); - surface.indexOfFirstIndex = static_cast(m_curr_bsp_world->indices.size()); - surface.indexOfFirstVertex = static_cast(m_curr_bsp_world->vertices.size()); + out_surface.vertexCount = static_cast(vertexCount); + out_surface.triCount = static_cast(faceCount); + out_surface.indexOfFirstIndex = static_cast(m_curr_bsp_world->indices.size()); + out_surface.indexOfFirstVertex = static_cast(m_curr_bsp_world->vertices.size()); + out_surface.materialIndex = materialIndex; + vec4_t materialColor = m_curr_bsp_world->materials.at(materialIndex).materialColour; + + Eigen::Vector4f tempPosition(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * tempPosition; + vec3_t surfaceOrigin; + surfaceOrigin.x = transformedPosition.x(); + surfaceOrigin.y = transformedPosition.y(); + surfaceOrigin.z = transformedPosition.z(); + RhcToLhcCoordinates(surfaceOrigin.v); for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++) { @@ -297,10 +316,10 @@ namespace assert(false); } - vertex.color.x *= vertexColor.x; - vertex.color.y *= vertexColor.y; - vertex.color.z *= vertexColor.z; - vertex.color.w *= vertexColor.w; + vertex.color.x *= materialColor.x; + vertex.color.y *= materialColor.y; + vertex.color.z *= materialColor.z; + vertex.color.w *= materialColor.w; Eigen::Vector4f position(vertex.pos.x, vertex.pos.y, vertex.pos.z, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; @@ -311,22 +330,29 @@ namespace RhcToLhcCoordinates(vertex.pos.v); RhcToLhcCoordinates(vertex.normal.v); + if (convertWorldToLocalPos) + { + vertex.pos.x -= surfaceOrigin.x; + vertex.pos.y -= surfaceOrigin.y; + vertex.pos.z -= surfaceOrigin.z; + } + m_curr_bsp_world->vertices.emplace_back(vertex); } // generate tangent and binormal vectors tangent_space::VertexData vertexData{ - &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].pos, + &m_curr_bsp_world->vertices[out_surface.indexOfFirstVertex].pos, sizeof(BSPVertex), - &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].normal, + &m_curr_bsp_world->vertices[out_surface.indexOfFirstVertex].normal, sizeof(BSPVertex), - &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].texCoord, + &m_curr_bsp_world->vertices[out_surface.indexOfFirstVertex].texCoord, sizeof(BSPVertex), - &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].tangent, + &m_curr_bsp_world->vertices[out_surface.indexOfFirstVertex].tangent, sizeof(BSPVertex), - &m_curr_bsp_world->vertices[surface.indexOfFirstVertex].binormal, + &m_curr_bsp_world->vertices[out_surface.indexOfFirstVertex].binormal, sizeof(BSPVertex), - &m_curr_bsp_world->indices[surface.indexOfFirstIndex], + &m_curr_bsp_world->indices[out_surface.indexOfFirstIndex], }; tangent_space::CalculateTangentSpace(vertexData, faceCount, vertexCount); @@ -457,10 +483,7 @@ namespace materialIndex = m_emptyMaterialIndex; BSPSurface surface; - surface.materialIndex = materialIndex; - vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; - CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); - + CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, false, surface); m_curr_bsp_world->staticSurfaces.emplace_back(surface); } @@ -752,14 +775,23 @@ namespace .m_index_accessor = *primitive.indices, }; - BSPSurface surface; + size_t materialIndex; if (primitive.material) - surface.materialIndex = *primitive.material; + { + size_t originalMaterialIdx = *primitive.material; + if (node.extras && node.extras->contains("flags")) + { + bool isNoDrawFlagSet; + materialIndex = createMaterialWithFlags(originalMaterialIdx, node.extras->at("flags"), isNoDrawFlagSet); + } + else + materialIndex = originalMaterialIdx; + } else - surface.materialIndex = m_emptyMaterialIndex; - vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; - CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); + materialIndex = m_emptyMaterialIndex; + BSPSurface surface; + CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, true, surface); m_curr_bsp_world->scriptSurfaces.emplace_back(surface); } @@ -836,12 +868,15 @@ namespace bool addClassNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { + assert(node.extras); + assert(node.extras->contains("classname")); + BSPEntity entity; bool doesClassContainModel = false; for (auto& element : node.extras->items()) { std::string key = element.key(); - if (!key.compare("origin") || !key.compare("angles")) + if (!key.compare("origin") || !key.compare("angles") || !key.compare("flags")) continue; if (!key.compare("model")) @@ -854,7 +889,12 @@ namespace } if (node.mesh && !doesClassContainModel) - entity.modelIndex = addScriptBrushModel(jRoot, node); + { + if (m_is_world_gfx) + entity.modelIndex = addScriptTerrainModel(jRoot, node); + else + entity.modelIndex = addScriptBrushModel(jRoot, node); + } else entity.modelIndex = 0; @@ -907,11 +947,11 @@ namespace if (node.extras->contains("xmodel")) return addXModelNode(jRoot, node); - - if (node.mesh) - return addMeshNode(jRoot, node); } + if (node.mesh) + return addMeshNode(jRoot, node); + return false; } @@ -1135,7 +1175,8 @@ namespace con::warn("Parent node has position data that won't be used"); } - addNodeToBSP(jRoot, node); + if (!addNodeToBSP(jRoot, node)) + con::warn("({}) Ignoring node: {}", getWorldTypeName(), node.name.value_or("unnamed node")); } } From 0a873b3f60eab8504058a6ddda025496c73013d3 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:41:01 +0800 Subject: [PATCH 103/123] feat: heirarchical transforms are now implemented --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 80 ++++++++++------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 2109cfc65..9c097ce58 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -359,14 +359,12 @@ namespace return vertexOffset; } - bool addLightNode(const gltf::JsonNode& node) + bool addLightNode(const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(m_is_world_gfx); assert(node.extensions); assert(node.extensions->KHR_lights_punctual); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - int lightIndex = node.extensions->KHR_lights_punctual->light; assert(lightIndex >= 0); @@ -438,13 +436,11 @@ namespace return newMaterialIndex; } - bool addMeshNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addMeshNode(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.mesh); assert(jRoot.meshes); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; for (const auto& primitive : mesh.primitives) { @@ -559,12 +555,11 @@ namespace } } - bool addXModelNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addXModelNode(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.extras); assert(node.extras->contains("xmodel")); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); BSPXModel xmodel; xmodel.name = node.extras->at("xmodel"); @@ -607,13 +602,11 @@ namespace return true; } - bool addSpawnPointNode(const gltf::JsonNode& node) + bool addSpawnPointNode(const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.extras); assert(node.extras->contains("spawnpoint")); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; vec3_t origin; @@ -650,12 +643,11 @@ namespace return true; } - size_t addScriptBrushModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + size_t addScriptBrushModel(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { if (!node.mesh || !jRoot.meshes) throw new GltfLoadException("Script model created with no mesh data"); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; if (mesh.primitives.size() == 0) @@ -737,12 +729,11 @@ namespace return m_bsp->models.size(); // script model index starts at 1 } - size_t addScriptTerrainModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + size_t addScriptTerrainModel(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { if (!node.mesh || !jRoot.meshes) throw new GltfLoadException("Script model created with no mesh data"); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; if (mesh.primitives.size() == 0) @@ -798,7 +789,7 @@ namespace return m_bsp->models.size(); // script model index starts at 1 } - bool addZoneNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addZoneNode(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.extras); assert(node.extras->contains("zone")); @@ -809,8 +800,6 @@ namespace return false; } - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; vec3_t origin; @@ -824,19 +813,17 @@ namespace zone.zoneName = node.extras->at("zone"); zone.spawnerGroupName = node.extras->at("spawner_group"); zone.spawnpointGroupName = node.extras->at("spawnpoint_group"); - zone.modelIndex = addScriptBrushModel(jRoot, node); + zone.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); m_bsp->zm_zones.emplace_back(zone); return true; } - bool addZSpawnerNode(const gltf::JsonNode& node) + bool addZSpawnerNode(const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.extras); assert(node.extras->contains("spawner")); - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; vec3_t origin; @@ -866,7 +853,7 @@ namespace return true; } - bool addClassNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addClassNode(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { assert(node.extras); assert(node.extras->contains("classname")); @@ -891,15 +878,13 @@ namespace if (node.mesh && !doesClassContainModel) { if (m_is_world_gfx) - entity.modelIndex = addScriptTerrainModel(jRoot, node); + entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); else - entity.modelIndex = addScriptBrushModel(jRoot, node); + entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); } else entity.modelIndex = 0; - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; entity.origin.x = transformedPosition.x(); @@ -920,37 +905,37 @@ namespace return true; } - bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) - return addLightNode(node); + return addLightNode(node, nodeMatrix); if (node.extras) { if (!m_is_world_gfx) { if (node.extras->contains("classname")) - return addClassNode(jRoot, node); + return addClassNode(jRoot, node, nodeMatrix); if (node.extras->contains("spawnpoint")) - return addSpawnPointNode(node); + return addSpawnPointNode(node, nodeMatrix); if (m_bsp->isZombiesMap) { if (node.extras->contains("zone")) - return addZoneNode(jRoot, node); + return addZoneNode(jRoot, node, nodeMatrix); if (node.extras->contains("spawner")) - return addZSpawnerNode(node); + return addZSpawnerNode(node, nodeMatrix); } } if (node.extras->contains("xmodel")) - return addXModelNode(jRoot, node); + return addXModelNode(jRoot, node, nodeMatrix); } if (node.mesh) - return addMeshNode(jRoot, node); + return addMeshNode(jRoot, node, nodeMatrix); return false; } @@ -1155,27 +1140,32 @@ namespace if (!jRoot.nodes || jRoot.nodes->empty()) return; - std::deque nodeQueue; - const std::vector rootNodes = GetRootNodes(jRoot); + struct s_nodes + { + unsigned int nodeIndex; + Eigen::Matrix4f parentNodeMatrix; + }; - for (const auto rootNode : rootNodes) - nodeQueue.emplace_back(rootNode); + std::deque nodeQueue; + for (const auto rootNode : GetRootNodes(jRoot)) + nodeQueue.emplace_back(s_nodes{rootNode, Eigen::Matrix4f::Identity()}); while (!nodeQueue.empty()) { - const auto& node = jRoot.nodes.value()[nodeQueue.front()]; + const auto& node = jRoot.nodes.value()[nodeQueue.front().nodeIndex]; + Eigen::Matrix4f parentNodeMatrix = nodeQueue.front().parentNodeMatrix; nodeQueue.pop_front(); + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + Eigen::Matrix4f transformedNodeMatrix = parentNodeMatrix * nodeMatrix; + if (node.children) { for (const auto childIndex : *node.children) - nodeQueue.emplace_back(childIndex); - - if (node.matrix || node.translation || node.rotation || node.scale) - con::warn("Parent node has position data that won't be used"); + nodeQueue.emplace_back(s_nodes{childIndex, transformedNodeMatrix}); } - if (!addNodeToBSP(jRoot, node)) + if (!addNodeToBSP(jRoot, node, transformedNodeMatrix)) con::warn("({}) Ignoring node: {}", getWorldTypeName(), node.name.value_or("unnamed node")); } } From a4bc88ec151883a4beac120be9b68f40557094f2 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 11:30:47 +0800 Subject: [PATCH 104/123] fix: slight update to lightgrid after new info found --- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 259a61b7b..b948f6840 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -416,17 +416,18 @@ namespace BSP void GfxWorldLinker::loadLightGrid(GfxWorld* gfxWorld) { - // the values in this code were chosen based on what looked correct when reverse engineering. - - // mins and maxs define the range that the lightgrid will work in. - // unknown how these values are calculated, but the below values are larger - // than official map values - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 200; - gfxWorld->lightGrid.maxs[1] = 200; - gfxWorld->lightGrid.maxs[2] = 50; + // gfxWorld->lightGrid.mins[0] = static_cast((static_cast(gfxWorld->lightGrid.mins[0]) + 0x20000) >> 5); + // gfxWorld->lightGrid.mins[1] = static_cast((static_cast(gfxWorld->lightGrid.mins[1]) + 0x20000) >> 5); + // gfxWorld->lightGrid.mins[2] = static_cast((static_cast(gfxWorld->lightGrid.mins[2]) + 0x20000) >> 6); + // gfxWorld->lightGrid.maxs[0] = static_cast((static_cast(gfxWorld->lightGrid.maxs[0]) + 0x20000) >> 5); + // gfxWorld->lightGrid.maxs[1] = static_cast((static_cast(gfxWorld->lightGrid.maxs[1]) + 0x20000) >> 5); + // gfxWorld->lightGrid.maxs[2] = static_cast((static_cast(gfxWorld->lightGrid.maxs[2]) + 0x20000) >> 6); + gfxWorld->lightGrid.mins[0] = 100; + gfxWorld->lightGrid.mins[1] = 100; + gfxWorld->lightGrid.mins[2] = 100; + gfxWorld->lightGrid.maxs[0] = 65435; + gfxWorld->lightGrid.maxs[1] = 65435; + gfxWorld->lightGrid.maxs[2] = 65435; gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value @@ -446,7 +447,7 @@ namespace BSP row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks row->firstEntry = 0; for (int i = 0; i < 0x10; i++) // set the lookup table to all 0 - row->lookupTable[i] = 0; + row->lookupTable[i] = -1; gfxWorld->lightGrid.rawRowData = reinterpret_cast(row); // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table From 87d9c8b948d389a300d8551af70fa579245a16e6 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 11:31:06 +0800 Subject: [PATCH 105/123] chore: cleanup and minor addition --- src/ObjCommon/XModel/Gltf/JsonGltf.h | 26 ----------------------- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index d07e2bbb6..3e48b072a 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -86,32 +86,6 @@ namespace gltf NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNodeExtension, KHR_lights_punctual); - class JsonNodeExtras - { - public: - // global - std::optional flags; // value: surface flags - std::optional pathnode; // value: n/a - - // Multiplayer only - std::optional xmodel; // value: xmodel name - std::optional spawnpoint; // value: (MP)team, (ZM)spawnpoint group name - - // Zombies only - std::optional zone; // value: zone name - std::optional zspawner_group; // value: zone zspawner group name - std::optional spawnpoint_group; // value: zone zspawner group name - - std::optional zspawner; // value: zspawner group name - - // scripting - std::optional trigger_use; - std::optional trigger_multiple; - }; - - NLOHMANN_DEFINE_TYPE_EXTENSION( - JsonNodeExtras, xmodel, spawnpoint, flags, pathnode, zone, zspawner_group, spawnpoint_group, zspawner, trigger_use, trigger_multiple); - class JsonNode { public: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 9c097ce58..c4e2c3dc3 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -270,7 +270,7 @@ namespace throw GltfLoadException("Index count must be dividable by 3 for triangles"); const auto faceCount = indexCount / 3u; if (faceCount > UINT16_MAX) - throw GltfLoadException("Face count exceeded the UINT16_MAX"); + throw GltfLoadException(std::format("Face count ({}) exceeded the UINT16_MAX", faceCount)); out_surface.vertexCount = static_cast(vertexCount); out_surface.triCount = static_cast(faceCount); From d758211bd997f05b8798a58ab2856b1df6da06e7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:23:14 +0800 Subject: [PATCH 106/123] feat: script_brushmodel now works correctly --- src/ObjLoading/Game/T6/BSP/BSP.h | 5 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 51 +++++++++---------- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 38 ++++++++++---- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 4 +- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index ca3b98560..e85f50c71 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -64,8 +64,8 @@ namespace BSP struct BSPBoxBrush { - vec3_t localMins; - vec3_t localMaxs; + size_t vertexIndex; + size_t vertexCount; int surfaceFlags; int contentFlags; @@ -82,6 +82,7 @@ namespace BSP std::vector xmodels; std::vector scriptBoxBrushes; + std::vector boxBrushVerts; }; enum BSPLightType diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index c4e2c3dc3..174f41b41 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -653,6 +653,14 @@ namespace if (mesh.primitives.size() == 0) throw new GltfLoadException("Script model created with no mesh data"); + Eigen::Vector4f tempPosition(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * tempPosition; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + BSPModel model; model.isGfxModel = m_is_world_gfx; model.surfaceIndex = 0; @@ -661,11 +669,16 @@ namespace model.brushIndex = m_curr_bsp_world->scriptBoxBrushes.size(); m_bsp->models.emplace_back(model); + size_t materialIndex = m_emptyMaterialIndex; + if (node.extras && node.extras->contains("flags")) + { + bool isNoDrawFlagSet; + materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags"), isNoDrawFlagSet); + } BSPBoxBrush boxBrush; - boxBrush.contentFlags = 1; - boxBrush.surfaceFlags = 1; - vec3_t worldMins; - vec3_t worldMaxs; + boxBrush.vertexIndex = m_curr_bsp_world->boxBrushVerts.size(); + boxBrush.contentFlags = m_curr_bsp_world->materials.at(materialIndex).contentFlags; + boxBrush.surfaceFlags = m_curr_bsp_world->materials.at(materialIndex).surfaceFlags; for (size_t primIdx = 0; primIdx < mesh.primitives.size(); primIdx++) { const auto& primitive = mesh.primitives.at(primIdx); @@ -699,31 +712,15 @@ namespace vertex.z = transformedPosition.z(); RhcToLhcCoordinates(vertex.v); - if (vertexIndex == 0 && primIdx == 0) - { - worldMins = vertex; - worldMaxs = vertex; - } - else - BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); + // brush verts use local position + vertex.x -= origin.x; + vertex.y -= origin.y; + vertex.z -= origin.z; + + m_curr_bsp_world->boxBrushVerts.emplace_back(vertex); } } - - // convert world position to local position - Eigen::Vector4f position(0, 0, 0, 1.0f); - Eigen::Vector4f transformedPosition = nodeMatrix * position; - vec3_t origin; - origin.x = transformedPosition.x(); - origin.y = transformedPosition.y(); - origin.z = transformedPosition.z(); - RhcToLhcCoordinates(origin.v); - boxBrush.localMins.x = worldMins.x - origin.x; - boxBrush.localMins.y = worldMins.y - origin.y; - boxBrush.localMins.z = worldMins.z - origin.z; - boxBrush.localMaxs.x = worldMaxs.x - origin.x; - boxBrush.localMaxs.y = worldMaxs.y - origin.y; - boxBrush.localMaxs.z = worldMaxs.z - origin.z; - + boxBrush.vertexCount = m_curr_bsp_world->boxBrushVerts.size() - boxBrush.vertexIndex; m_curr_bsp_world->scriptBoxBrushes.emplace_back(boxBrush); return m_bsp->models.size(); // script model index starts at 1 diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 4b8678698..4bfbd17fe 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -291,9 +291,24 @@ namespace BSP if (bspModel.hasBrush) { + BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); - clipModel->leaf.mins = bspBrush.localMins; - clipModel->leaf.maxs = bspBrush.localMaxs; + vec3_t mins; + vec3_t maxs; + for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++) + { + vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx]; + if (vertIdx == 0) + { + mins = *vertex; + maxs = *vertex; + } + else + BSPUtil::updateAABBWithPoint(*vertex, mins, maxs); + } + + clipModel->leaf.mins = mins; + clipModel->leaf.maxs = maxs; clipModel->leaf.brushContents = bspBrush.contentFlags; clipModel->leaf.leafBrushNode = static_cast(brushNodeVec.size()); assert(clipModel->leaf.leafBrushNode != 0); @@ -308,9 +323,11 @@ namespace BSP cbrush_array_t brush; memset(&brush, 0, sizeof(cbrush_array_t)); // if not sides or verts are given, the mins/maxs are used instead + brush.numverts = static_cast(bspBrush.vertexCount); + brush.verts = &clipMap->info.brushVerts[bspBrush.vertexIndex]; brush.contents = bspBrush.contentFlags; - brush.mins = bspBrush.localMins; - brush.maxs = bspBrush.localMaxs; + brush.mins = mins; + brush.maxs = maxs; brush.axial_cflags[0][0] = bspBrush.contentFlags; brush.axial_cflags[0][1] = bspBrush.contentFlags; brush.axial_cflags[0][2] = bspBrush.contentFlags; @@ -326,11 +343,11 @@ namespace BSP brushVec.emplace_back(brush); if (bspModel.surfaceCount != 0) - BSPUtil::updateAABB(bspBrush.localMins, bspBrush.localMaxs, clipModel->mins, clipModel->maxs); + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); else { - clipModel->mins = bspBrush.localMins; - clipModel->maxs = bspBrush.localMaxs; + clipModel->mins = mins; + clipModel->maxs = maxs; } } else @@ -757,8 +774,6 @@ namespace BSP clipMap->info.brushsides = nullptr; clipMap->info.numLeafBrushes = 0; clipMap->info.leafbrushes = nullptr; - clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = nullptr; clipMap->info.brushBounds = nullptr; clipMap->info.brushContents = nullptr; @@ -767,8 +782,9 @@ namespace BSP memset(&tempNode, 0, sizeof(cLeafBrushNode_s)); brushNodeVec.emplace_back(tempNode); - clipMap->info.numBrushes = 0; - clipMap->info.brushes = nullptr; + clipMap->info.numBrushVerts = static_cast(bsp->colWorld.boxBrushVerts.size()); + clipMap->info.brushVerts = m_memory.Alloc(bsp->colWorld.boxBrushVerts.size()); + memcpy(clipMap->info.brushVerts, bsp->colWorld.boxBrushVerts.data(), sizeof(vec3_t) * bsp->colWorld.boxBrushVerts.size()); // load verts, tris, uinds and partitions if (!loadPartitions(clipMap, bsp)) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index b948f6840..2777775a5 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -606,8 +606,8 @@ namespace BSP } else { - currEntModel->startSurfIndex = 0; - currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->startSurfIndex = -1; // -1 when it doesn't use map surfaces + currEntModel->surfaceCount = 0; currEntModel->bounds[0].x = 0.0f; currEntModel->bounds[0].y = 0.0f; currEntModel->bounds[0].z = 0.0f; From 4ee10a9f0ac8a16a6732c8c5bf3dc4fb7d9ef0a4 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:23:54 +0800 Subject: [PATCH 107/123] feat: minor bug fixes --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 51 +++++++++---------- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 4 +- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 174f41b41..2935adc3d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -463,20 +463,16 @@ namespace size_t materialIndex; if (primitive.material) - { - size_t originalMaterialIdx = *primitive.material; - if (node.extras && node.extras->contains("flags")) - { - bool isNoDrawFlagSet = false; - materialIndex = createMaterialWithFlags(originalMaterialIdx, node.extras->at("flags"), isNoDrawFlagSet); - if (isNoDrawFlagSet && m_is_world_gfx) - continue; // noDraw flag doesn't work, so remove the surface from the graphics data instead - } - else - materialIndex = originalMaterialIdx; - } + materialIndex = *primitive.material; else materialIndex = m_emptyMaterialIndex; + if (node.extras && node.extras->contains("flags")) + { + bool isNoDrawFlagSet = false; + materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags"), isNoDrawFlagSet); + if (isNoDrawFlagSet && m_is_world_gfx) + continue; // noDraw flag doesn't work, so remove the surface from the graphics data instead + } BSPSurface surface; CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, false, surface); @@ -856,32 +852,35 @@ namespace assert(node.extras->contains("classname")); BSPEntity entity; - bool doesClassContainModel = false; + std::string classname = node.extras->at("classname"); + if (node.mesh && (classname.starts_with("trigger_") || !classname.compare("info_volume") || !classname.compare("script_brushmodel"))) + { + if (node.extras->contains("model")) + { + con::error("Node {} cannot have a model property when its class is a trigger, info_volume or script_brushmodel"); + return false; + } + + if (m_is_world_gfx) + entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); + else + entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); + } + else + entity.modelIndex = 0; + for (auto& element : node.extras->items()) { std::string key = element.key(); if (!key.compare("origin") || !key.compare("angles") || !key.compare("flags")) continue; - if (!key.compare("model")) - doesClassContainModel = true; - BSPEntityEntry entry; entry.key = key; entry.value = element.value(); entity.entries.emplace_back(entry); } - if (node.mesh && !doesClassContainModel) - { - if (m_is_world_gfx) - entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); - else - entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); - } - else - entity.modelIndex = 0; - Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; entity.origin.x = transformedPosition.x(); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 548cff22d..943b773e6 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -159,11 +159,13 @@ namespace entityString.append("{\n"); entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(origin))); - entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + for (auto& entry : entity.entries) entityString.append(std::format("\"{}\" \"{}\"\n", entry.key, entry.value)); if (entity.modelIndex != 0) entityString.append(std::format("\"model\" \"*{}\"\n", entity.modelIndex)); + else // entities with generated models can't have rotation data + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); entityString.append("}\n"); } From 398b4305445aa58b39074b23f948f840a5a501f9 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:12:58 +0800 Subject: [PATCH 108/123] fix: always make an a material a textured one --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 28 +++++------------------ 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 2935adc3d..f75f759e2 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -985,32 +985,16 @@ namespace else throw GltfLoadException("Materials must have a name."); - if (jsMaterial.pbrMetallicRoughness) + material.materialType = MATERIAL_TYPE_TEXTURE; + if (jsMaterial.pbrMetallicRoughness && jsMaterial.pbrMetallicRoughness->baseColorFactor) { - if (jsMaterial.pbrMetallicRoughness->baseColorFactor) - { - material.materialColour.x = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[0]; - material.materialColour.y = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[1]; - material.materialColour.z = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[2]; - material.materialColour.w = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[3]; - } - else - { - - material.materialColour.x = 1.0f; - material.materialColour.y = 1.0f; - material.materialColour.z = 1.0f; - material.materialColour.w = 1.0f; - } - - if (jsMaterial.pbrMetallicRoughness->baseColorTexture) - material.materialType = MATERIAL_TYPE_TEXTURE; - else - material.materialType = MATERIAL_TYPE_COLOUR; + material.materialColour.x = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[0]; + material.materialColour.y = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[1]; + material.materialColour.z = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[2]; + material.materialColour.w = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[3]; } else { - material.materialType = MATERIAL_TYPE_COLOUR; material.materialColour.x = 1.0f; material.materialColour.y = 1.0f; material.materialColour.z = 1.0f; From 18aaa33e3aecd943abad7932d93eebdf3c201030 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:22:45 +0800 Subject: [PATCH 109/123] feat: Loading sun data through GLTF --- src/ObjLoading/Game/T6/BSP/BSP.h | 7 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 171 +++++++------- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 208 +++++++----------- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 69 +++--- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- 5 files changed, 198 insertions(+), 259 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index e85f50c71..18c49e63c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -88,7 +88,6 @@ namespace BSP enum BSPLightType { LIGHT_TYPE_DIRECTIONAL, - LIGHT_TYPE_POINT, LIGHT_TYPE_SPOT }; @@ -101,9 +100,8 @@ namespace BSP vec3_t pos; vec3_t direction; - bool hasPosBeenSet; - // angle is in radians. only used on spot/dir lights + // angle is in radians. only used on spot lights float innerConeAngle; float outerConeAngle; }; @@ -172,6 +170,9 @@ namespace BSP std::vector zm_spawners; std::vector entities; + bool hasSunlightBeenSet; + BSPLight sunlight; + std::vector models; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index f75f759e2..fc5023984 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -359,32 +359,85 @@ namespace return vertexOffset; } - bool addLightNode(const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) + bool addLightNode(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { - assert(m_is_world_gfx); + if (!m_is_world_gfx || !jRoot.extensions || !jRoot.extensions->KHR_lights_punctual || !jRoot.extensions->KHR_lights_punctual->lights) + return false; assert(node.extensions); assert(node.extensions->KHR_lights_punctual); int lightIndex = node.extensions->KHR_lights_punctual->light; + const JsonPunctualLight& jsLight = jRoot.extensions->KHR_lights_punctual->lights->at(lightIndex); + BSPLight light{}; + + light.outerConeAngle = std::numbers::pi_v / 4.0f; /// spec of 45 degrees + light.innerConeAngle = 0.0f; + if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) + { + if (m_bsp->hasSunlightBeenSet) + con::warn("WARNING: multiple sunlight nodes found, only one will be used as the sun."); + light.type = LIGHT_TYPE_DIRECTIONAL; + m_bsp->hasSunlightBeenSet = true; + } + else if (jsLight.type == JsonPunctualLightType::POINT) + { + con::warn("Ignoring point light as BO2 does not support point lights."); + return false; + } + else // JsonPunctualLightType::SPOT + { + light.type = LIGHT_TYPE_SPOT; - assert(lightIndex >= 0); - if (m_bsp->lights[lightIndex].hasPosBeenSet == true) - con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + assert(jsLight.spot); + if (jsLight.spot->innerConeAngle) + light.innerConeAngle = *jsLight.spot->innerConeAngle; + + if (jsLight.spot->outerConeAngle) + light.outerConeAngle = *jsLight.spot->outerConeAngle; + } + + if (!jsLight.color) + { + light.colour.x = 1.0f; + light.colour.y = 1.0f; + light.colour.z = 1.0f; + } + else + { + light.colour.x = (*jsLight.color)[0]; + light.colour.y = (*jsLight.color)[1]; + light.colour.z = (*jsLight.color)[2]; + } + + if (!jsLight.intensity) + light.intensity = 1000.0f; // adjusted from spec to better match BO2 + else + light.intensity = *jsLight.intensity; + + if (!jsLight.range) + light.range = 1000.0f; // adjusted from spec to better match BO2 + else + light.range = *jsLight.range; Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; - m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; - RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + light.pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; + RhcToLhcCoordinates(light.pos.v); - // GLTF spec uses +Y up and the default light direction is straight down - Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); + Eigen::Vector3f defaultDirection(0.0f, 0.0f, 1.0f); // lights use this value for the forward angle instead in BO2 Eigen::Affine3f affineTransform(nodeMatrix); Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; outputDirection.normalize(); - m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + light.direction.x = outputDirection.x(); + light.direction.y = outputDirection.y(); + light.direction.z = outputDirection.z(); + RhcToLhcCoordinates(light.direction.v); - m_bsp->lights[lightIndex].hasPosBeenSet = true; + if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) + m_bsp->sunlight = light; + else + m_bsp->lights.emplace_back(light); return true; } @@ -588,7 +641,6 @@ namespace xmodel.rotationQuaternion.w = rotationQuat.w(); RhcToLhcQuaternion(xmodel.rotationQuaternion.v); - con::warn("XModels don't support scale currently, keep it at 1 in your editor"); xmodel.scale = 1.0f; calculateXmodelBounds(xmodel, node.mesh, nodeMatrix, jRoot); @@ -904,7 +956,7 @@ namespace bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) - return addLightNode(node, nodeMatrix); + return addLightNode(jRoot, node, nodeMatrix); if (node.extras) { @@ -1040,80 +1092,6 @@ namespace m_curr_bsp_world->materials.emplace_back(emptyMaterial); } - void LoadLights(const JsonRoot& jRoot) - { - if (!m_is_world_gfx) - return; - if (!jRoot.extensions) - return; - if (!jRoot.extensions->KHR_lights_punctual) - return; - if (!jRoot.extensions->KHR_lights_punctual->lights) - return; - - const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); - m_bsp->lights.reserve(jsLightArray.size()); - for (const JsonPunctualLight& jsLight : jsLightArray) - { - if (jsLight.type == JsonPunctualLightType::POINT) - con::error("Any point lights will be converted to a spotlight as point lights are unsupported right now."); - - BSPLight light{}; - - // position and direction data will be set during node traversal - light.hasPosBeenSet = false; - - if (!jsLight.color) - { - light.colour.x = 1.0f; - light.colour.y = 1.0f; - light.colour.z = 1.0f; - } - else - { - light.colour.x = (*jsLight.color)[0]; - light.colour.y = (*jsLight.color)[1]; - light.colour.z = (*jsLight.color)[2]; - } - - if (!jsLight.intensity) - light.intensity = 100000.0f; // adjusted from spec to better match BO2 - else - light.intensity = *jsLight.intensity; - - if (!jsLight.range) - light.range = 1000.0f; // adjusted from spec to better match BO2 - else - light.range = *jsLight.range; - - if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) - { - light.type = LIGHT_TYPE_DIRECTIONAL; - } - else if (jsLight.type == JsonPunctualLightType::POINT) - { - light.type = LIGHT_TYPE_POINT; - } - else // JsonPunctualLightType::SPOT - { - light.type = LIGHT_TYPE_SPOT; - - assert(jsLight.spot); - if (!jsLight.spot->innerConeAngle) - light.innerConeAngle = 0.0f; - else - light.innerConeAngle = *jsLight.spot->innerConeAngle; - - if (!jsLight.spot->outerConeAngle) - light.outerConeAngle = 3.14159265359f / 4.0f; /// spec of 45 degrees - else - light.outerConeAngle = *jsLight.spot->outerConeAngle; - } - - m_bsp->lights.emplace_back(light); - } - } - void TraverseNodes(const JsonRoot& jRoot) { // Make sure there are any nodes to traverse @@ -1266,9 +1244,6 @@ namespace CreateBufferViews(jRoot); CreateAccessors(jRoot); - if (isGfxWorld) // lights aren't needed in collision data - LoadLights(jRoot); - LoadMaterials(jRoot); TraverseNodes(jRoot); // requires materials and lights } @@ -1330,6 +1305,9 @@ namespace BSP bsp->name = mapName; bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; bsp->isZombiesMap = isZombiesMap; + bsp->hasSunlightBeenSet = false; + + con::warn("XModels don't support scale currently, keep it at 1 in your editor"); BSPLoader loader(bsp.get()); if (isGfxFileGltf) @@ -1375,6 +1353,19 @@ namespace BSP } } + if (!bsp->hasSunlightBeenSet) + { + con::info("Writing default sun values"); + bsp->sunlight.type = LIGHT_TYPE_DIRECTIONAL; + bsp->sunlight.colour = {1.0f, 1.0f, 1.0f}; + bsp->sunlight.range = 1000.0f; + bsp->sunlight.intensity = 1000.0f; + bsp->sunlight.pos = {0.0f, 0.0f, 0.0f}; + bsp->sunlight.direction = {0.0f, -1.0f, 0.0f}; + bsp->sunlight.innerConeAngle = 0.0f; + bsp->sunlight.outerConeAngle = 0.0f; + } + return bsp; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index c52de71e1..12284f9b8 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -15,16 +15,6 @@ namespace BSP bool ComWorldLinker::createLightDefs() { - T6::GfxLightDef* lightDefCube = m_memory.Alloc(); - lightDefCube->name = m_memory.Dup("white_light_cube"); - lightDefCube->lmapLookupStart = 0; // always 0 - lightDefCube->attenuation.samplerState = 115; // always 115 - auto imageCubeAsset = m_context.LoadDependency("whitesquare_ft"); - if (imageCubeAsset == nullptr) - return false; - lightDefCube->attenuation.image = imageCubeAsset->Asset(); - m_context.AddAsset(lightDefCube->name, lightDefCube); - T6::GfxLightDef* lightDef2d = m_memory.Alloc(); lightDef2d->name = m_memory.Dup("white_light"); lightDef2d->lmapLookupStart = 0; // always 0 @@ -38,89 +28,6 @@ namespace BSP return true; } - // does not set: type, cosHalfFovOuter, cosHalfFovInner, cosHalfFovExpanded, roundness, defName - void setLightCommonValues(ComPrimaryLight* light, BSPLight* bspLight) - { - light->origin.x = bspLight->pos.x; - light->origin.y = bspLight->pos.y; - light->origin.z = bspLight->pos.z; - - // colour also effects the brightness of the light, it's unclear what the min/max is - vec3_t BSPColor; - BSPColor.x = bspLight->colour.x; - BSPColor.y = bspLight->colour.y; - BSPColor.z = bspLight->colour.z; - light->color.x = BSPColor.x; - light->color.y = BSPColor.y; - light->color.z = BSPColor.z; - light->diffuseColor.x = BSPColor.x; - light->diffuseColor.y = BSPColor.y; - light->diffuseColor.z = BSPColor.z; - light->diffuseColor.w = 0.0f; // always 0 - - // the forward dir of the light, default is (0, 0, 1) to face down (-Z up) - light->dir.x = bspLight->direction.x; - light->dir.y = bspLight->direction.y; - light->dir.z = bspLight->direction.z; - - light->dAttenuation = bspLight->intensity; // not 100% sure if these values are calculated the same - - // the radius of the light's lens - light->radius = bspLight->range; - - light->falloff.x = 0.0f; - light->falloff.y = bspLight->range; // only Y is set - light->falloff.z = 0.0f; - light->falloff.w = 0.0f; - - // allways 0 - light->angle.x = 0.0f; - light->angle.y = 0.0f; - light->angle.z = 0.0f; - light->angle.w = 0.0f; - - // 0 - light cannot move - // 1 <= x > 0 - limit that it can move each game update - light->translationLimit = 0.0f; - - // 1.0f - light cannot rotate - // -1.0f - infinitely rotate - // between 1 and -1 - limit that it can rotate each game update - light->rotationLimit = 1.0f; - - // default values from official map - light->useCookie = 0; - light->cookieControl0.x = 0.0f; - light->cookieControl0.y = 0.0f; - light->cookieControl0.z = 1.0f; - light->cookieControl0.w = 1.0f; - light->cookieControl1.x = 0.0f; - light->cookieControl1.y = 0.0f; - light->cookieControl1.z = 0.0f; - light->cookieControl1.w = 0.0f; - light->cookieControl2.x = 0.0f; - light->cookieControl2.y = 0.0f; - light->cookieControl2.z = 0.0f; - light->cookieControl2.w = 0.0f; - - // these are possibly the result of cos(), unsure how the angle is generated though - // values taken from official maps - light->aAbB.x = 0.5303301215171814f; - light->aAbB.y = 0.7071067690849304f; - light->aAbB.z = 0.5303301215171814f; - light->aAbB.w = 0.7071067690849304f; - - // light won't light the surface unless canUseShadowMap is set to 1 - // may be to do with lights casting shadows on other lights - light->canUseShadowMap = 1; - light->shadowmapVolume = 0; // ignored if this is set to 0 - - light->exponent = 0; - light->priority = 0; - light->cullDist = 1000; - light->mipDistance = 0.0f; - } - ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { // all lights that aren't the sunlight or default light need their own GfxLightDef asset @@ -142,52 +49,93 @@ namespace BSP for (size_t lightIdx = 0; lightIdx < totalLightCount; lightIdx++) { ComPrimaryLight* light = &comWorld->primaryLights[lightIdx]; - if (lightIdx == BSPGameConstants::EMPTY_LIGHT_INDEX) continue; // first (empty) light has no data else if (lightIdx == BSPGameConstants::SUN_LIGHT_INDEX) { - const vec4_t sunLightColor = BSPEditableConstants::SUNLIGHT_COLOR; - const vec3_t sunLightDirection = BSPEditableConstants::SUNLIGHT_DIRECTION; + BSPLight* bspLight = &bsp->sunlight; + light->type = GFX_LIGHT_TYPE_DIR; - light->diffuseColor.r = sunLightColor.r; - light->diffuseColor.g = sunLightColor.g; - light->diffuseColor.a = sunLightColor.a; - light->dir.x = sunLightDirection.x; - light->dir.y = sunLightDirection.y; - light->dir.z = sunLightDirection.z; + light->dir.x = bspLight->direction.x; + light->dir.y = bspLight->direction.y; + light->dir.z = bspLight->direction.z; + light->diffuseColor.x = bspLight->colour.x; + light->diffuseColor.y = bspLight->colour.y; + light->diffuseColor.z = bspLight->colour.z; + light->color.x = bspLight->colour.x; + light->color.y = bspLight->colour.y; + light->color.z = bspLight->colour.z; } else { BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); - if (bspLight->type == LIGHT_TYPE_DIRECTIONAL) - { - light->type = GFX_LIGHT_TYPE_DIR; - light->defName = "white_light"; - light->roundness = 1.0f; - light->cosHalfFovInner = cosf(bspLight->innerConeAngle); - light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); - light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); - } - else if (bspLight->type == LIGHT_TYPE_SPOT) - { - light->type = GFX_LIGHT_TYPE_SPOT; - light->defName = "white_light"; - light->roundness = 1.0f; - light->cosHalfFovInner = cosf(bspLight->innerConeAngle); - light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); - light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); - } - else // LIGHT_TYPE_POINT - { - light->type = GFX_LIGHT_TYPE_OMNI; - light->defName = "white_light_cube"; - light->roundness = 0.0f; - light->cosHalfFovInner = cosf(30.0f * (std::numbers::pi_v / 180.0f)); - light->cosHalfFovOuter = cosf(55.0f * (std::numbers::pi_v / 180.0f)); - light->cosHalfFovExpanded = cosf(55.0f * (std::numbers::pi_v / 180.0f)); - } - setLightCommonValues(light, bspLight); + + light->aAbB.x = 0.28173786401748657f; + light->aAbB.y = 0.5634757280349731f; + light->aAbB.z = 0.5634757280349731f; + light->aAbB.w = 0.7071067690849304f; + + light->angle.x = 0.0f; + light->angle.y = 0.0f; + light->angle.z = -0.5235987901687622f; + light->angle.w = 0.0f; + + light->canUseShadowMap = 1; + + light->cookieControl0.x = 0.0f; + light->cookieControl0.y = 0.0f; + light->cookieControl0.z = 1.0f; + light->cookieControl0.w = 1.0f; + light->cookieControl1.x = 0.0f; + light->cookieControl1.y = 0.0f; + light->cookieControl1.z = 0.0f; + light->cookieControl1.w = 0.0f; + light->cookieControl2.x = 0.0f; + light->cookieControl2.y = 0.0f; + light->cookieControl2.z = 0.0f; + light->cookieControl2.w = 0.0f; + + light->color.x = bspLight->colour.x; + light->color.y = bspLight->colour.y; + light->color.z = bspLight->colour.z; + light->cosHalfFovInner = cosf(bspLight->innerConeAngle); + light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); + light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); + + light->cullDist = 1000; + light->dAttenuation = 10000.0f; + light->defName = "white_light"; + + light->diffuseColor.x = bspLight->colour.x; + light->diffuseColor.y = bspLight->colour.y; + light->diffuseColor.z = bspLight->colour.z; + light->diffuseColor.w = 0.0f; + + light->dir.x = bspLight->direction.x; + light->dir.y = bspLight->direction.y; + light->dir.z = bspLight->direction.z; + + light->exponent = 0; + + light->falloff.y = bspLight->range; + + light->mipDistance = bspLight->range; + + light->priority = 0; + + light->origin.x = bspLight->pos.x; + light->origin.y = bspLight->pos.y; + light->origin.z = bspLight->pos.z; + + light->radius = bspLight->range; + light->rotationLimit = 1.0f; + light->shadowmapVolume = 0; + light->translationLimit = 0.0f; + light->useCookie = 0; + + light->roundness = 0.0f; + + light->type = GFX_LIGHT_TYPE_SPOT; } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 2777775a5..9ad676f64 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -4,6 +4,23 @@ #include "../BSPUtil.h" #include "Utils/Pack.h" +namespace +{ + unsigned int packLmapCoord(float X, float Y) + { + float XStep = X * 65535.0f; + float YStep = Y * 65535.0f; + + unsigned int result = 0; + result |= (unsigned int)XStep; + result &= 0xFFFF; + result |= (unsigned int)YStep << 16; + result &= 0xFFFFFFFF; + + return result; + } +} // namespace + namespace BSP { GfxWorldLinker::GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -30,9 +47,7 @@ namespace BSP gfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.normal.v); gfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(bspVertex.tangent.v); gfxVertex->binormalSign = bspVertex.binormal.v[0] > 0.0f ? 1.0f : -1.0f; - - // lightmap isn't implemented currently - gfxVertex->lmapCoord.packed = 0; + gfxVertex->lmapCoord.packed = packLmapCoord(0.0f, 0.0f); } gfxWorld->draw.vd0.data = reinterpret_cast(vertexBuffer); @@ -439,14 +454,14 @@ namespace BSP gfxWorld->lightGrid.rowDataStart = m_memory.Alloc(rowDataStartSize); // Adding 0x0F so the lookup table will be 0x10 bytes in size - gfxWorld->lightGrid.rawRowDataSize = static_cast(sizeof(GfxLightGridRow) + 0x0F); + gfxWorld->lightGrid.rawRowDataSize = static_cast(sizeof(GfxLightGridRow) + 0x1FF); GfxLightGridRow* row = static_cast(m_memory.AllocRaw(gfxWorld->lightGrid.rawRowDataSize)); row->colStart = 0; - row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->colCount = 0xFFFF; row->zStart = 0; - row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->zCount = 0xFF; // 0xFF as this seems to be the sweet spot of values row->firstEntry = 0; - for (int i = 0; i < 0x10; i++) // set the lookup table to all 0 + for (int i = 0; i < 0x200; i++) // set the lookup table to all 0xFF row->lookupTable[i] = -1; gfxWorld->lightGrid.rawRowData = reinterpret_cast(row); @@ -464,7 +479,7 @@ namespace BSP // colours are looked up with a lightgrid entries colorsIndex gfxWorld->lightGrid.colorCount = 0x1000; // 0x1000 as it should be enough to hold every index gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); - memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + memset(gfxWorld->lightGrid.colors, 32, rowDataStartSize * sizeof(uint16_t)); // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; @@ -619,33 +634,17 @@ namespace BSP } } - void GfxWorldLinker::loadSunData(GfxWorld* gfxWorld) + void GfxWorldLinker::loadSunData(BSPData* bsp, GfxWorld* gfxWorld) { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = 0.001f; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + BSPLight& sunlight = bsp->sunlight; + vec3_t viewAngles = BSPUtil::convertForwardVectorToViewAngles(sunlight.direction); + gfxWorld->sunParse.initWorldSun->angles.x = viewAngles.x; + gfxWorld->sunParse.initWorldSun->angles.y = viewAngles.y; + gfxWorld->sunParse.initWorldSun->angles.z = viewAngles.z; + gfxWorld->sunParse.initWorldSun->sunCd.x = sunlight.colour.x; + gfxWorld->sunParse.initWorldSun->sunCd.y = sunlight.colour.y; + gfxWorld->sunParse.initWorldSun->sunCd.z = sunlight.colour.z; + gfxWorld->sunParse.initWorldSun->sunCd.w = 1.0f; gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; @@ -849,7 +848,7 @@ namespace BSP loadModels(bsp, gfxWorld); // requires surfaces - loadSunData(gfxWorld); + loadSunData(bsp, gfxWorld); loadDynEntData(gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index e594cd019..570df5ac8 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -31,7 +31,7 @@ namespace BSP void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); void loadDynEntData(GfxWorld* gfxWorld); bool loadOutdoors(GfxWorld* gfxWorld); - void loadSunData(GfxWorld* gfxWorld); + void loadSunData(BSPData* projInfo, GfxWorld* gfxWorld); void loadWorldBounds(GfxWorld* gfxWorld); }; } // namespace BSP From 2cf520891d80bf8526b58f0e97f9b497c71613e9 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:03:01 +0800 Subject: [PATCH 110/123] feat: proper imlementation of spot lights --- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 63 ++++++------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 12284f9b8..07ff52874 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -70,72 +70,45 @@ namespace BSP { BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); - light->aAbB.x = 0.28173786401748657f; - light->aAbB.y = 0.5634757280349731f; - light->aAbB.z = 0.5634757280349731f; - light->aAbB.w = 0.7071067690849304f; - - light->angle.x = 0.0f; - light->angle.y = 0.0f; - light->angle.z = -0.5235987901687622f; - light->angle.w = 0.0f; - - light->canUseShadowMap = 1; - - light->cookieControl0.x = 0.0f; - light->cookieControl0.y = 0.0f; - light->cookieControl0.z = 1.0f; - light->cookieControl0.w = 1.0f; - light->cookieControl1.x = 0.0f; - light->cookieControl1.y = 0.0f; - light->cookieControl1.z = 0.0f; - light->cookieControl1.w = 0.0f; - light->cookieControl2.x = 0.0f; - light->cookieControl2.y = 0.0f; - light->cookieControl2.z = 0.0f; - light->cookieControl2.w = 0.0f; + light->type = GFX_LIGHT_TYPE_SPOT; light->color.x = bspLight->colour.x; light->color.y = bspLight->colour.y; light->color.z = bspLight->colour.z; - light->cosHalfFovInner = cosf(bspLight->innerConeAngle); - light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); - light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); - - light->cullDist = 1000; - light->dAttenuation = 10000.0f; - light->defName = "white_light"; - light->diffuseColor.x = bspLight->colour.x; light->diffuseColor.y = bspLight->colour.y; light->diffuseColor.z = bspLight->colour.z; - light->diffuseColor.w = 0.0f; + + light->cosHalfFovInner = cosf(bspLight->innerConeAngle); + light->cosHalfFovOuter = cosf(bspLight->outerConeAngle); + light->cosHalfFovExpanded = cosf(bspLight->outerConeAngle); light->dir.x = bspLight->direction.x; light->dir.y = bspLight->direction.y; light->dir.z = bspLight->direction.z; - light->exponent = 0; - light->falloff.y = bspLight->range; - + light->radius = bspLight->range; light->mipDistance = bspLight->range; - light->priority = 0; - light->origin.x = bspLight->pos.x; light->origin.y = bspLight->pos.y; light->origin.z = bspLight->pos.z; - light->radius = bspLight->range; - light->rotationLimit = 1.0f; - light->shadowmapVolume = 0; - light->translationLimit = 0.0f; - light->useCookie = 0; + light->dAttenuation = bspLight->intensity; - light->roundness = 0.0f; + // I think AABB controls the lens shape of the light + light->aAbB.x = 0.75f; + light->aAbB.y = 1.0f; + light->aAbB.z = 0.75f; + light->aAbB.w = 1.0f; - light->type = GFX_LIGHT_TYPE_SPOT; + light->cullDist = 10000; + light->defName = "white_light"; + light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation + light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated + light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle + light->canUseShadowMap = 1; // light does not show up with this set to 0 } } From 9e6c76ffd503fe8c7b3a9102e45b7a2c1e3f4fda Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:20:19 +0800 Subject: [PATCH 111/123] feat: Minor fixups and improvements --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 18 ++++---------- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 24 ++++++++++++++----- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index fc5023984..e527ad59d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -1038,20 +1038,10 @@ namespace throw GltfLoadException("Materials must have a name."); material.materialType = MATERIAL_TYPE_TEXTURE; - if (jsMaterial.pbrMetallicRoughness && jsMaterial.pbrMetallicRoughness->baseColorFactor) - { - material.materialColour.x = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[0]; - material.materialColour.y = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[1]; - material.materialColour.z = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[2]; - material.materialColour.w = (*jsMaterial.pbrMetallicRoughness->baseColorFactor)[3]; - } - else - { - material.materialColour.x = 1.0f; - material.materialColour.y = 1.0f; - material.materialColour.z = 1.0f; - material.materialColour.w = 1.0f; - } + material.materialColour.x = 1.0f; + material.materialColour.y = 1.0f; + material.materialColour.z = 1.0f; + material.materialColour.w = 1.0f; material.surfaceFlags = 0; material.contentFlags = 1; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 9ad676f64..e44007833 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -429,7 +429,7 @@ namespace BSP gfxWorld->primaryLightEntityShadowVis = nullptr; } - void GfxWorldLinker::loadLightGrid(GfxWorld* gfxWorld) + void GfxWorldLinker::loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld) { // gfxWorld->lightGrid.mins[0] = static_cast((static_cast(gfxWorld->lightGrid.mins[0]) + 0x20000) >> 5); // gfxWorld->lightGrid.mins[1] = static_cast((static_cast(gfxWorld->lightGrid.mins[1]) + 0x20000) >> 5); @@ -453,7 +453,7 @@ namespace BSP int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; gfxWorld->lightGrid.rowDataStart = m_memory.Alloc(rowDataStartSize); - // Adding 0x0F so the lookup table will be 0x10 bytes in size + // Adding 0x1FF so the lookup table will be 0x200 bytes in size gfxWorld->lightGrid.rawRowDataSize = static_cast(sizeof(GfxLightGridRow) + 0x1FF); GfxLightGridRow* row = static_cast(m_memory.AllocRaw(gfxWorld->lightGrid.rawRowDataSize)); row->colStart = 0; @@ -476,10 +476,22 @@ namespace BSP } gfxWorld->lightGrid.entries = entryArray; + char color = 32; + float sunIntensity = bsp->sunlight.intensity; + if (sunIntensity < 0.0f || sunIntensity > 20000.0f) + con::warn("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); + else + { + float normalised = sunIntensity / 20000.0f; + normalised *= 255.0f; + float clean = roundf(normalised); + color = static_cast(clean); + } + // colours are looked up with a lightgrid entries colorsIndex gfxWorld->lightGrid.colorCount = 0x1000; // 0x1000 as it should be enough to hold every index gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); - memset(gfxWorld->lightGrid.colors, 32, rowDataStartSize * sizeof(uint16_t)); + memset(gfxWorld->lightGrid.colors, color, rowDataStartSize * sizeof(uint16_t)); // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; @@ -692,7 +704,7 @@ namespace BSP gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr; - std::string probeImageName = "reflection_probe0"; + std::string probeImageName = "$white"; auto probeImageAsset = m_context.LoadDependency(probeImageName); if (probeImageAsset == nullptr) { @@ -711,7 +723,7 @@ namespace BSP gfxWorld->draw.lightmapPrimaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); gfxWorld->draw.lightmapSecondaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); - std::string secondaryTexture = "lightmap0_secondary"; + std::string secondaryTexture = ",$white"; auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); if (secondaryTextureAsset == nullptr) { @@ -842,7 +854,7 @@ namespace BSP // gfx cells depend on surface/smodel count loadGfxCells(gfxWorld); - loadLightGrid(gfxWorld); + loadLightGrid(bsp, gfxWorld); loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 570df5ac8..1ed12d562 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -23,7 +23,7 @@ namespace BSP bool loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld); void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); - void loadLightGrid(GfxWorld* gfxWorld); + void loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld); void loadModels(BSPData* bsp, GfxWorld* gfxWorld); bool loadReflectionProbeData(GfxWorld* gfxWorld); From 6e04b4b6930c1296eca2afb87bda36b1e37f7996 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:48:51 +0800 Subject: [PATCH 112/123] feat: removed ents file, worldspawn and intermission are added through gltf nodes now --- src/ObjLoading/Game/T6/BSP/BSP.h | 10 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 37 +++++- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 113 ++++-------------- 3 files changed, 63 insertions(+), 97 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 18c49e63c..3cf05271c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -164,14 +164,18 @@ namespace BSP BSPWorld gfxWorld; BSPWorld colWorld; + bool hasSunlightBeenSet; + BSPLight sunlight; std::vector lights; + std::vector spawnpoints; std::vector zm_zones; std::vector zm_spawners; - std::vector entities; - bool hasSunlightBeenSet; - BSPLight sunlight; + bool containsWorldspawn; + bool containsIntermssion; + std::vector entities; + BSPEntity worldspawn; std::vector models; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e527ad59d..b99a34c22 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -374,10 +374,7 @@ namespace light.innerConeAngle = 0.0f; if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) { - if (m_bsp->hasSunlightBeenSet) - con::warn("WARNING: multiple sunlight nodes found, only one will be used as the sun."); light.type = LIGHT_TYPE_DIRECTIONAL; - m_bsp->hasSunlightBeenSet = true; } else if (jsLight.type == JsonPunctualLightType::POINT) { @@ -435,7 +432,12 @@ namespace RhcToLhcCoordinates(light.direction.v); if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) + { + if (m_bsp->hasSunlightBeenSet) + con::warn("WARNING: multiple sunlight nodes found, only one will be used as the sun."); m_bsp->sunlight = light; + m_bsp->hasSunlightBeenSet = true; + } else m_bsp->lights.emplace_back(light); @@ -949,7 +951,21 @@ namespace entity.rotationQuaternion.w = rotationQuat.w(); RhcToLhcQuaternion(entity.rotationQuaternion.v); - m_bsp->entities.emplace_back(entity); + if (!classname.compare("worldspawn")) + { + if (m_bsp->containsWorldspawn) + con::warn("WARNING: multiple worldspawn classes found, only one will be used."); + m_bsp->worldspawn = entity; + m_bsp->containsWorldspawn = true; + } + else if (!classname.compare("mp_global_intermission")) + { + if (m_bsp->containsIntermssion) + con::warn("WARNING: multiple mp_global_intermission classes found, only one will be used."); + m_bsp->containsIntermssion = true; + } + else + m_bsp->entities.emplace_back(entity); return true; } @@ -1296,6 +1312,8 @@ namespace BSP bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; bsp->isZombiesMap = isZombiesMap; bsp->hasSunlightBeenSet = false; + bsp->containsIntermssion = false; + bsp->containsWorldspawn = false; con::warn("XModels don't support scale currently, keep it at 1 in your editor"); @@ -1356,6 +1374,17 @@ namespace BSP bsp->sunlight.outerConeAngle = 0.0f; } + if (!bsp->containsIntermssion) + { + con::error("Map does not contain a mp_global_intermission class"); + return nullptr; + } + if (!bsp->containsWorldspawn) + { + con::error("Map does not contain a worldspawn class"); + return nullptr; + } + return bsp; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 943b773e6..7556e3ce9 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -7,38 +7,6 @@ using namespace nlohmann; namespace { - bool addJSONToEntString(json& entArrayJs, std::string& entityString) - { - for (size_t entIdx = 0; entIdx < entArrayJs.size(); entIdx++) - { - auto& entity = entArrayJs[entIdx]; - - if (entIdx == 0) - { - std::string className; - entity.at("classname").get_to(className); - if (className.compare("worldspawn") != 0) - { - con::error("ERROR: first entity in the map entity string must be the worldspawn class!"); - return false; - } - } - - entityString.append("{\n"); - - for (auto& element : entity.items()) - { - std::string key = element.key(); - std::string value = element.value(); - entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); - } - - entityString.append("}\n"); - } - - return true; - } - inline const std::vector DEFENDER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_allies", "mp_ctf_spawn_allies_start", "mp_sd_spawn_defender", @@ -170,24 +138,6 @@ namespace entityString.append("}\n"); } } - - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ - "entities": [ - { - "classname": "worldspawn" - }, - { - "angles": "0 0 0", - "classname": "info_player_start", - "origin": "0 0 0" - }, - { - "angles": "0 0 0", - "classname": "mp_global_intermission", - "origin": "0 0 0" - } - ] - })"; } // namespace namespace BSP @@ -201,52 +151,35 @@ namespace BSP MapEnts* MapEntsLinker::linkMapEnts(BSPData* bsp) { - try - { - json entJs; - std::string entityFileName = "entities.json"; - std::string entityFilePath = BSPUtil::getFileNameForBSPAsset(entityFileName); - const auto entFile = m_search_path.Open(entityFilePath); - if (!entFile.IsOpen()) - { - con::warn("Can't find entity file {}, using default entities instead", entityFilePath); - entJs = json::parse(DEFAULT_MAP_ENTS_STRING); - } - else - { - entJs = json::parse(*entFile.m_stream); - } - std::string entityString; - if (!addJSONToEntString(entJs["entities"], entityString)) - return nullptr; + std::string entityString; - addSpawnsToEntString(bsp, entityString); + // worldspawn must be the first entity + entityString.append("{\n"); + for (auto& entry : bsp->worldspawn.entries) + entityString.append(std::format("\"{}\" \"{}\"\n", entry.key, entry.value)); + entityString.append("}\n"); - if (bsp->isZombiesMap) - addZombiesEntitiesToEntString(bsp, entityString); + addSpawnsToEntString(bsp, entityString); - addClassEntitiesToEntString(bsp, entityString); + if (bsp->isZombiesMap) + addZombiesEntitiesToEntString(bsp, entityString); - MapEnts* mapEnts = m_memory.Alloc(); - mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); + addClassEntitiesToEntString(bsp, entityString); - mapEnts->entityString = m_memory.Dup(entityString.c_str()); - mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character + MapEnts* mapEnts = m_memory.Alloc(); + mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); - // don't need these, unused by the game - mapEnts->trigger.count = 0; - mapEnts->trigger.models = nullptr; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = nullptr; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = nullptr; + mapEnts->entityString = m_memory.Dup(entityString.c_str()); + mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character - return mapEnts; - } - catch (const json::exception& e) - { - con::error("JSON error when parsing map ents and spawns: {}", e.what()); - return nullptr; - } + // don't need these, unused by the game + mapEnts->trigger.count = 0; + mapEnts->trigger.models = nullptr; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = nullptr; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = nullptr; + + return mapEnts; } } // namespace BSP From 935db3f841e30e615099899750c080349a2803f0 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:13:21 +0800 Subject: [PATCH 113/123] feat: removed outdoor imagem dependency --- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index e44007833..38142310d 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -807,11 +807,11 @@ namespace BSP gfxWorld->outdoorLookupMatrix[3].z = zScale; gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - std::string outdoorImageName = std::string("$outdoor"); + std::string outdoorImageName = std::string("$white"); auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); if (outdoorImageAsset == nullptr) { - con::error("ERROR! unable to find outdoor image $outdoor!"); + con::error("ERROR! unable to find outdoor image!"); return false; } gfxWorld->outdoorImage = outdoorImageAsset->Asset(); From 83dae99e0f733ba0fab558edb938f5c92a79b1ba Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:52:22 +0800 Subject: [PATCH 114/123] feat: proper implementation of GFX models and refactor of node ignore/allow --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 105 ++++++++++++++-------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index b99a34c22..157e7db94 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -444,7 +444,30 @@ namespace return true; } - size_t createMaterialWithFlags(size_t originalMaterialIdx, const std::string& flags, bool& isNoDrawFlagSet) + bool getSurfaceAndContentFlags(const std::string& flagStr, int& surfaceFlags, int& contentFlags) + { + bool matchedAnyFlag = false; + std::vector flagStrVec = utils::StringSplit(flagStr, ','); + for (std::string& flag : flagStrVec) + { + utils::MakeStringLowerCase(flag); + utils::StringTrim(flag); + for (size_t typeIdx = 0; typeIdx < BSPFlags::SURF_TYPE_COUNT; typeIdx++) + { + if (!flag.compare(BSPFlags::surfaceTypeToNameMap[typeIdx])) + { + BSPFlags::s_SurfaceTypeFlags flags = BSPFlags::surfaceTypeToFlagMap[typeIdx]; + surfaceFlags |= flags.surfaceFlags; + contentFlags |= flags.contentFlags; + matchedAnyFlag = true; + break; + } + } + } + return matchedAnyFlag; + } + + size_t createMaterialWithFlags(size_t originalMaterialIdx, const std::string& flags) { BSPMaterial newMaterial = m_curr_bsp_world->materials.at(originalMaterialIdx); @@ -481,11 +504,6 @@ namespace else newMaterial.contentFlags |= 1; - if ((newMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) != 0) - isNoDrawFlagSet = true; - else - isNoDrawFlagSet = false; - size_t newMaterialIndex = m_curr_bsp_world->materials.size(); m_curr_bsp_world->materials.emplace_back(newMaterial); return newMaterialIndex; @@ -522,12 +540,7 @@ namespace else materialIndex = m_emptyMaterialIndex; if (node.extras && node.extras->contains("flags")) - { - bool isNoDrawFlagSet = false; - materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags"), isNoDrawFlagSet); - if (isNoDrawFlagSet && m_is_world_gfx) - continue; // noDraw flag doesn't work, so remove the surface from the graphics data instead - } + materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags")); BSPSurface surface; CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, false, surface); @@ -695,6 +708,8 @@ namespace size_t addScriptBrushModel(const JsonRoot& jRoot, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix) { + assert(!m_is_world_gfx); + if (!node.mesh || !jRoot.meshes) throw new GltfLoadException("Script model created with no mesh data"); @@ -721,10 +736,7 @@ namespace size_t materialIndex = m_emptyMaterialIndex; if (node.extras && node.extras->contains("flags")) - { - bool isNoDrawFlagSet; - materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags"), isNoDrawFlagSet); - } + materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags")); BSPBoxBrush boxBrush; boxBrush.vertexIndex = m_curr_bsp_world->boxBrushVerts.size(); boxBrush.contentFlags = m_curr_bsp_world->materials.at(materialIndex).contentFlags; @@ -818,10 +830,7 @@ namespace { size_t originalMaterialIdx = *primitive.material; if (node.extras && node.extras->contains("flags")) - { - bool isNoDrawFlagSet; - materialIndex = createMaterialWithFlags(originalMaterialIdx, node.extras->at("flags"), isNoDrawFlagSet); - } + materialIndex = createMaterialWithFlags(originalMaterialIdx, node.extras->at("flags")); else materialIndex = originalMaterialIdx; } @@ -906,32 +915,37 @@ namespace assert(node.extras->contains("classname")); BSPEntity entity; - std::string classname = node.extras->at("classname"); - if (node.mesh && (classname.starts_with("trigger_") || !classname.compare("info_volume") || !classname.compare("script_brushmodel"))) - { - if (node.extras->contains("model")) - { - con::error("Node {} cannot have a model property when its class is a trigger, info_volume or script_brushmodel"); - return false; - } - if (m_is_world_gfx) - entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); - else - entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); - } + std::string classname = node.extras->at("classname"); + if (!classname.compare("script_brushmodel_gfx") || !classname.compare("script_brushmodel_tris")) + entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); + else if ((!classname.compare("script_brushmodel_box") || !classname.compare("script_brushmodel")) + || (classname.starts_with("trigger_") || !classname.compare("info_volume"))) + entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); else entity.modelIndex = 0; + if (entity.modelIndex != 0 && node.extras->contains("model")) + { + con::error("Node {} cannot have a model property when its class is a trigger, info_volume, script_brushmodel_gfx, script_brushmodel_tris, " + "script_brushmodel_box or script_brushmodel"); + return false; + } + for (auto& element : node.extras->items()) { std::string key = element.key(); + std::string value = element.value(); if (!key.compare("origin") || !key.compare("angles") || !key.compare("flags")) continue; + if (!key.compare("classname") + && (!value.compare("script_brushmodel_gfx") || !value.compare("script_brushmodel_tris") || !value.compare("script_brushmodel_box"))) + value = "script_brushmodel"; + BSPEntityEntry entry; entry.key = key; - entry.value = element.value(); + entry.value = value; entity.entries.emplace_back(entry); } @@ -976,11 +990,17 @@ namespace if (node.extras) { - if (!m_is_world_gfx) + if (node.extras->contains("classname")) { - if (node.extras->contains("classname")) + std::string classnameVal = node.extras->at("classname"); + + if ((m_is_world_gfx && !classnameVal.compare("script_brushmodel_gfx")) // make sure only GFX loads script_brushmodel_gfx + || (!m_is_world_gfx && classnameVal.compare("script_brushmodel_gfx"))) return addClassNode(jRoot, node, nodeMatrix); + } + if (!m_is_world_gfx) + { if (node.extras->contains("spawnpoint")) return addSpawnPointNode(node, nodeMatrix); @@ -999,7 +1019,16 @@ namespace } if (node.mesh) - return addMeshNode(jRoot, node, nodeMatrix); + { + int surfaceFlags = 0; + int contentFlags = 0; + if (node.extras && node.extras->contains("flags")) + getSurfaceAndContentFlags(node.extras->at("flags"), surfaceFlags, contentFlags); + + if (!m_is_world_gfx + || ((surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) == 0)) // ignore if gfx mesh has nodraw flag + return addMeshNode(jRoot, node, nodeMatrix); + } return false; } @@ -1060,7 +1089,7 @@ namespace material.materialColour.w = 1.0f; material.surfaceFlags = 0; - material.contentFlags = 1; + material.contentFlags = 0; if (jsMaterial.extras && jsMaterial.extras->type) { bool foundType = false; From 682af9e39b1e8d5db8539c29ce2879d0960a48fc Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:21:38 +0800 Subject: [PATCH 115/123] feat: use xmodels to calculate skinnedverts --- src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp index 33dba83e2..99171aef9 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -12,10 +12,9 @@ namespace BSP SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) { // Pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have - // But setting it to the world vertex count seems to work SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); skinnedVerts->name = m_memory.Dup("skinnedverts"); - skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.vertices.size()); + skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.xmodels.size() * 1000); return skinnedVerts; } From 29f6ae2070678b2bb54d0e89da6eef36f16ccfc3 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:30:59 +0800 Subject: [PATCH 116/123] refactor: remove extra classes from BSP.h and move vars to their respective files --- src/ObjLoading/Game/T6/BSP/BSP.h | 67 +--- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 378 +++++++++--------- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 22 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 34 +- 4 files changed, 229 insertions(+), 272 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 3cf05271c..385ce73eb 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -180,65 +180,10 @@ namespace BSP std::vector models; }; - // BSPGameConstants: - // These values are hardcoded ingame and will break the map if they are changed - namespace BSPGameConstants - { - constexpr unsigned int MAX_COLLISION_VERTS = UINT16_MAX; - - constexpr size_t MAX_AABB_TREE_CHILDREN = 128; - - enum BSPDefaultLights - { - EMPTY_LIGHT_INDEX = 0, - SUN_LIGHT_INDEX = 1, - BSP_DEFAULT_LIGHT_COUNT = 2 - }; - } // namespace BSPGameConstants - - // BSPLinkingConstants: - // These values are BSP linking constants that are required for the link to be successful - namespace BSPLinkingConstants - { - constexpr const char* MISSING_IMAGE_NAME = ",mc/lambert1"; - constexpr const char* COLOR_ONLY_IMAGE_NAME = ",mc/lambert1"; - } // namespace BSPLinkingConstants - - // BSPEditableConstants: - // These values are BSP constants that can be edited and may not break the linker/game if changed - namespace BSPEditableConstants - { - // Default xmodel values - // Unused as there is no support for xmodels right now - constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; - constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; - constexpr int DEFAULT_SMODEL_LIGHT = 1; - constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; - - // Default surface values - constexpr int DEFAULT_SURFACE_LIGHT = 1; - constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; - constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; - constexpr int DEFAULT_SURFACE_FLAGS = 0; - - // material flags determine the features of a surface - // unsure which flag type changes what right now - // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast - // 1 results in: normal surface features, grenades work, seems normal - constexpr int MATERIAL_SURFACE_FLAGS = 1; - constexpr int MATERIAL_CONTENT_FLAGS = 1; - - // terrain/world flags: does not change the type of terrain or what features they have - // from testing, as long at it isn't 0 things will work correctly - constexpr int LEAF_TERRAIN_CONTENTS = 1; - constexpr int WORLD_TERRAIN_CONTENTS = 1; - - // lightgrid (global) lighting colour - // since lightgrids are not well understood, this colour is used for the R, G and B values right now - constexpr unsigned char LIGHTGRID_COLOUR = 128; - - // Sunlight values - constexpr vec4_t SUNLIGHT_COLOR = {0.75f, 0.75f, 0.75f, 1.0f}; - constexpr vec3_t SUNLIGHT_DIRECTION = {0.0f, 0.0f, 0.0f}; - }; // namespace BSPEditableConstants + enum BSPDefaultLights + { + EMPTY_LIGHT_INDEX = 0, + SUN_LIGHT_INDEX = 1, + BSP_DEFAULT_LIGHT_COUNT = 2 + }; } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 4bfbd17fe..197085d16 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -2,6 +2,17 @@ #include "../BSPUtil.h" +namespace +{ + struct uniqueMatData + { + size_t materialIndex; + std::vector partitionIndexes; + }; + + constexpr size_t MAX_AABB_TREE_CHILDREN = 128; +} // namespace + namespace BSP { ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -202,179 +213,6 @@ namespace BSP return true; } - void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs); - void addAABBTreeFromPartitions( - clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); - - void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) - { - // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) - clipMap->numSubModels = static_cast(bsp->models.size() + 1); - clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); - - // first model is always the world model - for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) - { - vec3_t vertex = clipMap->verts[vertIdx]; - if (vertIdx == 0) - { - clipMap->cmodels[0].mins = vertex; - clipMap->cmodels[0].maxs = vertex; - } - else - BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); - } - clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - // The world sub model has no data apart from the bounds - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = 0; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = nullptr; - - for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) - { - auto clipModel = &clipMap->cmodels[modelIdx + 1]; - auto& bspModel = bsp->models.at(modelIdx); - - if (bspModel.isGfxModel) - { - memset(clipModel, 0, sizeof(cmodel_t)); - continue; - } - - if (bspModel.surfaceCount != 0) - { - std::vector partitionIndexes; - for (size_t surfIdx = 0; surfIdx < bspModel.surfaceCount; surfIdx++) - { - ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspModel.surfaceIndex + surfIdx); - for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) - { - size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; - partitionIndexes.emplace_back(clipMapPartitionIndex); - vec3_t mins; - vec3_t maxs; - calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); - if (surfIdx == 0 && partitionIdx == 0) - { - clipModel->mins = mins; - clipModel->maxs = maxs; - } - else - BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); - } - } - - int terrainContents = 0; - size_t firstCollAabbIndex = 0; - size_t collAabbCount = 0; - addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); - clipModel->leaf.terrainContents = terrainContents; - clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); - clipModel->leaf.collAabbCount = static_cast(collAabbCount); - } - else - { - clipModel->leaf.terrainContents = 0; - clipModel->leaf.firstCollAabbIndex = 0; - clipModel->leaf.collAabbCount = 0; - } - - if (bspModel.hasBrush) - { - - BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); - vec3_t mins; - vec3_t maxs; - for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++) - { - vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx]; - if (vertIdx == 0) - { - mins = *vertex; - maxs = *vertex; - } - else - BSPUtil::updateAABBWithPoint(*vertex, mins, maxs); - } - - clipModel->leaf.mins = mins; - clipModel->leaf.maxs = maxs; - clipModel->leaf.brushContents = bspBrush.contentFlags; - clipModel->leaf.leafBrushNode = static_cast(brushNodeVec.size()); - assert(clipModel->leaf.leafBrushNode != 0); - - cLeafBrushNode_s brushNode; - brushNode.axis = 0; - brushNode.contents = bspBrush.contentFlags; - brushNode.leafBrushCount = 1; - brushNode.data.leaf.brushes = m_memory.Alloc(1); - brushNode.data.leaf.brushes[0] = static_cast(brushVec.size()); - brushNodeVec.emplace_back(brushNode); - - cbrush_array_t brush; - memset(&brush, 0, sizeof(cbrush_array_t)); // if not sides or verts are given, the mins/maxs are used instead - brush.numverts = static_cast(bspBrush.vertexCount); - brush.verts = &clipMap->info.brushVerts[bspBrush.vertexIndex]; - brush.contents = bspBrush.contentFlags; - brush.mins = mins; - brush.maxs = maxs; - brush.axial_cflags[0][0] = bspBrush.contentFlags; - brush.axial_cflags[0][1] = bspBrush.contentFlags; - brush.axial_cflags[0][2] = bspBrush.contentFlags; - brush.axial_cflags[1][0] = bspBrush.contentFlags; - brush.axial_cflags[1][1] = bspBrush.contentFlags; - brush.axial_cflags[1][2] = bspBrush.contentFlags; - brush.axial_sflags[0][0] = bspBrush.surfaceFlags; - brush.axial_sflags[0][1] = bspBrush.surfaceFlags; - brush.axial_sflags[0][2] = bspBrush.surfaceFlags; - brush.axial_sflags[1][0] = bspBrush.surfaceFlags; - brush.axial_sflags[1][1] = bspBrush.surfaceFlags; - brush.axial_sflags[1][2] = bspBrush.surfaceFlags; - brushVec.emplace_back(brush); - - if (bspModel.surfaceCount != 0) - BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); - else - { - clipModel->mins = mins; - clipModel->maxs = maxs; - } - } - else - { - clipModel->leaf.brushContents = 0; - clipModel->leaf.mins.x = 0.0f; - clipModel->leaf.mins.y = 0.0f; - clipModel->leaf.mins.z = 0.0f; - clipModel->leaf.maxs.x = 0.0f; - clipModel->leaf.maxs.y = 0.0f; - clipModel->leaf.maxs.z = 0.0f; - clipModel->leaf.leafBrushNode = 0; - } - - if (bspModel.surfaceCount == 0 && !bspModel.hasBrush) - { - clipModel->mins = {0.0f, 0.0f, 0.0f}; - clipModel->maxs = {0.0f, 0.0f, 0.0f}; - clipModel->radius = 0.0f; - } - else - clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; - clipModel->leaf.cluster = 0; - clipModel->info = nullptr; - } - } - // out_mins and out_maxs are initialised in the function void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs) { @@ -394,12 +232,6 @@ namespace BSP } } - struct uniqueMatData - { - size_t materialIndex; - std::vector partitionIndexes; - }; - void ClipMapLinker::addAABBTreeFromPartitions( clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) { @@ -438,8 +270,8 @@ namespace BSP for (auto& matData : uniqueMaterials) { size_t objCount = matData.partitionIndexes.size(); - size_t result = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t result = objCount / MAX_AABB_TREE_CHILDREN; + size_t remainder = objCount % MAX_AABB_TREE_CHILDREN; if (remainder > 0) result++; totalParentCount += result; @@ -454,8 +286,8 @@ namespace BSP for (auto& matData : uniqueMaterials) { size_t matPartCount = matData.partitionIndexes.size(); - size_t parentCount = matPartCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; - size_t remainder = matPartCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t parentCount = matPartCount / MAX_AABB_TREE_CHILDREN; + size_t remainder = matPartCount % MAX_AABB_TREE_CHILDREN; if (remainder > 0) parentCount++; @@ -463,11 +295,11 @@ namespace BSP size_t addedObjectCount = 0; for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) { - size_t currChildObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; - if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) + size_t currChildObjectCount = MAX_AABB_TREE_CHILDREN; + if (unaddedObjectCount <= MAX_AABB_TREE_CHILDREN) currChildObjectCount = unaddedObjectCount; else - unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; + unaddedObjectCount -= MAX_AABB_TREE_CHILDREN; vec3_t parentMins; vec3_t parentMaxs; @@ -680,10 +512,9 @@ namespace BSP { // due to tris using uint16_t as the type for indexing the vert array, // any vertex count over the uint16_t max means the vertices above the uint16_t max can't be indexed - if (static_cast(bsp->colWorld.vertices.size()) > BSPGameConstants::MAX_COLLISION_VERTS) + if (static_cast(bsp->colWorld.vertices.size()) > UINT16_MAX) { - con::error( - "ERROR: collision vertex count {} exceeds the maximum number: {}!\n", bsp->colWorld.vertices.size(), BSPGameConstants::MAX_COLLISION_VERTS); + con::error("ERROR: collision vertex count {} exceeds the maximum number: {}!\n", bsp->colWorld.vertices.size(), UINT16_MAX); return false; } @@ -826,6 +657,175 @@ namespace BSP return true; } + void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) + { + // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) + clipMap->numSubModels = static_cast(bsp->models.size() + 1); + clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + + // first model is always the world model + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) + { + vec3_t vertex = clipMap->verts[vertIdx]; + if (vertIdx == 0) + { + clipMap->cmodels[0].mins = vertex; + clipMap->cmodels[0].maxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); + } + clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + // The world sub model has no data apart from the bounds + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = 0; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = nullptr; + + for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) + { + auto clipModel = &clipMap->cmodels[modelIdx + 1]; + auto& bspModel = bsp->models.at(modelIdx); + + if (bspModel.isGfxModel) + { + memset(clipModel, 0, sizeof(cmodel_t)); + continue; + } + + if (bspModel.surfaceCount != 0) + { + std::vector partitionIndexes; + for (size_t surfIdx = 0; surfIdx < bspModel.surfaceCount; surfIdx++) + { + ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspModel.surfaceIndex + surfIdx); + for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) + { + size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; + partitionIndexes.emplace_back(clipMapPartitionIndex); + vec3_t mins; + vec3_t maxs; + calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); + if (surfIdx == 0 && partitionIdx == 0) + { + clipModel->mins = mins; + clipModel->maxs = maxs; + } + else + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); + } + } + + int terrainContents = 0; + size_t firstCollAabbIndex = 0; + size_t collAabbCount = 0; + addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); + clipModel->leaf.terrainContents = terrainContents; + clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); + clipModel->leaf.collAabbCount = static_cast(collAabbCount); + } + else + { + clipModel->leaf.terrainContents = 0; + clipModel->leaf.firstCollAabbIndex = 0; + clipModel->leaf.collAabbCount = 0; + } + + if (bspModel.hasBrush) + { + + BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); + vec3_t mins; + vec3_t maxs; + for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++) + { + vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx]; + if (vertIdx == 0) + { + mins = *vertex; + maxs = *vertex; + } + else + BSPUtil::updateAABBWithPoint(*vertex, mins, maxs); + } + + clipModel->leaf.mins = mins; + clipModel->leaf.maxs = maxs; + clipModel->leaf.brushContents = bspBrush.contentFlags; + clipModel->leaf.leafBrushNode = static_cast(brushNodeVec.size()); + assert(clipModel->leaf.leafBrushNode != 0); + + cLeafBrushNode_s brushNode; + brushNode.axis = 0; + brushNode.contents = bspBrush.contentFlags; + brushNode.leafBrushCount = 1; + brushNode.data.leaf.brushes = m_memory.Alloc(1); + brushNode.data.leaf.brushes[0] = static_cast(brushVec.size()); + brushNodeVec.emplace_back(brushNode); + + cbrush_array_t brush; + memset(&brush, 0, sizeof(cbrush_array_t)); // if not sides or verts are given, the mins/maxs are used instead + brush.numverts = static_cast(bspBrush.vertexCount); + brush.verts = &clipMap->info.brushVerts[bspBrush.vertexIndex]; + brush.contents = bspBrush.contentFlags; + brush.mins = mins; + brush.maxs = maxs; + brush.axial_cflags[0][0] = bspBrush.contentFlags; + brush.axial_cflags[0][1] = bspBrush.contentFlags; + brush.axial_cflags[0][2] = bspBrush.contentFlags; + brush.axial_cflags[1][0] = bspBrush.contentFlags; + brush.axial_cflags[1][1] = bspBrush.contentFlags; + brush.axial_cflags[1][2] = bspBrush.contentFlags; + brush.axial_sflags[0][0] = bspBrush.surfaceFlags; + brush.axial_sflags[0][1] = bspBrush.surfaceFlags; + brush.axial_sflags[0][2] = bspBrush.surfaceFlags; + brush.axial_sflags[1][0] = bspBrush.surfaceFlags; + brush.axial_sflags[1][1] = bspBrush.surfaceFlags; + brush.axial_sflags[1][2] = bspBrush.surfaceFlags; + brushVec.emplace_back(brush); + + if (bspModel.surfaceCount != 0) + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); + else + { + clipModel->mins = mins; + clipModel->maxs = maxs; + } + } + else + { + clipModel->leaf.brushContents = 0; + clipModel->leaf.mins.x = 0.0f; + clipModel->leaf.mins.y = 0.0f; + clipModel->leaf.mins.z = 0.0f; + clipModel->leaf.maxs.x = 0.0f; + clipModel->leaf.maxs.y = 0.0f; + clipModel->leaf.maxs.z = 0.0f; + clipModel->leaf.leafBrushNode = 0; + } + + if (bspModel.surfaceCount == 0 && !bspModel.hasBrush) + { + clipModel->mins = {0.0f, 0.0f, 0.0f}; + clipModel->maxs = {0.0f, 0.0f, 0.0f}; + clipModel->radius = 0.0f; + } + else + clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; + clipModel->leaf.cluster = 0; + clipModel->info = nullptr; + } + } + bool ClipMapLinker::loadMaterials(clipMap_t* clipMap, BSPData* bsp) { // Clipmap materials define the properties of a material (bullet penetration, no collision, water, etc) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 07ff52874..8672f8bc6 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -4,6 +4,12 @@ #include +namespace +{ + constexpr const char* DEFAULT_LIGHTDEF_NAME = "white_light"; + constexpr short LIGHT_CULLDIST = 10000; +} // namespace + namespace BSP { ComWorldLinker::ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -16,10 +22,10 @@ namespace BSP bool ComWorldLinker::createLightDefs() { T6::GfxLightDef* lightDef2d = m_memory.Alloc(); - lightDef2d->name = m_memory.Dup("white_light"); + lightDef2d->name = m_memory.Dup(DEFAULT_LIGHTDEF_NAME); lightDef2d->lmapLookupStart = 0; // always 0 lightDef2d->attenuation.samplerState = 115; // always 115 - auto image2dAsset = m_context.LoadDependency("whitesquare"); + auto image2dAsset = m_context.LoadDependency(",$white"); if (image2dAsset == nullptr) return false; lightDef2d->attenuation.image = image2dAsset->Asset(); @@ -36,7 +42,7 @@ namespace BSP comWorld->isInUse = 1; // first two lights are the empty light and the sun light. - size_t totalLightCount = bsp->lights.size() + BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; + size_t totalLightCount = bsp->lights.size() + BSP_DEFAULT_LIGHT_COUNT; comWorld->primaryLightCount = static_cast(totalLightCount); comWorld->primaryLights = m_memory.Alloc(totalLightCount); @@ -49,9 +55,9 @@ namespace BSP for (size_t lightIdx = 0; lightIdx < totalLightCount; lightIdx++) { ComPrimaryLight* light = &comWorld->primaryLights[lightIdx]; - if (lightIdx == BSPGameConstants::EMPTY_LIGHT_INDEX) + if (lightIdx == EMPTY_LIGHT_INDEX) continue; // first (empty) light has no data - else if (lightIdx == BSPGameConstants::SUN_LIGHT_INDEX) + else if (lightIdx == SUN_LIGHT_INDEX) { BSPLight* bspLight = &bsp->sunlight; @@ -68,7 +74,7 @@ namespace BSP } else { - BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); + BSPLight* bspLight = &bsp->lights.at(lightIdx - BSP_DEFAULT_LIGHT_COUNT); light->type = GFX_LIGHT_TYPE_SPOT; @@ -103,8 +109,8 @@ namespace BSP light->aAbB.z = 0.75f; light->aAbB.w = 1.0f; - light->cullDist = 10000; - light->defName = "white_light"; + light->cullDist = LIGHT_CULLDIST; + light->defName = DEFAULT_LIGHTDEF_NAME; light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 38142310d..390364020 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -19,6 +19,12 @@ namespace return result; } + + constexpr const char* DEFAULT_IMAGE_NAME = ",mc/lambert1"; + constexpr char EMPTY_LIGHTMAP_INDEX = 0; + constexpr char EMPTY_RPROBE_INDEX = 0; + constexpr float XMODEL_CULL_DIST = 10000.0f; + constexpr char DEFAULT_LIGHTGRID_COLOUR = 32; } // namespace namespace BSP @@ -99,12 +105,12 @@ namespace BSP if (bspMaterial.materialType == MATERIAL_TYPE_TEXTURE) materialName = bspMaterial.materialName; else // MATERIAL_TYPE_COLOUR - materialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + materialName = DEFAULT_IMAGE_NAME; auto surfMaterialAsset = m_context.LoadDependency(materialName); if (surfMaterialAsset == nullptr) { - surfMaterialAsset = m_context.LoadDependency(BSPLinkingConstants::MISSING_IMAGE_NAME); + surfMaterialAsset = m_context.LoadDependency(DEFAULT_IMAGE_NAME); assert(surfMaterialAsset != nullptr); } gfxSurface->material = surfMaterialAsset->Asset(); @@ -138,9 +144,9 @@ namespace BSP if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0) gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW; - gfxSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - gfxSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - gfxSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxSurface->primaryLightIndex = SUN_LIGHT_INDEX; + gfxSurface->lightmapIndex = EMPTY_LIGHTMAP_INDEX; + gfxSurface->reflectionProbeIndex = EMPTY_RPROBE_INDEX; // unknown value gfxSurface->tris.himipRadiusInvSq = 0.0f; @@ -210,9 +216,9 @@ namespace BSP if (!bspModel.doesCastShadow) currModel->flags |= STATIC_MODEL_FLAG_NO_SHADOW; - currModel->cullDist = 10000.0f; - currModel->primaryLightIndex = 1; - currModel->reflectionProbeIndex = 0; + currModel->cullDist = XMODEL_CULL_DIST; + currModel->primaryLightIndex = SUN_LIGHT_INDEX; + currModel->reflectionProbeIndex = EMPTY_LIGHTMAP_INDEX; currModel->smid = modelIdx; // unknown use / unused @@ -392,8 +398,8 @@ namespace BSP void GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld) { // there must be 2 or more lights, first is the static light and second is the sun light - gfxWorld->primaryLightCount = BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT + static_cast(bsp->lights.size()); - gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->primaryLightCount = BSP_DEFAULT_LIGHT_COUNT + static_cast(bsp->lights.size()); + gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX; gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) @@ -446,7 +452,7 @@ namespace BSP gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; gfxWorld->lightGrid.offset = 0.0f; // default value // setting all rowDataStart indexes to 0 will always index the first row in rawRowData @@ -471,12 +477,12 @@ namespace BSP for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; entryArray[i].visibility = 0; } gfxWorld->lightGrid.entries = entryArray; - char color = 32; + char color = DEFAULT_LIGHTGRID_COLOUR; float sunIntensity = bsp->sunlight.intensity; if (sunIntensity < 0.0f || sunIntensity > 20000.0f) con::warn("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); @@ -534,7 +540,7 @@ namespace BSP // there is only 1 reflection probe gfxWorld->cells[0].reflectionProbeCount = 1; gfxWorld->cells[0].reflectionProbes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); - gfxWorld->cells[0].reflectionProbes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes[0] = EMPTY_RPROBE_INDEX; // AABB trees are used to detect what should be rendered and what shouldn't // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn From 0f5426eec91da859883182645f6000bf49993431 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:38:15 +0800 Subject: [PATCH 117/123] refactor: simplified brushmodels - gfx uses tris, col uses brushes only --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 157e7db94..6e5c4fc3d 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -917,18 +917,16 @@ namespace BSPEntity entity; std::string classname = node.extras->at("classname"); - if (!classname.compare("script_brushmodel_gfx") || !classname.compare("script_brushmodel_tris")) + if (!classname.compare("script_brushmodel_gfx")) entity.modelIndex = addScriptTerrainModel(jRoot, node, nodeMatrix); - else if ((!classname.compare("script_brushmodel_box") || !classname.compare("script_brushmodel")) - || (classname.starts_with("trigger_") || !classname.compare("info_volume"))) + else if (!classname.compare("script_brushmodel") || (classname.starts_with("trigger_") || !classname.compare("info_volume"))) entity.modelIndex = addScriptBrushModel(jRoot, node, nodeMatrix); else entity.modelIndex = 0; if (entity.modelIndex != 0 && node.extras->contains("model")) { - con::error("Node {} cannot have a model property when its class is a trigger, info_volume, script_brushmodel_gfx, script_brushmodel_tris, " - "script_brushmodel_box or script_brushmodel"); + con::error("Node {} cannot have a model property when its class is a trigger, info_volume, script_brushmodel_gfx or script_brushmodel"); return false; } @@ -939,8 +937,7 @@ namespace if (!key.compare("origin") || !key.compare("angles") || !key.compare("flags")) continue; - if (!key.compare("classname") - && (!value.compare("script_brushmodel_gfx") || !value.compare("script_brushmodel_tris") || !value.compare("script_brushmodel_box"))) + if (!key.compare("classname") && !value.compare("script_brushmodel_gfx")) value = "script_brushmodel"; BSPEntityEntry entry; From cf54f230ab86e0a649f5d5b409a82ff02f8590f1 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:50:15 +0800 Subject: [PATCH 118/123] feat: fixed models flickering and not being drawn due to skinned verts --- src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp index 99171aef9..e9639e83e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -11,10 +11,11 @@ namespace BSP SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) { - // Pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // maxSkinnedVerts defines how many model verts can be drawn at once + // Low values cause models not to be drawn, so double origin's maxSkinnedVerts is used as a safe bet SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); skinnedVerts->name = m_memory.Dup("skinnedverts"); - skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.xmodels.size() * 1000); + skinnedVerts->maxSkinnedVerts = static_cast(300000); return skinnedVerts; } From 82510960682b960570b71edc322e419008f347ce Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:32:21 +0800 Subject: [PATCH 119/123] chore: updated default images to look nicer ingame --- src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 390364020..d1af05757 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -710,7 +710,7 @@ namespace BSP gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr; - std::string probeImageName = "$white"; + std::string probeImageName = ",$black"; auto probeImageAsset = m_context.LoadDependency(probeImageName); if (probeImageAsset == nullptr) { @@ -729,7 +729,7 @@ namespace BSP gfxWorld->draw.lightmapPrimaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); gfxWorld->draw.lightmapSecondaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); - std::string secondaryTexture = ",$white"; + std::string secondaryTexture = ",$gray"; // gray makes shadows a nice looking shade of black auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); if (secondaryTextureAsset == nullptr) { @@ -813,7 +813,7 @@ namespace BSP gfxWorld->outdoorLookupMatrix[3].z = zScale; gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - std::string outdoorImageName = std::string("$white"); + std::string outdoorImageName = std::string(",$black"); auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); if (outdoorImageAsset == nullptr) { From d27379a061adba5f9f77e51d52945b6bb91f5632 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:21:11 +0800 Subject: [PATCH 120/123] fix: better ignore/allow for meshes and classes when loading gltf --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 6e5c4fc3d..627e29278 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -214,6 +214,7 @@ namespace Eigen::Matrix4f& nodeMatrix, size_t materialIndex, bool convertWorldToLocalPos, + const gltf::JsonNode& node, BSPSurface& out_surface) { // clang-format off @@ -270,7 +271,7 @@ namespace throw GltfLoadException("Index count must be dividable by 3 for triangles"); const auto faceCount = indexCount / 3u; if (faceCount > UINT16_MAX) - throw GltfLoadException(std::format("Face count ({}) exceeded the UINT16_MAX", faceCount)); + throw GltfLoadException(std::format("Face count ({}) on node {} exceeded the UINT16_MAX", faceCount, node.name.value_or("unnamed node"))); out_surface.vertexCount = static_cast(vertexCount); out_surface.triCount = static_cast(faceCount); @@ -543,7 +544,7 @@ namespace materialIndex = createMaterialWithFlags(materialIndex, node.extras->at("flags")); BSPSurface surface; - CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, false, surface); + CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, false, node, surface); m_curr_bsp_world->staticSurfaces.emplace_back(surface); } @@ -838,7 +839,7 @@ namespace materialIndex = m_emptyMaterialIndex; BSPSurface surface; - CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, true, surface); + CreateSurface(accessorsForVertex, nodeMatrix, materialIndex, true, node, surface); m_curr_bsp_world->scriptSurfaces.emplace_back(surface); } @@ -991,9 +992,11 @@ namespace { std::string classnameVal = node.extras->at("classname"); - if ((m_is_world_gfx && !classnameVal.compare("script_brushmodel_gfx")) // make sure only GFX loads script_brushmodel_gfx - || (!m_is_world_gfx && classnameVal.compare("script_brushmodel_gfx"))) - return addClassNode(jRoot, node, nodeMatrix); + if ((!m_is_world_gfx && !classnameVal.compare("script_brushmodel_gfx")) // skip GFX brush in collision + || (m_is_world_gfx && classnameVal.compare("script_brushmodel_gfx"))) // include GFX brush in graphics + return false; + + return addClassNode(jRoot, node, nodeMatrix); } if (!m_is_world_gfx) @@ -1022,8 +1025,8 @@ namespace if (node.extras && node.extras->contains("flags")) getSurfaceAndContentFlags(node.extras->at("flags"), surfaceFlags, contentFlags); - if (!m_is_world_gfx - || ((surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) == 0)) // ignore if gfx mesh has nodraw flag + bool isNoDraw = (surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) != 0; + if (!m_is_world_gfx || (m_is_world_gfx && !isNoDraw)) // nodraw flag doesn't set faces to invisible, so remove it from the draw data return addMeshNode(jRoot, node, nodeMatrix); } @@ -1086,7 +1089,7 @@ namespace material.materialColour.w = 1.0f; material.surfaceFlags = 0; - material.contentFlags = 0; + material.contentFlags = 1; // all materials start out as solid if (jsMaterial.extras && jsMaterial.extras->type) { bool foundType = false; From f9d8b216b232a67036e6b0b8a5eaf8a9ddb6bafa Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:06:44 +0800 Subject: [PATCH 121/123] feat: better filtering for xmodels --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 627e29278..281f99b97 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -629,17 +629,19 @@ namespace xmodel.name = node.extras->at("xmodel"); - xmodel.doesCastShadow = true; - if (node.extras->contains("flags")) - { - std::vector flagStrVec = utils::StringSplit(node.extras->at("flags"), ','); - for (std::string& flag : flagStrVec) - if (!flag.compare(surfaceTypeToNameMap[SURF_TYPE_NOCASTSHADOW])) - { - xmodel.doesCastShadow = false; - break; - } - } + int surfaceFlags = 0; + int contentFlags = 0; + if (node.extras && node.extras->contains("flags")) + getSurfaceAndContentFlags(node.extras->at("flags"), surfaceFlags, contentFlags); + + bool isNoDraw = (surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) != 0; + bool isNoCastShadow = (surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) != 0; + bool isNonSolid = (surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NONSOLID].surfaceFlags) != 0; + if (m_is_world_gfx && isNoDraw) + return true; + if (!m_is_world_gfx && isNonSolid) + return true; + xmodel.doesCastShadow = !isNoCastShadow; Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; From 36b6769713dd559fa0904ad18ae0031ac2aea739 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:09:36 +0800 Subject: [PATCH 122/123] chore: refactored node filtering --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 39 +++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index 281f99b97..f7275b8fd 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -626,7 +626,6 @@ namespace assert(node.extras->contains("xmodel")); BSPXModel xmodel; - xmodel.name = node.extras->at("xmodel"); int surfaceFlags = 0; @@ -990,36 +989,42 @@ namespace if (node.extras) { - if (node.extras->contains("classname")) + bool hasSpawnpoint = node.extras->contains("spawnpoint"); + bool hasZone = node.extras->contains("zone"); + bool hasSpawner = node.extras->contains("spawner"); + bool hasXmodel = node.extras->contains("xmodel"); + bool hasClassname = node.extras->contains("classname"); + + if (m_is_world_gfx && (hasSpawnpoint || hasZone || hasSpawner)) + return true; + + if (hasClassname) { std::string classnameVal = node.extras->at("classname"); - - if ((!m_is_world_gfx && !classnameVal.compare("script_brushmodel_gfx")) // skip GFX brush in collision - || (m_is_world_gfx && classnameVal.compare("script_brushmodel_gfx"))) // include GFX brush in graphics - return false; + if (m_is_world_gfx && classnameVal.compare("script_brushmodel_gfx")) + return true; // skip any gfx node with classname not script_brushmodel_gfx + if (!m_is_world_gfx && !classnameVal.compare("script_brushmodel_gfx")) + return true; // skip any col node with classname script_brushmodel_gfx return addClassNode(jRoot, node, nodeMatrix); } - if (!m_is_world_gfx) - { - if (node.extras->contains("spawnpoint")) + if (hasXmodel) + return addXModelNode(jRoot, node, nodeMatrix); + + if (hasSpawnpoint) return addSpawnPointNode(node, nodeMatrix); if (m_bsp->isZombiesMap) { - if (node.extras->contains("zone")) + if (hasZone) return addZoneNode(jRoot, node, nodeMatrix); - if (node.extras->contains("spawner")) + if (hasSpawner) return addZSpawnerNode(node, nodeMatrix); } } - if (node.extras->contains("xmodel")) - return addXModelNode(jRoot, node, nodeMatrix); - } - if (node.mesh) { int surfaceFlags = 0; @@ -1028,7 +1033,9 @@ namespace getSurfaceAndContentFlags(node.extras->at("flags"), surfaceFlags, contentFlags); bool isNoDraw = (surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NODRAW].surfaceFlags) != 0; - if (!m_is_world_gfx || (m_is_world_gfx && !isNoDraw)) // nodraw flag doesn't set faces to invisible, so remove it from the draw data + if (m_is_world_gfx && isNoDraw) + return true; + return addMeshNode(jRoot, node, nodeMatrix); } From e4fb8b4708f780bb2dccba0792e18fefa57f3be8 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:38:16 +0800 Subject: [PATCH 123/123] chore: added many bound checks to prevent integer overflows and game imposed limits, beautified code, added comments where logic wasn't very obvious. --- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 35 ++- .../Game/T6/BSP/Linker/BSPLinker.cpp | 33 ++- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 174 +++++++------ .../Game/T6/BSP/Linker/ClipMapLinker.h | 2 +- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 13 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 241 +++++++++--------- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 4 +- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 11 - .../Game/T6/BSP/Linker/SkinnedVertsLinker.cpp | 4 +- 9 files changed, 283 insertions(+), 234 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index f7275b8fd..e4d10c90f 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -272,6 +272,8 @@ namespace const auto faceCount = indexCount / 3u; if (faceCount > UINT16_MAX) throw GltfLoadException(std::format("Face count ({}) on node {} exceeded the UINT16_MAX", faceCount, node.name.value_or("unnamed node"))); + if (vertexCount > UINT16_MAX) + throw GltfLoadException(std::format("Vertex count ({}) on node {} exceeded the UINT16_MAX", vertexCount, node.name.value_or("unnamed node"))); out_surface.vertexCount = static_cast(vertexCount); out_surface.triCount = static_cast(faceCount); @@ -1013,17 +1015,17 @@ namespace return addXModelNode(jRoot, node, nodeMatrix); if (hasSpawnpoint) - return addSpawnPointNode(node, nodeMatrix); + return addSpawnPointNode(node, nodeMatrix); - if (m_bsp->isZombiesMap) - { + if (m_bsp->isZombiesMap) + { if (hasZone) - return addZoneNode(jRoot, node, nodeMatrix); + return addZoneNode(jRoot, node, nodeMatrix); if (hasSpawner) - return addZSpawnerNode(node, nodeMatrix); - } + return addZSpawnerNode(node, nodeMatrix); } + } if (node.mesh) { @@ -1036,7 +1038,7 @@ namespace if (m_is_world_gfx && isNoDraw) return true; - return addMeshNode(jRoot, node, nodeMatrix); + return addMeshNode(jRoot, node, nodeMatrix); } return false; @@ -1354,6 +1356,7 @@ namespace BSP bsp->containsWorldspawn = false; con::warn("XModels don't support scale currently, keep it at 1 in your editor"); + con::warn("All brushmodels, zones, triggers, and info_volumes must be an axis aligned box with 6 sides to work correctly."); BSPLoader loader(bsp.get()); if (isGfxFileGltf) @@ -1411,7 +1414,6 @@ namespace BSP bsp->sunlight.innerConeAngle = 0.0f; bsp->sunlight.outerConeAngle = 0.0f; } - if (!bsp->containsIntermssion) { con::error("Map does not contain a mp_global_intermission class"); @@ -1422,6 +1424,23 @@ namespace BSP con::error("Map does not contain a worldspawn class"); return nullptr; } + if (bsp->spawnpoints.size() == 0) + { + con::error("Map must have spawn points!"); + return nullptr; + } + if (bsp->gfxWorld.staticSurfaces.size() + bsp->gfxWorld.scriptSurfaces.size() == 0 || bsp->gfxWorld.vertices.size() == 0 + || bsp->gfxWorld.indices.size() == 0) + { + con::error("GFX world has no surfaces, indicies or vertices!"); + return nullptr; + } + if (bsp->colWorld.staticSurfaces.size() + bsp->colWorld.scriptSurfaces.size() == 0 || bsp->colWorld.vertices.size() == 0 + || bsp->colWorld.indices.size() == 0) + { + con::error("Collision world has no surfaces, indicies or vertices!"); + return nullptr; + } return bsp; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp index 7f29b6395..786364bea 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -26,18 +26,23 @@ namespace BSP bool BSPLinker::addDefaultRequiredAssets(BSPData* bsp) { if (m_context.LoadDependency("maps/mp/" + bsp->name + ".gsc") == nullptr) - return false; - if (m_context.LoadDependency("maps/mp/" + bsp->name + "_amb.gsc") == nullptr) - return false; - if (m_context.LoadDependency("maps/mp/" + bsp->name + "_fx.gsc") == nullptr) - return false; - - if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + ".csc") == nullptr) - return false; - if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr) - return false; - if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr) - return false; + { + con::error("maps/mp/" + bsp->name + ".gsc not found, make sure GSC file is in another fastfile."); + } + else + { + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_amb.gsc") == nullptr) + return false; + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_fx.gsc") == nullptr) + return false; + + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + ".csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr) + return false; + } addEmptyFootstepTableAsset("default_1st_person"); addEmptyFootstepTableAsset("default_3rd_person"); @@ -91,12 +96,12 @@ namespace BSP return false; m_context.AddAsset(skinnedVerts->name, skinnedVerts); - GfxWorld* gfxWorld = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + GfxWorld* gfxWorld = gfxWorldLinker.linkGfxWorld(bsp); if (gfxWorld == nullptr) return false; m_context.AddAsset(gfxWorld->name, gfxWorld); - clipMap_t* clipMap = clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + clipMap_t* clipMap = clipMapLinker.linkClipMap(bsp); // requires mapents asset if (clipMap == nullptr) return false; m_context.AddAsset(clipMap->name, clipMap); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 197085d16..4951eb83a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -134,12 +134,20 @@ namespace BSP bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) { // it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid. - // A lot of XModels don't implement collmaps (OAT also doesn't generate these collmaps), and + // A lot of XModels don't implement collmaps (OAT also doesn't generate these collmaps atm), and // even official maps instead use terrain or brushes to cover where the collision should be. clipMap->numStaticModels = bsp->colWorld.xmodels.size(); clipMap->staticModelList = m_memory.Alloc(clipMap->numStaticModels); + if (clipMap->numStaticModels == 0) + return true; + if (clipMap->numStaticModels > 0xFFFF) + { + con::error("COLWorld exceeded the maximum number of static xmodels (65535 max, map count: {})", clipMap->numStaticModels); + return false; + } + for (unsigned int i = 0; i < clipMap->numStaticModels; i++) { cStaticModel_s* currModel = &clipMap->staticModelList[i]; @@ -386,12 +394,8 @@ namespace BSP // no brushes used so data is set to 0 leaf.brushContents = 0; - leaf.mins.x = 0.0f; - leaf.mins.y = 0.0f; - leaf.mins.z = 0.0f; - leaf.maxs.x = 0.0f; - leaf.maxs.y = 0.0f; - leaf.maxs.z = 0.0f; + leaf.mins = {}; + leaf.maxs = {}; leaf.leafBrushNode = 0; if (tree->leaf->getObjectCount() > 0) @@ -527,8 +531,6 @@ namespace BSP size_t scriptSurfaceCount = bsp->colWorld.scriptSurfaces.size(); size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; - // TODO: figure out SV_EntityContact and see how it interacts with partitions - std::vector triIndexVec; std::vector partitionVec; std::vector uniqueIndicesVec; @@ -598,71 +600,17 @@ namespace BSP return true; } - bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp) - { - // No support for brushes, only tris right now - clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = nullptr; - clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = nullptr; - clipMap->info.brushBounds = nullptr; - clipMap->info.brushContents = nullptr; - - // first brush node is always empty - cLeafBrushNode_s tempNode; - memset(&tempNode, 0, sizeof(cLeafBrushNode_s)); - brushNodeVec.emplace_back(tempNode); - - clipMap->info.numBrushVerts = static_cast(bsp->colWorld.boxBrushVerts.size()); - clipMap->info.brushVerts = m_memory.Alloc(bsp->colWorld.boxBrushVerts.size()); - memcpy(clipMap->info.brushVerts, bsp->colWorld.boxBrushVerts.data(), sizeof(vec3_t) * bsp->colWorld.boxBrushVerts.size()); - - // load verts, tris, uinds and partitions - if (!loadPartitions(clipMap, bsp)) - return false; - - if (!loadBSPTree(clipMap, bsp)) - return false; - - loadSubModelCollision(clipMap, bsp); // requires tri verts - - clipMap->info.planeCount = static_cast(planeVec.size()); - clipMap->info.planes = m_memory.Alloc(planeVec.size()); - memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); - - clipMap->numNodes = static_cast(nodeVec.size()); - clipMap->nodes = m_memory.Alloc(nodeVec.size()); - memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); - - clipMap->numLeafs = static_cast(leafVec.size()); - clipMap->leafs = m_memory.Alloc(leafVec.size()); - memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); - - clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); - clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); - memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); - - clipMap->info.leafbrushNodesCount = static_cast(brushNodeVec.size()); - clipMap->info.leafbrushNodes = m_memory.Alloc(brushNodeVec.size()); - memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size()); - - clipMap->info.numBrushes = static_cast(brushVec.size()); - clipMap->info.brushes = m_memory.Alloc(brushVec.size()); - memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.size()); - - // The plane of each node have the same index - for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) - clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; - - return true; - } - - void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) + bool ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) { // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) clipMap->numSubModels = static_cast(bsp->models.size() + 1); clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + if (clipMap->numSubModels > 0x3FFF) + { + con::error("ERROR: There are more than 0x3FFF entity brushmodels."); + return false; + } // first model is always the world model for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) { @@ -742,10 +690,9 @@ namespace BSP if (bspModel.hasBrush) { - BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); - vec3_t mins; - vec3_t maxs; + vec3_t mins = {}; + vec3_t maxs = {}; for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++) { vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx]; @@ -824,6 +771,89 @@ namespace BSP clipModel->leaf.cluster = 0; clipModel->info = nullptr; } + + return true; + } + + bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp) + { + // unused brush data + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = nullptr; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = nullptr; + clipMap->info.brushBounds = nullptr; + clipMap->info.brushContents = nullptr; + + // first brush node is always empty + cLeafBrushNode_s tempNode; + memset(&tempNode, 0, sizeof(cLeafBrushNode_s)); + brushNodeVec.emplace_back(tempNode); + + clipMap->info.numBrushVerts = static_cast(bsp->colWorld.boxBrushVerts.size()); + clipMap->info.brushVerts = m_memory.Alloc(bsp->colWorld.boxBrushVerts.size()); + memcpy(clipMap->info.brushVerts, bsp->colWorld.boxBrushVerts.data(), sizeof(vec3_t) * bsp->colWorld.boxBrushVerts.size()); + + // load verts, tris, uinds and partitions + if (!loadPartitions(clipMap, bsp)) + return false; + + if (!loadBSPTree(clipMap, bsp)) + return false; + + if (!loadSubModelCollision(clipMap, bsp)) // requires tri verts + return false; + + if (nodeVec.size() > 0xFFFF) + { + con::error("exceeded 0xFFFF nodes in clipmap"); + return false; + } + if (leafVec.size() > 0xFFFF) + { + con::error("exceeded 0xFFFF leafs in clipmap"); + return false; + } + if (AABBTreeVec.size() > 0xFFFF) + { + con::error("exceeded 0xFFFF AABBTrees in clipmap"); + return false; + } + if (brushVec.size() > 0xFFFF) + { + con::error("exceeded 0xFFFF brushes in clipmap"); + return false; + } + + clipMap->info.planeCount = static_cast(planeVec.size()); + clipMap->info.planes = m_memory.Alloc(planeVec.size()); + memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); + + clipMap->numNodes = static_cast(nodeVec.size()); + clipMap->nodes = m_memory.Alloc(nodeVec.size()); + memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); + + clipMap->numLeafs = static_cast(leafVec.size()); + clipMap->leafs = m_memory.Alloc(leafVec.size()); + memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); + + clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); + clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); + memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); + + clipMap->info.leafbrushNodesCount = static_cast(brushNodeVec.size()); + clipMap->info.leafbrushNodes = m_memory.Alloc(brushNodeVec.size()); + memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size()); + + clipMap->info.numBrushes = static_cast(brushVec.size()); + clipMap->info.brushes = m_memory.Alloc(brushVec.size()); + memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.size()); + + // The plane of each node have the same index + for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) + clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; + + return true; } bool ClipMapLinker::loadMaterials(clipMap_t* clipMap, BSPData* bsp) diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 53feb5fd2..5b921355c 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -30,7 +30,7 @@ namespace BSP void loadVisibility(clipMap_t* clipMap); void loadDynEnts(clipMap_t* clipMap); void loadRopesAndConstraints(clipMap_t* clipMap); - void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); + bool loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); bool loadXModelCollision(clipMap_t* clipMap, BSPData* bsp); std::vector planeVec; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 8672f8bc6..a2c29f075 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -1,7 +1,5 @@ #include "ComWorldLinker.h" -#include "../BSPUtil.h" - #include namespace @@ -36,7 +34,6 @@ namespace BSP ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = m_memory.Alloc(); comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->isInUse = 1; @@ -110,11 +107,11 @@ namespace BSP light->aAbB.w = 1.0f; light->cullDist = LIGHT_CULLDIST; - light->defName = DEFAULT_LIGHTDEF_NAME; - light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation - light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated - light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle - light->canUseShadowMap = 1; // light does not show up with this set to 0 + light->defName = DEFAULT_LIGHTDEF_NAME; // all lights that aren't the sunlight or default light need their own GfxLightDef asset + light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation + light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated + light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle + light->canUseShadowMap = 1; // light does not show up with this set to 0 } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index d1af05757..ade30d93f 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -21,8 +21,9 @@ namespace } constexpr const char* DEFAULT_IMAGE_NAME = ",mc/lambert1"; - constexpr char EMPTY_LIGHTMAP_INDEX = 0; - constexpr char EMPTY_RPROBE_INDEX = 0; + constexpr char DEFAULT_PRIMARYLIGHT_INDEX = 1; // max 254 + constexpr char DEFAULT_LIGHTMAP_INDEX = 0; // max 30 + constexpr char DEFAULT_RPROBE_INDEX = 0; // max 254 constexpr float XMODEL_CULL_DIST = 10000.0f; constexpr char DEFAULT_LIGHTGRID_COLOUR = 32; } // namespace @@ -65,7 +66,7 @@ namespace BSP size_t indexCount = bsp->gfxWorld.indices.size(); assert(indexCount % 3 == 0); gfxWorld->draw.indexCount = static_cast(indexCount); - gfxWorld->draw.indices = m_memory.Alloc(indexCount); + gfxWorld->draw.indices = m_memory.Alloc(indexCount); // overflow checked in bspcreator static_assert(sizeof(bsp->gfxWorld.indices.data()[0]) == sizeof(uint16_t)); memcpy(gfxWorld->draw.indices, bsp->gfxWorld.indices.data(), sizeof(uint16_t) * indexCount); } @@ -78,6 +79,12 @@ namespace BSP size_t scriptSurfaceCount = bsp->gfxWorld.scriptSurfaces.size(); size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; + if (staticSurfaceCount > 0xffff) + { + con::error("There are more than 65535 Static surfaces. count: {}", staticSurfaceCount); + return false; + } + // Static surfaces go first in the array then script surfaces after gfxWorld->surfaceCount = static_cast(totalSurfaceCount); gfxWorld->dpvs.staticSurfaceCount = static_cast(staticSurfaceCount); @@ -91,9 +98,9 @@ namespace BSP bspSurface = bsp->gfxWorld.scriptSurfaces.at(surfIdx - staticSurfaceCount); GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx]; - gfxSurface->tris.triCount = bspSurface.triCount; + gfxSurface->tris.triCount = bspSurface.triCount; // overflow checked in bspcreator gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex; - gfxSurface->tris.vertexCount = bspSurface.vertexCount; + gfxSurface->tris.vertexCount = bspSurface.vertexCount; // overflow checked in bspcreator gfxSurface->tris.firstVertex = bspSurface.indexOfFirstVertex; gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); @@ -116,23 +123,15 @@ namespace BSP gfxSurface->material = surfMaterialAsset->Asset(); GfxPackedWorldVertex* firstVert = reinterpret_cast(&gfxWorld->draw.vd0.data[gfxSurface->tris.vertexDataOffset0]); - gfxSurface->bounds[0].x = firstVert[0].xyz.x; - gfxSurface->bounds[0].y = firstVert[0].xyz.y; - gfxSurface->bounds[0].z = firstVert[0].xyz.z; - gfxSurface->bounds[1].x = firstVert[0].xyz.x; - gfxSurface->bounds[1].y = firstVert[0].xyz.y; - gfxSurface->bounds[1].z = firstVert[0].xyz.z; + gfxSurface->bounds[0] = firstVert[0].xyz; + gfxSurface->bounds[1] = firstVert[0].xyz; for (size_t indexIdx = 0; indexIdx < static_cast(gfxSurface->tris.triCount * 3); indexIdx++) { uint16_t vertIndex = gfxWorld->draw.indices[gfxSurface->tris.baseIndex + indexIdx]; BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, gfxSurface->bounds[0], gfxSurface->bounds[1]); } - gfxSurface->tris.mins.x = gfxSurface->bounds[0].x; - gfxSurface->tris.mins.y = gfxSurface->bounds[0].y; - gfxSurface->tris.mins.z = gfxSurface->bounds[0].z; - gfxSurface->tris.maxs.x = gfxSurface->bounds[1].x; - gfxSurface->tris.maxs.y = gfxSurface->bounds[1].y; - gfxSurface->tris.maxs.z = gfxSurface->bounds[1].z; + gfxSurface->tris.mins = gfxSurface->bounds[0]; + gfxSurface->tris.maxs = gfxSurface->bounds[1]; gfxSurface->flags = 0; if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_SKY].contentFlags) != 0) @@ -144,12 +143,9 @@ namespace BSP if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0) gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW; - gfxSurface->primaryLightIndex = SUN_LIGHT_INDEX; - gfxSurface->lightmapIndex = EMPTY_LIGHTMAP_INDEX; - gfxSurface->reflectionProbeIndex = EMPTY_RPROBE_INDEX; - - // unknown value - gfxSurface->tris.himipRadiusInvSq = 0.0f; + gfxSurface->primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX; + gfxSurface->lightmapIndex = DEFAULT_LIGHTMAP_INDEX; + gfxSurface->reflectionProbeIndex = DEFAULT_RPROBE_INDEX; } // Some code uses Sorted surfs to index surfaces, so for simplicity keep the indexes sequential and from 0 @@ -191,6 +187,29 @@ namespace BSP gfxWorld->dpvs.smodelInsts = m_memory.Alloc(modelCount); gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc(modelCount); + if (modelCount == 0) + return true; + if (modelCount > 0xFFFF) + { + con::error("GFXWorld exceeded the maximum number of static xmodels (65535 max, map count: {})", modelCount); + return false; + } + + char color = DEFAULT_LIGHTGRID_COLOUR; + float sunIntensity = bsp->sunlight.intensity; + if (sunIntensity < 0.0f || sunIntensity > 20000.0f) + { + con::error("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); + return false; + } + else + { + float normalised = sunIntensity / 20000.0f; + normalised *= 255.0f; + float clean = roundf(normalised); + color = static_cast(clean); + } + for (size_t modelIdx = 0; modelIdx < modelCount; modelIdx++) { auto currModel = &gfxWorld->dpvs.smodelDrawInsts[modelIdx]; @@ -206,9 +225,7 @@ namespace BSP else currModel->model = (XModel*)xModelAsset->Asset(); - currModel->placement.origin.x = bspModel.origin.x; - currModel->placement.origin.y = bspModel.origin.y; - currModel->placement.origin.z = bspModel.origin.z; + currModel->placement.origin = bspModel.origin; BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, currModel->placement.axis); currModel->placement.scale = bspModel.scale; @@ -217,16 +234,11 @@ namespace BSP currModel->flags |= STATIC_MODEL_FLAG_NO_SHADOW; currModel->cullDist = XMODEL_CULL_DIST; - currModel->primaryLightIndex = SUN_LIGHT_INDEX; - currModel->reflectionProbeIndex = EMPTY_LIGHTMAP_INDEX; + currModel->primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX; + currModel->reflectionProbeIndex = DEFAULT_RPROBE_INDEX; currModel->smid = modelIdx; - // unknown use / unused - memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - currModel->invScaleSq = 0.0f; - currModel->lightingHandle = 0; // overwritten to by the game - currModel->colorsIndex = 0; - currModel->visibility = 0; + currModelInst->lightingOrigin = bspModel.origin; if (!xModelAsset->IsReference()) { @@ -249,8 +261,7 @@ namespace BSP currModel->lmapVertexInfo[lodIdx].numLmapVertexColors = vertCount; currModel->lmapVertexInfo[lodIdx].lmapVertexColors = m_memory.Alloc(vertCount); - // a value of 1 makes shadowed surfaces slightly easier to see - memset(currModel->lmapVertexInfo[lodIdx].lmapVertexColors, 1, sizeof(unsigned int) * vertCount); + memset(currModel->lmapVertexInfo[lodIdx].lmapVertexColors, color, sizeof(unsigned int) * vertCount); } } else @@ -270,20 +281,7 @@ namespace BSP currModelInst->maxs.y = bspModel.origin.y + 1.0f; currModelInst->maxs.z = bspModel.origin.z + 1.0f; } - - // setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - currModel->lmapVertexInfo[0].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - currModel->lmapVertexInfo[1].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - currModel->lmapVertexInfo[2].lmapVertexColors = nullptr; - currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - currModel->lmapVertexInfo[3].lmapVertexColors = nullptr; } - currModelInst->lightingOrigin.x = 0.0f; - currModelInst->lightingOrigin.y = 0.0f; - currModelInst->lightingOrigin.z = 0.0f; } // visdata is written to by the game @@ -299,8 +297,6 @@ namespace BSP { if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; } return true; @@ -395,12 +391,18 @@ namespace BSP gfxWorld->sunLight = m_memory.Alloc(); } - void GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld) + bool GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld) { // there must be 2 or more lights, first is the static light and second is the sun light gfxWorld->primaryLightCount = BSP_DEFAULT_LIGHT_COUNT + static_cast(bsp->lights.size()); gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX; + if (gfxWorld->primaryLightCount > 254) + { + con::error("Exceeded 254 lights in BSP, count: {}", gfxWorld->primaryLightCount); + return false; + } + gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) { @@ -416,7 +418,7 @@ namespace BSP { if ((gfxWorld->dpvs.smodelDrawInsts[modelIdx].flags & STATIC_MODEL_FLAG_NO_SHADOW) != 0) continue; - char lightIndex = gfxWorld->dpvs.smodelDrawInsts[modelIdx].primaryLightIndex; + unsigned char lightIndex = gfxWorld->dpvs.smodelDrawInsts[modelIdx].primaryLightIndex; gfxWorld->shadowGeom[lightIndex].smodelIndex[gfxWorld->shadowGeom[lightIndex].smodelCount] = modelIdx; gfxWorld->shadowGeom[lightIndex].smodelCount++; } @@ -433,22 +435,41 @@ namespace BSP gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc(lightEntShadowVisSize); else gfxWorld->primaryLightEntityShadowVis = nullptr; + + return true; } - void GfxWorldLinker::loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld) + bool GfxWorldLinker::loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld) { - // gfxWorld->lightGrid.mins[0] = static_cast((static_cast(gfxWorld->lightGrid.mins[0]) + 0x20000) >> 5); - // gfxWorld->lightGrid.mins[1] = static_cast((static_cast(gfxWorld->lightGrid.mins[1]) + 0x20000) >> 5); - // gfxWorld->lightGrid.mins[2] = static_cast((static_cast(gfxWorld->lightGrid.mins[2]) + 0x20000) >> 6); - // gfxWorld->lightGrid.maxs[0] = static_cast((static_cast(gfxWorld->lightGrid.maxs[0]) + 0x20000) >> 5); - // gfxWorld->lightGrid.maxs[1] = static_cast((static_cast(gfxWorld->lightGrid.maxs[1]) + 0x20000) >> 5); - // gfxWorld->lightGrid.maxs[2] = static_cast((static_cast(gfxWorld->lightGrid.maxs[2]) + 0x20000) >> 6); - gfxWorld->lightGrid.mins[0] = 100; - gfxWorld->lightGrid.mins[1] = 100; - gfxWorld->lightGrid.mins[2] = 100; - gfxWorld->lightGrid.maxs[0] = 65435; - gfxWorld->lightGrid.maxs[1] = 65435; - gfxWorld->lightGrid.maxs[2] = 65435; + // world to lightgrid coords conversion: + // l_x = (w_x + 131072.0f) / 32.0f; + // l_y = (w_y + 131072.0f) / 32.0f; + // l_z = (w_z + 131072.0f) / 64.0f; + // + // lightgrid to world coords conversion: + // w_x = (double)l_x * 32.0f - 131072.0f; + // w_y = (double)l_y * 32.0f - 131072.0f; + // w_z = (double)l_z * 64.0f - 131072.0f; + // + // Lightrid coords are bounded by the uint16 limits: + // w_minX = -131072, w_maxX = 1966048 + // w_minY = -131072, w_maxY = 1966048 + // w_minZ = -131072, w_maxZ = 4063168 + + if (gfxWorld->mins.x < -131072.0f || gfxWorld->mins.y < -131072.0f || gfxWorld->mins.z < -131072.0f || gfxWorld->maxs.x > 1966048.0f + || gfxWorld->maxs.y > 1966048.0f || gfxWorld->maxs.z > 4063168.0f) + { + con::error("Lightrid mins/maxs exceeded"); + return false; + } + + // integer-only method used by the game + gfxWorld->lightGrid.mins[0] = static_cast((static_cast(gfxWorld->mins.x) + 0x20000) >> 5); + gfxWorld->lightGrid.mins[1] = static_cast((static_cast(gfxWorld->mins.y) + 0x20000) >> 5); + gfxWorld->lightGrid.mins[2] = static_cast((static_cast(gfxWorld->mins.z) + 0x20000) >> 6); + gfxWorld->lightGrid.maxs[0] = static_cast((static_cast(gfxWorld->maxs.x) + 0x20000) >> 5); + gfxWorld->lightGrid.maxs[1] = static_cast((static_cast(gfxWorld->maxs.y) + 0x20000) >> 5); + gfxWorld->lightGrid.maxs[2] = static_cast((static_cast(gfxWorld->maxs.z) + 0x20000) >> 6); gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value @@ -467,8 +488,8 @@ namespace BSP row->zStart = 0; row->zCount = 0xFF; // 0xFF as this seems to be the sweet spot of values row->firstEntry = 0; - for (int i = 0; i < 0x200; i++) // set the lookup table to all 0xFF - row->lookupTable[i] = -1; + for (int i = 0; i < 0x200; i++) + row->lookupTable[i] = -1; // values in the lookup table are subtracted from the sample pos until it reaches 0, so use 0xFF gfxWorld->lightGrid.rawRowData = reinterpret_cast(row); // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table @@ -477,7 +498,7 @@ namespace BSP for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) { entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; + entryArray[i].primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX; entryArray[i].visibility = 0; } gfxWorld->lightGrid.entries = entryArray; @@ -485,7 +506,10 @@ namespace BSP char color = DEFAULT_LIGHTGRID_COLOUR; float sunIntensity = bsp->sunlight.intensity; if (sunIntensity < 0.0f || sunIntensity > 20000.0f) - con::warn("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); + { + con::error("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); + return false; + } else { float normalised = sunIntensity / 20000.0f; @@ -504,6 +528,8 @@ namespace BSP gfxWorld->lightGrid.coeffs = nullptr; gfxWorld->lightGrid.skyGridVolumeCount = 0; gfxWorld->lightGrid.skyGridVolumes = nullptr; + + return true; } struct mnode_t @@ -530,17 +556,13 @@ namespace BSP gfxWorld->cells = m_memory.Alloc(cellCount); gfxWorld->cells[0].portalCount = 0; gfxWorld->cells[0].portals = nullptr; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + gfxWorld->cells[0].mins = gfxWorld->mins; + gfxWorld->cells[0].maxs = gfxWorld->maxs; // there is only 1 reflection probe gfxWorld->cells[0].reflectionProbeCount = 1; gfxWorld->cells[0].reflectionProbes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); - gfxWorld->cells[0].reflectionProbes[0] = EMPTY_RPROBE_INDEX; + gfxWorld->cells[0].reflectionProbes[0] = DEFAULT_RPROBE_INDEX; // AABB trees are used to detect what should be rendered and what shouldn't // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn @@ -556,12 +578,8 @@ namespace BSP { gfxWorld->cells[0].aabbTree[0].smodelIndexes[smodelIdx] = smodelIdx; } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + gfxWorld->cells[0].aabbTree[0].mins = gfxWorld->mins; + gfxWorld->cells[0].aabbTree[0].maxs = gfxWorld->maxs; // nodes have the struct mnode_t, and there must be at least 1 node (similar to BSP nodes) // Nodes mnode_t.cellIndex indexes gfxWorld->cells @@ -595,6 +613,7 @@ namespace BSP void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld) { // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) + // bounds are checked in clipmap linker gfxWorld->modelCount = static_cast(bsp->models.size() + 1); gfxWorld->models = m_memory.Alloc(gfxWorld->modelCount); @@ -614,6 +633,11 @@ namespace BSP gfxWorld->models[0].bounds[0], gfxWorld->models[0].bounds[1]); } + if (gfxWorld->dpvs.staticSurfaceCount == 0) + { + gfxWorld->models[0].bounds[0] = {}; + gfxWorld->models[0].bounds[1] = {}; + } memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) @@ -641,12 +665,8 @@ namespace BSP { currEntModel->startSurfIndex = -1; // -1 when it doesn't use map surfaces currEntModel->surfaceCount = 0; - currEntModel->bounds[0].x = 0.0f; - currEntModel->bounds[0].y = 0.0f; - currEntModel->bounds[0].z = 0.0f; - currEntModel->bounds[1].x = 0.0f; - currEntModel->bounds[1].y = 0.0f; - currEntModel->bounds[1].z = 0.0f; + currEntModel->bounds[0] = {}; + currEntModel->bounds[1] = {}; } memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); } @@ -656,14 +676,13 @@ namespace BSP { BSPLight& sunlight = bsp->sunlight; vec3_t viewAngles = BSPUtil::convertForwardVectorToViewAngles(sunlight.direction); - gfxWorld->sunParse.initWorldSun->angles.x = viewAngles.x; - gfxWorld->sunParse.initWorldSun->angles.y = viewAngles.y; - gfxWorld->sunParse.initWorldSun->angles.z = viewAngles.z; + gfxWorld->sunParse.initWorldSun->angles = viewAngles; gfxWorld->sunParse.initWorldSun->sunCd.x = sunlight.colour.x; gfxWorld->sunParse.initWorldSun->sunCd.y = sunlight.colour.y; gfxWorld->sunParse.initWorldSun->sunCd.z = sunlight.colour.z; gfxWorld->sunParse.initWorldSun->sunCd.w = 1.0f; + // fog is not implemented yet, values taken from mp_dig gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; @@ -690,22 +709,11 @@ namespace BSP // default values taken from mp_dig gfxWorld->draw.reflectionProbes = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; // always -8.0f + gfxWorld->draw.reflectionProbes[0].origin = {}; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0 = {}; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1 = {}; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2 = {}; gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr; @@ -753,7 +761,7 @@ namespace BSP con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName); } - // default skybox values from mp_dig + // always 0 and 1 gfxWorld->skyDynIntensity.angle0 = 0.0f; gfxWorld->skyDynIntensity.angle1 = 0.0f; gfxWorld->skyDynIntensity.factor0 = 1.0f; @@ -831,9 +839,9 @@ namespace BSP gfxWorld->baseName = m_memory.Dup(bsp->name.c_str()); gfxWorld->name = m_memory.Dup(bsp->bspName.c_str()); - // Default values taken from official maps - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; + // Default values taken from origins + gfxWorld->lightingFlags = 4; + gfxWorld->lightingQuality = 10000; cleanGfxWorld(gfxWorld); @@ -854,21 +862,22 @@ namespace BSP // world bounds are based on loaded surface mins/maxs and xmodels loadWorldBounds(gfxWorld); - if (!loadOutdoors(gfxWorld)) + if (!loadOutdoors(gfxWorld)) // requires world mins/maxs return nullptr; // gfx cells depend on surface/smodel count loadGfxCells(gfxWorld); - loadLightGrid(bsp, gfxWorld); + loadLightGrid(bsp, gfxWorld); // requires world mins/maxs - loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces + if (!loadGfxLights(bsp, gfxWorld)) // requires xmodels and surfaces + return nullptr; loadModels(bsp, gfxWorld); // requires surfaces loadSunData(bsp, gfxWorld); - loadDynEntData(gfxWorld); + loadDynEntData(gfxWorld); // requires cells and lights return gfxWorld; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 1ed12d562..e3fa09629 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -22,8 +22,8 @@ namespace BSP bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); bool loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld); - void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); - void loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld); + bool loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); + bool loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld); void loadModels(BSPData* bsp, GfxWorld* gfxWorld); bool loadReflectionProbeData(GfxWorld* gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 7556e3ce9..13f72665a 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -2,9 +2,6 @@ #include "../BSPUtil.h" -#include -using namespace nlohmann; - namespace { inline const std::vector DEFENDER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_allies", @@ -172,14 +169,6 @@ namespace BSP mapEnts->entityString = m_memory.Dup(entityString.c_str()); mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character - // don't need these, unused by the game - mapEnts->trigger.count = 0; - mapEnts->trigger.models = nullptr; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = nullptr; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = nullptr; - return mapEnts; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp index e9639e83e..541c7515d 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -12,10 +12,10 @@ namespace BSP SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) { // maxSkinnedVerts defines how many model verts can be drawn at once - // Low values cause models not to be drawn, so double origin's maxSkinnedVerts is used as a safe bet + // Low values cause models not to be drawn, so double origin's maxSkinnedVerts (163840) is used as a safe bet SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); skinnedVerts->name = m_memory.Dup("skinnedverts"); - skinnedVerts->maxSkinnedVerts = static_cast(300000); + skinnedVerts->maxSkinnedVerts = 300000; return skinnedVerts; }