Merge pull request #182 from Laupetin/feature/gltf-xmodel-dumping

feat: dump xmodels as gltf/glb
This commit is contained in:
Jan 2024-05-11 02:08:55 +02:00 committed by GitHub
commit 35474c217e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 4842 additions and 3542 deletions

View File

@ -22,6 +22,8 @@ namespace T6
typedef tdef_align(128) float float_align128;
typedef uint16_t ScriptString;
struct dvar_t;
struct MenuCell;
struct cplane_s;
@ -591,14 +593,22 @@ namespace T6
int partBits[5];
};
enum XModelLodRampType : unsigned char
{
XMODEL_LOD_RAMP_RIGID = 0x0,
XMODEL_LOD_RAMP_SKINNED = 0x1,
XMODEL_LOD_RAMP_COUNT
};
struct XModel
{
const char* name;
unsigned char numBones;
unsigned char numRootBones;
unsigned char numsurfs;
char lodRampType;
uint16_t* boneNames;
XModelLodRampType lodRampType;
ScriptString* boneNames;
unsigned char* parentList;
uint16_t (*quats)[4];
float (*trans)[4];
@ -618,7 +628,7 @@ namespace T6
uint16_t collLod;
float* himipInvSqRadii;
int memUsage;
int flags;
unsigned int flags;
bool bad;
PhysPreset* physPreset;
unsigned char numCollmaps;

View File

@ -0,0 +1,36 @@
#pragma once
#include "Game/T6/T6.h"
#include "Json/JsonCommon.h"
#include "Json/JsonExtension.h"
#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace T6
{
class JsonXModelLod
{
public:
std::string file;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file);
class JsonXModel
{
public:
std::vector<JsonXModelLod> lods;
unsigned collLod;
std::optional<std::string> physPreset;
std::optional<std::string> physConstraints;
unsigned flags;
JsonVec3 lightingOriginOffset;
float lightingOriginRange;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physConstraints, flags, lightingOriginOffset, lightingOriginRange);
} // namespace T6

View File

@ -0,0 +1,27 @@
#pragma once
#include "Utils/FileUtils.h"
#include <cstdint>
namespace gltf
{
constexpr uint32_t GLTF_MAGIC = FileUtils::MakeMagic32('g', 'l', 'T', 'F');
constexpr uint32_t GLTF_VERSION = 2u;
constexpr auto GLTF_VERSION_STRING = "2.0";
constexpr uint32_t CHUNK_MAGIC_JSON = FileUtils::MakeMagic32('J', 'S', 'O', 'N');
constexpr uint32_t CHUNK_MAGIC_BIN = FileUtils::MakeMagic32('B', 'I', 'N', '\x00');
constexpr auto GLTF_LENGTH_OFFSET = 8u;
constexpr auto GLTF_JSON_CHUNK_LENGTH_OFFSET = 12u;
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,";
constexpr auto GLTF_ATTRIBUTE_POSITION = "POSITION";
constexpr auto GLTF_ATTRIBUTE_NORMAL = "NORMAL";
constexpr auto GLTF_ATTRIBUTE_TEXCOORD_0 = "TEXCOORD_0";
constexpr auto GLTF_ATTRIBUTE_JOINTS_0 = "JOINTS_0";
constexpr auto GLTF_ATTRIBUTE_WEIGHTS_0 = "WEIGHTS_0";
} // namespace gltf

View File

@ -0,0 +1,343 @@
#pragma once
#include "Json/JsonExtension.h"
#include <array>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace gltf
{
class JsonAsset
{
public:
std::string version;
std::optional<std::string> generator;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAsset, version, generator);
class JsonNode
{
public:
std::optional<std::string> name;
std::optional<std::array<float, 3>> translation;
std::optional<std::array<float, 4>> rotation;
std::optional<std::array<float, 3>> scale;
std::optional<std::vector<unsigned>> children;
std::optional<unsigned> skin;
std::optional<unsigned> mesh;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, children, skin, mesh);
class JsonBuffer
{
public:
unsigned byteLength;
std::optional<std::string> uri;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBuffer, byteLength, uri);
enum class JsonAccessorComponentType
{
SIGNED_BYTE = 5120,
UNSIGNED_BYTE = 5121,
SIGNED_SHORT = 5122,
UNSIGNED_SHORT = 5123,
UNSIGNED_INT = 5125,
FLOAT = 5126
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAccessorComponentType,
{
{JsonAccessorComponentType::SIGNED_BYTE, static_cast<unsigned>(JsonAccessorComponentType::SIGNED_BYTE) },
{JsonAccessorComponentType::UNSIGNED_BYTE, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_BYTE) },
{JsonAccessorComponentType::SIGNED_SHORT, static_cast<unsigned>(JsonAccessorComponentType::SIGNED_SHORT) },
{JsonAccessorComponentType::UNSIGNED_SHORT, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_SHORT)},
{JsonAccessorComponentType::UNSIGNED_INT, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_INT) },
{JsonAccessorComponentType::FLOAT, static_cast<unsigned>(JsonAccessorComponentType::FLOAT) },
});
enum class JsonAccessorType
{
SCALAR,
VEC2,
VEC3,
VEC4,
MAT2,
MAT3,
MAT4
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAccessorType,
{
{JsonAccessorType::SCALAR, "SCALAR"},
{JsonAccessorType::VEC2, "VEC2" },
{JsonAccessorType::VEC3, "VEC3" },
{JsonAccessorType::VEC4, "VEC4" },
{JsonAccessorType::MAT2, "MAT2" },
{JsonAccessorType::MAT3, "MAT3" },
{JsonAccessorType::MAT4, "MAT4" },
});
class JsonAccessor
{
public:
std::optional<unsigned> bufferView;
std::optional<unsigned> byteOffset;
JsonAccessorComponentType componentType;
// std::optional<boolean> normalized
unsigned count;
JsonAccessorType type;
std::optional<std::vector<float>> max;
std::optional<std::vector<float>> min;
// std::optional<JsonAccessorSparse> sparse;
// std::optional<std::string> name;
// extensions
// extras
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAccessor, bufferView, byteOffset, componentType, count, type, min, max);
enum class JsonBufferViewTarget
{
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonBufferViewTarget,
{
{JsonBufferViewTarget::ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ARRAY_BUFFER) },
{JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER)},
});
class JsonBufferView
{
public:
unsigned buffer;
unsigned byteLength;
std::optional<unsigned> byteOffset;
std::optional<unsigned> byteStride;
std::optional<JsonBufferViewTarget> target;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBufferView, buffer, byteLength, byteOffset, byteStride, target);
enum class JsonAnimationChannelTargetPath
{
TRANSLATION,
ROTATION,
SCALE,
WEIGHTS
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAnimationChannelTargetPath,
{
{JsonAnimationChannelTargetPath::TRANSLATION, "translation"},
{JsonAnimationChannelTargetPath::ROTATION, "rotation" },
{JsonAnimationChannelTargetPath::SCALE, "scale" },
{JsonAnimationChannelTargetPath::WEIGHTS, "weights" },
});
class JsonAnimationChannelTarget
{
public:
unsigned node;
JsonAnimationChannelTargetPath path;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationChannelTarget, node, path);
class JsonAnimationChannel
{
public:
unsigned sampler;
JsonAnimationChannelTarget target;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationChannel, sampler, target);
enum class JsonAnimationSamplerInterpolation
{
LINEAR,
STEP,
CUBIC_SPLINE
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAnimationSamplerInterpolation,
{
{JsonAnimationSamplerInterpolation::LINEAR, "LINEAR" },
{JsonAnimationSamplerInterpolation::STEP, "STEP" },
{JsonAnimationSamplerInterpolation::CUBIC_SPLINE, "CUBICSPLINE"},
});
class JsonAnimationSampler
{
public:
unsigned input;
std::optional<JsonAnimationSamplerInterpolation> interpolation;
unsigned output;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationSampler, input, interpolation, output);
class JsonAnimation
{
public:
std::vector<JsonAnimationChannel> channels;
std::vector<JsonAnimationSampler> samplers;
std::optional<std::string> name;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimation, channels, samplers, name);
class JsonTextureInfo
{
public:
unsigned index;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTextureInfo, index);
class JsonPbrMetallicRoughness
{
public:
std::optional<JsonTextureInfo> baseColorTexture;
std::optional<float> metallicFactor;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPbrMetallicRoughness, baseColorTexture, metallicFactor);
class JsonNormalTextureInfo
{
public:
unsigned index;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNormalTextureInfo, index);
class JsonMaterial
{
public:
std::optional<std::string> name;
std::optional<JsonPbrMetallicRoughness> pbrMetallicRoughness;
std::optional<JsonNormalTextureInfo> normalTexture;
std::optional<bool> doubleSided;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial, name, pbrMetallicRoughness, normalTexture, doubleSided);
enum class JsonMeshPrimitivesMode
{
POINTS = 0,
LINES = 1,
LINE_LOOP = 2,
LINE_STRIP = 3,
TRIANGLES = 4,
TRIANGLES_STRIP = 5,
TRIANGLE_FAN = 6
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonMeshPrimitivesMode,
{
{JsonMeshPrimitivesMode::POINTS, static_cast<unsigned>(JsonMeshPrimitivesMode::POINTS) },
{JsonMeshPrimitivesMode::LINES, static_cast<unsigned>(JsonMeshPrimitivesMode::LINES) },
{JsonMeshPrimitivesMode::LINE_LOOP, static_cast<unsigned>(JsonMeshPrimitivesMode::LINE_LOOP) },
{JsonMeshPrimitivesMode::LINE_STRIP, static_cast<unsigned>(JsonMeshPrimitivesMode::LINE_STRIP) },
{JsonMeshPrimitivesMode::TRIANGLES, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLES) },
{JsonMeshPrimitivesMode::TRIANGLES_STRIP, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLES_STRIP)},
{JsonMeshPrimitivesMode::TRIANGLE_FAN, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLE_FAN) },
});
// This should probably be a map, but the supported models do not have arbitrary primitives anyway
class JsonMeshPrimitivesAttributes
{
public:
std::optional<unsigned> POSITION;
std::optional<unsigned> NORMAL;
std::optional<unsigned> TEXCOORD_0;
std::optional<unsigned> JOINTS_0;
std::optional<unsigned> WEIGHTS_0;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitivesAttributes, POSITION, NORMAL, TEXCOORD_0, JOINTS_0, WEIGHTS_0);
class JsonMeshPrimitives
{
public:
JsonMeshPrimitivesAttributes attributes;
std::optional<unsigned> indices;
std::optional<unsigned> material;
std::optional<JsonMeshPrimitivesMode> mode;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitives, attributes, indices, material, mode);
class JsonMesh
{
public:
std::vector<JsonMeshPrimitives> primitives;
std::optional<std::vector<float>> weights;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMesh, primitives, weights);
class JsonSkin
{
public:
std::optional<unsigned> inverseBindMatrices;
std::optional<unsigned> skeleton;
std::vector<unsigned> joints;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSkin, inverseBindMatrices, skeleton, joints);
class JsonScene
{
public:
std::vector<unsigned> nodes;
std::optional<std::string> name;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonScene, nodes, name);
class JsonTexture
{
public:
unsigned source;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTexture, source);
class JsonImage
{
public:
std::optional<std::string> uri;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonImage, uri);
class JsonRoot
{
public:
std::optional<std::vector<JsonAccessor>> accessors;
std::optional<std::vector<JsonAnimation>> animations;
JsonAsset asset;
std::optional<std::vector<JsonBuffer>> buffers;
std::optional<std::vector<JsonBufferView>> bufferViews;
std::optional<std::vector<JsonImage>> images;
std::optional<std::vector<JsonMaterial>> materials;
std::optional<std::vector<JsonMesh>> meshes;
std::optional<std::vector<JsonNode>> nodes;
std::optional<std::vector<JsonSkin>> skins;
std::optional<unsigned> scene;
std::optional<std::vector<JsonScene>> scenes;
std::optional<std::vector<JsonTexture>> textures;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(
JsonRoot, accessors, animations, asset, buffers, bufferViews, images, materials, meshes, nodes, skins, scene, scenes, textures);
} // namespace gltf

View File

@ -3,13 +3,8 @@
#include "Math/Quaternion.h"
#include "Utils/DistinctMapper.h"
#include <memory>
#include <string>
struct XModelObject
{
std::string name;
};
#include <vector>
struct XModelBone
{
@ -30,8 +25,7 @@ struct XModelBoneWeight
struct XModelVertexBoneWeightCollection
{
std::unique_ptr<XModelBoneWeight[]> weights;
size_t totalWeightCount;
std::vector<XModelBoneWeight> weights;
};
struct XModelVertexBoneWeights
@ -51,8 +45,6 @@ struct XModelVertex
struct XModelFace
{
int vertexIndex[3];
int objectIndex;
int materialIndex;
};
struct XModelMaterial
@ -89,10 +81,30 @@ struct XModelMaterial
float blinn[2];
float phong;
std::string colorMapName;
std::string normalMapName;
std::string specularMapName;
void ApplyDefaults();
};
struct XModelObject
{
std::string name;
int materialIndex;
std::vector<XModelFace> m_faces;
};
struct XModelCommon
{
std::string m_name;
std::vector<XModelObject> m_objects;
std::vector<XModelBone> m_bones;
std::vector<XModelMaterial> m_materials;
std::vector<XModelVertex> m_vertices;
std::vector<XModelVertexBoneWeights> m_vertex_bone_weights;
XModelVertexBoneWeightCollection m_bone_weight_data;
};
struct VertexMergerPos
{
float x;

View File

@ -0,0 +1,44 @@
#include "AssetLoaderXModel.h"
#include "Game/T6/T6.h"
#include "Game/T6/XModel/JsonXModelLoader.h"
#include "Pool/GlobalAssetPool.h"
#include <cstring>
#include <format>
#include <iostream>
using namespace T6;
void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
{
auto* xmodel = memory->Create<XModel>();
memset(xmodel, 0, sizeof(XModel));
xmodel->name = memory->Dup(assetName.c_str());
return xmodel;
}
bool AssetLoaderXModel::CanLoadFromRaw() const
{
return true;
}
bool AssetLoaderXModel::LoadFromRaw(
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
{
const auto file = searchPath->Open(std::format("xmodel/{}.json", assetName));
if (!file.IsOpen())
return false;
auto* xmodel = memory->Alloc<XModel>();
xmodel->name = memory->Dup(assetName.c_str());
std::vector<XAssetInfoGeneric*> dependencies;
if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies))
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
else
std::cerr << "Failed to load xmodel \"" << assetName << "\"\n";
return true;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "AssetLoading/BasicAssetLoader.h"
#include "AssetLoading/IAssetLoadingManager.h"
#include "Game/T6/T6.h"
#include "SearchPath/ISearchPath.h"
namespace T6
{
class AssetLoaderXModel final : public BasicAssetLoader<ASSET_TYPE_XMODEL, XModel>
{
static std::string GetFileNameForAsset(const std::string& assetName);
public:
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
_NODISCARD bool CanLoadFromRaw() const override;
bool
LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override;
};
} // namespace T6

View File

@ -0,0 +1,117 @@
#include "JsonXModelLoader.h"
#include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h"
#include <format>
#include <iostream>
#include <nlohmann/json.hpp>
#include <vector>
using namespace nlohmann;
using namespace T6;
namespace
{
class JsonLoader
{
public:
JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set<XAssetInfoGeneric*>& dependencies)
: m_stream(stream),
m_memory(memory),
m_manager(manager),
m_dependencies(dependencies)
{
}
bool Load(XModel& xmodel) const
{
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 != "xmodel" || version != 1u)
{
std::cerr << "Tried to load xmodel \"" << xmodel.name << "\" but did not find expected type material of version 1\n";
return false;
}
const auto jXModel = jRoot.get<JsonXModel>();
return CreateXModelFromJson(jXModel, xmodel);
}
private:
static void PrintError(const XModel& xmodel, const std::string& message)
{
std::cerr << "Cannot load xmodel \"" << xmodel.name << "\": " << message << "\n";
}
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel) const
{
xmodel.collLod = static_cast<uint16_t>(jXModel.collLod);
if (jXModel.physPreset)
{
auto* physPreset = m_manager.LoadDependency<AssetPhysPreset>(jXModel.physPreset.value());
if (!physPreset)
{
PrintError(xmodel, "Could not find phys preset");
return false;
}
m_dependencies.emplace(physPreset);
xmodel.physPreset = physPreset->Asset();
}
else
{
xmodel.physPreset = nullptr;
}
if (jXModel.physConstraints)
{
auto* physConstraints = m_manager.LoadDependency<AssetPhysConstraints>(jXModel.physConstraints.value());
if (!physConstraints)
{
PrintError(xmodel, "Could not find phys constraints");
return false;
}
m_dependencies.emplace(physConstraints);
xmodel.physConstraints = physConstraints->Asset();
}
else
{
xmodel.physConstraints = nullptr;
}
xmodel.flags = jXModel.flags;
xmodel.lightingOriginOffset.x = jXModel.lightingOriginOffset.x;
xmodel.lightingOriginOffset.y = jXModel.lightingOriginOffset.y;
xmodel.lightingOriginOffset.z = jXModel.lightingOriginOffset.z;
xmodel.lightingOriginRange = jXModel.lightingOriginRange;
return true;
}
std::istream& m_stream;
MemoryManager& m_memory;
IAssetLoadingManager& m_manager;
std::set<XAssetInfoGeneric*>& m_dependencies;
};
} // namespace
namespace T6
{
bool LoadXModelAsJson(
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
{
std::set<XAssetInfoGeneric*> dependenciesSet;
const JsonLoader loader(stream, *memory, *manager, dependenciesSet);
dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend());
return loader.Load(xmodel);
}
} // namespace T6

View File

@ -0,0 +1,13 @@
#pragma once
#include "AssetLoading/IAssetLoadingManager.h"
#include "Game/T6/T6.h"
#include "Utils/MemoryManager.h"
#include <istream>
namespace T6
{
bool LoadXModelAsJson(
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies);
} // namespace T6

View File

@ -19,6 +19,7 @@ function ObjWriting:link(links)
links:linkto(ZoneCommon)
links:linkto(minilzo)
links:linkto(minizip)
links:linkto(libtomcrypt)
end
function ObjWriting:use()
@ -55,5 +56,6 @@ function ObjWriting:project()
minilzo:include(includes)
minizip:include(includes)
json:include(includes)
libtomcrypt:include(includes)
end

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW3/IW3.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW3
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod);
static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod);
static void DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void
AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW4/IW4.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW4
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs);
static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs);
static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs);
static void AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs);
static void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection);
static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection);
static void
AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW5/IW5.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW5
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs);
static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs);
static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs);
static void AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs);
static void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection);
static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection);
static void
AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h"
#include "Game/T5/T5.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace T5
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod);
static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod);
static void DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void
AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h"
#include "Game/T6/T6.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace T6
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod);
static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod);
static void DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod);
static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void
AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection);
static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -0,0 +1,71 @@
#include "JsonXModelWriter.h"
#include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h"
#include <iomanip>
#include <nlohmann/json.hpp>
using namespace nlohmann;
using namespace T6;
namespace
{
class JsonDumper
{
public:
JsonDumper(AssetDumpingContext& context, std::ostream& stream)
: m_stream(stream)
{
}
void Dump(const XModel* xmodel) const
{
JsonXModel jsonXModel;
CreateJsonXModel(jsonXModel, *xmodel);
json jRoot = jsonXModel;
jRoot["_type"] = "xmodel";
jRoot["_version"] = 1;
m_stream << std::setw(4) << jRoot << "\n";
}
private:
static const char* AssetName(const char* input)
{
if (input && input[0] == ',')
return &input[1];
return input;
}
void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) const
{
jXModel.collLod = xmodel.collLod;
if (xmodel.physPreset && xmodel.physPreset->name)
jXModel.physPreset = AssetName(xmodel.physPreset->name);
if (xmodel.physConstraints && xmodel.physConstraints->name)
jXModel.physConstraints = AssetName(xmodel.physConstraints->name);
jXModel.flags = xmodel.flags;
jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x;
jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y;
jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z;
jXModel.lightingOriginRange = xmodel.lightingOriginRange;
}
std::ostream& m_stream;
};
} // namespace
namespace T6
{
void DumpXModelAsJson(std::ostream& stream, const XModel* xmodel, AssetDumpingContext& context)
{
const JsonDumper dumper(context, stream);
dumper.Dump(xmodel);
}
} // namespace T6

View File

@ -0,0 +1,11 @@
#pragma once
#include "Dumping/AssetDumpingContext.h"
#include "Game/T6/T6.h"
#include <ostream>
namespace T6
{
void DumpXModelAsJson(std::ostream& stream, const XModel* xmodel, AssetDumpingContext& context);
} // namespace T6

View File

@ -1,157 +0,0 @@
#include "ObjWriter.h"
#include "Game/IW4/CommonIW4.h"
ObjWriter::ObjWriter(std::string gameName, std::string zoneName)
: m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void ObjWriter::AddObject(ObjObject object)
{
m_objects.emplace_back(std::move(object));
m_object_data.emplace_back();
}
void ObjWriter::AddMaterial(MtlMaterial material)
{
m_materials.emplace_back(std::move(material));
}
void ObjWriter::AddVertex(const int objectId, const ObjVertex vertex)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_vertices.Add(vertex);
}
void ObjWriter::AddNormal(const int objectId, const ObjNormal normal)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_normals.Add(normal);
}
void ObjWriter::AddUv(const int objectId, const ObjUv uv)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_uvs.Add(uv);
}
void ObjWriter::AddFace(const int objectId, const ObjFace face)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_faces.push_back(face);
}
void ObjWriter::GetObjObjectDataOffsets(std::vector<ObjObjectDataOffsets>& inputOffsets, std::vector<ObjObjectDataOffsets>& distinctOffsets)
{
ObjObjectDataOffsets currentInputOffsets{};
ObjObjectDataOffsets currentDistinctOffsets{};
for (const auto& objectData : m_object_data)
{
inputOffsets.push_back(currentInputOffsets);
distinctOffsets.push_back(currentDistinctOffsets);
currentInputOffsets.vertexOffset += objectData.m_vertices.GetInputValueCount();
currentInputOffsets.normalOffset += objectData.m_normals.GetInputValueCount();
currentInputOffsets.uvOffset += objectData.m_uvs.GetInputValueCount();
currentDistinctOffsets.vertexOffset += objectData.m_vertices.GetDistinctValueCount();
currentDistinctOffsets.normalOffset += objectData.m_normals.GetDistinctValueCount();
currentDistinctOffsets.uvOffset += objectData.m_uvs.GetDistinctValueCount();
}
}
void ObjWriter::WriteObj(std::ostream& stream)
{
WriteObj(stream, std::string());
}
void ObjWriter::WriteObj(std::ostream& stream, const std::string& mtlName)
{
stream << "# OpenAssetTools OBJ File ( " << m_game_name << ")\n";
stream << "# Game Origin: " << m_game_name << "\n";
stream << "# Zone Origin: " << m_zone_name << "\n";
if (!mtlName.empty())
stream << "mtllib " << mtlName << "\n";
std::vector<ObjObjectDataOffsets> inputOffsetsByObject;
std::vector<ObjObjectDataOffsets> distinctOffsetsByObject;
GetObjObjectDataOffsets(inputOffsetsByObject, distinctOffsetsByObject);
auto objectIndex = 0;
for (const auto& object : m_objects)
{
const auto& objectData = m_object_data[objectIndex];
stream << "o " << object.name << "\n";
for (const auto& v : objectData.m_vertices.GetDistinctValues())
stream << "v " << v.coordinates[0] << " " << v.coordinates[1] << " " << v.coordinates[2] << "\n";
for (const auto& uv : objectData.m_uvs.GetDistinctValues())
stream << "vt " << uv.uv[0] << " " << uv.uv[1] << "\n";
for (const auto& n : objectData.m_normals.GetDistinctValues())
stream << "vn " << n.normal[0] << " " << n.normal[1] << " " << n.normal[2] << "\n";
if (object.materialIndex >= 0 && static_cast<unsigned>(object.materialIndex) < m_materials.size())
stream << "usemtl " << m_materials[object.materialIndex].materialName << "\n";
for (const auto& f : objectData.m_faces)
{
const size_t v[3]{objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[0] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[1] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[2] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1};
const size_t n[3]{objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[0] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[1] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[2] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1};
const size_t uv[3]{objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[0] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[1] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[2] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1};
stream << "f " << v[0] << "/" << uv[0] << "/" << n[0] << " " << v[1] << "/" << uv[1] << "/" << n[1] << " " << v[2] << "/" << uv[2] << "/" << n[2]
<< "\n";
}
objectIndex++;
}
}
void ObjWriter::WriteMtl(std::ostream& stream)
{
stream << "# OpenAssetTools MAT File ( " << m_game_name << ")\n";
stream << "# Game Origin: " << m_game_name << "\n";
stream << "# Zone Origin: " << m_zone_name << "\n";
stream << "# Material count: " << m_materials.size() << "\n";
for (const auto& material : m_materials)
{
stream << "\n";
stream << "newmtl " << material.materialName << "\n";
if (!material.colorMapName.empty())
stream << "map_Kd ../images/" << material.colorMapName << ".dds\n";
if (!material.normalMapName.empty())
stream << "map_bump ../images/" << material.normalMapName << ".dds\n";
if (!material.specularMapName.empty())
stream << "map_Ks ../images/" << material.specularMapName << ".dds\n";
}
}

View File

@ -1,48 +0,0 @@
#pragma once
#include "Model/Obj/ObjCommon.h"
#include "Utils/DistinctMapper.h"
#include <ostream>
#include <vector>
class ObjWriter
{
protected:
struct ObjObjectData
{
DistinctMapper<ObjVertex> m_vertices;
DistinctMapper<ObjNormal> m_normals;
DistinctMapper<ObjUv> m_uvs;
std::vector<ObjFace> m_faces;
};
struct ObjObjectDataOffsets
{
size_t vertexOffset;
size_t normalOffset;
size_t uvOffset;
};
std::string m_game_name;
std::string m_zone_name;
std::vector<ObjObject> m_objects;
std::vector<ObjObjectData> m_object_data;
std::vector<MtlMaterial> m_materials;
void GetObjObjectDataOffsets(std::vector<ObjObjectDataOffsets>& inputOffsets, std::vector<ObjObjectDataOffsets>& distinctOffsets);
public:
ObjWriter(std::string gameName, std::string zoneName);
void AddObject(ObjObject object);
void AddMaterial(MtlMaterial material);
void AddVertex(int objectId, ObjVertex vertex);
void AddNormal(int objectId, ObjNormal normal);
void AddUv(int objectId, ObjUv uv);
void AddFace(int objectId, ObjFace face);
void WriteObj(std::ostream& stream);
void WriteObj(std::ostream& stream, const std::string& mtlName);
void WriteMtl(std::ostream& stream);
};

View File

@ -1,33 +0,0 @@
#include "AbstractXModelWriter.h"
AbstractXModelWriter::AbstractXModelWriter() = default;
void AbstractXModelWriter::AddObject(XModelObject object)
{
m_objects.emplace_back(std::move(object));
}
void AbstractXModelWriter::AddBone(XModelBone bone)
{
m_bones.emplace_back(std::move(bone));
}
void AbstractXModelWriter::AddMaterial(XModelMaterial material)
{
m_materials.emplace_back(std::move(material));
}
void AbstractXModelWriter::AddVertex(XModelVertex vertex)
{
m_vertices.emplace_back(vertex);
}
void AbstractXModelWriter::AddVertexBoneWeights(XModelVertexBoneWeights vertexBoneWeights)
{
m_vertex_bone_weights.emplace_back(vertexBoneWeights);
}
void AbstractXModelWriter::AddFace(XModelFace face)
{
m_faces.emplace_back(face);
}

View File

@ -1,26 +0,0 @@
#pragma once
#include "Model/XModel/XModelCommon.h"
#include <vector>
class AbstractXModelWriter
{
protected:
std::vector<XModelObject> m_objects;
std::vector<XModelBone> m_bones;
std::vector<XModelMaterial> m_materials;
std::vector<XModelVertex> m_vertices;
std::vector<XModelVertexBoneWeights> m_vertex_bone_weights;
std::vector<XModelFace> m_faces;
public:
AbstractXModelWriter();
void AddObject(XModelObject object);
void AddBone(XModelBone bone);
void AddMaterial(XModelMaterial material);
void AddVertex(XModelVertex vertex);
void AddVertexBoneWeights(XModelVertexBoneWeights vertexBoneWeights);
void AddFace(XModelFace face);
};

View File

@ -1,221 +0,0 @@
#include "XModelExportWriter.h"
#include "Math/Quaternion.h"
#include <iomanip>
#include <iostream>
class XModelExportWriterBase : public XModelExportWriter
{
protected:
std::string m_game_name;
std::string m_zone_name;
VertexMerger m_vertex_merger;
void PrepareVertexMerger()
{
m_vertex_merger = VertexMerger(m_vertices.size());
auto vertexOffset = 0u;
for (const auto& vertex : m_vertices)
{
XModelVertexBoneWeights weights{nullptr, 0};
if (vertexOffset < m_vertex_bone_weights.size())
weights = m_vertex_bone_weights[vertexOffset];
m_vertex_merger.Add(VertexMergerPos{vertex.coordinates[0], vertex.coordinates[1], vertex.coordinates[2], weights.weights, weights.weightCount});
vertexOffset++;
}
}
void WriteHeader(std::ostream& stream, const int version) const
{
stream << "// OpenAssetTools XMODEL_EXPORT File\n";
stream << "// Game Origin: " << m_game_name << "\n";
stream << "// Zone Origin: " << m_zone_name << "\n";
stream << "MODEL\n";
stream << "VERSION " << version << "\n";
stream << "\n";
}
void WriteBones(std::ostream& stream) const
{
stream << "NUMBONES " << m_bones.size() << "\n";
size_t boneNum = 0u;
for (const auto& bone : m_bones)
{
stream << "BONE " << boneNum << " ";
if (bone.parentIndex < 0)
stream << "-1";
else
stream << bone.parentIndex;
stream << " \"" << bone.name << "\"\n";
boneNum++;
}
stream << "\n";
boneNum = 0u;
for (const auto& bone : m_bones)
{
stream << "BONE " << boneNum << "\n";
stream << "OFFSET ";
stream << std::setprecision(6) << std::fixed << bone.globalOffset[0] << ", " << std::setprecision(6) << std::fixed << bone.globalOffset[1] << ", "
<< std::setprecision(6) << std::fixed << bone.globalOffset[2] << "\n";
stream << "SCALE ";
stream << std::setprecision(6) << std::fixed << bone.scale[0] << ", " << std::setprecision(6) << std::fixed << bone.scale[1] << ", "
<< std::setprecision(6) << std::fixed << bone.scale[2] << "\n";
const Matrix32 mat = bone.globalRotation.ToMatrix();
stream << "X " << std::setprecision(6) << std::fixed << mat.m_data[0][0] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][0] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][0] << "\n";
stream << "Y " << std::setprecision(6) << std::fixed << mat.m_data[0][1] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][1] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][1] << "\n";
stream << "Z " << std::setprecision(6) << std::fixed << mat.m_data[0][2] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][2] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][2] << "\n";
stream << "\n";
boneNum++;
}
}
XModelExportWriterBase(std::string gameName, std::string zoneName)
: m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
};
class XModelExportWriter6 final : public XModelExportWriterBase
{
void WriteVertices(std::ostream& stream) const
{
const auto& distinctVertexValues = m_vertex_merger.GetDistinctValues();
stream << "NUMVERTS " << distinctVertexValues.size() << "\n";
size_t vertexNum = 0u;
for (const auto& vertexPos : distinctVertexValues)
{
stream << "VERT " << vertexNum << "\n";
stream << "OFFSET ";
stream << std::setprecision(6) << std::fixed << vertexPos.x << ", " << std::setprecision(6) << std::fixed << vertexPos.y << ", "
<< std::setprecision(6) << std::fixed << vertexPos.z << "\n";
stream << "BONES " << vertexPos.weightCount << "\n";
for (auto weightIndex = 0u; weightIndex < vertexPos.weightCount; weightIndex++)
{
stream << "BONE " << vertexPos.weights[weightIndex].boneIndex << " " << std::setprecision(6) << std::fixed
<< vertexPos.weights[weightIndex].weight << "\n";
}
stream << "\n";
vertexNum++;
}
}
static void WriteFaceVertex(std::ostream& stream, const size_t index, const XModelVertex& vertex)
{
stream << "VERT " << index << "\n";
stream << "NORMAL " << std::setprecision(6) << std::fixed << vertex.normal[0] << " " << std::setprecision(6) << std::fixed << vertex.normal[1] << " "
<< std::setprecision(6) << std::fixed << vertex.normal[2] << "\n";
stream << "COLOR " << std::setprecision(6) << std::fixed << vertex.color[0] << " " << std::setprecision(6) << std::fixed << vertex.color[1] << " "
<< std::setprecision(6) << std::fixed << vertex.color[2] << " " << std::setprecision(6) << std::fixed << vertex.color[3] << "\n";
stream << "UV 1 " << std::setprecision(6) << std::fixed << vertex.uv[0] << " " << std::setprecision(6) << std::fixed << vertex.uv[1] << "\n";
}
void WriteFaces(std::ostream& stream) const
{
stream << "NUMFACES " << m_faces.size() << "\n";
for (const auto& face : m_faces)
{
const size_t distinctPositions[3]{
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[0]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[1]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[2]),
};
const XModelVertex& v0 = m_vertices[face.vertexIndex[0]];
const XModelVertex& v1 = m_vertices[face.vertexIndex[1]];
const XModelVertex& v2 = m_vertices[face.vertexIndex[2]];
stream << "TRI " << face.objectIndex << " " << face.materialIndex << " 0 0\n";
WriteFaceVertex(stream, distinctPositions[0], v0);
WriteFaceVertex(stream, distinctPositions[1], v1);
WriteFaceVertex(stream, distinctPositions[2], v2);
stream << "\n";
}
}
void WriteObjects(std::ostream& stream) const
{
stream << "NUMOBJECTS " << m_objects.size() << "\n";
size_t objectNum = 0u;
for (const auto& object : m_objects)
{
stream << "OBJECT " << objectNum << " \"" << object.name << "\"\n";
objectNum++;
}
stream << "\n";
}
void WriteMaterials(std::ostream& stream) const
{
stream << "NUMMATERIALS " << m_materials.size() << "\n";
size_t materialNum = 0u;
for (const auto& material : m_materials)
{
const auto colorMapPath = "../images/" + material.colorMapName + ".dds";
stream << "MATERIAL " << materialNum << " \"" << material.name << "\" \"" << material.materialTypeName << "\" \"" << colorMapPath << "\"\n";
stream << "COLOR " << std::setprecision(6) << std::fixed << material.color[0] << " " << std::setprecision(6) << std::fixed << material.color[1]
<< " " << std::setprecision(6) << std::fixed << material.color[2] << " " << std::setprecision(6) << std::fixed << material.color[3] << "\n";
stream << "TRANSPARENCY " << std::setprecision(6) << std::fixed << material.transparency[0] << " " << std::setprecision(6) << std::fixed
<< material.transparency[1] << " " << std::setprecision(6) << std::fixed << material.transparency[2] << " " << std::setprecision(6)
<< std::fixed << material.transparency[3] << "\n";
stream << "AMBIENTCOLOR " << std::setprecision(6) << std::fixed << material.ambientColor[0] << " " << std::setprecision(6) << std::fixed
<< material.ambientColor[1] << " " << std::setprecision(6) << std::fixed << material.ambientColor[2] << " " << std::setprecision(6)
<< std::fixed << material.ambientColor[3] << "\n";
stream << "INCANDESCENCE " << std::setprecision(6) << std::fixed << material.incandescence[0] << " " << std::setprecision(6) << std::fixed
<< material.incandescence[1] << " " << std::setprecision(6) << std::fixed << material.incandescence[2] << " " << std::setprecision(6)
<< std::fixed << material.incandescence[3] << "\n";
stream << "COEFFS " << std::setprecision(6) << std::fixed << material.coeffs[0] << " " << std::setprecision(6) << std::fixed << material.coeffs[1]
<< "\n";
stream << "GLOW " << std::setprecision(6) << std::fixed << material.glow.x << " " << material.glow.y << "\n";
stream << "REFRACTIVE " << material.refractive.x << " " << std::setprecision(6) << std::fixed << material.refractive.y << "\n";
stream << "SPECULARCOLOR " << std::setprecision(6) << std::fixed << material.specularColor[0] << " " << std::setprecision(6) << std::fixed
<< material.specularColor[1] << " " << std::setprecision(6) << std::fixed << material.specularColor[2] << " " << std::setprecision(6)
<< std::fixed << material.specularColor[3] << "\n";
stream << "REFLECTIVECOLOR " << std::setprecision(6) << std::fixed << material.reflectiveColor[0] << " " << std::setprecision(6) << std::fixed
<< material.reflectiveColor[1] << " " << std::setprecision(6) << std::fixed << material.reflectiveColor[2] << " " << std::setprecision(6)
<< std::fixed << material.reflectiveColor[3] << "\n";
stream << "REFLECTIVE " << material.reflective.x << " " << std::setprecision(6) << std::fixed << material.reflective.y << "\n";
stream << "BLINN " << std::setprecision(6) << std::fixed << material.blinn[0] << " " << std::setprecision(6) << std::fixed << material.blinn[1]
<< "\n";
stream << "PHONG " << std::setprecision(6) << std::fixed << material.phong << "\n";
stream << "\n";
materialNum++;
}
}
public:
XModelExportWriter6(std::string gameName, std::string zoneName)
: XModelExportWriterBase(std::move(gameName), std::move(zoneName))
{
}
void Write(std::ostream& stream) override
{
PrepareVertexMerger();
WriteHeader(stream, 6);
WriteBones(stream);
WriteVertices(stream);
WriteFaces(stream);
WriteObjects(stream);
WriteMaterials(stream);
}
};
std::unique_ptr<XModelExportWriter> XModelExportWriter::CreateWriterForVersion6(std::string gameName, std::string zoneName)
{
return std::make_unique<XModelExportWriter6>(std::move(gameName), std::move(zoneName));
}

View File

@ -1,21 +0,0 @@
#pragma once
#include "AbstractXModelWriter.h"
#include <memory>
#include <ostream>
class XModelExportWriter : public AbstractXModelWriter
{
public:
XModelExportWriter() = default;
virtual ~XModelExportWriter() = default;
XModelExportWriter(const XModelExportWriter& other) = default;
XModelExportWriter(XModelExportWriter&& other) noexcept = default;
XModelExportWriter& operator=(const XModelExportWriter& other) = default;
XModelExportWriter& operator=(XModelExportWriter&& other) noexcept = default;
virtual void Write(std::ostream& stream) = 0;
static std::unique_ptr<XModelExportWriter> CreateWriterForVersion6(std::string gameName, std::string zoneName);
};

View File

@ -20,14 +20,16 @@ public:
enum class ModelOutputFormat_e
{
XMODEL_EXPORT,
OBJ
OBJ,
GLTF,
GLB
};
bool Verbose = false;
std::vector<bool> AssetTypesToHandleBitfield;
ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS;
ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT;
ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::GLB;
bool MenuLegacyMode = false;
} Configuration;

View File

@ -0,0 +1,232 @@
#include "XModelExportWriter.h"
#include "Math/Quaternion.h"
#include <chrono>
#include <iomanip>
#include <iostream>
class XModelExportWriterBase : public XModelWriter
{
protected:
void PrepareVertexMerger(const XModelCommon& xmodel)
{
m_vertex_merger = VertexMerger(xmodel.m_vertices.size());
auto vertexOffset = 0u;
for (const auto& vertex : xmodel.m_vertices)
{
XModelVertexBoneWeights weights{nullptr, 0};
if (vertexOffset < xmodel.m_vertex_bone_weights.size())
weights = xmodel.m_vertex_bone_weights[vertexOffset];
m_vertex_merger.Add(VertexMergerPos{vertex.coordinates[0], vertex.coordinates[1], vertex.coordinates[2], weights.weights, weights.weightCount});
vertexOffset++;
}
}
void WriteHeader(const int version) const
{
m_stream << "// OpenAssetTools XMODEL_EXPORT File\n";
m_stream << "// Game Origin: " << m_game_name << "\n";
m_stream << "// Zone Origin: " << m_zone_name << "\n";
m_stream << "MODEL\n";
m_stream << "VERSION " << version << "\n";
m_stream << "\n";
}
void WriteBones(const XModelCommon& xmodel) const
{
m_stream << "NUMBONES " << xmodel.m_bones.size() << "\n";
size_t boneNum = 0u;
for (const auto& bone : xmodel.m_bones)
{
m_stream << "BONE " << boneNum << " ";
if (bone.parentIndex < 0)
m_stream << "-1";
else
m_stream << bone.parentIndex;
m_stream << " \"" << bone.name << "\"\n";
boneNum++;
}
m_stream << "\n";
boneNum = 0u;
for (const auto& bone : xmodel.m_bones)
{
m_stream << "BONE " << boneNum << "\n";
m_stream << std::format("OFFSET {:.6f}, {:.6f}, {:.6f}\n", bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]);
m_stream << std::format("SCALE {:.6f}, {:.6f}, {:.6f}\n", bone.scale[0], bone.scale[1], bone.scale[2]);
const Matrix32 mat = bone.globalRotation.ToMatrix();
m_stream << std::format("X {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][0], mat.m_data[1][0], mat.m_data[2][0]);
m_stream << std::format("Y {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][1], mat.m_data[1][1], mat.m_data[2][1]);
m_stream << std::format("Z {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][2], mat.m_data[1][2], mat.m_data[2][2]);
m_stream << '\n';
boneNum++;
}
}
XModelExportWriterBase(std::ostream& stream, std::string gameName, std::string zoneName)
: m_stream(stream),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
std::ostream& m_stream;
std::string m_game_name;
std::string m_zone_name;
VertexMerger m_vertex_merger;
};
class XModelExportWriter6 final : public XModelExportWriterBase
{
void WriteVertices(const XModelCommon& xmodel) const
{
const auto& distinctVertexValues = m_vertex_merger.GetDistinctValues();
m_stream << "NUMVERTS " << distinctVertexValues.size() << "\n";
size_t vertexNum = 0u;
for (const auto& vertexPos : distinctVertexValues)
{
m_stream << "VERT " << vertexNum << "\n";
m_stream << std::format("OFFSET {:.6f}, {:.6f}, {:.6f}\n", vertexPos.x, vertexPos.y, vertexPos.z);
m_stream << "BONES " << vertexPos.weightCount << "\n";
for (auto weightIndex = 0u; weightIndex < vertexPos.weightCount; weightIndex++)
{
const auto& weight = vertexPos.weights[weightIndex];
m_stream << std::format("BONE {} {:.6f}\n", weight.boneIndex, weight.weight);
}
m_stream << "\n";
vertexNum++;
}
}
void WriteFaceVertex(const size_t index, const XModelVertex& vertex) const
{
m_stream << "VERT " << index << "\n";
m_stream << std::format("NORMAL {:.6f} {:.6f} {:.6f}\n", vertex.normal[0], vertex.normal[1], vertex.normal[2]);
m_stream << std::format("COLOR {:.6f} {:.6f} {:.6f} {:.6f}\n", vertex.color[0], vertex.color[1], vertex.color[2], vertex.color[3]);
m_stream << std::format("UV 1 {:.6f} {:.6f}\n", vertex.uv[0], vertex.uv[1]);
}
void WriteFaces(const XModelCommon& xmodel) const
{
auto totalFaceCount = 0u;
for (const auto& object : xmodel.m_objects)
totalFaceCount += object.m_faces.size();
m_stream << "NUMFACES " << totalFaceCount << "\n";
auto objectIndex = 0u;
for (const auto& object : xmodel.m_objects)
{
for (const auto& face : object.m_faces)
{
const size_t distinctPositions[3]{
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[0]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[1]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[2]),
};
const XModelVertex& v0 = xmodel.m_vertices[face.vertexIndex[0]];
const XModelVertex& v1 = xmodel.m_vertices[face.vertexIndex[1]];
const XModelVertex& v2 = xmodel.m_vertices[face.vertexIndex[2]];
m_stream << "TRI " << objectIndex << " " << object.materialIndex << " 0 0\n";
WriteFaceVertex(distinctPositions[0], v0);
WriteFaceVertex(distinctPositions[1], v1);
WriteFaceVertex(distinctPositions[2], v2);
m_stream << "\n";
}
objectIndex++;
}
}
void WriteObjects(const XModelCommon& xmodel) const
{
m_stream << "NUMOBJECTS " << xmodel.m_objects.size() << "\n";
size_t objectNum = 0u;
for (const auto& object : xmodel.m_objects)
{
m_stream << "OBJECT " << objectNum << " \"" << object.name << "\"\n";
objectNum++;
}
m_stream << "\n";
}
void WriteMaterials(const XModelCommon& xmodel) const
{
m_stream << "NUMMATERIALS " << xmodel.m_materials.size() << "\n";
size_t materialNum = 0u;
for (const auto& material : xmodel.m_materials)
{
const auto colorMapPath = "../images/" + material.colorMapName + ".dds";
m_stream << "MATERIAL " << materialNum << " \"" << material.name << "\" \"" << material.materialTypeName << "\" \"" << colorMapPath << "\"\n";
m_stream << std::format("COLOR {:.6f} {:.6f} {:.6f} {:.6f}\n", material.color[0], material.color[1], material.color[2], material.color[3]);
m_stream << std::format("TRANSPARENCY {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.transparency[0],
material.transparency[1],
material.transparency[2],
material.transparency[3]);
m_stream << std::format("AMBIENTCOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.ambientColor[0],
material.ambientColor[1],
material.ambientColor[2],
material.ambientColor[3]);
m_stream << std::format("INCANDESCENCE {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.incandescence[0],
material.incandescence[1],
material.incandescence[2],
material.incandescence[3]);
m_stream << std::format("COEFFS {:.6f} {:.6f}\n", material.coeffs[0], material.coeffs[1]);
m_stream << std::format("GLOW {:.6f} {}\n", material.glow.x, material.glow.y);
m_stream << std::format("REFRACTIVE {} {:.6f}\n", material.refractive.x, material.refractive.y);
m_stream << std::format("SPECULARCOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.specularColor[0],
material.specularColor[1],
material.specularColor[2],
material.specularColor[3]);
m_stream << std::format("REFLECTIVECOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.reflectiveColor[0],
material.reflectiveColor[1],
material.reflectiveColor[2],
material.reflectiveColor[3]);
m_stream << std::format("REFLECTIVE {} {:.6f}\n", material.reflective.x, material.reflective.y);
m_stream << std::format("BLINN {:.6f} {:.6f}\n", material.blinn[0], material.blinn[1]);
m_stream << std::format("PHONG {:.6f}\n", material.phong);
m_stream << '\n';
materialNum++;
}
}
public:
XModelExportWriter6(std::ostream& stream, std::string gameName, std::string zoneName)
: XModelExportWriterBase(stream, std::move(gameName), std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
PrepareVertexMerger(xmodel);
WriteHeader(6);
WriteBones(xmodel);
WriteVertices(xmodel);
WriteFaces(xmodel);
WriteObjects(xmodel);
WriteMaterials(xmodel);
}
};
namespace xmodel_export
{
std::unique_ptr<XModelWriter> CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName)
{
return std::make_unique<XModelExportWriter6>(stream, std::move(gameName), std::move(zoneName));
}
} // namespace xmodel_export

View File

@ -0,0 +1,11 @@
#pragma once
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace xmodel_export
{
std::unique_ptr<XModelWriter> CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName);
}

View File

@ -0,0 +1,86 @@
#include "GltfBinOutput.h"
#include "Utils/Alignment.h"
#include "XModel/Gltf/GltfConstants.h"
#include <cstdint>
#include <nlohmann/json.hpp>
using namespace gltf;
BinOutput::BinOutput(std::ostream& stream)
: m_stream(stream)
{
}
void BinOutput::Write(const void* data, const size_t dataSize) const
{
m_stream.write(static_cast<const char*>(data), dataSize);
}
void BinOutput::AlignToFour(const char value) const
{
const auto offset = m_stream.tellp();
if (offset % 4 > 0)
{
const uint32_t alignmentValue = FileUtils::MakeMagic32(value, value, value, value);
Write(&alignmentValue, 4u - (offset % 4u));
}
}
std::optional<std::string> BinOutput::CreateBufferUri(const void* buffer, size_t bufferSize) const
{
return std::nullopt;
}
void BinOutput::EmitJson(const nlohmann::json& json) const
{
static constexpr uint32_t ZERO = 0u;
Write(&GLTF_MAGIC, sizeof(GLTF_MAGIC));
Write(&GLTF_VERSION, sizeof(GLTF_VERSION));
// File length will be filled later
Write(&ZERO, sizeof(ZERO));
// Chunk length will be filled after writing json
Write(&ZERO, sizeof(ZERO));
Write(&CHUNK_MAGIC_JSON, sizeof(CHUNK_MAGIC_JSON));
m_stream << json;
AlignToFour(' ');
const auto offsetAfterData = m_stream.tellp();
const auto jsonDataLength = static_cast<uint32_t>(static_cast<unsigned>(offsetAfterData) - GLTF_JSON_CHUNK_DATA_OFFSET);
// Fill chunk length now
m_stream.seekp(GLTF_JSON_CHUNK_LENGTH_OFFSET, std::ios::beg);
Write(&jsonDataLength, sizeof(jsonDataLength));
// Return to previous pos
m_stream.seekp(offsetAfterData, std::ios::beg);
}
void BinOutput::EmitBuffer(const void* buffer, const size_t bufferSize) const
{
const auto chunkLength = utils::Align<uint32_t>(bufferSize, 4u);
Write(&chunkLength, sizeof(chunkLength));
Write(&CHUNK_MAGIC_BIN, sizeof(CHUNK_MAGIC_BIN));
Write(buffer, bufferSize);
AlignToFour('\0');
}
void BinOutput::Finalize() const
{
const auto fileEndOffset = m_stream.tellp();
const auto fileSize = static_cast<uint32_t>(fileEndOffset);
// Fill chunk length now
m_stream.seekp(GLTF_LENGTH_OFFSET, std::ios::beg);
Write(&fileSize, sizeof(fileSize));
// Return to file end
m_stream.seekp(fileEndOffset, std::ios::beg);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "GltfOutput.h"
#include <ostream>
namespace gltf
{
class BinOutput final : public Output
{
public:
explicit BinOutput(std::ostream& stream);
std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const override;
void EmitJson(const nlohmann::json& json) const override;
void EmitBuffer(const void* buffer, size_t bufferSize) const override;
void Finalize() const override;
private:
void Write(const void* data, size_t dataSize) const;
void AlignToFour(char value) const;
std::ostream& m_stream;
};
} // namespace gltf

View File

@ -0,0 +1,25 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <string>
namespace gltf
{
class Output
{
protected:
Output() = default;
virtual ~Output() = default;
Output(const Output& other) = default;
Output(Output&& other) noexcept = default;
Output& operator=(const Output& other) = default;
Output& operator=(Output&& other) noexcept = default;
public:
virtual std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const = 0;
virtual void EmitJson(const nlohmann::json& json) const = 0;
virtual void EmitBuffer(const void* buffer, size_t bufferSize) const = 0;
virtual void Finalize() const = 0;
};
} // namespace gltf

View File

@ -0,0 +1,51 @@
#include "GltfTextOutput.h"
#include "Utils/Alignment.h"
#include "XModel/Gltf/GltfConstants.h"
#include <iomanip>
#include <nlohmann/json.hpp>
#define LTC_NO_PROTOTYPES
#include <tomcrypt.h>
using namespace gltf;
TextOutput::TextOutput(std::ostream& stream)
: m_stream(stream)
{
}
std::optional<std::string> TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const
{
static constexpr auto URI_PREFIX_LENGTH = std::char_traits<char>::length(GLTF_DATA_URI_PREFIX);
const auto base64Length = 4u * ((bufferSize + 2u) / 3u);
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
std::string output(base64BufferSize, '\0');
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
unsigned long outLength = base64Length + 1u;
const auto result = base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
assert(result == CRYPT_OK);
assert(outLength == base64Length);
return output;
}
void TextOutput::EmitJson(const nlohmann::json& json) const
{
m_stream << std::setw(4) << json;
}
void TextOutput::EmitBuffer(const void* buffer, const size_t bufferSize) const
{
// Nothing to do
}
void TextOutput::Finalize() const
{
// Nothing to do
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "GltfOutput.h"
#include <ostream>
namespace gltf
{
class TextOutput final : public Output
{
public:
explicit TextOutput(std::ostream& stream);
std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const override;
void EmitJson(const nlohmann::json& json) const override;
void EmitBuffer(const void* buffer, size_t bufferSize) const override;
void Finalize() const override;
private:
std::ostream& m_stream;
};
} // namespace gltf

View File

@ -0,0 +1,621 @@
#include "GltfWriter.h"
#include "GitVersion.h"
#include "Math/Vector.h"
#include "XModel/Gltf/GltfConstants.h"
#include "XModel/Gltf/JsonGltf.h"
#include <format>
using namespace gltf;
using namespace nlohmann;
namespace
{
constexpr auto GLTF_GENERATOR = "OpenAssetTools " GIT_VERSION;
struct GltfVertex
{
float coordinates[3];
float normal[3];
float uv[2];
};
class GltfWriterImpl final : public gltf::Writer
{
public:
GltfWriterImpl(const Output* output, std::string gameName, std::string zoneName)
: m_output(output),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
JsonRoot gltf;
std::vector<uint8_t> bufferData;
CreateJsonAsset(gltf.asset);
CreateSkeletonNodes(gltf, xmodel);
CreateMeshNode(gltf, xmodel);
CreateRootNode(gltf, xmodel);
CreateMaterials(gltf, xmodel);
CreateBufferViews(gltf, xmodel);
CreateAccessors(gltf, xmodel);
CreateSkin(gltf, xmodel);
CreateMesh(gltf, xmodel);
CreateScene(gltf, xmodel);
FillBufferData(gltf, xmodel, bufferData);
CreateBuffer(gltf, xmodel, bufferData);
const json jRoot = gltf;
m_output->EmitJson(jRoot);
if (!bufferData.empty())
m_output->EmitBuffer(bufferData.data(), bufferData.size());
m_output->Finalize();
}
private:
static void CreateJsonAsset(JsonAsset& asset)
{
asset.version = GLTF_VERSION_STRING;
asset.generator = GLTF_GENERATOR;
}
void CreateMeshNode(JsonRoot& gltf, const XModelCommon& xmodel)
{
JsonNode meshNode;
if (!xmodel.m_name.empty())
meshNode.name = xmodel.m_name;
// We only have one mesh
meshNode.mesh = 0u;
// Only add skin if the model has bones
if (!xmodel.m_bones.empty())
{
// We only have one skin
meshNode.skin = 0u;
}
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
m_mesh_node = gltf.nodes->size();
gltf.nodes->emplace_back(std::move(meshNode));
}
void CreateRootNode(JsonRoot& gltf, const XModelCommon& xmodel)
{
JsonNode rootNode;
if (!xmodel.m_name.empty())
rootNode.name = std::format("{}_skel", xmodel.m_name);
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
rootNode.children.emplace();
rootNode.children->push_back(m_mesh_node);
if (!xmodel.m_bones.empty())
rootNode.children->push_back(m_first_bone_node);
m_root_node = gltf.nodes->size();
gltf.nodes->emplace_back(std::move(rootNode));
}
void CreateMesh(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.meshes.has_value())
gltf.meshes.emplace();
JsonMesh mesh;
const auto hasBoneWeightData = !xmodel.m_vertex_bone_weights.empty();
auto objectIndex = 0u;
for (const auto& object : xmodel.m_objects)
{
JsonMeshPrimitives primitives;
if (object.materialIndex >= 0)
primitives.material = static_cast<unsigned>(object.materialIndex);
primitives.attributes.POSITION = m_position_accessor;
primitives.attributes.NORMAL = m_normal_accessor;
primitives.attributes.TEXCOORD_0 = m_uv_accessor;
if (hasBoneWeightData)
{
primitives.attributes.JOINTS_0 = m_joints_accessor;
primitives.attributes.WEIGHTS_0 = m_weights_accessor;
}
primitives.mode = JsonMeshPrimitivesMode::TRIANGLES;
primitives.indices = m_first_index_accessor + objectIndex;
mesh.primitives.emplace_back(primitives);
objectIndex++;
}
gltf.meshes->emplace_back(std::move(mesh));
}
static void CreateMaterials(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.materials.has_value())
gltf.materials.emplace();
for (const auto& modelMaterial : xmodel.m_materials)
{
JsonMaterial material;
material.name = modelMaterial.name;
if (!modelMaterial.colorMapName.empty())
{
material.pbrMetallicRoughness.emplace();
material.pbrMetallicRoughness->baseColorTexture.emplace();
material.pbrMetallicRoughness->baseColorTexture->index = CreateTexture(gltf, modelMaterial.colorMapName);
material.pbrMetallicRoughness->metallicFactor = 0.0f;
}
if (!modelMaterial.normalMapName.empty())
material.normalTexture.emplace().index = CreateTexture(gltf, modelMaterial.normalMapName);
material.doubleSided = true;
gltf.materials->emplace_back(material);
}
}
static unsigned CreateTexture(JsonRoot& gltf, const std::string& textureName)
{
if (!gltf.textures.has_value())
gltf.textures.emplace();
if (!gltf.images.has_value())
gltf.images.emplace();
auto uri = std::format("../images/{}.dds", textureName);
auto existingTexIndex = 0u;
for (const auto& existingTex : gltf.textures.value())
{
const auto& existingImage = gltf.images.value()[existingTex.source];
if (existingImage.uri == uri)
return existingTexIndex;
existingTexIndex++;
}
JsonImage image;
image.uri = std::move(uri);
const auto imageIndex = gltf.images->size();
gltf.images->emplace_back(std::move(image));
JsonTexture texture;
texture.source = imageIndex;
const auto textureIndex = gltf.textures->size();
gltf.textures->emplace_back(texture);
return textureIndex;
}
void CreateSkeletonNodes(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (xmodel.m_bones.empty())
return;
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
const auto boneCount = xmodel.m_bones.size();
m_first_bone_node = gltf.nodes->size();
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
{
JsonNode boneNode;
const auto& bone = xmodel.m_bones[boneIndex];
Vector3f translation(bone.globalOffset[0], bone.globalOffset[2], -bone.globalOffset[1]);
Quaternion32 rotation(bone.globalRotation.m_x, bone.globalRotation.m_z, -bone.globalRotation.m_y, bone.globalRotation.m_w);
if (bone.parentIndex >= 0)
{
const auto& parentBone = xmodel.m_bones[bone.parentIndex];
translation -= Vector3f(parentBone.globalOffset[0], parentBone.globalOffset[2], -parentBone.globalOffset[1]);
rotation /= Quaternion32(
parentBone.globalRotation.m_x, parentBone.globalRotation.m_z, -parentBone.globalRotation.m_y, parentBone.globalRotation.m_w);
}
rotation.Normalize();
boneNode.name = bone.name;
boneNode.translation = std::to_array({translation.x(), translation.y(), translation.z()});
boneNode.rotation = std::to_array({rotation.m_x, rotation.m_y, rotation.m_z, rotation.m_w});
std::vector<unsigned> children;
for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++)
{
if (xmodel.m_bones[maybeChildIndex].parentIndex == static_cast<int>(boneIndex))
children.emplace_back(maybeChildIndex + m_first_bone_node);
}
if (!children.empty())
boneNode.children = std::move(children);
gltf.nodes->emplace_back(std::move(boneNode));
}
}
void CreateSkin(JsonRoot& gltf, const XModelCommon& xmodel) const
{
if (xmodel.m_bones.empty())
return;
if (!gltf.skins.has_value())
gltf.skins.emplace();
JsonSkin skin;
const auto boneCount = xmodel.m_bones.size();
skin.joints.reserve(boneCount);
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
skin.joints.emplace_back(boneIndex + m_first_bone_node);
skin.inverseBindMatrices = m_inverse_bind_matrices_accessor;
gltf.skins->emplace_back(std::move(skin));
}
void CreateScene(JsonRoot& gltf, const XModelCommon& xmodel) const
{
JsonScene scene;
// Only add skin if the model has bones
scene.nodes.emplace_back(m_root_node);
if (!gltf.scenes.has_value())
gltf.scenes.emplace();
gltf.scenes->emplace_back(std::move(scene));
gltf.scene = 0u;
}
void CreateBufferViews(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.bufferViews.has_value())
gltf.bufferViews.emplace();
unsigned bufferOffset = 0u;
JsonBufferView vertexBufferView;
vertexBufferView.buffer = 0u;
vertexBufferView.byteOffset = bufferOffset;
vertexBufferView.byteStride = sizeof(GltfVertex);
vertexBufferView.byteLength = sizeof(GltfVertex) * xmodel.m_vertices.size();
vertexBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += vertexBufferView.byteLength;
m_vertex_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(vertexBufferView);
if (!xmodel.m_vertex_bone_weights.empty())
{
JsonBufferView jointsBufferView;
jointsBufferView.buffer = 0u;
jointsBufferView.byteOffset = bufferOffset;
jointsBufferView.byteLength = sizeof(uint8_t) * xmodel.m_vertices.size() * 4u;
jointsBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += jointsBufferView.byteLength;
m_joints_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(jointsBufferView);
JsonBufferView weightsBufferView;
weightsBufferView.buffer = 0u;
weightsBufferView.byteOffset = bufferOffset;
weightsBufferView.byteLength = sizeof(float) * xmodel.m_vertices.size() * 4u;
weightsBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += weightsBufferView.byteLength;
m_weights_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(weightsBufferView);
JsonBufferView inverseBindMatricesBufferView;
inverseBindMatricesBufferView.buffer = 0u;
inverseBindMatricesBufferView.byteOffset = bufferOffset;
inverseBindMatricesBufferView.byteLength = sizeof(float) * xmodel.m_bones.size() * 16u;
bufferOffset += inverseBindMatricesBufferView.byteLength;
m_inverse_bind_matrices_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(inverseBindMatricesBufferView);
}
m_first_index_buffer_view = gltf.bufferViews->size();
for (const auto& object : xmodel.m_objects)
{
JsonBufferView indicesBufferView;
indicesBufferView.buffer = 0u;
indicesBufferView.byteOffset = bufferOffset;
indicesBufferView.byteLength = sizeof(unsigned short) * object.m_faces.size() * 3u;
indicesBufferView.target = JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER;
bufferOffset += indicesBufferView.byteLength;
gltf.bufferViews->emplace_back(indicesBufferView);
}
}
void CreateAccessors(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.accessors.has_value())
gltf.accessors.emplace();
JsonAccessor positionAccessor;
positionAccessor.bufferView = m_vertex_buffer_view;
positionAccessor.byteOffset = offsetof(GltfVertex, coordinates);
positionAccessor.componentType = JsonAccessorComponentType::FLOAT;
positionAccessor.count = xmodel.m_vertices.size();
positionAccessor.type = JsonAccessorType::VEC3;
m_position_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(positionAccessor);
JsonAccessor normalAccessor;
normalAccessor.bufferView = m_vertex_buffer_view;
normalAccessor.byteOffset = offsetof(GltfVertex, normal);
normalAccessor.componentType = JsonAccessorComponentType::FLOAT;
normalAccessor.count = xmodel.m_vertices.size();
normalAccessor.type = JsonAccessorType::VEC3;
m_normal_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(normalAccessor);
JsonAccessor uvAccessor;
uvAccessor.bufferView = m_vertex_buffer_view;
uvAccessor.byteOffset = offsetof(GltfVertex, uv);
uvAccessor.componentType = JsonAccessorComponentType::FLOAT;
uvAccessor.count = xmodel.m_vertices.size();
uvAccessor.type = JsonAccessorType::VEC2;
m_uv_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(uvAccessor);
if (!xmodel.m_vertex_bone_weights.empty())
{
JsonAccessor jointsAccessor;
jointsAccessor.bufferView = m_joints_buffer_view;
jointsAccessor.componentType = JsonAccessorComponentType::UNSIGNED_BYTE;
jointsAccessor.count = xmodel.m_vertices.size();
jointsAccessor.type = JsonAccessorType::VEC4;
m_joints_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(jointsAccessor);
JsonAccessor weightsAccessor;
weightsAccessor.bufferView = m_weights_buffer_view;
weightsAccessor.componentType = JsonAccessorComponentType::FLOAT;
weightsAccessor.count = xmodel.m_vertices.size();
weightsAccessor.type = JsonAccessorType::VEC4;
m_weights_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(weightsAccessor);
JsonAccessor inverseBindMatricesAccessor;
inverseBindMatricesAccessor.bufferView = m_inverse_bind_matrices_buffer_view;
inverseBindMatricesAccessor.componentType = JsonAccessorComponentType::FLOAT;
inverseBindMatricesAccessor.count = xmodel.m_bones.size();
inverseBindMatricesAccessor.type = JsonAccessorType::MAT4;
m_inverse_bind_matrices_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(inverseBindMatricesAccessor);
}
m_first_index_accessor = gltf.accessors->size();
for (auto i = 0u; i < xmodel.m_objects.size(); i++)
{
const auto& object = xmodel.m_objects[i];
JsonAccessor indicesAccessor;
indicesAccessor.bufferView = m_first_index_buffer_view + i;
indicesAccessor.componentType = JsonAccessorComponentType::UNSIGNED_SHORT;
indicesAccessor.count = object.m_faces.size() * 3u;
indicesAccessor.type = JsonAccessorType::SCALAR;
gltf.accessors->emplace_back(indicesAccessor);
}
}
void FillBufferData(JsonRoot& gltf, const XModelCommon& xmodel, std::vector<uint8_t>& bufferData) const
{
const auto expectedBufferSize = GetExpectedBufferSize(xmodel);
bufferData.resize(expectedBufferSize);
auto currentBufferOffset = 0u;
float minPosition[3]{
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
};
float maxPosition[3]{
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
};
for (const auto& commonVertex : xmodel.m_vertices)
{
auto* vertex = reinterpret_cast<GltfVertex*>(&bufferData[currentBufferOffset]);
vertex->coordinates[0] = commonVertex.coordinates[0];
vertex->coordinates[1] = commonVertex.coordinates[2];
vertex->coordinates[2] = -commonVertex.coordinates[1];
if (minPosition[0] > vertex->coordinates[0])
minPosition[0] = vertex->coordinates[0];
if (minPosition[1] > vertex->coordinates[1])
minPosition[1] = vertex->coordinates[1];
if (minPosition[2] > vertex->coordinates[2])
minPosition[2] = vertex->coordinates[2];
if (maxPosition[0] < vertex->coordinates[0])
maxPosition[0] = vertex->coordinates[0];
if (maxPosition[1] < vertex->coordinates[1])
maxPosition[1] = vertex->coordinates[1];
if (maxPosition[2] < vertex->coordinates[2])
maxPosition[2] = vertex->coordinates[2];
vertex->normal[0] = commonVertex.normal[0];
vertex->normal[1] = commonVertex.normal[2];
vertex->normal[2] = -commonVertex.normal[1];
vertex->uv[0] = commonVertex.uv[0];
vertex->uv[1] = commonVertex.uv[1];
currentBufferOffset += sizeof(GltfVertex);
}
if (gltf.accessors.has_value())
{
gltf.accessors.value()[m_position_accessor].min = std::vector({minPosition[0], minPosition[1], minPosition[2]});
gltf.accessors.value()[m_position_accessor].max = std::vector({maxPosition[0], maxPosition[1], maxPosition[2]});
}
if (!xmodel.m_vertex_bone_weights.empty())
{
assert(xmodel.m_vertex_bone_weights.size() == xmodel.m_vertices.size());
auto* joints = reinterpret_cast<uint8_t*>(&bufferData[currentBufferOffset]);
auto* weights = reinterpret_cast<float*>(&bufferData[currentBufferOffset + sizeof(uint8_t) * 4u * xmodel.m_vertex_bone_weights.size()]);
for (const auto& commonVertexWeights : xmodel.m_vertex_bone_weights)
{
assert(commonVertexWeights.weights != nullptr);
assert(commonVertexWeights.weightCount <= 4u);
const auto commonVertexWeightCount = std::min(commonVertexWeights.weightCount, 4u);
for (auto i = 0u; i < commonVertexWeightCount; i++)
{
joints[i] = static_cast<unsigned char>(commonVertexWeights.weights[i].boneIndex);
weights[i] = commonVertexWeights.weights[i].weight;
}
for (auto i = commonVertexWeightCount; i < 4u; i++)
{
joints[i] = 0u;
weights[i] = 0.0f;
}
joints += 4u;
weights += 4u;
}
currentBufferOffset += (sizeof(uint8_t) + sizeof(float)) * 4u * xmodel.m_vertex_bone_weights.size();
auto* inverseBindMatrixData = reinterpret_cast<float*>(&bufferData[currentBufferOffset]);
for (const auto& bone : xmodel.m_bones)
{
Matrix32 inverseBindMatrix;
inverseBindMatrix.m_data[0][3] = -bone.globalOffset[0];
inverseBindMatrix.m_data[1][3] = -bone.globalOffset[2];
inverseBindMatrix.m_data[2][3] = bone.globalOffset[1];
// In-memory = row major
// gltf = column major
inverseBindMatrixData[0] = inverseBindMatrix.m_data[0][0];
inverseBindMatrixData[1] = inverseBindMatrix.m_data[1][0];
inverseBindMatrixData[2] = inverseBindMatrix.m_data[2][0];
inverseBindMatrixData[3] = inverseBindMatrix.m_data[3][0];
inverseBindMatrixData[4] = inverseBindMatrix.m_data[0][1];
inverseBindMatrixData[5] = inverseBindMatrix.m_data[1][1];
inverseBindMatrixData[6] = inverseBindMatrix.m_data[2][1];
inverseBindMatrixData[7] = inverseBindMatrix.m_data[3][1];
inverseBindMatrixData[8] = inverseBindMatrix.m_data[0][2];
inverseBindMatrixData[9] = inverseBindMatrix.m_data[1][2];
inverseBindMatrixData[10] = inverseBindMatrix.m_data[2][2];
inverseBindMatrixData[11] = inverseBindMatrix.m_data[3][2];
inverseBindMatrixData[12] = inverseBindMatrix.m_data[0][3];
inverseBindMatrixData[13] = inverseBindMatrix.m_data[1][3];
inverseBindMatrixData[14] = inverseBindMatrix.m_data[2][3];
inverseBindMatrixData[15] = inverseBindMatrix.m_data[3][3];
inverseBindMatrixData += 16u;
}
currentBufferOffset += sizeof(float) * 16u * xmodel.m_bones.size();
}
for (const auto& object : xmodel.m_objects)
{
for (const auto& face : object.m_faces)
{
auto* faceIndices = reinterpret_cast<unsigned short*>(&bufferData[currentBufferOffset]);
faceIndices[0] = static_cast<unsigned short>(face.vertexIndex[2]);
faceIndices[1] = static_cast<unsigned short>(face.vertexIndex[1]);
faceIndices[2] = static_cast<unsigned short>(face.vertexIndex[0]);
currentBufferOffset += sizeof(unsigned short) * 3u;
}
}
assert(expectedBufferSize == currentBufferOffset);
}
static size_t GetExpectedBufferSize(const XModelCommon& xmodel)
{
auto result = 0u;
result += xmodel.m_vertices.size() * sizeof(GltfVertex);
if (!xmodel.m_vertex_bone_weights.empty())
{
// Joints and weights
result += xmodel.m_vertices.size() * 4u * (sizeof(uint8_t) + sizeof(float));
// Inverse bind matrices
result += xmodel.m_bones.size() * 16u * sizeof(float);
}
for (const auto& object : xmodel.m_objects)
{
result += object.m_faces.size() * sizeof(unsigned short) * 3u;
}
return result;
}
void CreateBuffer(JsonRoot& gltf, const XModelCommon& xmodel, const std::vector<uint8_t>& bufferData) const
{
if (!gltf.buffers.has_value())
gltf.buffers.emplace();
JsonBuffer jsonBuffer;
jsonBuffer.byteLength = bufferData.size();
if (!bufferData.empty())
jsonBuffer.uri = m_output->CreateBufferUri(bufferData.data(), bufferData.size());
gltf.buffers->emplace_back(std::move(jsonBuffer));
}
unsigned m_mesh_node = 0u;
unsigned m_root_node = 0u;
unsigned m_first_bone_node = 0u;
unsigned m_position_accessor = 0u;
unsigned m_normal_accessor = 0u;
unsigned m_uv_accessor = 0u;
unsigned m_joints_accessor = 0u;
unsigned m_weights_accessor = 0u;
unsigned m_inverse_bind_matrices_accessor = 0u;
unsigned m_vertex_buffer_view = 0u;
unsigned m_joints_buffer_view = 0u;
unsigned m_weights_buffer_view = 0u;
unsigned m_inverse_bind_matrices_buffer_view = 0u;
unsigned m_first_index_buffer_view = 0u;
unsigned m_first_index_accessor = 0u;
const Output* m_output;
std::string m_game_name;
std::string m_zone_name;
};
} // namespace
std::unique_ptr<Writer> Writer::CreateWriter(const Output* output, std::string gameName, std::string zoneName)
{
return std::make_unique<GltfWriterImpl>(output, std::move(gameName), std::move(zoneName));
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "GltfOutput.h"
#include "XModel/Gltf/JsonGltf.h"
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace gltf
{
class Writer : public XModelWriter
{
public:
Writer() = default;
~Writer() override = default;
Writer(const Writer& other) = default;
Writer(Writer&& other) noexcept = default;
Writer& operator=(const Writer& other) = default;
Writer& operator=(Writer&& other) noexcept = default;
static std::unique_ptr<Writer> CreateWriter(const Output* output, std::string gameName, std::string zoneName);
};
} // namespace gltf

View File

@ -0,0 +1,227 @@
#include "ObjWriter.h"
#include "Utils/DistinctMapper.h"
#include "XModel/Obj/ObjCommon.h"
#include <format>
namespace
{
struct ObjObjectData
{
DistinctMapper<ObjVertex> m_vertices;
DistinctMapper<ObjNormal> m_normals;
DistinctMapper<ObjUv> m_uvs;
};
struct ObjObjectDataOffsets
{
size_t vertexOffset;
size_t normalOffset;
size_t uvOffset;
};
class ObjWriter final : public XModelWriter
{
public:
ObjWriter(std::ostream& stream, std::string gameName, std::string zoneName, std::string mtlName)
: m_stream(stream),
m_mtl_name(std::move(mtlName)),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
m_stream << "# OpenAssetTools OBJ File ( " << m_game_name << ")\n";
m_stream << "# Game Origin: " << m_game_name << "\n";
m_stream << "# Zone Origin: " << m_zone_name << "\n";
if (!m_mtl_name.empty())
m_stream << "mtllib " << m_mtl_name << "\n";
std::vector<ObjObjectDataOffsets> inputOffsetsByObject;
std::vector<ObjObjectDataOffsets> distinctOffsetsByObject;
GetObjObjectDataOffsets(xmodel, inputOffsetsByObject, distinctOffsetsByObject);
auto objectIndex = 0;
for (const auto& object : xmodel.m_objects)
{
const auto& objectData = m_object_data[objectIndex];
m_stream << "o " << object.name << "\n";
for (const auto& v : objectData.m_vertices.GetDistinctValues())
m_stream << "v " << v.coordinates[0] << " " << v.coordinates[1] << " " << v.coordinates[2] << "\n";
for (const auto& uv : objectData.m_uvs.GetDistinctValues())
m_stream << "vt " << uv.uv[0] << " " << uv.uv[1] << "\n";
for (const auto& n : objectData.m_normals.GetDistinctValues())
m_stream << "vn " << n.normal[0] << " " << n.normal[1] << " " << n.normal[2] << "\n";
if (object.materialIndex >= 0 && static_cast<unsigned>(object.materialIndex) < xmodel.m_materials.size())
m_stream << "usemtl " << xmodel.m_materials[object.materialIndex].name << "\n";
auto faceIndex = 0u;
for (const auto& f : object.m_faces)
{
const auto faceVertexOffset = 3u * faceIndex;
const size_t v[3]{
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
};
const size_t n[3]{
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
};
const size_t uv[3]{
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
};
m_stream << std::format("f {}/{}/{} {}/{}/{} {}/{}/{}\n", v[0], uv[0], n[0], v[1], uv[1], n[1], v[2], uv[2], n[2]);
faceIndex++;
}
objectIndex++;
}
}
void GetObjObjectDataOffsets(const XModelCommon& xmodel,
std::vector<ObjObjectDataOffsets>& inputOffsets,
std::vector<ObjObjectDataOffsets>& distinctOffsets)
{
ObjObjectDataOffsets currentInputOffsets{};
ObjObjectDataOffsets currentDistinctOffsets{};
AddObjFaces(xmodel);
for (const auto& objectData : m_object_data)
{
inputOffsets.push_back(currentInputOffsets);
distinctOffsets.push_back(currentDistinctOffsets);
currentInputOffsets.vertexOffset += objectData.m_vertices.GetInputValueCount();
currentInputOffsets.normalOffset += objectData.m_normals.GetInputValueCount();
currentInputOffsets.uvOffset += objectData.m_uvs.GetInputValueCount();
currentDistinctOffsets.vertexOffset += objectData.m_vertices.GetDistinctValueCount();
currentDistinctOffsets.normalOffset += objectData.m_normals.GetDistinctValueCount();
currentDistinctOffsets.uvOffset += objectData.m_uvs.GetDistinctValueCount();
}
}
void AddObjFaces(const XModelCommon& common)
{
m_object_data.resize(common.m_objects.size());
auto objectIndex = 0u;
for (const auto& commonObject : common.m_objects)
{
auto& objectData = m_object_data[objectIndex];
for (const auto& commonFace : commonObject.m_faces)
{
ObjFace face{};
face.vertexIndex[0] = commonFace.vertexIndex[2];
face.vertexIndex[1] = commonFace.vertexIndex[1];
face.vertexIndex[2] = commonFace.vertexIndex[0];
face.normalIndex[0] = face.vertexIndex[0];
face.normalIndex[1] = face.vertexIndex[1];
face.normalIndex[2] = face.vertexIndex[2];
face.uvIndex[0] = face.vertexIndex[0];
face.uvIndex[1] = face.vertexIndex[1];
face.uvIndex[2] = face.vertexIndex[2];
// Reverse order for OBJ
AddObjVertex(objectData, common, commonFace.vertexIndex[2]);
AddObjVertex(objectData, common, commonFace.vertexIndex[1]);
AddObjVertex(objectData, common, commonFace.vertexIndex[0]);
}
objectIndex++;
}
}
static void AddObjVertex(ObjObjectData& objectData, const XModelCommon& common, const int commonVertexIndex)
{
const auto& commonVertex = common.m_vertices[commonVertexIndex];
ObjVertex objVertex{};
objVertex.coordinates[0] = commonVertex.coordinates[0];
objVertex.coordinates[1] = commonVertex.coordinates[2];
objVertex.coordinates[2] = -commonVertex.coordinates[1];
objectData.m_vertices.Add(objVertex);
ObjNormal objNormal{};
objNormal.normal[0] = commonVertex.normal[0];
objNormal.normal[1] = commonVertex.normal[2];
objNormal.normal[2] = -commonVertex.normal[1];
objectData.m_normals.Add(objNormal);
ObjUv objUv{};
objUv.uv[0] = commonVertex.uv[0];
objUv.uv[1] = 1.0f - commonVertex.uv[1];
objectData.m_uvs.Add(objUv);
}
std::ostream& m_stream;
std::string m_mtl_name;
std::string m_game_name;
std::string m_zone_name;
std::vector<ObjObjectData> m_object_data;
};
class MtlWriter final : public XModelWriter
{
public:
MtlWriter(std::ostream& stream, std::string gameName, std::string zoneName)
: m_stream(stream),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
m_stream << "# OpenAssetTools MAT File ( " << m_game_name << ")\n";
m_stream << "# Game Origin: " << m_game_name << "\n";
m_stream << "# Zone Origin: " << m_zone_name << "\n";
m_stream << "# Material count: " << xmodel.m_materials.size() << "\n";
for (const auto& material : xmodel.m_materials)
{
m_stream << "\n";
m_stream << "newmtl " << material.name << "\n";
if (!material.colorMapName.empty())
m_stream << "map_Kd ../images/" << material.colorMapName << ".dds\n";
if (!material.normalMapName.empty())
m_stream << "map_bump ../images/" << material.normalMapName << ".dds\n";
if (!material.specularMapName.empty())
m_stream << "map_Ks ../images/" << material.specularMapName << ".dds\n";
}
}
private:
std::ostream& m_stream;
std::string m_game_name;
std::string m_zone_name;
};
} // namespace
namespace obj
{
std::unique_ptr<XModelWriter> CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName)
{
return std::make_unique<ObjWriter>(stream, std::move(mtlName), std::move(gameName), std::move(zoneName));
}
std::unique_ptr<XModelWriter> CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName)
{
return std::make_unique<MtlWriter>(stream, std::move(gameName), std::move(zoneName));
}
} // namespace obj

View File

@ -0,0 +1,12 @@
#pragma once
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace obj
{
std::unique_ptr<XModelWriter> CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName);
std::unique_ptr<XModelWriter> CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName);
} // namespace obj

View File

@ -0,0 +1,18 @@
#pragma once
#include "XModel/XModelCommon.h"
#include <vector>
class XModelWriter
{
public:
XModelWriter() = default;
virtual ~XModelWriter() = default;
XModelWriter(const XModelWriter& other) = default;
XModelWriter(XModelWriter&& other) noexcept = default;
XModelWriter& operator=(const XModelWriter& other) = default;
XModelWriter& operator=(XModelWriter&& other) noexcept = default;
virtual void Write(const XModelCommon& xmodel) = 0;
};

View File

@ -5,6 +5,7 @@
#include "ObjWriting.h"
#include "Utils/Arguments/UsageInformation.h"
#include "Utils/FileUtils.h"
#include "Utils/StringUtils.h"
#include <iostream>
#include <regex>
@ -79,7 +80,7 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT =
const CommandLineOption* const OPTION_MODEL_FORMAT =
CommandLineOption::Builder::Create()
.WithLongName("model-format")
.WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ")
.WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ, GLTF, GLB")
.WithParameter("modelFormatValue")
.Build();
@ -179,8 +180,7 @@ void UnlinkerArgs::SetVerbose(const bool isVerbose)
bool UnlinkerArgs::SetImageDumpingMode()
{
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT);
for (auto& c : specifiedValue)
c = static_cast<char>(tolower(c));
utils::MakeStringLowerCase(specifiedValue);
if (specifiedValue == "dds")
{
@ -202,8 +202,7 @@ bool UnlinkerArgs::SetImageDumpingMode()
bool UnlinkerArgs::SetModelDumpingMode()
{
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
for (auto& c : specifiedValue)
c = static_cast<char>(tolower(c));
utils::MakeStringLowerCase(specifiedValue);
if (specifiedValue == "xmodel_export")
{
@ -217,6 +216,18 @@ bool UnlinkerArgs::SetModelDumpingMode()
return true;
}
if (specifiedValue == "gltf")
{
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF;
return true;
}
if (specifiedValue == "glb")
{
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLB;
return true;
}
const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
printf("Illegal value: \"%s\" is not a valid model output format. Use -? to see usage information.\n", originalValue.c_str());
return false;
@ -238,8 +249,7 @@ void UnlinkerArgs::ParseCommaSeparatedAssetTypeString(const std::string& input)
size_t endPos;
std::string lowerInput(input);
for (auto& c : lowerInput)
c = static_cast<char>(tolower(c));
utils::MakeStringLowerCase(lowerInput);
while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos)
{

View File

@ -51,6 +51,135 @@ public:
return Matrix<T>(m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, T(1.0));
}
static T dot(const Quaternion& q1, const Quaternion& q2)
{
return static_cast<T>((q1.m_x * q2.m_x) + (q1.m_y * q2.m_y) + (q1.m_z * q2.m_z) + (q1.m_w * q2.m_w));
}
static Quaternion& conj(Quaternion& result)
{
result.m_x = -result.m_x;
result.m_y = -result.m_y;
result.m_z = -result.m_z;
return result;
}
static Quaternion& invert(Quaternion& result)
{
// from game programming gems p198
// do result = conj( q ) / norm( q )
Quaternion::conj(result);
// return if norm() is near 0 (divide by 0 would result in NaN)
T l = result.lengthSquared();
if (l < static_cast<T>(0.0001))
{
return result;
}
T l_inv = static_cast<T>(1.0) / l;
result.m_x *= l_inv;
result.m_y *= l_inv;
result.m_z *= l_inv;
result.m_w *= l_inv;
return result;
}
T lengthSquared() const
{
return Quaternion::dot(*this, *this);
}
T length() const
{
return sqrt(lengthSquared());
}
void Normalize()
{
const auto l = length();
// return if no magnitude (already as normalized as possible)
if (l < static_cast<T>(0.0001))
return;
T inverseLength = static_cast<T>(1.0) / l;
m_x *= inverseLength;
m_y *= inverseLength;
m_z *= inverseLength;
m_w *= inverseLength;
}
friend Quaternion operator+(const Quaternion& lhs, const Quaternion& rhs)
{
return Quaternion(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y, lhs.m_z + rhs.m_z, lhs.m_w + rhs.m_w);
}
friend Quaternion operator-(const Quaternion& lhs, const Quaternion& rhs)
{
return Quaternion(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y, lhs.m_z - rhs.m_z, lhs.m_w - rhs.m_w);
}
friend Quaternion& operator+=(Quaternion& lhs, const Quaternion& rhs)
{
lhs.m_x += rhs.m_x;
lhs.m_y += rhs.m_y;
lhs.m_z += rhs.m_z;
lhs.m_w += rhs.m_w;
return lhs;
}
friend Quaternion& operator-=(Quaternion& lhs, const Quaternion& rhs)
{
lhs.m_x -= rhs.m_x;
lhs.m_y -= rhs.m_y;
lhs.m_z -= rhs.m_z;
lhs.m_w -= rhs.m_w;
return lhs;
}
friend Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs)
{
const T x2 = lhs.m_w * rhs.m_x + lhs.m_x * rhs.m_w + lhs.m_y * rhs.m_z - lhs.m_z * rhs.m_y;
const T y2 = lhs.m_w * rhs.m_y + lhs.m_y * rhs.m_w + lhs.m_z * rhs.m_x - lhs.m_x * rhs.m_z;
const T z2 = lhs.m_w * rhs.m_z + lhs.m_z * rhs.m_w + lhs.m_x * rhs.m_y - lhs.m_y * rhs.m_x;
const T w2 = lhs.m_w * rhs.m_w - lhs.m_x * rhs.m_x - lhs.m_y * rhs.m_y - lhs.m_z * rhs.m_z;
return Quaternion(x2, y2, z2, w2);
}
friend Quaternion operator/(const Quaternion& lhs, const Quaternion& rhs)
{
Quaternion rhsInv = rhs;
Quaternion::invert(rhsInv);
return lhs * rhsInv;
}
friend Quaternion& operator*=(Quaternion& lhs, const Quaternion& rhs)
{
const T x2 = lhs.m_w * rhs.m_x + lhs.m_x * rhs.m_w + lhs.m_y * rhs.m_z - lhs.m_z * rhs.m_y;
const T y2 = lhs.m_w * rhs.m_y + lhs.m_y * rhs.m_w + lhs.m_z * rhs.m_x - lhs.m_x * rhs.m_z;
const T z2 = lhs.m_w * rhs.m_z + lhs.m_z * rhs.m_w + lhs.m_x * rhs.m_y - lhs.m_y * rhs.m_x;
const T w2 = lhs.m_w * rhs.m_w - lhs.m_x * rhs.m_x - lhs.m_y * rhs.m_y - lhs.m_z * rhs.m_z;
lhs.m_x = x2;
lhs.m_y = y2;
lhs.m_z = z2;
lhs.m_w = w2;
return lhs;
}
friend Quaternion& operator/=(Quaternion& lhs, const Quaternion& rhs)
{
Quaternion rhsInv = rhs;
Quaternion::invert(rhsInv);
lhs *= rhsInv;
return lhs;
}
};
typedef Quaternion<float> Quaternion32;

View File

@ -24,6 +24,12 @@ public:
{
}
~Vector2() = default;
Vector2(const Vector2& other) = default;
Vector2(Vector2&& other) noexcept = default;
Vector2& operator=(const Vector2& other) = default;
Vector2& operator=(Vector2&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index)
{
assert(index < 2);
@ -55,6 +61,32 @@ public:
{
return m_value[1];
}
friend Vector2 operator+(const Vector2& lhs, const Vector2& rhs)
{
return Vector2(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1]);
}
friend Vector2 operator-(const Vector2& lhs, const Vector2& rhs)
{
return Vector2(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1]);
}
friend Vector2& operator+=(Vector2& lhs, const Vector2& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
return lhs;
}
friend Vector2& operator-=(Vector2& lhs, const Vector2& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
return lhs;
}
};
typedef Vector2<float> Vector2f;
@ -80,6 +112,12 @@ public:
{
}
~Vector3() = default;
Vector3(const Vector3& other) = default;
Vector3(Vector3&& other) noexcept = default;
Vector3& operator=(const Vector3& other) = default;
Vector3& operator=(Vector3&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index)
{
assert(index < 3);
@ -151,6 +189,34 @@ public:
{
return m_value[2];
}
friend Vector3 operator+(const Vector3& lhs, const Vector3& rhs)
{
return Vector3(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1], lhs.m_value[2] + rhs.m_value[2]);
}
friend Vector3 operator-(const Vector3& lhs, const Vector3& rhs)
{
return Vector3(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1], lhs.m_value[2] - rhs.m_value[2]);
}
friend Vector3& operator+=(Vector3& lhs, const Vector3& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
lhs.m_value[2] += rhs.m_value[2];
return lhs;
}
friend Vector3& operator-=(Vector3& lhs, const Vector3& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
lhs.m_value[2] -= rhs.m_value[2];
return lhs;
}
};
typedef Vector3<float> Vector3f;
@ -176,6 +242,12 @@ public:
{
}
~Vector4() = default;
Vector4(const Vector4& other) = default;
Vector4(Vector4&& other) noexcept = default;
Vector4& operator=(const Vector4& other) = default;
Vector4& operator=(Vector4&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index)
{
assert(index < 4);
@ -267,6 +339,36 @@ public:
{
return m_value[3];
}
friend Vector4 operator+(const Vector4& lhs, const Vector4& rhs)
{
return Vector4(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1], lhs.m_value[2] + rhs.m_value[2], lhs.m_value[3] + rhs.m_value[3]);
}
friend Vector4 operator-(const Vector4& lhs, const Vector4& rhs)
{
return Vector4(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1], lhs.m_value[2] - rhs.m_value[2], lhs.m_value[3] - rhs.m_value[3]);
}
friend Vector4& operator+=(Vector4& lhs, const Vector4& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
lhs.m_value[2] += rhs.m_value[2];
lhs.m_value[3] += rhs.m_value[3];
return lhs;
}
friend Vector4& operator-=(Vector4& lhs, const Vector4& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
lhs.m_value[2] -= rhs.m_value[2];
lhs.m_value[3] -= rhs.m_value[3];
return lhs;
}
};
typedef Vector4<float> Vector4f;