mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-04-20 00:02:55 +00:00
Merge pull request #267 from Laupetin/refactor/generalize-xmodel-loading
feat: generalize xmodel loading for IW5
This commit is contained in:
commit
042c68a8fc
@ -4,6 +4,7 @@ include "tools/scripts/linking.lua"
|
|||||||
include "tools/scripts/options.lua"
|
include "tools/scripts/options.lua"
|
||||||
include "tools/scripts/platform.lua"
|
include "tools/scripts/platform.lua"
|
||||||
include "tools/scripts/version.lua"
|
include "tools/scripts/version.lua"
|
||||||
|
include "tools/scripts/source_templating.lua"
|
||||||
|
|
||||||
-- ==================
|
-- ==================
|
||||||
-- Workspace
|
-- Workspace
|
||||||
|
21
raw/iw5/partclassification.csv
Normal file
21
raw/iw5/partclassification.csv
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
J_Hip_RI,right_leg_upper
|
||||||
|
J_Hip_LE,left_leg_upper
|
||||||
|
J_Knee_RI,right_leg_lower
|
||||||
|
J_SpineUpper,torso_upper
|
||||||
|
J_Knee_LE,left_leg_lower
|
||||||
|
J_Ankle_RI,right_foot
|
||||||
|
J_Ankle_LE,left_foot
|
||||||
|
J_Clavicle_RI,torso_upper
|
||||||
|
J_Clavicle_LE,torso_upper
|
||||||
|
J_Shoulder_RI,right_arm_upper
|
||||||
|
J_Shoulder_LE,left_arm_upper
|
||||||
|
J_Neck,neck
|
||||||
|
J_Head,head
|
||||||
|
J_Elbow_RI,right_arm_lower
|
||||||
|
J_Elbow_LE,left_arm_lower
|
||||||
|
J_Wrist_RI,right_hand
|
||||||
|
J_Wrist_LE,left_hand
|
||||||
|
J_MainRoot,torso_lower
|
||||||
|
TAG_WEAPON_LEFT,gun
|
||||||
|
TAG_WEAPON_RIGHT,gun
|
||||||
|
J_Helmet,helmet
|
|
19
raw/iw5/partclassification_mp.csv
Normal file
19
raw/iw5/partclassification_mp.csv
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
J_Hip_RI,right_leg_upper
|
||||||
|
J_Hip_LE,left_leg_upper
|
||||||
|
J_Knee_RI,right_leg_lower
|
||||||
|
J_SpineUpper,torso_lower
|
||||||
|
J_SpineLower,torso_lower
|
||||||
|
J_MainRoot,torso_lower
|
||||||
|
J_Knee_LE,left_leg_lower
|
||||||
|
J_Ankle_RI,right_foot
|
||||||
|
J_Ankle_LE,left_foot
|
||||||
|
J_Clavicle_RI,torso_upper
|
||||||
|
J_Clavicle_LE,torso_upper
|
||||||
|
J_Shoulder_RI,right_arm_upper
|
||||||
|
J_Shoulder_LE,left_arm_upper
|
||||||
|
J_Neck,neck
|
||||||
|
J_Head,head
|
||||||
|
J_Elbow_RI,right_arm_lower
|
||||||
|
J_Elbow_LE,left_arm_lower
|
||||||
|
J_Wrist_RI,right_hand
|
||||||
|
J_Wrist_LE,left_hand
|
|
@ -9,6 +9,21 @@ namespace IW5
|
|||||||
public:
|
public:
|
||||||
static int StringTable_HashString(const char* str);
|
static int StringTable_HashString(const char* str);
|
||||||
|
|
||||||
|
static constexpr uint32_t R_HashString(const char* str, uint32_t hash)
|
||||||
|
{
|
||||||
|
for (const auto* pos = str; *pos; pos++)
|
||||||
|
{
|
||||||
|
hash = 33 * hash ^ (*pos | 0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint32_t R_HashString(const char* string)
|
||||||
|
{
|
||||||
|
return R_HashString(string, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
static PackedTexCoords Vec2PackTexCoords(const float (&in)[2]);
|
static PackedTexCoords Vec2PackTexCoords(const float (&in)[2]);
|
||||||
static PackedUnitVec Vec3PackUnitVec(const float (&in)[3]);
|
static PackedUnitVec Vec3PackUnitVec(const float (&in)[3]);
|
||||||
static GfxColor Vec4PackGfxColor(const float (&in)[4]);
|
static GfxColor Vec4PackGfxColor(const float (&in)[4]);
|
||||||
|
@ -177,11 +177,50 @@ namespace IW5
|
|||||||
void* data;
|
void* data;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef float vec2_t[2];
|
union vec2_t
|
||||||
typedef float vec3_t[3];
|
{
|
||||||
typedef float vec4_t[4];
|
float v[2];
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
union vec3_t
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
};
|
||||||
|
|
||||||
|
float v[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
union vec4_t
|
||||||
|
{
|
||||||
|
float v[4];
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
float w;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float a;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
typedef tdef_align(16) uint16_t r_index16_t;
|
|
||||||
typedef tdef_align(16) char raw_byte16;
|
typedef tdef_align(16) char raw_byte16;
|
||||||
typedef tdef_align(16) float raw_float16;
|
typedef tdef_align(16) float raw_float16;
|
||||||
typedef tdef_align(128) unsigned int raw_uint128;
|
typedef tdef_align(128) unsigned int raw_uint128;
|
||||||
@ -220,8 +259,8 @@ namespace IW5
|
|||||||
|
|
||||||
struct Bounds
|
struct Bounds
|
||||||
{
|
{
|
||||||
float midPoint[3];
|
vec3_t midPoint;
|
||||||
float halfSize[3];
|
vec3_t halfSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cplane_s
|
struct cplane_s
|
||||||
@ -446,34 +485,15 @@ namespace IW5
|
|||||||
unsigned int packed;
|
unsigned int packed;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GfxQuantizedNoColorVertex
|
|
||||||
{
|
|
||||||
short xyz[3];
|
|
||||||
short binormalSign;
|
|
||||||
PackedUnitVec normal;
|
|
||||||
PackedUnitVec tangent;
|
|
||||||
PackedTexCoords texCoord;
|
|
||||||
};
|
|
||||||
|
|
||||||
union GfxColor
|
union GfxColor
|
||||||
{
|
{
|
||||||
unsigned int packed;
|
unsigned int packed;
|
||||||
unsigned char array[4];
|
unsigned char array[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GfxQuantizedVertex
|
|
||||||
{
|
|
||||||
short xyz[3];
|
|
||||||
short binormalSign;
|
|
||||||
PackedUnitVec normal;
|
|
||||||
PackedUnitVec tangent;
|
|
||||||
PackedTexCoords texCoord;
|
|
||||||
GfxColor color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct type_align(16) GfxPackedVertex
|
struct type_align(16) GfxPackedVertex
|
||||||
{
|
{
|
||||||
float xyz[3];
|
vec3_t xyz;
|
||||||
float binormalSign;
|
float binormalSign;
|
||||||
GfxColor color;
|
GfxColor color;
|
||||||
PackedTexCoords texCoord;
|
PackedTexCoords texCoord;
|
||||||
@ -481,14 +501,6 @@ namespace IW5
|
|||||||
PackedUnitVec tangent;
|
PackedUnitVec tangent;
|
||||||
};
|
};
|
||||||
|
|
||||||
union GfxVertexUnion0
|
|
||||||
{
|
|
||||||
GfxQuantizedNoColorVertex* quantizedNoColorVerts0;
|
|
||||||
GfxQuantizedVertex* quantizedVerts0;
|
|
||||||
GfxPackedVertex* packedVerts0;
|
|
||||||
void* verts0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct XSurfaceCollisionAabb
|
struct XSurfaceCollisionAabb
|
||||||
{
|
{
|
||||||
unsigned short mins[3];
|
unsigned short mins[3];
|
||||||
@ -526,6 +538,13 @@ namespace IW5
|
|||||||
XSurfaceCollisionTree* collisionTree;
|
XSurfaceCollisionTree* collisionTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct XSurfaceTri
|
||||||
|
{
|
||||||
|
uint16_t i[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef tdef_align(16) XSurfaceTri XSurfaceTri16;
|
||||||
|
|
||||||
struct XSurface
|
struct XSurface
|
||||||
{
|
{
|
||||||
unsigned char tileMode;
|
unsigned char tileMode;
|
||||||
@ -536,9 +555,9 @@ namespace IW5
|
|||||||
uint16_t baseTriIndex;
|
uint16_t baseTriIndex;
|
||||||
uint16_t baseVertIndex;
|
uint16_t baseVertIndex;
|
||||||
float quantizeScale;
|
float quantizeScale;
|
||||||
r_index16_t (*triIndices)[3];
|
XSurfaceTri16* triIndices;
|
||||||
XSurfaceVertexInfo vertInfo;
|
XSurfaceVertexInfo vertInfo;
|
||||||
GfxVertexUnion0 verts0;
|
GfxPackedVertex* verts0;
|
||||||
unsigned int vertListCount;
|
unsigned int vertListCount;
|
||||||
XRigidVertList* vertList;
|
XRigidVertList* vertList;
|
||||||
int partBits[6];
|
int partBits[6];
|
||||||
@ -554,8 +573,8 @@ namespace IW5
|
|||||||
|
|
||||||
struct DObjAnimMat
|
struct DObjAnimMat
|
||||||
{
|
{
|
||||||
float quat[4];
|
vec4_t quat;
|
||||||
float trans[3];
|
vec3_t trans;
|
||||||
float transWeight;
|
float transWeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -567,7 +586,7 @@ namespace IW5
|
|||||||
XModelSurfs* modelSurfs;
|
XModelSurfs* modelSurfs;
|
||||||
int partBits[6];
|
int partBits[6];
|
||||||
XSurface* surfs;
|
XSurface* surfs;
|
||||||
char lod;
|
unsigned char lod;
|
||||||
char smcBaseIndexPlusOne;
|
char smcBaseIndexPlusOne;
|
||||||
char smcSubIndexMask;
|
char smcSubIndexMask;
|
||||||
char smcBucket;
|
char smcBucket;
|
||||||
@ -596,6 +615,11 @@ namespace IW5
|
|||||||
float radiusSquared;
|
float radiusSquared;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct XModelQuat
|
||||||
|
{
|
||||||
|
int16_t v[4];
|
||||||
|
};
|
||||||
|
|
||||||
struct XModel
|
struct XModel
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -606,15 +630,15 @@ namespace IW5
|
|||||||
unsigned int noScalePartBits[6];
|
unsigned int noScalePartBits[6];
|
||||||
ScriptString* boneNames;
|
ScriptString* boneNames;
|
||||||
unsigned char* parentList;
|
unsigned char* parentList;
|
||||||
short (*quats)[4];
|
XModelQuat* quats;
|
||||||
float (*trans)[3];
|
float* trans;
|
||||||
unsigned char* partClassification;
|
unsigned char* partClassification;
|
||||||
DObjAnimMat* baseMat;
|
DObjAnimMat* baseMat;
|
||||||
Material** materialHandles;
|
Material** materialHandles;
|
||||||
XModelLodInfo lodInfo[4];
|
XModelLodInfo lodInfo[4];
|
||||||
char maxLoadedLod;
|
char maxLoadedLod;
|
||||||
unsigned char numLods;
|
unsigned char numLods;
|
||||||
unsigned char collLod;
|
char collLod;
|
||||||
unsigned char flags;
|
unsigned char flags;
|
||||||
XModelCollSurf_s* collSurfs;
|
XModelCollSurf_s* collSurfs;
|
||||||
int numCollSurfs;
|
int numCollSurfs;
|
||||||
@ -651,6 +675,23 @@ namespace IW5
|
|||||||
gcc_align(8) uint64_t packed;
|
gcc_align(8) uint64_t packed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum MaterialGameFlags
|
||||||
|
{
|
||||||
|
MTL_GAMEFLAG_1 = 0x1,
|
||||||
|
MTL_GAMEFLAG_2 = 0x2,
|
||||||
|
MTL_GAMEFLAG_4 = 0x4,
|
||||||
|
MTL_GAMEFLAG_8 = 0x8,
|
||||||
|
MTL_GAMEFLAG_10 = 0x10,
|
||||||
|
MTL_GAMEFLAG_20 = 0x20,
|
||||||
|
MTL_GAMEFLAG_40 = 0x40,
|
||||||
|
MTL_GAMEFLAG_80 = 0x80,
|
||||||
|
MTL_GAMEFLAG_100 = 0x100,
|
||||||
|
MTL_GAMEFLAG_200 = 0x200,
|
||||||
|
MTL_GAMEFLAG_400 = 0x400,
|
||||||
|
MTL_GAMEFLAG_800 = 0x800,
|
||||||
|
MTL_GAMEFLAG_1000 = 0x1000,
|
||||||
|
};
|
||||||
|
|
||||||
struct MaterialInfo
|
struct MaterialInfo
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -713,13 +754,71 @@ namespace IW5
|
|||||||
water_t* water;
|
water_t* water;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum TextureFilter
|
||||||
|
{
|
||||||
|
TEXTURE_FILTER_DISABLED = 0x0,
|
||||||
|
TEXTURE_FILTER_NEAREST = 0x1,
|
||||||
|
TEXTURE_FILTER_LINEAR = 0x2,
|
||||||
|
TEXTURE_FILTER_ANISO2X = 0x3,
|
||||||
|
TEXTURE_FILTER_ANISO4X = 0x4,
|
||||||
|
|
||||||
|
TEXTURE_FILTER_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SamplerStateBitsMipMap_e
|
||||||
|
{
|
||||||
|
SAMPLER_MIPMAP_ENUM_DISABLED,
|
||||||
|
SAMPLER_MIPMAP_ENUM_NEAREST,
|
||||||
|
SAMPLER_MIPMAP_ENUM_LINEAR,
|
||||||
|
|
||||||
|
SAMPLER_MIPMAP_ENUM_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SamplerStateBits_e
|
||||||
|
{
|
||||||
|
SAMPLER_FILTER_SHIFT = 0x0,
|
||||||
|
SAMPLER_FILTER_NEAREST = 0x1,
|
||||||
|
SAMPLER_FILTER_LINEAR = 0x2,
|
||||||
|
SAMPLER_FILTER_ANISO2X = 0x3,
|
||||||
|
SAMPLER_FILTER_ANISO4X = 0x4,
|
||||||
|
SAMPLER_FILTER_MASK = 0x7,
|
||||||
|
|
||||||
|
SAMPLER_MIPMAP_SHIFT = 0x3,
|
||||||
|
SAMPLER_MIPMAP_DISABLED = 0x0,
|
||||||
|
SAMPLER_MIPMAP_NEAREST = 0x8,
|
||||||
|
SAMPLER_MIPMAP_LINEAR = 0x10,
|
||||||
|
SAMPLER_MIPMAP_COUNT = 0x3,
|
||||||
|
SAMPLER_MIPMAP_MASK = 0x18,
|
||||||
|
|
||||||
|
SAMPLER_CLAMP_U_SHIFT = 0x5,
|
||||||
|
SAMPLER_CLAMP_V_SHIFT = 0x6,
|
||||||
|
SAMPLER_CLAMP_W_SHIFT = 0x7,
|
||||||
|
SAMPLER_CLAMP_U = 0x20,
|
||||||
|
SAMPLER_CLAMP_V = 0x40,
|
||||||
|
SAMPLER_CLAMP_W = 0x80,
|
||||||
|
SAMPLER_CLAMP_MASK = 0xE0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialTextureDefSamplerState
|
||||||
|
{
|
||||||
|
unsigned char filter : 3;
|
||||||
|
unsigned char mipMap : 2;
|
||||||
|
unsigned char clampU : 1;
|
||||||
|
unsigned char clampV : 1;
|
||||||
|
unsigned char clampW : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef __zonecodegenerator
|
||||||
|
static_assert(sizeof(MaterialTextureDefSamplerState) == 1u);
|
||||||
|
#endif
|
||||||
|
|
||||||
struct MaterialTextureDef
|
struct MaterialTextureDef
|
||||||
{
|
{
|
||||||
unsigned int nameHash;
|
unsigned int nameHash;
|
||||||
char nameStart;
|
char nameStart;
|
||||||
char nameEnd;
|
char nameEnd;
|
||||||
unsigned char samplerState;
|
MaterialTextureDefSamplerState samplerState;
|
||||||
unsigned char semantic;
|
unsigned char semantic; // TextureSemantic
|
||||||
MaterialTextureDefInfo u;
|
MaterialTextureDefInfo u;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -727,18 +826,161 @@ namespace IW5
|
|||||||
{
|
{
|
||||||
unsigned int nameHash;
|
unsigned int nameHash;
|
||||||
char name[12];
|
char name[12];
|
||||||
float literal[4];
|
vec4_t literal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum GfxBlend
|
||||||
|
{
|
||||||
|
GFXS_BLEND_DISABLED = 0x0,
|
||||||
|
GFXS_BLEND_ZERO = 0x1,
|
||||||
|
GFXS_BLEND_ONE = 0x2,
|
||||||
|
GFXS_BLEND_SRCCOLOR = 0x3,
|
||||||
|
GFXS_BLEND_INVSRCCOLOR = 0x4,
|
||||||
|
GFXS_BLEND_SRCALPHA = 0x5,
|
||||||
|
GFXS_BLEND_INVSRCALPHA = 0x6,
|
||||||
|
GFXS_BLEND_DESTALPHA = 0x7,
|
||||||
|
GFXS_BLEND_INVDESTALPHA = 0x8,
|
||||||
|
GFXS_BLEND_DESTCOLOR = 0x9,
|
||||||
|
GFXS_BLEND_INVDESTCOLOR = 0xA,
|
||||||
|
|
||||||
|
GFXS_BLEND_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxBlendOp
|
||||||
|
{
|
||||||
|
GFXS_BLENDOP_DISABLED = 0x0,
|
||||||
|
GFXS_BLENDOP_ADD = 0x1,
|
||||||
|
GFXS_BLENDOP_SUBTRACT = 0x2,
|
||||||
|
GFXS_BLENDOP_REVSUBTRACT = 0x3,
|
||||||
|
GFXS_BLENDOP_MIN = 0x4,
|
||||||
|
GFXS_BLENDOP_MAX = 0x5,
|
||||||
|
|
||||||
|
GFXS_BLENDOP_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxAlphaTest_e
|
||||||
|
{
|
||||||
|
GFXS_ALPHA_TEST_GT_0 = 1,
|
||||||
|
GFXS_ALPHA_TEST_LT_128 = 2,
|
||||||
|
GFXS_ALPHA_TEST_GE_128 = 3,
|
||||||
|
|
||||||
|
GFXS_ALPHA_TEST_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxCullFace_e
|
||||||
|
{
|
||||||
|
GFXS_CULL_NONE = 1,
|
||||||
|
GFXS_CULL_BACK = 2,
|
||||||
|
GFXS_CULL_FRONT = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxDepthTest_e
|
||||||
|
{
|
||||||
|
GFXS_DEPTHTEST_ALWAYS = 0,
|
||||||
|
GFXS_DEPTHTEST_LESS = 1,
|
||||||
|
GFXS_DEPTHTEST_EQUAL = 2,
|
||||||
|
GFXS_DEPTHTEST_LESSEQUAL = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxPolygonOffset_e
|
||||||
|
{
|
||||||
|
GFXS_POLYGON_OFFSET_0 = 0,
|
||||||
|
GFXS_POLYGON_OFFSET_1 = 1,
|
||||||
|
GFXS_POLYGON_OFFSET_2 = 2,
|
||||||
|
GFXS_POLYGON_OFFSET_SHADOWMAP = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxStencilOp
|
||||||
|
{
|
||||||
|
GFXS_STENCILOP_KEEP = 0x0,
|
||||||
|
GFXS_STENCILOP_ZERO = 0x1,
|
||||||
|
GFXS_STENCILOP_REPLACE = 0x2,
|
||||||
|
GFXS_STENCILOP_INCRSAT = 0x3,
|
||||||
|
GFXS_STENCILOP_DECRSAT = 0x4,
|
||||||
|
GFXS_STENCILOP_INVERT = 0x5,
|
||||||
|
GFXS_STENCILOP_INCR = 0x6,
|
||||||
|
GFXS_STENCILOP_DECR = 0x7
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxStencilFunc
|
||||||
|
{
|
||||||
|
GFXS_STENCILFUNC_NEVER = 0x0,
|
||||||
|
GFXS_STENCILFUNC_LESS = 0x1,
|
||||||
|
GFXS_STENCILFUNC_EQUAL = 0x2,
|
||||||
|
GFXS_STENCILFUNC_LESSEQUAL = 0x3,
|
||||||
|
GFXS_STENCILFUNC_GREATER = 0x4,
|
||||||
|
GFXS_STENCILFUNC_NOTEQUAL = 0x5,
|
||||||
|
GFXS_STENCILFUNC_GREATEREQUAL = 0x6,
|
||||||
|
GFXS_STENCILFUNC_ALWAYS = 0x7
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GfxStateBitsLoadBitsStructured
|
||||||
|
{
|
||||||
|
// Byte 0
|
||||||
|
unsigned int srcBlendRgb : 4; // 0-3
|
||||||
|
unsigned int dstBlendRgb : 4; // 4-7
|
||||||
|
unsigned int blendOpRgb : 3; // 8-10
|
||||||
|
unsigned int alphaTestDisabled : 1; // 11
|
||||||
|
unsigned int alphaTest : 2; // 12-13
|
||||||
|
unsigned int cullFace : 2; // 14-15
|
||||||
|
unsigned int srcBlendAlpha : 4; // 16-19
|
||||||
|
unsigned int dstBlendAlpha : 4; // 20-23
|
||||||
|
unsigned int blendOpAlpha : 3; // 24-26
|
||||||
|
unsigned int colorWriteRgb : 1; // 27
|
||||||
|
unsigned int colorWriteAlpha : 1; // 28
|
||||||
|
unsigned int unused0 : 1; // 29
|
||||||
|
unsigned int gammaWrite : 1; // 30
|
||||||
|
unsigned int polymodeLine : 1; // 31
|
||||||
|
|
||||||
|
// Byte 1
|
||||||
|
unsigned int depthWrite : 1; // 0
|
||||||
|
unsigned int depthTestDisabled : 1; // 1
|
||||||
|
unsigned int depthTest : 2; // 2-3
|
||||||
|
unsigned int polygonOffset : 2; // 4-5
|
||||||
|
unsigned int stencilFrontEnabled : 1; // 6
|
||||||
|
unsigned int stencilBackEnabled : 1; // 7
|
||||||
|
unsigned int stencilFrontPass : 3; // 8-10
|
||||||
|
unsigned int stencilFrontFail : 3; // 11-13
|
||||||
|
unsigned int stencilFrontZFail : 3; // 14-16
|
||||||
|
unsigned int stencilFrontFunc : 3; // 17-19
|
||||||
|
unsigned int stencilBackPass : 3; // 20-22
|
||||||
|
unsigned int stencilBackFail : 3; // 23-25
|
||||||
|
unsigned int stencilBackZFail : 3; // 26-28
|
||||||
|
unsigned int stencilBackFunc : 3; // 29-31
|
||||||
|
};
|
||||||
|
|
||||||
|
union GfxStateBitsLoadBits
|
||||||
|
{
|
||||||
|
unsigned int raw[2];
|
||||||
|
GfxStateBitsLoadBitsStructured structured;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef __zonecodegenerator
|
||||||
|
static_assert(sizeof(GfxStateBitsLoadBits) == 8);
|
||||||
|
static_assert(sizeof(GfxStateBitsLoadBitsStructured) == 8);
|
||||||
|
#endif
|
||||||
|
|
||||||
struct GfxStateBits
|
struct GfxStateBits
|
||||||
{
|
{
|
||||||
unsigned int loadBits[2];
|
GfxStateBitsLoadBits loadBits;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GfxCameraRegionType
|
||||||
|
{
|
||||||
|
CAMERA_REGION_LIT_OPAQUE = 0x0,
|
||||||
|
CAMERA_REGION_LIT_TRANS = 0x1,
|
||||||
|
CAMERA_REGION_EMISSIVE = 0x2,
|
||||||
|
CAMERA_REGION_DEPTH_HACK = 0x3,
|
||||||
|
CAMERA_REGION_LIGHT_MAP_OPAQUE = 0x4,
|
||||||
|
|
||||||
|
CAMERA_REGION_COUNT,
|
||||||
|
CAMERA_REGION_NONE = CAMERA_REGION_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Material
|
struct Material
|
||||||
{
|
{
|
||||||
MaterialInfo info;
|
MaterialInfo info;
|
||||||
unsigned char stateBitsEntry[54];
|
char stateBitsEntry[54];
|
||||||
unsigned char textureCount;
|
unsigned char textureCount;
|
||||||
unsigned char constantCount;
|
unsigned char constantCount;
|
||||||
unsigned char stateBitsCount;
|
unsigned char stateBitsCount;
|
||||||
|
@ -208,6 +208,8 @@ namespace T5
|
|||||||
typedef char cbrushedge_t;
|
typedef char cbrushedge_t;
|
||||||
typedef tdef_align(128) unsigned int raw_uint128;
|
typedef tdef_align(128) unsigned int raw_uint128;
|
||||||
|
|
||||||
|
typedef uint16_t ScriptString;
|
||||||
|
|
||||||
struct PhysPreset
|
struct PhysPreset
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -459,8 +461,8 @@ namespace T5
|
|||||||
|
|
||||||
struct DObjAnimMat
|
struct DObjAnimMat
|
||||||
{
|
{
|
||||||
float quat[4];
|
vec4_t quat;
|
||||||
float trans[3];
|
vec3_t trans;
|
||||||
float transWeight;
|
float transWeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -490,7 +492,7 @@ namespace T5
|
|||||||
|
|
||||||
struct type_align(16) GfxPackedVertex
|
struct type_align(16) GfxPackedVertex
|
||||||
{
|
{
|
||||||
float xyz[3];
|
vec3_t xyz;
|
||||||
float binormalSign;
|
float binormalSign;
|
||||||
GfxColor color;
|
GfxColor color;
|
||||||
PackedTexCoords texCoord;
|
PackedTexCoords texCoord;
|
||||||
@ -535,7 +537,12 @@ namespace T5
|
|||||||
XSurfaceCollisionTree* collisionTree;
|
XSurfaceCollisionTree* collisionTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef tdef_align(16) uint16_t r_index16_t;
|
struct XSurfaceTri
|
||||||
|
{
|
||||||
|
uint16_t i[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef tdef_align(16) XSurfaceTri XSurfaceTri16;
|
||||||
|
|
||||||
struct XSurface
|
struct XSurface
|
||||||
{
|
{
|
||||||
@ -546,7 +553,7 @@ namespace T5
|
|||||||
uint16_t triCount;
|
uint16_t triCount;
|
||||||
uint16_t baseTriIndex;
|
uint16_t baseTriIndex;
|
||||||
uint16_t baseVertIndex;
|
uint16_t baseVertIndex;
|
||||||
r_index16_t (*triIndices)[3];
|
XSurfaceTri16* triIndices;
|
||||||
XSurfaceVertexInfo vertInfo;
|
XSurfaceVertexInfo vertInfo;
|
||||||
GfxPackedVertex* verts0;
|
GfxPackedVertex* verts0;
|
||||||
void /*IDirect3DVertexBuffer9*/* vb0;
|
void /*IDirect3DVertexBuffer9*/* vb0;
|
||||||
@ -587,8 +594,8 @@ namespace T5
|
|||||||
|
|
||||||
struct XBoneInfo
|
struct XBoneInfo
|
||||||
{
|
{
|
||||||
float bounds[2][3];
|
vec3_t bounds[2];
|
||||||
float offset[3];
|
vec3_t offset;
|
||||||
float radiusSquared;
|
float radiusSquared;
|
||||||
char collmap;
|
char collmap;
|
||||||
};
|
};
|
||||||
@ -657,6 +664,14 @@ namespace T5
|
|||||||
PhysGeomList* geomList;
|
PhysGeomList* geomList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum XModelLodRampType : unsigned char
|
||||||
|
{
|
||||||
|
XMODEL_LOD_RAMP_RIGID = 0x0,
|
||||||
|
XMODEL_LOD_RAMP_SKINNED = 0x1,
|
||||||
|
|
||||||
|
XMODEL_LOD_RAMP_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
struct XModelQuat
|
struct XModelQuat
|
||||||
{
|
{
|
||||||
int16_t v[4];
|
int16_t v[4];
|
||||||
@ -668,12 +683,12 @@ namespace T5
|
|||||||
unsigned char numBones;
|
unsigned char numBones;
|
||||||
unsigned char numRootBones;
|
unsigned char numRootBones;
|
||||||
unsigned char numsurfs;
|
unsigned char numsurfs;
|
||||||
char lodRampType;
|
XModelLodRampType lodRampType;
|
||||||
uint16_t* boneNames;
|
ScriptString* boneNames;
|
||||||
char* parentList;
|
unsigned char* parentList;
|
||||||
XModelQuat* quats;
|
XModelQuat* quats;
|
||||||
float* trans;
|
float* trans;
|
||||||
char* partClassification;
|
unsigned char* partClassification;
|
||||||
DObjAnimMat* baseMat;
|
DObjAnimMat* baseMat;
|
||||||
XSurface* surfs;
|
XSurface* surfs;
|
||||||
Material** materialHandles;
|
Material** materialHandles;
|
||||||
@ -684,13 +699,13 @@ namespace T5
|
|||||||
int contents;
|
int contents;
|
||||||
XBoneInfo* boneInfo;
|
XBoneInfo* boneInfo;
|
||||||
float radius;
|
float radius;
|
||||||
float mins[3];
|
vec3_t mins;
|
||||||
float maxs[3];
|
vec3_t maxs;
|
||||||
uint16_t numLods;
|
uint16_t numLods;
|
||||||
uint16_t collLod;
|
int16_t collLod;
|
||||||
XModelStreamInfo streamInfo;
|
XModelStreamInfo streamInfo;
|
||||||
int memUsage;
|
int memUsage;
|
||||||
int flags;
|
unsigned int flags;
|
||||||
bool bad;
|
bool bad;
|
||||||
PhysPreset* physPreset;
|
PhysPreset* physPreset;
|
||||||
unsigned char numCollmaps;
|
unsigned char numCollmaps;
|
||||||
@ -775,7 +790,7 @@ namespace T5
|
|||||||
char nameStart;
|
char nameStart;
|
||||||
char nameEnd;
|
char nameEnd;
|
||||||
char samplerState;
|
char samplerState;
|
||||||
char semantic;
|
unsigned char semantic; // TextureSemantic
|
||||||
char isMatureContent;
|
char isMatureContent;
|
||||||
char pad[3];
|
char pad[3];
|
||||||
MaterialTextureDefInfo u;
|
MaterialTextureDefInfo u;
|
||||||
|
@ -21,6 +21,11 @@ namespace T6
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr uint32_t R_HashString(const char* string)
|
||||||
|
{
|
||||||
|
return R_HashString(string, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr uint32_t SND_HashName(const char* str)
|
static constexpr uint32_t SND_HashName(const char* str)
|
||||||
{
|
{
|
||||||
if (!str || !*str)
|
if (!str || !*str)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
union PackUtil32
|
union PackUtil32
|
||||||
@ -17,6 +18,8 @@ union PackUtil32
|
|||||||
|
|
||||||
namespace pack32
|
namespace pack32
|
||||||
{
|
{
|
||||||
|
typedef float pvec3[3];
|
||||||
|
|
||||||
uint32_t Vec2PackTexCoordsUV(const float (&in)[2])
|
uint32_t Vec2PackTexCoordsUV(const float (&in)[2])
|
||||||
{
|
{
|
||||||
return static_cast<uint32_t>(HalfFloat::ToHalf(in[1])) << 16 | HalfFloat::ToHalf(in[0]);
|
return static_cast<uint32_t>(HalfFloat::ToHalf(in[1])) << 16 | HalfFloat::ToHalf(in[0]);
|
||||||
@ -27,11 +30,57 @@ namespace pack32
|
|||||||
return static_cast<uint32_t>(HalfFloat::ToHalf(in[0])) << 16 | HalfFloat::ToHalf(in[1]);
|
return static_cast<uint32_t>(HalfFloat::ToHalf(in[0])) << 16 | HalfFloat::ToHalf(in[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Vec3_Normalize(pvec3& vector)
|
||||||
|
{
|
||||||
|
float length = std::sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
||||||
|
if (-length >= 0.0f)
|
||||||
|
length = 1.0f;
|
||||||
|
const auto lengthInv = 1.0f / length;
|
||||||
|
vector[0] = lengthInv * vector[0];
|
||||||
|
vector[1] = lengthInv * vector[1];
|
||||||
|
vector[2] = lengthInv * vector[2];
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t Vec3PackUnitVecScaleBased(const float (&in)[3])
|
uint32_t Vec3PackUnitVecScaleBased(const float (&in)[3])
|
||||||
{
|
{
|
||||||
// TODO: Implement
|
PackUtil32 testEncoding{};
|
||||||
assert(false);
|
float normalized[3]{in[0], in[1], in[2]};
|
||||||
return 0;
|
float decoded[3];
|
||||||
|
|
||||||
|
Vec3_Normalize(normalized);
|
||||||
|
uint32_t out = 0u;
|
||||||
|
auto bestDirError = 3.4028235e38f;
|
||||||
|
auto bestLenError = 3.4028235e38f;
|
||||||
|
testEncoding.uc[3] = 0u;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
const auto encodeScale = 32385.0f / (static_cast<float>(testEncoding.uc[3]) - -192.0f);
|
||||||
|
testEncoding.c[0] = static_cast<int8_t>(normalized[0] * encodeScale + 127.5f);
|
||||||
|
testEncoding.c[1] = static_cast<int8_t>(normalized[1] * encodeScale + 127.5f);
|
||||||
|
testEncoding.c[2] = static_cast<int8_t>(normalized[2] * encodeScale + 127.5f);
|
||||||
|
const auto decodeScale = (static_cast<float>(testEncoding.uc[3]) - -192.0f) / 32385.0f;
|
||||||
|
decoded[0] = (static_cast<float>(testEncoding.uc[0]) - 127.0f) * decodeScale;
|
||||||
|
decoded[1] = (static_cast<float>(testEncoding.uc[1]) - 127.0f) * decodeScale;
|
||||||
|
decoded[2] = (static_cast<float>(testEncoding.uc[2]) - 127.0f) * decodeScale;
|
||||||
|
const auto v2 = Vec3_Normalize(decoded) - 1.0f;
|
||||||
|
const auto lenError = std::abs(v2);
|
||||||
|
if (lenError < 0.001f)
|
||||||
|
{
|
||||||
|
const auto dirError = std::abs(decoded[0] * normalized[0] + decoded[1] * normalized[1] + decoded[2] * normalized[2] - 1.0f);
|
||||||
|
if (bestDirError > dirError || bestDirError <= dirError && bestLenError > lenError)
|
||||||
|
{
|
||||||
|
bestDirError = dirError;
|
||||||
|
bestLenError = lenError;
|
||||||
|
out = testEncoding.u;
|
||||||
|
if (lenError + dirError == 0.0f)
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++testEncoding.c[3];
|
||||||
|
} while (testEncoding.c[3]);
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Vec3PackUnitVecThirdBased(const float (&in)[3])
|
uint32_t Vec3PackUnitVecThirdBased(const float (&in)[3])
|
||||||
|
49
src/Crypto/Impl/Base64.cpp
Normal file
49
src/Crypto/Impl/Base64.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "Base64.h"
|
||||||
|
|
||||||
|
#define LTC_NO_PROTOTYPES
|
||||||
|
#include <tomcrypt.h>
|
||||||
|
|
||||||
|
namespace base64
|
||||||
|
{
|
||||||
|
std::string EncodeBase64(const void* inputData, const size_t inputLength)
|
||||||
|
{
|
||||||
|
const auto base64BufferSize = GetBase64EncodeOutputLength(inputLength);
|
||||||
|
|
||||||
|
std::string output(base64BufferSize, '\0');
|
||||||
|
const auto outLength = base64BufferSize + 1u;
|
||||||
|
|
||||||
|
const auto result = EncodeBase64(inputData, inputLength, output.data(), outLength);
|
||||||
|
assert(result);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EncodeBase64(const void* inputData, const size_t inputLength, void* outputBuffer, const size_t outputBufferSize)
|
||||||
|
{
|
||||||
|
unsigned long outLength = outputBufferSize;
|
||||||
|
const auto result = base64_encode(static_cast<const unsigned char*>(inputData), inputLength, static_cast<char*>(outputBuffer), &outLength);
|
||||||
|
return result == CRYPT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetBase64EncodeOutputLength(const size_t inputLength)
|
||||||
|
{
|
||||||
|
return 4u * ((inputLength + 2u) / 3u);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DecodeBase64(const void* base64Data, const size_t inputLength, void* outputBuffer, const size_t outputBufferSize)
|
||||||
|
{
|
||||||
|
unsigned long outLength = GetBase64DecodeOutputLength(inputLength);
|
||||||
|
if (outLength > outputBufferSize)
|
||||||
|
return 0u;
|
||||||
|
|
||||||
|
const auto result = base64_decode(static_cast<const char*>(base64Data), inputLength, static_cast<unsigned char*>(outputBuffer), &outLength);
|
||||||
|
assert(result == CRYPT_OK);
|
||||||
|
|
||||||
|
return static_cast<size_t>(outLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetBase64DecodeOutputLength(const size_t inputLength)
|
||||||
|
{
|
||||||
|
return inputLength / 4u;
|
||||||
|
}
|
||||||
|
} // namespace base64
|
12
src/Crypto/Impl/Base64.h
Normal file
12
src/Crypto/Impl/Base64.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace base64
|
||||||
|
{
|
||||||
|
std::string EncodeBase64(const void* inputData, size_t inputLength);
|
||||||
|
bool EncodeBase64(const void* inputData, size_t inputLength, void* outputBuffer, size_t outputBufferSize);
|
||||||
|
size_t GetBase64EncodeOutputLength(size_t inputLength);
|
||||||
|
|
||||||
|
size_t DecodeBase64(const void* base64Data, size_t inputLength, void* outputBuffer, size_t outputBufferSize);
|
||||||
|
size_t GetBase64DecodeOutputLength(size_t inputLength);
|
||||||
|
} // namespace base64
|
391
src/ObjCommon/Game/IW5/Material/JsonMaterial.h
Normal file
391
src/ObjCommon/Game/IW5/Material/JsonMaterial.h
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Game/IW5/IW5.h"
|
||||||
|
|
||||||
|
#include "Json/JsonExtension.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxStencilOp,
|
||||||
|
{
|
||||||
|
{GFXS_STENCILOP_KEEP, "keep" },
|
||||||
|
{GFXS_STENCILOP_ZERO, "zero" },
|
||||||
|
{GFXS_STENCILOP_REPLACE, "replace"},
|
||||||
|
{GFXS_STENCILOP_INCRSAT, "incrsat"},
|
||||||
|
{GFXS_STENCILOP_DECRSAT, "decrsat"},
|
||||||
|
{GFXS_STENCILOP_INVERT, "invert" },
|
||||||
|
{GFXS_STENCILOP_INCR, "incr" },
|
||||||
|
{GFXS_STENCILOP_DECR, "decr" },
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxStencilFunc,
|
||||||
|
{
|
||||||
|
{GFXS_STENCILFUNC_NEVER, "never" },
|
||||||
|
{GFXS_STENCILFUNC_LESS, "less" },
|
||||||
|
{GFXS_STENCILFUNC_EQUAL, "equal" },
|
||||||
|
{GFXS_STENCILFUNC_LESSEQUAL, "lessequal" },
|
||||||
|
{GFXS_STENCILFUNC_GREATER, "greater" },
|
||||||
|
{GFXS_STENCILFUNC_NOTEQUAL, "notequal" },
|
||||||
|
{GFXS_STENCILFUNC_GREATEREQUAL, "greaterequal"},
|
||||||
|
{GFXS_STENCILFUNC_ALWAYS, "always" },
|
||||||
|
});
|
||||||
|
|
||||||
|
class JsonStencil
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GfxStencilOp pass;
|
||||||
|
GfxStencilOp fail;
|
||||||
|
GfxStencilOp zfail;
|
||||||
|
GfxStencilFunc func;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonStencil, pass, fail, zfail, func);
|
||||||
|
|
||||||
|
enum class JsonAlphaTest
|
||||||
|
{
|
||||||
|
INVALID,
|
||||||
|
DISABLED,
|
||||||
|
GT0,
|
||||||
|
LT128,
|
||||||
|
GE128
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAlphaTest,
|
||||||
|
{
|
||||||
|
{JsonAlphaTest::INVALID, nullptr },
|
||||||
|
{JsonAlphaTest::DISABLED, "disabled"},
|
||||||
|
{JsonAlphaTest::GT0, "gt0" },
|
||||||
|
{JsonAlphaTest::LT128, "lt128" },
|
||||||
|
{JsonAlphaTest::GE128, "ge128" }
|
||||||
|
});
|
||||||
|
|
||||||
|
enum class JsonCullFace
|
||||||
|
{
|
||||||
|
INVALID,
|
||||||
|
NONE,
|
||||||
|
BACK,
|
||||||
|
FRONT
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(
|
||||||
|
JsonCullFace,
|
||||||
|
{
|
||||||
|
{JsonCullFace::INVALID, nullptr},
|
||||||
|
{JsonCullFace::NONE, "none" },
|
||||||
|
{JsonCullFace::BACK, "back" },
|
||||||
|
{JsonCullFace::FRONT, "front"}
|
||||||
|
});
|
||||||
|
|
||||||
|
enum class JsonDepthTest
|
||||||
|
{
|
||||||
|
INVALID,
|
||||||
|
DISABLED,
|
||||||
|
ALWAYS,
|
||||||
|
LESS,
|
||||||
|
EQUAL,
|
||||||
|
LESS_EQUAL
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(JsonDepthTest,
|
||||||
|
{
|
||||||
|
{JsonDepthTest::INVALID, nullptr },
|
||||||
|
{JsonDepthTest::DISABLED, "disabled" },
|
||||||
|
{JsonDepthTest::ALWAYS, "always" },
|
||||||
|
{JsonDepthTest::LESS, "less" },
|
||||||
|
{JsonDepthTest::EQUAL, "equal" },
|
||||||
|
{JsonDepthTest::LESS_EQUAL, "less_equal"}
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxBlend,
|
||||||
|
{
|
||||||
|
{GFXS_BLEND_DISABLED, "disabled" },
|
||||||
|
{GFXS_BLEND_ZERO, "zero" },
|
||||||
|
{GFXS_BLEND_ONE, "one" },
|
||||||
|
{GFXS_BLEND_SRCCOLOR, "srccolor" },
|
||||||
|
{GFXS_BLEND_INVSRCCOLOR, "invsrccolor" },
|
||||||
|
{GFXS_BLEND_SRCALPHA, "srcalpha" },
|
||||||
|
{GFXS_BLEND_INVSRCALPHA, "invsrcalpha" },
|
||||||
|
{GFXS_BLEND_DESTALPHA, "destalpha" },
|
||||||
|
{GFXS_BLEND_INVDESTALPHA, "invdestalpha"},
|
||||||
|
{GFXS_BLEND_DESTCOLOR, "destcolor" },
|
||||||
|
{GFXS_BLEND_INVDESTCOLOR, "invdestcolor"},
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxBlendOp,
|
||||||
|
{
|
||||||
|
{GFXS_BLENDOP_DISABLED, "disabled" },
|
||||||
|
{GFXS_BLENDOP_ADD, "add" },
|
||||||
|
{GFXS_BLENDOP_SUBTRACT, "subtract" },
|
||||||
|
{GFXS_BLENDOP_REVSUBTRACT, "revsubtract"},
|
||||||
|
{GFXS_BLENDOP_MIN, "min" },
|
||||||
|
{GFXS_BLENDOP_MAX, "max" },
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxPolygonOffset_e,
|
||||||
|
{
|
||||||
|
{GFXS_POLYGON_OFFSET_0, "offset0" },
|
||||||
|
{GFXS_POLYGON_OFFSET_1, "offset1" },
|
||||||
|
{GFXS_POLYGON_OFFSET_2, "offset2" },
|
||||||
|
{GFXS_POLYGON_OFFSET_SHADOWMAP, "offsetShadowmap"},
|
||||||
|
});
|
||||||
|
|
||||||
|
class JsonStateBitsTableEntry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GfxBlend srcBlendRgb;
|
||||||
|
GfxBlend dstBlendRgb;
|
||||||
|
GfxBlendOp blendOpRgb;
|
||||||
|
JsonAlphaTest alphaTest;
|
||||||
|
JsonCullFace cullFace;
|
||||||
|
GfxBlend srcBlendAlpha;
|
||||||
|
GfxBlend dstBlendAlpha;
|
||||||
|
GfxBlendOp blendOpAlpha;
|
||||||
|
bool colorWriteRgb;
|
||||||
|
bool colorWriteAlpha;
|
||||||
|
bool gammaWrite;
|
||||||
|
bool polymodeLine;
|
||||||
|
bool depthWrite;
|
||||||
|
JsonDepthTest depthTest;
|
||||||
|
GfxPolygonOffset_e polygonOffset;
|
||||||
|
std::optional<JsonStencil> stencilFront;
|
||||||
|
std::optional<JsonStencil> stencilBack;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonStateBitsTableEntry,
|
||||||
|
srcBlendRgb,
|
||||||
|
dstBlendRgb,
|
||||||
|
blendOpRgb,
|
||||||
|
alphaTest,
|
||||||
|
cullFace,
|
||||||
|
srcBlendAlpha,
|
||||||
|
dstBlendAlpha,
|
||||||
|
blendOpAlpha,
|
||||||
|
colorWriteRgb,
|
||||||
|
colorWriteAlpha,
|
||||||
|
polymodeLine,
|
||||||
|
depthWrite,
|
||||||
|
depthWrite,
|
||||||
|
depthTest,
|
||||||
|
polygonOffset,
|
||||||
|
stencilFront,
|
||||||
|
stencilBack);
|
||||||
|
|
||||||
|
class JsonConstant
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::optional<std::string> name;
|
||||||
|
std::optional<std::string> nameFragment;
|
||||||
|
std::optional<unsigned> nameHash;
|
||||||
|
std::vector<float> literal;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(nlohmann::json& out, const JsonConstant& in)
|
||||||
|
{
|
||||||
|
if (in.name.has_value())
|
||||||
|
{
|
||||||
|
optional_to_json(out, "name", in.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
optional_to_json(out, "nameFragment", in.nameFragment);
|
||||||
|
optional_to_json(out, "nameHash", in.nameHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
out["literal"] = in.literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void from_json(const nlohmann::json& in, JsonConstant& out)
|
||||||
|
{
|
||||||
|
optional_from_json(in, "name", out.name);
|
||||||
|
optional_from_json(in, "nameFragment", out.nameFragment);
|
||||||
|
optional_from_json(in, "nameHash", out.nameHash);
|
||||||
|
in.at("literal").get_to(out.literal);
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(TextureFilter,
|
||||||
|
{
|
||||||
|
{TEXTURE_FILTER_DISABLED, "disabled"},
|
||||||
|
{TEXTURE_FILTER_NEAREST, "nearest" },
|
||||||
|
{TEXTURE_FILTER_LINEAR, "linear" },
|
||||||
|
{TEXTURE_FILTER_ANISO2X, "aniso2x" },
|
||||||
|
{TEXTURE_FILTER_ANISO4X, "aniso4x" },
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(SamplerStateBitsMipMap_e,
|
||||||
|
{
|
||||||
|
{SAMPLER_MIPMAP_ENUM_DISABLED, "disabled"},
|
||||||
|
{SAMPLER_MIPMAP_ENUM_NEAREST, "nearest" },
|
||||||
|
{SAMPLER_MIPMAP_ENUM_LINEAR, "linear" },
|
||||||
|
});
|
||||||
|
|
||||||
|
class JsonSamplerState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TextureFilter filter;
|
||||||
|
SamplerStateBitsMipMap_e mipMap;
|
||||||
|
bool clampU;
|
||||||
|
bool clampV;
|
||||||
|
bool clampW;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSamplerState, filter, mipMap, clampU, clampV, clampW);
|
||||||
|
|
||||||
|
class JsonComplex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
float real;
|
||||||
|
float imag;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonComplex, real, imag);
|
||||||
|
|
||||||
|
class JsonWater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
float floatTime;
|
||||||
|
int m;
|
||||||
|
int n;
|
||||||
|
std::string h0;
|
||||||
|
std::string wTerm;
|
||||||
|
float lx;
|
||||||
|
float lz;
|
||||||
|
float gravity;
|
||||||
|
float windvel;
|
||||||
|
std::array<float, 2> winddir;
|
||||||
|
float amplitude;
|
||||||
|
std::array<float, 4> codeConstant;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonWater, floatTime, m, n, h0, wTerm, lx, lz, gravity, windvel, winddir, amplitude, codeConstant);
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(TextureSemantic,
|
||||||
|
{
|
||||||
|
{TS_2D, "2D" },
|
||||||
|
{TS_FUNCTION, "function" },
|
||||||
|
{TS_COLOR_MAP, "colorMap" },
|
||||||
|
{TS_DETAIL_MAP, "detailMap" },
|
||||||
|
{TS_UNUSED_2, "unused2" },
|
||||||
|
{TS_NORMAL_MAP, "normalMap" },
|
||||||
|
{TS_UNUSED_3, "unused3" },
|
||||||
|
{TS_UNUSED_4, "unused4" },
|
||||||
|
{TS_SPECULAR_MAP, "specularMap" },
|
||||||
|
{TS_UNUSED_5, "unused5" },
|
||||||
|
{TS_UNUSED_6, "unused6" },
|
||||||
|
{TS_WATER_MAP, "waterMap" },
|
||||||
|
{TS_DISPLACEMENT_MAP, "displacementMap"},
|
||||||
|
});
|
||||||
|
|
||||||
|
class JsonTexture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::optional<std::string> name;
|
||||||
|
std::optional<unsigned> nameHash;
|
||||||
|
std::optional<std::string> nameStart;
|
||||||
|
std::optional<std::string> nameEnd;
|
||||||
|
TextureSemantic semantic;
|
||||||
|
JsonSamplerState samplerState;
|
||||||
|
std::string image;
|
||||||
|
std::optional<JsonWater> water;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(nlohmann::json& out, const JsonTexture& in)
|
||||||
|
{
|
||||||
|
if (in.name.has_value())
|
||||||
|
{
|
||||||
|
optional_to_json(out, "name", in.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
optional_to_json(out, "nameHash", in.nameHash);
|
||||||
|
optional_to_json(out, "nameStart", in.nameStart);
|
||||||
|
optional_to_json(out, "nameEnd", in.nameEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
out["semantic"] = in.semantic;
|
||||||
|
out["samplerState"] = in.samplerState;
|
||||||
|
out["image"] = in.image;
|
||||||
|
optional_to_json(out, "water", in.water);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void from_json(const nlohmann::json& in, JsonTexture& out)
|
||||||
|
{
|
||||||
|
optional_from_json(in, "name", out.name);
|
||||||
|
optional_from_json(in, "nameHash", out.nameHash);
|
||||||
|
optional_from_json(in, "nameStart", out.nameStart);
|
||||||
|
optional_from_json(in, "nameEnd", out.nameEnd);
|
||||||
|
in.at("semantic").get_to(out.semantic);
|
||||||
|
in.at("samplerState").get_to(out.samplerState);
|
||||||
|
in.at("image").get_to(out.image);
|
||||||
|
optional_from_json(in, "water", out.water);
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonTextureAtlas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint8_t rows;
|
||||||
|
uint8_t columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTextureAtlas, rows, columns);
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(MaterialGameFlags,
|
||||||
|
{
|
||||||
|
{MTL_GAMEFLAG_1, "1" },
|
||||||
|
{MTL_GAMEFLAG_2, "2" },
|
||||||
|
{MTL_GAMEFLAG_4, "4" },
|
||||||
|
{MTL_GAMEFLAG_8, "8" },
|
||||||
|
{MTL_GAMEFLAG_10, "10" },
|
||||||
|
{MTL_GAMEFLAG_20, "20" },
|
||||||
|
{MTL_GAMEFLAG_40, "40" },
|
||||||
|
{MTL_GAMEFLAG_80, "80" },
|
||||||
|
{MTL_GAMEFLAG_100, "100" },
|
||||||
|
{MTL_GAMEFLAG_200, "200" },
|
||||||
|
{MTL_GAMEFLAG_400, "400" },
|
||||||
|
{MTL_GAMEFLAG_800, "800" },
|
||||||
|
{MTL_GAMEFLAG_1000, "1000"},
|
||||||
|
});
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(GfxCameraRegionType,
|
||||||
|
{
|
||||||
|
{CAMERA_REGION_LIT_OPAQUE, "litOpaque" },
|
||||||
|
{CAMERA_REGION_LIT_TRANS, "litTrans" },
|
||||||
|
{CAMERA_REGION_EMISSIVE, "emissive" },
|
||||||
|
{CAMERA_REGION_DEPTH_HACK, "depthHack" },
|
||||||
|
{CAMERA_REGION_LIGHT_MAP_OPAQUE, "lightMapOpaque"},
|
||||||
|
{CAMERA_REGION_NONE, "none" },
|
||||||
|
});
|
||||||
|
|
||||||
|
class JsonMaterial
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<MaterialGameFlags> gameFlags;
|
||||||
|
unsigned sortKey;
|
||||||
|
std::optional<JsonTextureAtlas> textureAtlas;
|
||||||
|
unsigned surfaceTypeBits;
|
||||||
|
std::vector<int8_t> stateBitsEntry;
|
||||||
|
unsigned stateFlags;
|
||||||
|
GfxCameraRegionType cameraRegion;
|
||||||
|
std::string techniqueSet;
|
||||||
|
std::vector<JsonTexture> textures;
|
||||||
|
std::vector<JsonConstant> constants;
|
||||||
|
std::vector<JsonStateBitsTableEntry> stateBits;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial,
|
||||||
|
gameFlags,
|
||||||
|
sortKey,
|
||||||
|
textureAtlas,
|
||||||
|
surfaceTypeBits,
|
||||||
|
stateBitsEntry,
|
||||||
|
stateFlags,
|
||||||
|
cameraRegion,
|
||||||
|
techniqueSet,
|
||||||
|
textures,
|
||||||
|
constants,
|
||||||
|
stateBits);
|
||||||
|
} // namespace IW5
|
32
src/ObjCommon/Game/IW5/XModel/JsonXModelIW5.h
Normal file
32
src/ObjCommon/Game/IW5/XModel/JsonXModelIW5.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Json/JsonCommon.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
class JsonXModelLod
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string file;
|
||||||
|
float distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file, distance);
|
||||||
|
|
||||||
|
class JsonXModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<JsonXModelLod> lods;
|
||||||
|
std::optional<int> collLod;
|
||||||
|
std::optional<std::string> physPreset;
|
||||||
|
std::optional<std::string> physCollmap;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physCollmap, flags);
|
||||||
|
} // namespace IW5
|
32
src/ObjCommon/Game/IW5/XModel/XModelConstantsIW5.h
Normal file
32
src/ObjCommon/Game/IW5/XModel/XModelConstantsIW5.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Game/IW5/IW5.h"
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
inline const char* HITLOC_NAMES[]{
|
||||||
|
// clang-format off
|
||||||
|
"none",
|
||||||
|
"helmet",
|
||||||
|
"head",
|
||||||
|
"neck",
|
||||||
|
"torso_upper",
|
||||||
|
"torso_lower",
|
||||||
|
"right_arm_upper",
|
||||||
|
"left_arm_upper",
|
||||||
|
"right_arm_lower",
|
||||||
|
"left_arm_lower",
|
||||||
|
"right_hand",
|
||||||
|
"left_hand",
|
||||||
|
"right_leg_upper",
|
||||||
|
"left_leg_upper",
|
||||||
|
"right_leg_lower",
|
||||||
|
"left_leg_lower",
|
||||||
|
"right_foot",
|
||||||
|
"left_foot",
|
||||||
|
"gun",
|
||||||
|
"shield",
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
static_assert(std::extent_v<decltype(HITLOC_NAMES)> == HITLOC_COUNT);
|
||||||
|
} // namespace IW5
|
31
src/ObjCommon/Game/T5/XModel/JsonXModelT5.h
Normal file
31
src/ObjCommon/Game/T5/XModel/JsonXModelT5.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Json/JsonCommon.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace T5
|
||||||
|
{
|
||||||
|
class JsonXModelLod
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string file;
|
||||||
|
float distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file, distance);
|
||||||
|
|
||||||
|
class JsonXModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<JsonXModelLod> lods;
|
||||||
|
std::optional<int> collLod;
|
||||||
|
std::optional<std::string> physPreset;
|
||||||
|
std::optional<std::string> physConstraints;
|
||||||
|
unsigned flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physConstraints, flags);
|
||||||
|
} // namespace T5
|
31
src/ObjCommon/Game/T5/XModel/XModelConstantsT5.h
Normal file
31
src/ObjCommon/Game/T5/XModel/XModelConstantsT5.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Game/T5/T5.h"
|
||||||
|
|
||||||
|
namespace T5
|
||||||
|
{
|
||||||
|
inline const char* HITLOC_NAMES[]{
|
||||||
|
// clang-format off
|
||||||
|
"none",
|
||||||
|
"helmet",
|
||||||
|
"head",
|
||||||
|
"neck",
|
||||||
|
"torso_upper",
|
||||||
|
"torso_lower",
|
||||||
|
"right_arm_upper",
|
||||||
|
"left_arm_upper",
|
||||||
|
"right_arm_lower",
|
||||||
|
"left_arm_lower",
|
||||||
|
"right_hand",
|
||||||
|
"left_hand",
|
||||||
|
"right_leg_upper",
|
||||||
|
"left_leg_upper",
|
||||||
|
"right_leg_lower",
|
||||||
|
"left_leg_lower",
|
||||||
|
"right_foot",
|
||||||
|
"left_foot",
|
||||||
|
"gun",
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
static_assert(std::extent_v<decltype(HITLOC_NAMES)> == HITLOC_COUNT);
|
||||||
|
} // namespace T5
|
@ -1,11 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Game/T6/T6.h"
|
|
||||||
|
|
||||||
#include "Json/JsonCommon.h"
|
#include "Json/JsonCommon.h"
|
||||||
#include "Json/JsonExtension.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
33
src/ObjCommon/Game/T6/XModel/XModelConstantsT6.h
Normal file
33
src/ObjCommon/Game/T6/XModel/XModelConstantsT6.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Game/T6/T6.h"
|
||||||
|
|
||||||
|
namespace T6
|
||||||
|
{
|
||||||
|
inline const char* HITLOC_NAMES[]{
|
||||||
|
// clang-format off
|
||||||
|
"none",
|
||||||
|
"helmet",
|
||||||
|
"head",
|
||||||
|
"neck",
|
||||||
|
"torso_upper",
|
||||||
|
"torso_middle",
|
||||||
|
"torso_lower",
|
||||||
|
"right_arm_upper",
|
||||||
|
"left_arm_upper",
|
||||||
|
"right_arm_lower",
|
||||||
|
"left_arm_lower",
|
||||||
|
"right_hand",
|
||||||
|
"left_hand",
|
||||||
|
"right_leg_upper",
|
||||||
|
"left_leg_upper",
|
||||||
|
"right_leg_lower",
|
||||||
|
"left_leg_lower",
|
||||||
|
"right_foot",
|
||||||
|
"left_foot",
|
||||||
|
"gun",
|
||||||
|
"shield",
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
static_assert(std::extent_v<decltype(HITLOC_NAMES)> == HITLOC_COUNT);
|
||||||
|
} // namespace T6
|
@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Json/JsonExtension.h"
|
#include "Json/JsonExtension.h"
|
||||||
|
|
||||||
|
#pragma warning(push, 0)
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#pragma warning(pop)
|
||||||
|
|
||||||
class JsonVec2
|
class JsonVec2
|
||||||
{
|
{
|
||||||
|
@ -61,12 +61,12 @@ namespace d3d9
|
|||||||
uint32_t TypeInfo;
|
uint32_t TypeInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool PopulateVersionInfo(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize)
|
bool PopulateVersionInfo(ShaderInfo& shaderInfo, const void* shaderByteCode, const size_t shaderByteCodeSize)
|
||||||
{
|
{
|
||||||
if (shaderByteCodeSize < sizeof(uint32_t))
|
if (shaderByteCodeSize < sizeof(uint32_t))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto version = *shaderByteCode;
|
const auto version = *static_cast<const uint32_t*>(shaderByteCode);
|
||||||
shaderInfo.m_version_minor = version & 0xFF;
|
shaderInfo.m_version_minor = version & 0xFF;
|
||||||
shaderInfo.m_version_major = (version & 0xFF00) >> 8;
|
shaderInfo.m_version_major = (version & 0xFF00) >> 8;
|
||||||
|
|
||||||
@ -91,10 +91,10 @@ namespace d3d9
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FindComment(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize, const uint32_t magic, const char*& commentStart, size_t& commentSize)
|
bool FindComment(const uint8_t* shaderByteCode, const size_t shaderByteCodeSize, const uint32_t magic, const char*& commentStart, size_t& commentSize)
|
||||||
{
|
{
|
||||||
const uint32_t* currentPos = shaderByteCode + 1;
|
const auto* currentPos = reinterpret_cast<const uint32_t*>(shaderByteCode + sizeof(uint32_t));
|
||||||
size_t currentOffset = sizeof(uint32_t);
|
auto currentOffset = sizeof(uint32_t);
|
||||||
while (*currentPos != OPCODE_END && (currentOffset + sizeof(uint32_t) - 1) < shaderByteCodeSize)
|
while (*currentPos != OPCODE_END && (currentOffset + sizeof(uint32_t) - 1) < shaderByteCodeSize)
|
||||||
{
|
{
|
||||||
const auto currentValue = *currentPos;
|
const auto currentValue = *currentPos;
|
||||||
@ -215,7 +215,7 @@ namespace d3d9
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PopulateShaderInfoFromShaderByteCode(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize)
|
bool PopulateShaderInfoFromShaderByteCode(ShaderInfo& shaderInfo, const uint8_t* shaderByteCode, const size_t shaderByteCodeSize)
|
||||||
{
|
{
|
||||||
if (!PopulateVersionInfo(shaderInfo, shaderByteCode, shaderByteCodeSize))
|
if (!PopulateVersionInfo(shaderInfo, shaderByteCode, shaderByteCodeSize))
|
||||||
return false;
|
return false;
|
||||||
@ -236,14 +236,14 @@ namespace d3d9
|
|||||||
}
|
}
|
||||||
} // namespace d3d9
|
} // namespace d3d9
|
||||||
|
|
||||||
std::unique_ptr<ShaderInfo> ShaderAnalyser::GetShaderInfo(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize)
|
std::unique_ptr<ShaderInfo> ShaderAnalyser::GetShaderInfo(const void* shaderByteCode, const size_t shaderByteCodeSize)
|
||||||
{
|
{
|
||||||
if (shaderByteCode == nullptr || shaderByteCodeSize == 0)
|
if (shaderByteCode == nullptr || shaderByteCodeSize == 0)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto shaderInfo = std::make_unique<ShaderInfo>();
|
auto shaderInfo = std::make_unique<ShaderInfo>();
|
||||||
|
|
||||||
if (!PopulateShaderInfoFromShaderByteCode(*shaderInfo, shaderByteCode, shaderByteCodeSize))
|
if (!PopulateShaderInfoFromShaderByteCode(*shaderInfo, static_cast<const uint8_t*>(shaderByteCode), shaderByteCodeSize))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return shaderInfo;
|
return shaderInfo;
|
||||||
|
@ -95,6 +95,6 @@ namespace d3d9
|
|||||||
class ShaderAnalyser
|
class ShaderAnalyser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<ShaderInfo> GetShaderInfo(const uint32_t* shaderByteCode, size_t shaderByteCodeSize);
|
static std::unique_ptr<ShaderInfo> GetShaderInfo(const void* shaderByteCode, size_t shaderByteCodeSize);
|
||||||
};
|
};
|
||||||
} // namespace d3d9
|
} // namespace d3d9
|
||||||
|
@ -50,6 +50,8 @@ function ObjLoading:project()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useSourceTemplating("ObjLoading")
|
||||||
|
|
||||||
self:include(includes)
|
self:include(includes)
|
||||||
Crypto:include(includes)
|
Crypto:include(includes)
|
||||||
Utils:include(includes)
|
Utils:include(includes)
|
||||||
|
@ -1,17 +1,62 @@
|
|||||||
#include "AssetLoaderGfxImage.h"
|
#include "AssetLoaderGfxImage.h"
|
||||||
|
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "ObjLoading.h"
|
#include "Image/IwiLoader.h"
|
||||||
#include "Pool/GlobalAssetPool.h"
|
#include "Pool/GlobalAssetPool.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
using namespace IW5;
|
using namespace IW5;
|
||||||
|
|
||||||
void* AssetLoaderGfxImage::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
void* AssetLoaderGfxImage::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
||||||
{
|
{
|
||||||
|
auto* asset = memory->Alloc<AssetImage::Type>();
|
||||||
|
asset->name = memory->Dup(assetName.c_str());
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetLoaderGfxImage::CanLoadFromRaw() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetLoaderGfxImage::LoadFromRaw(
|
||||||
|
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
|
||||||
|
{
|
||||||
|
const auto fileName = std::format("images/{}.iwi", assetName);
|
||||||
|
const auto file = searchPath->Open(fileName);
|
||||||
|
if (!file.IsOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto fileSize = static_cast<size_t>(file.m_length);
|
||||||
|
const auto fileData = std::make_unique<char[]>(fileSize);
|
||||||
|
file.m_stream->read(fileData.get(), fileSize);
|
||||||
|
|
||||||
|
MemoryManager tempMemory;
|
||||||
|
IwiLoader iwiLoader(&tempMemory);
|
||||||
|
std::istringstream ss(std::string(fileData.get(), fileSize));
|
||||||
|
const auto texture = iwiLoader.LoadIwi(ss);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Failed to load texture from: {}\n", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto* image = memory->Create<GfxImage>();
|
auto* image = memory->Create<GfxImage>();
|
||||||
memset(image, 0, sizeof(GfxImage));
|
memset(image, 0, sizeof(GfxImage));
|
||||||
|
|
||||||
image->name = memory->Dup(assetName.c_str());
|
image->name = memory->Dup(assetName.c_str());
|
||||||
return image;
|
image->noPicmip = !texture->HasMipMaps();
|
||||||
|
image->width = static_cast<uint16_t>(texture->GetWidth());
|
||||||
|
image->height = static_cast<uint16_t>(texture->GetHeight());
|
||||||
|
image->depth = static_cast<uint16_t>(texture->GetDepth());
|
||||||
|
|
||||||
|
image->texture.loadDef = memory->Alloc<GfxImageLoadDef>();
|
||||||
|
|
||||||
|
manager->AddAsset<AssetImage>(assetName, image);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AssetLoading/BasicAssetLoader.h"
|
#include "AssetLoading/BasicAssetLoader.h"
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "SearchPath/ISearchPath.h"
|
#include "SearchPath/ISearchPath.h"
|
||||||
|
|
||||||
@ -10,5 +10,8 @@ namespace IW5
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
|
_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 IW5
|
} // namespace IW5
|
||||||
|
@ -1,18 +1,57 @@
|
|||||||
#include "AssetLoaderMaterial.h"
|
#include "AssetLoaderMaterial.h"
|
||||||
|
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "ObjLoading.h"
|
#include "Game/IW5/Material/JsonMaterialLoader.h"
|
||||||
#include "Pool/GlobalAssetPool.h"
|
#include "Pool/GlobalAssetPool.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace IW5;
|
using namespace IW5;
|
||||||
|
|
||||||
void* AssetLoaderMaterial::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
void* AssetLoaderMaterial::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
||||||
{
|
{
|
||||||
auto* material = memory->Create<Material>();
|
auto* asset = memory->Alloc<AssetMaterial::Type>();
|
||||||
memset(material, 0, sizeof(Material));
|
asset->info.name = memory->Dup(assetName.c_str());
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetLoaderMaterial::CanLoadFromRaw() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AssetLoaderMaterial::GetFileNameForAsset(const std::string& assetName)
|
||||||
|
{
|
||||||
|
std::string sanitizedFileName(assetName);
|
||||||
|
if (sanitizedFileName[0] == '*')
|
||||||
|
{
|
||||||
|
std::ranges::replace(sanitizedFileName, '*', '_');
|
||||||
|
const auto parenthesisPos = sanitizedFileName.find('(');
|
||||||
|
if (parenthesisPos != std::string::npos)
|
||||||
|
sanitizedFileName.erase(parenthesisPos);
|
||||||
|
sanitizedFileName = "generated/" + sanitizedFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::format("materials/{}.json", sanitizedFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetLoaderMaterial::LoadFromRaw(
|
||||||
|
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
|
||||||
|
{
|
||||||
|
const auto file = searchPath->Open(GetFileNameForAsset(assetName));
|
||||||
|
if (!file.IsOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto* material = memory->Alloc<Material>();
|
||||||
material->info.name = memory->Dup(assetName.c_str());
|
material->info.name = memory->Dup(assetName.c_str());
|
||||||
|
|
||||||
return material;
|
std::vector<XAssetInfoGeneric*> dependencies;
|
||||||
|
if (LoadMaterialAsJson(*file.m_stream, *material, memory, manager, dependencies))
|
||||||
|
manager->AddAsset<AssetMaterial>(assetName, material, std::move(dependencies));
|
||||||
|
else
|
||||||
|
std::cerr << std::format("Failed to load material \"{}\"\n", assetName);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AssetLoading/BasicAssetLoader.h"
|
#include "AssetLoading/BasicAssetLoader.h"
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "SearchPath/ISearchPath.h"
|
#include "SearchPath/ISearchPath.h"
|
||||||
|
|
||||||
@ -8,7 +8,12 @@ namespace IW5
|
|||||||
{
|
{
|
||||||
class AssetLoaderMaterial final : public BasicAssetLoader<AssetMaterial>
|
class AssetLoaderMaterial final : public BasicAssetLoader<AssetMaterial>
|
||||||
{
|
{
|
||||||
|
static std::string GetFileNameForAsset(const std::string& assetName);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
|
_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 IW5
|
} // namespace IW5
|
||||||
|
@ -814,8 +814,8 @@ namespace
|
|||||||
for (auto i = 0u; i < originalGraphKnotCount; i++)
|
for (auto i = 0u; i < originalGraphKnotCount; i++)
|
||||||
{
|
{
|
||||||
const auto& commonKnot = graph.knots[i];
|
const auto& commonKnot = graph.knots[i];
|
||||||
originalGraphKnots[i][0] = static_cast<float>(commonKnot.x);
|
originalGraphKnots[i].x = static_cast<float>(commonKnot.x);
|
||||||
originalGraphKnots[i][1] = static_cast<float>(commonKnot.y);
|
originalGraphKnots[i].y = static_cast<float>(commonKnot.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
graphKnots = originalGraphKnots;
|
graphKnots = originalGraphKnots;
|
||||||
|
@ -1,17 +1,47 @@
|
|||||||
#include "AssetLoaderXModel.h"
|
#include "AssetLoaderXModel.h"
|
||||||
|
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "ObjLoading.h"
|
#include "Game/IW5/XModel/XModelLoaderIW5.h"
|
||||||
#include "Pool/GlobalAssetPool.h"
|
#include "Pool/GlobalAssetPool.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace IW5;
|
using namespace IW5;
|
||||||
|
|
||||||
void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
||||||
{
|
{
|
||||||
auto* model = memory->Create<XModel>();
|
auto* asset = memory->Alloc<AssetXModel::Type>();
|
||||||
memset(model, 0, sizeof(XModel));
|
asset->name = memory->Dup(assetName.c_str());
|
||||||
model->name = memory->Dup(assetName.c_str());
|
return asset;
|
||||||
return model;
|
}
|
||||||
|
|
||||||
|
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 (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies))
|
||||||
|
{
|
||||||
|
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AssetLoading/BasicAssetLoader.h"
|
#include "AssetLoading/BasicAssetLoader.h"
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
#include "Game/IW5/IW5.h"
|
#include "Game/IW5/IW5.h"
|
||||||
#include "SearchPath/ISearchPath.h"
|
#include "SearchPath/ISearchPath.h"
|
||||||
|
|
||||||
@ -8,7 +8,12 @@ namespace IW5
|
|||||||
{
|
{
|
||||||
class AssetLoaderXModel final : public BasicAssetLoader<AssetXModel>
|
class AssetLoaderXModel final : public BasicAssetLoader<AssetXModel>
|
||||||
{
|
{
|
||||||
|
static std::string GetFileNameForAsset(const std::string& assetName);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
|
_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 IW5
|
} // namespace IW5
|
||||||
|
447
src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.cpp
Normal file
447
src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.cpp
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
#include "JsonMaterialLoader.h"
|
||||||
|
|
||||||
|
#include "Game/IW5/CommonIW5.h"
|
||||||
|
#include "Game/IW5/Material/JsonMaterial.h"
|
||||||
|
#include "Impl/Base64.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using namespace nlohmann;
|
||||||
|
using namespace IW5;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class JsonLoader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::vector<XAssetInfoGeneric*>& dependencies)
|
||||||
|
: m_stream(stream),
|
||||||
|
m_memory(memory),
|
||||||
|
m_manager(manager),
|
||||||
|
m_dependencies(dependencies)
|
||||||
|
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Load(Material& material) const
|
||||||
|
{
|
||||||
|
const auto jRoot = json::parse(m_stream);
|
||||||
|
std::string game;
|
||||||
|
std::string type;
|
||||||
|
unsigned version;
|
||||||
|
|
||||||
|
jRoot.at("_type").get_to(type);
|
||||||
|
jRoot.at("_game").get_to(game);
|
||||||
|
jRoot.at("_version").get_to(version);
|
||||||
|
|
||||||
|
if (type != "material" || version != 1u || game != "iw5")
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Tried to load material \"{}\" but did not find expected type material of version 1\n", material.info.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto jMaterial = jRoot.get<JsonMaterial>();
|
||||||
|
return CreateMaterialFromJson(jMaterial, material);
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Failed to parse json of material: {}\n", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void PrintError(const Material& material, const std::string& message)
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Cannot load material \"{}\": {}\n", material.info.name, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CreateGameFlagsFromJson(const JsonMaterial& jMaterial, unsigned char& gameFlags)
|
||||||
|
{
|
||||||
|
for (const auto gameFlag : jMaterial.gameFlags)
|
||||||
|
gameFlags |= gameFlag;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateSamplerStateFromJson(const JsonSamplerState& jSamplerState, MaterialTextureDefSamplerState& samplerState)
|
||||||
|
{
|
||||||
|
samplerState.filter = jSamplerState.filter;
|
||||||
|
samplerState.mipMap = jSamplerState.mipMap;
|
||||||
|
samplerState.clampU = jSamplerState.clampU;
|
||||||
|
samplerState.clampV = jSamplerState.clampV;
|
||||||
|
samplerState.clampW = jSamplerState.clampW;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateWaterFromJson(const JsonWater& jWater, water_t& water, const Material& material) const
|
||||||
|
{
|
||||||
|
water.writable.floatTime = jWater.floatTime;
|
||||||
|
water.M = jWater.m;
|
||||||
|
water.N = jWater.n;
|
||||||
|
water.Lx = jWater.lx;
|
||||||
|
water.Lz = jWater.lz;
|
||||||
|
water.gravity = jWater.gravity;
|
||||||
|
water.windvel = jWater.windvel;
|
||||||
|
water.winddir[0] = jWater.winddir[0];
|
||||||
|
water.winddir[1] = jWater.winddir[1];
|
||||||
|
water.amplitude = jWater.amplitude;
|
||||||
|
water.codeConstant[0] = jWater.codeConstant[0];
|
||||||
|
water.codeConstant[1] = jWater.codeConstant[1];
|
||||||
|
water.codeConstant[2] = jWater.codeConstant[2];
|
||||||
|
water.codeConstant[3] = jWater.codeConstant[3];
|
||||||
|
|
||||||
|
const auto expectedH0Size = water.M * water.N * sizeof(complex_s);
|
||||||
|
if (expectedH0Size > 0)
|
||||||
|
{
|
||||||
|
water.H0 = m_memory.Alloc<complex_s>(water.M * water.N);
|
||||||
|
const auto h0Size = base64::DecodeBase64(jWater.h0.data(), jWater.h0.size(), water.H0, expectedH0Size);
|
||||||
|
if (h0Size != expectedH0Size)
|
||||||
|
{
|
||||||
|
PrintError(material, std::format("Water h0 size {} does not match expected {}", h0Size, expectedH0Size));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto expectedWTermSize = water.M * water.N * sizeof(float);
|
||||||
|
if (expectedWTermSize > 0)
|
||||||
|
{
|
||||||
|
water.wTerm = m_memory.Alloc<float>(water.M * water.N);
|
||||||
|
auto wTermSize = base64::DecodeBase64(jWater.wTerm.data(), jWater.wTerm.size(), water.wTerm, expectedWTermSize);
|
||||||
|
if (wTermSize != expectedWTermSize)
|
||||||
|
{
|
||||||
|
PrintError(material, std::format("Water wTerm size {} does not match expected {}", wTermSize, expectedWTermSize));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateTextureDefFromJson(const JsonTexture& jTexture, MaterialTextureDef& textureDef, const Material& material) const
|
||||||
|
{
|
||||||
|
if (jTexture.name)
|
||||||
|
{
|
||||||
|
if (jTexture.name->empty())
|
||||||
|
{
|
||||||
|
PrintError(material, "textureDef name cannot be empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
textureDef.nameStart = jTexture.name.value()[0];
|
||||||
|
textureDef.nameEnd = jTexture.name.value()[jTexture.name->size() - 1];
|
||||||
|
textureDef.nameHash = Common::R_HashString(jTexture.name.value().c_str(), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!jTexture.nameStart || !jTexture.nameEnd || !jTexture.nameHash)
|
||||||
|
{
|
||||||
|
PrintError(material, "textureDefs without name must have nameStart, nameEnd and nameHash");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jTexture.nameStart->size() != 1 || jTexture.nameEnd->size() != 1)
|
||||||
|
{
|
||||||
|
PrintError(material, "nameStart and nameEnd must be a string of exactly one character");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
textureDef.nameStart = jTexture.nameStart.value()[0];
|
||||||
|
textureDef.nameEnd = jTexture.nameEnd.value()[0];
|
||||||
|
textureDef.nameHash = jTexture.nameHash.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateSamplerStateFromJson(jTexture.samplerState, textureDef.samplerState);
|
||||||
|
|
||||||
|
textureDef.semantic = jTexture.semantic;
|
||||||
|
|
||||||
|
auto* imageAsset = m_manager.LoadDependency<AssetImage>(jTexture.image);
|
||||||
|
if (!imageAsset)
|
||||||
|
{
|
||||||
|
PrintError(material, std::format("Could not find textureDef image: {}", jTexture.image));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dependencies.push_back(imageAsset);
|
||||||
|
|
||||||
|
if (jTexture.water)
|
||||||
|
{
|
||||||
|
if (jTexture.semantic != TS_WATER_MAP)
|
||||||
|
{
|
||||||
|
PrintError(material, "Only textureDefs with semantic waterMap can define water params");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (jTexture.semantic == TS_WATER_MAP)
|
||||||
|
{
|
||||||
|
PrintError(material, "TextureDefs with semantic waterMap must define water params");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jTexture.water)
|
||||||
|
{
|
||||||
|
auto* water = m_memory.Alloc<water_t>();
|
||||||
|
water->image = imageAsset->Asset();
|
||||||
|
|
||||||
|
if (!CreateWaterFromJson(*jTexture.water, *water, material))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
textureDef.u.water = water;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
textureDef.u.image = imageAsset->Asset();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CreateConstantDefFromJson(const JsonConstant& jConstant, MaterialConstantDef& constantDef, const Material& material)
|
||||||
|
{
|
||||||
|
if (jConstant.name)
|
||||||
|
{
|
||||||
|
const auto copyCount = std::min(jConstant.name->size() + 1, std::extent_v<decltype(MaterialConstantDef::name)>);
|
||||||
|
strncpy(constantDef.name, jConstant.name->c_str(), copyCount);
|
||||||
|
if (copyCount < std::extent_v<decltype(MaterialConstantDef::name)>)
|
||||||
|
memset(&constantDef.name[copyCount], 0, std::extent_v<decltype(MaterialConstantDef::name)> - copyCount);
|
||||||
|
constantDef.nameHash = Common::R_HashString(jConstant.name->c_str(), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!jConstant.nameFragment || !jConstant.nameHash)
|
||||||
|
{
|
||||||
|
PrintError(material, "constantDefs without name must have nameFragment and nameHash");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto copyCount = std::min(jConstant.nameFragment->size() + 1, std::extent_v<decltype(MaterialConstantDef::name)>);
|
||||||
|
strncpy(constantDef.name, jConstant.nameFragment->c_str(), copyCount);
|
||||||
|
if (copyCount < std::extent_v<decltype(MaterialConstantDef::name)>)
|
||||||
|
memset(&constantDef.name[copyCount], 0, std::extent_v<decltype(MaterialConstantDef::name)> - copyCount);
|
||||||
|
constantDef.nameHash = jConstant.nameHash.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jConstant.literal.size() != 4)
|
||||||
|
{
|
||||||
|
PrintError(material, "constantDef literal must be array of size 4");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constantDef.literal.x = jConstant.literal[0];
|
||||||
|
constantDef.literal.y = jConstant.literal[1];
|
||||||
|
constantDef.literal.z = jConstant.literal[2];
|
||||||
|
constantDef.literal.w = jConstant.literal[3];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
CreateStateBitsTableEntryFromJson(const JsonStateBitsTableEntry& jStateBitsTableEntry, GfxStateBits& stateBitsTableEntry, const Material& material)
|
||||||
|
{
|
||||||
|
auto& structured = stateBitsTableEntry.loadBits.structured;
|
||||||
|
|
||||||
|
structured.srcBlendRgb = jStateBitsTableEntry.srcBlendRgb;
|
||||||
|
structured.dstBlendRgb = jStateBitsTableEntry.dstBlendRgb;
|
||||||
|
structured.blendOpRgb = jStateBitsTableEntry.blendOpRgb;
|
||||||
|
|
||||||
|
if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::DISABLED)
|
||||||
|
{
|
||||||
|
structured.alphaTestDisabled = 1;
|
||||||
|
structured.alphaTest = 0;
|
||||||
|
}
|
||||||
|
else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GT0)
|
||||||
|
{
|
||||||
|
structured.alphaTestDisabled = 0;
|
||||||
|
structured.alphaTest = GFXS_ALPHA_TEST_GT_0;
|
||||||
|
}
|
||||||
|
else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::LT128)
|
||||||
|
{
|
||||||
|
structured.alphaTestDisabled = 0;
|
||||||
|
structured.alphaTest = GFXS_ALPHA_TEST_LT_128;
|
||||||
|
}
|
||||||
|
else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GE128)
|
||||||
|
{
|
||||||
|
structured.alphaTestDisabled = 0;
|
||||||
|
structured.alphaTest = GFXS_ALPHA_TEST_GE_128;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError(material, "Invalid value for alphaTest");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jStateBitsTableEntry.cullFace == JsonCullFace::NONE)
|
||||||
|
structured.cullFace = GFXS_CULL_NONE;
|
||||||
|
else if (jStateBitsTableEntry.cullFace == JsonCullFace::BACK)
|
||||||
|
structured.cullFace = GFXS_CULL_BACK;
|
||||||
|
else if (jStateBitsTableEntry.cullFace == JsonCullFace::FRONT)
|
||||||
|
structured.cullFace = GFXS_CULL_FRONT;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError(material, "Invalid value for cull face");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
structured.srcBlendAlpha = jStateBitsTableEntry.srcBlendAlpha;
|
||||||
|
structured.dstBlendAlpha = jStateBitsTableEntry.dstBlendAlpha;
|
||||||
|
structured.blendOpAlpha = jStateBitsTableEntry.blendOpAlpha;
|
||||||
|
structured.colorWriteRgb = jStateBitsTableEntry.colorWriteRgb;
|
||||||
|
structured.colorWriteAlpha = jStateBitsTableEntry.colorWriteAlpha;
|
||||||
|
structured.gammaWrite = jStateBitsTableEntry.gammaWrite;
|
||||||
|
structured.polymodeLine = jStateBitsTableEntry.polymodeLine;
|
||||||
|
structured.depthWrite = jStateBitsTableEntry.depthWrite;
|
||||||
|
|
||||||
|
if (jStateBitsTableEntry.depthTest == JsonDepthTest::DISABLED)
|
||||||
|
structured.depthTestDisabled = 1;
|
||||||
|
else if (jStateBitsTableEntry.depthTest == JsonDepthTest::ALWAYS)
|
||||||
|
structured.depthTest = GFXS_DEPTHTEST_ALWAYS;
|
||||||
|
else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS)
|
||||||
|
structured.depthTest = GFXS_DEPTHTEST_LESS;
|
||||||
|
else if (jStateBitsTableEntry.depthTest == JsonDepthTest::EQUAL)
|
||||||
|
structured.depthTest = GFXS_DEPTHTEST_EQUAL;
|
||||||
|
else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS_EQUAL)
|
||||||
|
structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintError(material, "Invalid value for depth test");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
structured.polygonOffset = jStateBitsTableEntry.polygonOffset;
|
||||||
|
|
||||||
|
if (jStateBitsTableEntry.stencilFront)
|
||||||
|
{
|
||||||
|
structured.stencilFrontEnabled = 1;
|
||||||
|
structured.stencilFrontPass = jStateBitsTableEntry.stencilFront->pass;
|
||||||
|
structured.stencilFrontFail = jStateBitsTableEntry.stencilFront->fail;
|
||||||
|
structured.stencilFrontZFail = jStateBitsTableEntry.stencilFront->zfail;
|
||||||
|
structured.stencilFrontFunc = jStateBitsTableEntry.stencilFront->func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jStateBitsTableEntry.stencilBack)
|
||||||
|
{
|
||||||
|
structured.stencilBackEnabled = 1;
|
||||||
|
structured.stencilBackPass = jStateBitsTableEntry.stencilBack->pass;
|
||||||
|
structured.stencilBackFail = jStateBitsTableEntry.stencilBack->fail;
|
||||||
|
structured.stencilBackZFail = jStateBitsTableEntry.stencilBack->zfail;
|
||||||
|
structured.stencilBackFunc = jStateBitsTableEntry.stencilBack->func;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateMaterialFromJson(const JsonMaterial& jMaterial, Material& material) const
|
||||||
|
{
|
||||||
|
if (!CreateGameFlagsFromJson(jMaterial, material.info.gameFlags))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
material.info.sortKey = static_cast<unsigned char>(jMaterial.sortKey);
|
||||||
|
|
||||||
|
if (jMaterial.textureAtlas)
|
||||||
|
{
|
||||||
|
material.info.textureAtlasRowCount = jMaterial.textureAtlas->rows;
|
||||||
|
material.info.textureAtlasColumnCount = jMaterial.textureAtlas->columns;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
material.info.textureAtlasRowCount = 0;
|
||||||
|
material.info.textureAtlasColumnCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
material.info.surfaceTypeBits = jMaterial.surfaceTypeBits;
|
||||||
|
|
||||||
|
if (jMaterial.stateBitsEntry.size() != std::extent_v<decltype(Material::stateBitsEntry)>)
|
||||||
|
{
|
||||||
|
PrintError(material, std::format("StateBitsEntry size is not {}", std::extent_v<decltype(Material::stateBitsEntry)>));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto i = 0u; i < std::extent_v<decltype(Material::stateBitsEntry)>; i++)
|
||||||
|
material.stateBitsEntry[i] = jMaterial.stateBitsEntry[i];
|
||||||
|
|
||||||
|
material.stateFlags = static_cast<unsigned char>(jMaterial.stateFlags);
|
||||||
|
material.cameraRegion = jMaterial.cameraRegion;
|
||||||
|
|
||||||
|
auto* techniqueSet = m_manager.LoadDependency<AssetTechniqueSet>(jMaterial.techniqueSet);
|
||||||
|
if (!techniqueSet)
|
||||||
|
{
|
||||||
|
PrintError(material, "Could not find technique set");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dependencies.push_back(techniqueSet);
|
||||||
|
material.techniqueSet = techniqueSet->Asset();
|
||||||
|
|
||||||
|
if (!jMaterial.textures.empty())
|
||||||
|
{
|
||||||
|
material.textureCount = static_cast<unsigned char>(jMaterial.textures.size());
|
||||||
|
material.textureTable = m_memory.Alloc<MaterialTextureDef>(material.textureCount);
|
||||||
|
|
||||||
|
for (auto i = 0u; i < material.textureCount; i++)
|
||||||
|
{
|
||||||
|
if (!CreateTextureDefFromJson(jMaterial.textures[i], material.textureTable[i], material))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
material.textureCount = 0;
|
||||||
|
material.textureTable = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jMaterial.constants.empty())
|
||||||
|
{
|
||||||
|
material.constantCount = static_cast<unsigned char>(jMaterial.constants.size());
|
||||||
|
material.constantTable = m_memory.Alloc<MaterialConstantDef>(material.constantCount);
|
||||||
|
|
||||||
|
for (auto i = 0u; i < material.constantCount; i++)
|
||||||
|
{
|
||||||
|
if (!CreateConstantDefFromJson(jMaterial.constants[i], material.constantTable[i], material))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
material.constantCount = 0;
|
||||||
|
material.constantTable = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jMaterial.stateBits.empty())
|
||||||
|
{
|
||||||
|
material.stateBitsCount = static_cast<unsigned char>(jMaterial.stateBits.size());
|
||||||
|
material.stateBitsTable = m_memory.Alloc<GfxStateBits>(material.stateBitsCount);
|
||||||
|
|
||||||
|
for (auto i = 0u; i < material.stateBitsCount; i++)
|
||||||
|
{
|
||||||
|
if (!CreateStateBitsTableEntryFromJson(jMaterial.stateBits[i], material.stateBitsTable[i], material))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
material.stateBitsCount = 0;
|
||||||
|
material.stateBitsTable = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream& m_stream;
|
||||||
|
MemoryManager& m_memory;
|
||||||
|
IAssetLoadingManager& m_manager;
|
||||||
|
std::vector<XAssetInfoGeneric*>& m_dependencies;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
bool LoadMaterialAsJson(
|
||||||
|
std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
|
||||||
|
{
|
||||||
|
const JsonLoader loader(stream, *memory, *manager, dependencies);
|
||||||
|
|
||||||
|
return loader.Load(material);
|
||||||
|
}
|
||||||
|
} // namespace IW5
|
13
src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.h
Normal file
13
src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
|
#include "Game/IW5/IW5.h"
|
||||||
|
#include "Utils/MemoryManager.h"
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
bool LoadMaterialAsJson(
|
||||||
|
std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies);
|
||||||
|
} // namespace IW5
|
47
src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.cpp
Normal file
47
src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "AssetLoaderXModel.h"
|
||||||
|
|
||||||
|
#include "Game/T5/T5.h"
|
||||||
|
#include "Game/T5/XModel/XModelLoaderT5.h"
|
||||||
|
#include "Pool/GlobalAssetPool.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace T5;
|
||||||
|
|
||||||
|
void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
|
||||||
|
{
|
||||||
|
auto* asset = memory->Alloc<AssetXModel::Type>();
|
||||||
|
asset->name = memory->Dup(assetName.c_str());
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies))
|
||||||
|
{
|
||||||
|
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
19
src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.h
Normal file
19
src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "AssetLoading/BasicAssetLoader.h"
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
|
#include "Game/T5/T5.h"
|
||||||
|
#include "SearchPath/ISearchPath.h"
|
||||||
|
|
||||||
|
namespace T5
|
||||||
|
{
|
||||||
|
class AssetLoaderXModel final : public BasicAssetLoader<AssetXModel>
|
||||||
|
{
|
||||||
|
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 T5
|
@ -3,6 +3,7 @@
|
|||||||
#include "AssetLoaders/AssetLoaderLocalizeEntry.h"
|
#include "AssetLoaders/AssetLoaderLocalizeEntry.h"
|
||||||
#include "AssetLoaders/AssetLoaderRawFile.h"
|
#include "AssetLoaders/AssetLoaderRawFile.h"
|
||||||
#include "AssetLoaders/AssetLoaderStringTable.h"
|
#include "AssetLoaders/AssetLoaderStringTable.h"
|
||||||
|
#include "AssetLoaders/AssetLoaderXModel.h"
|
||||||
#include "AssetLoading/AssetLoadingManager.h"
|
#include "AssetLoading/AssetLoadingManager.h"
|
||||||
#include "Game/T5/GameAssetPoolT5.h"
|
#include "Game/T5/GameAssetPoolT5.h"
|
||||||
#include "Game/T5/GameT5.h"
|
#include "Game/T5/GameT5.h"
|
||||||
@ -27,7 +28,7 @@ ObjLoader::ObjLoader()
|
|||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetPhysConstraints>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetPhysConstraints>)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetDestructibleDef>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetDestructibleDef>)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetXAnim>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetXAnim>)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetXModel>)
|
REGISTER_ASSET_LOADER(AssetLoaderXModel)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetMaterial>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetMaterial>)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetTechniqueSet>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetTechniqueSet>)
|
||||||
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetImage>)
|
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetImage>)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include "AssetLoaderXModel.h"
|
#include "AssetLoaderXModel.h"
|
||||||
|
|
||||||
#include "Game/T6/T6.h"
|
#include "Game/T6/T6.h"
|
||||||
#include "Game/T6/XModel/JsonXModelLoader.h"
|
#include "Game/T6/XModel/XModelLoaderT6.h"
|
||||||
#include "Pool/GlobalAssetPool.h"
|
#include "Pool/GlobalAssetPool.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -33,10 +33,15 @@ bool AssetLoaderXModel::LoadFromRaw(
|
|||||||
xmodel->name = memory->Dup(assetName.c_str());
|
xmodel->name = memory->Dup(assetName.c_str());
|
||||||
|
|
||||||
std::vector<XAssetInfoGeneric*> dependencies;
|
std::vector<XAssetInfoGeneric*> dependencies;
|
||||||
if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies))
|
if (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies))
|
||||||
|
{
|
||||||
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
|
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName);
|
std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
#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
|
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
#include "GltfInput.h"
|
#include "GltfInput.h"
|
||||||
#include "XModel/Gltf/JsonGltf.h"
|
#include "XModel/Gltf/JsonGltf.h"
|
||||||
#include "XModel/XModelLoader.h"
|
#include "XModel/XModelFileLoader.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
namespace gltf
|
namespace gltf
|
||||||
{
|
{
|
||||||
class Loader : public XModelLoader
|
class Loader : public XModelFileLoader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Loader() = default;
|
Loader() = default;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
#include "GltfBuffer.h"
|
#include "GltfBuffer.h"
|
||||||
|
|
||||||
|
#include "Impl/Base64.h"
|
||||||
#include "XModel/Gltf/GltfConstants.h"
|
#include "XModel/Gltf/GltfConstants.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#define LTC_NO_PROTOTYPES
|
|
||||||
#include <tomcrypt.h>
|
|
||||||
|
|
||||||
using namespace gltf;
|
using namespace gltf;
|
||||||
|
|
||||||
EmbeddedBuffer::EmbeddedBuffer(const void* data, const size_t dataSize)
|
EmbeddedBuffer::EmbeddedBuffer(const void* data, const size_t dataSize)
|
||||||
@ -51,15 +49,12 @@ bool DataUriBuffer::ReadDataFromUri(const std::string& uri)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto base64DataLength = uri.size() - URI_PREFIX_LENGTH;
|
const auto base64DataLength = uri.size() - URI_PREFIX_LENGTH;
|
||||||
|
m_data_size = base64::GetBase64DecodeOutputLength(base64DataLength);
|
||||||
|
m_data = std::make_unique<uint8_t[]>(m_data_size);
|
||||||
|
|
||||||
unsigned long outLength = base64DataLength / 4u;
|
m_data_size = base64::DecodeBase64(&uri[URI_PREFIX_LENGTH], base64DataLength, m_data.get(), m_data_size);
|
||||||
m_data = std::make_unique<uint8_t[]>(outLength);
|
|
||||||
const auto result = base64_decode(&uri[URI_PREFIX_LENGTH], base64DataLength, m_data.get(), &outLength);
|
|
||||||
m_data_size = static_cast<size_t>(outLength);
|
|
||||||
|
|
||||||
assert(result == CRYPT_OK);
|
return m_data_size > 0;
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataUriBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
|
bool DataUriBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
|
||||||
|
79
src/ObjLoading/XModel/PartClassificationState.cpp
Normal file
79
src/ObjLoading/XModel/PartClassificationState.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "PartClassificationState.h"
|
||||||
|
|
||||||
|
#include "Csv/CsvStream.h"
|
||||||
|
#include "ObjLoading.h"
|
||||||
|
#include "Utils/StringUtils.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
PartClassificationState::PartClassificationState()
|
||||||
|
: m_loaded(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PartClassificationState::Load(const char** hitLocNames, const size_t hitLocNameCount, const IAssetLoadingManager& manager)
|
||||||
|
{
|
||||||
|
if (m_loaded)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ObjLoading::Configuration.Verbose)
|
||||||
|
std::cout << "Loading part classification...\n";
|
||||||
|
|
||||||
|
const auto file = manager.GetAssetLoadingContext()->m_raw_search_path->Open(PART_CLASSIFICATION_FILE);
|
||||||
|
if (!file.IsOpen())
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Could not load part classification: Failed to open {}\n", PART_CLASSIFICATION_FILE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hitLocStart = hitLocNames;
|
||||||
|
const auto hitLocEnd = &hitLocNames[hitLocNameCount];
|
||||||
|
|
||||||
|
const CsvInputStream csvStream(*file.m_stream);
|
||||||
|
std::vector<std::string> row;
|
||||||
|
auto rowIndex = 0u;
|
||||||
|
while (csvStream.NextRow(row))
|
||||||
|
{
|
||||||
|
if (!LoadRow(hitLocStart, hitLocEnd, rowIndex++, row))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_loaded = true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] unsigned PartClassificationState::GetPartClassificationForBoneName(const std::string& boneName) const
|
||||||
|
{
|
||||||
|
const auto entry = m_part_classifications.find(boneName);
|
||||||
|
|
||||||
|
return entry != m_part_classifications.end() ? entry->second : HITLOC_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PartClassificationState::LoadRow(const char** hitLocStart, const char** hitLocEnd, const unsigned rowIndex, std::vector<std::string>& row)
|
||||||
|
{
|
||||||
|
if (row.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (row.size() != 2)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not load part classification: Invalid row\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::MakeStringLowerCase(row[0]);
|
||||||
|
utils::MakeStringLowerCase(row[1]);
|
||||||
|
|
||||||
|
const auto foundHitLoc = std::find(hitLocStart, hitLocEnd, row[1]);
|
||||||
|
if (foundHitLoc == hitLocEnd)
|
||||||
|
{
|
||||||
|
std::cerr << std::format("Invalid hitloc name in row {}: {}\n", rowIndex + 1, row[1]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hitLocNum = std::distance(hitLocStart, foundHitLoc);
|
||||||
|
|
||||||
|
m_part_classifications.emplace(row[0], hitLocNum);
|
||||||
|
return true;
|
||||||
|
}
|
24
src/ObjLoading/XModel/PartClassificationState.h
Normal file
24
src/ObjLoading/XModel/PartClassificationState.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
|
#include "AssetLoading/IZoneAssetLoaderState.h"
|
||||||
|
|
||||||
|
class PartClassificationState final : public IZoneAssetLoaderState
|
||||||
|
{
|
||||||
|
// TODO: Use MP part classifications when building an mp fastfile
|
||||||
|
static constexpr auto PART_CLASSIFICATION_FILE = "partclassification.csv";
|
||||||
|
|
||||||
|
static constexpr auto HITLOC_NONE = 0u;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PartClassificationState();
|
||||||
|
|
||||||
|
bool Load(const char** hitLocNames, size_t hitLocNameCount, const IAssetLoadingManager& manager);
|
||||||
|
|
||||||
|
[[nodiscard]] unsigned GetPartClassificationForBoneName(const std::string& boneName) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool LoadRow(const char** hitLocStart, const char** hitLocEnd, unsigned rowIndex, std::vector<std::string>& row);
|
||||||
|
|
||||||
|
bool m_loaded;
|
||||||
|
std::unordered_map<std::string, unsigned> m_part_classifications;
|
||||||
|
};
|
47
src/ObjLoading/XModel/TangentData.cpp
Normal file
47
src/ObjLoading/XModel/TangentData.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "TangentData.h"
|
||||||
|
|
||||||
|
#include "Tangentspace.h"
|
||||||
|
|
||||||
|
void TangentData::CreateTangentData(const XModelCommon& common)
|
||||||
|
{
|
||||||
|
if (common.m_vertices.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto vertexCount = common.m_vertices.size();
|
||||||
|
m_tangents.resize(vertexCount);
|
||||||
|
m_binormals.resize(vertexCount);
|
||||||
|
|
||||||
|
auto triCount = 0u;
|
||||||
|
for (const auto& object : common.m_objects)
|
||||||
|
triCount += object.m_faces.size();
|
||||||
|
|
||||||
|
std::vector<uint16_t> indices(triCount * 3u);
|
||||||
|
auto triOffset = 0u;
|
||||||
|
for (const auto& object : common.m_objects)
|
||||||
|
{
|
||||||
|
for (const auto& face : object.m_faces)
|
||||||
|
{
|
||||||
|
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[0]);
|
||||||
|
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[1]);
|
||||||
|
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& firstVertex = common.m_vertices[0];
|
||||||
|
|
||||||
|
tangent_space::VertexData vertexData{
|
||||||
|
firstVertex.coordinates,
|
||||||
|
sizeof(XModelVertex),
|
||||||
|
firstVertex.normal,
|
||||||
|
sizeof(XModelVertex),
|
||||||
|
firstVertex.uv,
|
||||||
|
sizeof(XModelVertex),
|
||||||
|
m_tangents.data(),
|
||||||
|
sizeof(float) * 3,
|
||||||
|
m_binormals.data(),
|
||||||
|
sizeof(float) * 3,
|
||||||
|
indices.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tangent_space::CalculateTangentSpace(vertexData, triCount, vertexCount);
|
||||||
|
}
|
15
src/ObjLoading/XModel/TangentData.h
Normal file
15
src/ObjLoading/XModel/TangentData.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "XModel/XModelCommon.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class TangentData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void CreateTangentData(const XModelCommon& common);
|
||||||
|
|
||||||
|
std::vector<std::array<float, 3>> m_tangents;
|
||||||
|
std::vector<std::array<float, 3>> m_binormals;
|
||||||
|
};
|
18
src/ObjLoading/XModel/XModelFileLoader.h
Normal file
18
src/ObjLoading/XModel/XModelFileLoader.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "XModel/XModelCommon.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class XModelFileLoader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
XModelFileLoader() = default;
|
||||||
|
virtual ~XModelFileLoader() = default;
|
||||||
|
XModelFileLoader(const XModelFileLoader& other) = default;
|
||||||
|
XModelFileLoader(XModelFileLoader&& other) noexcept = default;
|
||||||
|
XModelFileLoader& operator=(const XModelFileLoader& other) = default;
|
||||||
|
XModelFileLoader& operator=(XModelFileLoader&& other) noexcept = default;
|
||||||
|
|
||||||
|
virtual std::unique_ptr<XModelCommon> Load() = 0;
|
||||||
|
};
|
@ -1,8 +1,26 @@
|
|||||||
#include "JsonXModelLoader.h"
|
#options GAME(IW5, T5, T6)
|
||||||
|
|
||||||
|
#filename "Game/" + GAME + "/XModel/XModelLoader" + GAME + ".cpp"
|
||||||
|
|
||||||
|
#set LOADER_HEADER "\"XModelLoader" + GAME + ".h\""
|
||||||
|
#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\""
|
||||||
|
#set CONSTANTS_HEADER "\"Game/" + GAME + "/XModel/XModelConstants" + GAME + ".h\""
|
||||||
|
#set JSON_HEADER "\"Game/" + GAME + "/XModel/JsonXModel" + GAME + ".h\""
|
||||||
|
|
||||||
|
#if GAME == "IW5"
|
||||||
|
#define FEATURE_IW5
|
||||||
|
#elif GAME == "T5"
|
||||||
|
#define FEATURE_T5
|
||||||
|
#elif GAME == "T6"
|
||||||
|
#define FEATURE_T6
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include LOADER_HEADER
|
||||||
|
|
||||||
|
#include COMMON_HEADER
|
||||||
|
#include CONSTANTS_HEADER
|
||||||
|
#include JSON_HEADER
|
||||||
|
|
||||||
#include "Csv/CsvStream.h"
|
|
||||||
#include "Game/T6/CommonT6.h"
|
|
||||||
#include "Game/T6/Json/JsonXModel.h"
|
|
||||||
#include "ObjLoading.h"
|
#include "ObjLoading.h"
|
||||||
#include "Utils/QuatInt16.h"
|
#include "Utils/QuatInt16.h"
|
||||||
#include "Utils/StringUtils.h"
|
#include "Utils/StringUtils.h"
|
||||||
@ -13,186 +31,27 @@
|
|||||||
|
|
||||||
#pragma warning(push, 0)
|
#pragma warning(push, 0)
|
||||||
#include <Eigen>
|
#include <Eigen>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
#include "XModel/PartClassificationState.h"
|
||||||
|
#include "XModel/TangentData.h"
|
||||||
#include "XModel/Tangentspace.h"
|
#include "XModel/Tangentspace.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace nlohmann;
|
namespace GAME
|
||||||
using namespace T6;
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
const char* HITLOC_NAMES[]{
|
class XModelLoader
|
||||||
// clang-format off
|
|
||||||
"none",
|
|
||||||
"helmet",
|
|
||||||
"head",
|
|
||||||
"neck",
|
|
||||||
"torso_upper",
|
|
||||||
"torso_middle",
|
|
||||||
"torso_lower",
|
|
||||||
"right_arm_upper",
|
|
||||||
"left_arm_upper",
|
|
||||||
"right_arm_lower",
|
|
||||||
"left_arm_lower",
|
|
||||||
"right_hand",
|
|
||||||
"left_hand",
|
|
||||||
"right_leg_upper",
|
|
||||||
"left_leg_upper",
|
|
||||||
"right_leg_lower",
|
|
||||||
"left_leg_lower",
|
|
||||||
"right_foot",
|
|
||||||
"left_foot",
|
|
||||||
"gun",
|
|
||||||
"shield",
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
static_assert(std::extent_v<decltype(HITLOC_NAMES)> == HITLOC_COUNT);
|
|
||||||
|
|
||||||
class PartClassificationState final : public IZoneAssetLoaderState
|
|
||||||
{
|
|
||||||
// TODO: Use MP part classifications when building an mp fastfile
|
|
||||||
static constexpr auto PART_CLASSIFICATION_FILE = "partclassification.csv";
|
|
||||||
|
|
||||||
public:
|
|
||||||
PartClassificationState()
|
|
||||||
: m_loaded(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Load(const IAssetLoadingManager& manager)
|
|
||||||
{
|
|
||||||
if (m_loaded)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (ObjLoading::Configuration.Verbose)
|
|
||||||
std::cout << "Loading part classification...\n";
|
|
||||||
|
|
||||||
const auto file = manager.GetAssetLoadingContext()->m_raw_search_path->Open(PART_CLASSIFICATION_FILE);
|
|
||||||
if (!file.IsOpen())
|
|
||||||
{
|
|
||||||
std::cerr << std::format("Could not load part classification: Failed to open {}\n", PART_CLASSIFICATION_FILE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CsvInputStream csvStream(*file.m_stream);
|
|
||||||
std::vector<std::string> row;
|
|
||||||
auto rowIndex = 0u;
|
|
||||||
while (csvStream.NextRow(row))
|
|
||||||
{
|
|
||||||
if (!LoadRow(rowIndex++, row))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_loaded = true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] unsigned GetPartClassificationForBoneName(const std::string& boneName) const
|
|
||||||
{
|
|
||||||
const auto entry = m_part_classifications.find(boneName);
|
|
||||||
|
|
||||||
return entry != m_part_classifications.end() ? entry->second : HITLOC_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool LoadRow(const unsigned rowIndex, std::vector<std::string>& row)
|
|
||||||
{
|
|
||||||
if (row.empty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (row.size() != 2)
|
|
||||||
{
|
|
||||||
std::cerr << "Could not load part classification: Invalid row\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::MakeStringLowerCase(row[0]);
|
|
||||||
utils::MakeStringLowerCase(row[1]);
|
|
||||||
|
|
||||||
const auto foundHitLoc = std::ranges::find(HITLOC_NAMES, row[1]);
|
|
||||||
if (foundHitLoc == std::end(HITLOC_NAMES))
|
|
||||||
{
|
|
||||||
std::cerr << std::format("Invalid hitloc name in row {}: {}\n", rowIndex + 1, row[1]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto hitLocNum = std::distance(std::begin(HITLOC_NAMES), foundHitLoc);
|
|
||||||
|
|
||||||
m_part_classifications.emplace(row[0], hitLocNum);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool m_loaded;
|
|
||||||
std::unordered_map<std::string, unsigned> m_part_classifications;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TangentData
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void CreateTangentData(const XModelCommon& common)
|
XModelLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set<XAssetInfoGeneric*>& dependencies)
|
||||||
{
|
|
||||||
if (common.m_vertices.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto vertexCount = common.m_vertices.size();
|
|
||||||
m_tangents.resize(vertexCount);
|
|
||||||
m_binormals.resize(vertexCount);
|
|
||||||
|
|
||||||
auto triCount = 0u;
|
|
||||||
for (const auto& object : common.m_objects)
|
|
||||||
triCount += object.m_faces.size();
|
|
||||||
|
|
||||||
std::vector<uint16_t> indices(triCount * 3u);
|
|
||||||
auto triOffset = 0u;
|
|
||||||
for (const auto& object : common.m_objects)
|
|
||||||
{
|
|
||||||
for (const auto& face : object.m_faces)
|
|
||||||
{
|
|
||||||
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[0]);
|
|
||||||
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[1]);
|
|
||||||
indices[triOffset++] = static_cast<uint16_t>(face.vertexIndex[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& firstVertex = common.m_vertices[0];
|
|
||||||
|
|
||||||
tangent_space::VertexData vertexData{
|
|
||||||
firstVertex.coordinates,
|
|
||||||
sizeof(XModelVertex),
|
|
||||||
firstVertex.normal,
|
|
||||||
sizeof(XModelVertex),
|
|
||||||
firstVertex.uv,
|
|
||||||
sizeof(XModelVertex),
|
|
||||||
m_tangents.data(),
|
|
||||||
sizeof(float) * 3,
|
|
||||||
m_binormals.data(),
|
|
||||||
sizeof(float) * 3,
|
|
||||||
indices.data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
tangent_space::CalculateTangentSpace(vertexData, triCount, vertexCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::array<float, 3>> m_tangents;
|
|
||||||
std::vector<std::array<float, 3>> m_binormals;
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonLoader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set<XAssetInfoGeneric*>& dependencies)
|
|
||||||
: m_stream(stream),
|
: m_stream(stream),
|
||||||
m_memory(memory),
|
m_memory(memory),
|
||||||
m_script_strings(manager.GetAssetLoadingContext()->m_zone->m_script_strings),
|
m_script_strings(manager.GetAssetLoadingContext()->m_zone->m_script_strings),
|
||||||
@ -205,7 +64,7 @@ namespace
|
|||||||
|
|
||||||
bool Load(XModel& xmodel)
|
bool Load(XModel& xmodel)
|
||||||
{
|
{
|
||||||
const auto jRoot = json::parse(m_stream);
|
const auto jRoot = nlohmann::json::parse(m_stream);
|
||||||
std::string type;
|
std::string type;
|
||||||
unsigned version;
|
unsigned version;
|
||||||
|
|
||||||
@ -223,7 +82,7 @@ namespace
|
|||||||
const auto jXModel = jRoot.get<JsonXModel>();
|
const auto jXModel = jRoot.get<JsonXModel>();
|
||||||
return CreateXModelFromJson(jXModel, xmodel);
|
return CreateXModelFromJson(jXModel, xmodel);
|
||||||
}
|
}
|
||||||
catch (const json::exception& e)
|
catch (const nlohmann::json::exception& e)
|
||||||
{
|
{
|
||||||
std::cerr << std::format("Failed to parse json of xmodel: {}\n", e.what());
|
std::cerr << std::format("Failed to parse json of xmodel: {}\n", e.what());
|
||||||
}
|
}
|
||||||
@ -316,15 +175,24 @@ namespace
|
|||||||
if (common.m_bone_weight_data.weights.empty())
|
if (common.m_bone_weight_data.weights.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
info.bounds[0].x = 0.0f;
|
#ifdef FEATURE_IW5
|
||||||
info.bounds[0].y = 0.0f;
|
vec3_t minCoordinate, maxCoordinate;
|
||||||
info.bounds[0].z = 0.0f;
|
auto& offset = info.bounds.midPoint;
|
||||||
info.bounds[1].x = 0.0f;
|
#else
|
||||||
info.bounds[1].y = 0.0f;
|
auto& offset = info.offset;
|
||||||
info.bounds[1].z = 0.0f;
|
auto& minCoordinate = info.bounds[0];
|
||||||
info.offset.x = 0.0f;
|
auto& maxCoordinate = info.bounds[1];
|
||||||
info.offset.y = 0.0f;
|
#endif
|
||||||
info.offset.z = 0.0f;
|
|
||||||
|
minCoordinate.x = 0.0f;
|
||||||
|
minCoordinate.y = 0.0f;
|
||||||
|
minCoordinate.z = 0.0f;
|
||||||
|
maxCoordinate.x = 0.0f;
|
||||||
|
maxCoordinate.y = 0.0f;
|
||||||
|
maxCoordinate.z = 0.0f;
|
||||||
|
offset.x = 0.0f;
|
||||||
|
offset.y = 0.0f;
|
||||||
|
offset.z = 0.0f;
|
||||||
info.radiusSquared = 0.0f;
|
info.radiusSquared = 0.0f;
|
||||||
|
|
||||||
const auto vertexCount = common.m_vertex_bone_weights.size();
|
const auto vertexCount = common.m_vertex_bone_weights.size();
|
||||||
@ -339,22 +207,31 @@ namespace
|
|||||||
if (weight.boneIndex != boneIndex)
|
if (weight.boneIndex != boneIndex)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
info.bounds[0].x = std::min(info.bounds[0].x, vertex.coordinates[0]);
|
minCoordinate.x = std::min(minCoordinate.x, vertex.coordinates[0]);
|
||||||
info.bounds[0].y = std::min(info.bounds[0].y, vertex.coordinates[1]);
|
minCoordinate.y = std::min(minCoordinate.y, vertex.coordinates[1]);
|
||||||
info.bounds[0].z = std::min(info.bounds[0].z, vertex.coordinates[2]);
|
minCoordinate.z = std::min(minCoordinate.z, vertex.coordinates[2]);
|
||||||
info.bounds[1].x = std::max(info.bounds[1].x, vertex.coordinates[0]);
|
maxCoordinate.x = std::max(maxCoordinate.x, vertex.coordinates[0]);
|
||||||
info.bounds[1].y = std::max(info.bounds[1].y, vertex.coordinates[1]);
|
maxCoordinate.y = std::max(maxCoordinate.y, vertex.coordinates[1]);
|
||||||
info.bounds[1].z = std::max(info.bounds[1].z, vertex.coordinates[2]);
|
maxCoordinate.z = std::max(maxCoordinate.z, vertex.coordinates[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Eigen::Vector3f minEigen(info.bounds[0].x, info.bounds[0].y, info.bounds[0].z);
|
const Eigen::Vector3f minEigen(minCoordinate.x, minCoordinate.y, minCoordinate.z);
|
||||||
const Eigen::Vector3f maxEigen(info.bounds[1].x, info.bounds[1].y, info.bounds[1].z);
|
const Eigen::Vector3f maxEigen(maxCoordinate.x, maxCoordinate.y, maxCoordinate.z);
|
||||||
const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f;
|
const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f;
|
||||||
info.offset.x = boundsCenter.x();
|
const Eigen::Vector3f halfSizeEigen = maxEigen - boundsCenter;
|
||||||
info.offset.y = boundsCenter.y();
|
#ifdef FEATURE_IW5
|
||||||
info.offset.z = boundsCenter.z();
|
|
||||||
info.radiusSquared = Eigen::Vector3f(maxEigen - boundsCenter).squaredNorm();
|
info.bounds.halfSize.x = halfSizeEigen.x();
|
||||||
|
info.bounds.halfSize.y = halfSizeEigen.y();
|
||||||
|
info.bounds.halfSize.z = halfSizeEigen.z();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
offset.x = boundsCenter.x();
|
||||||
|
offset.y = boundsCenter.y();
|
||||||
|
offset.z = boundsCenter.z();
|
||||||
|
|
||||||
|
info.radiusSquared = halfSizeEigen.squaredNorm();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApplyCommonBonesToXModel(const JsonXModelLod& jLod, XModel& xmodel, unsigned lodNumber, const XModelCommon& common) const
|
bool ApplyCommonBonesToXModel(const JsonXModelLod& jLod, XModel& xmodel, unsigned lodNumber, const XModelCommon& common) const
|
||||||
@ -362,7 +239,7 @@ namespace
|
|||||||
if (common.m_bones.empty())
|
if (common.m_bones.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
m_part_classification_state.Load(m_manager);
|
m_part_classification_state.Load(HITLOC_NAMES, std::extent_v<decltype(HITLOC_NAMES)>, m_manager);
|
||||||
|
|
||||||
const auto boneCount = common.m_bones.size();
|
const auto boneCount = common.m_bones.size();
|
||||||
constexpr auto maxBones = std::numeric_limits<decltype(XModel::numBones)>::max();
|
constexpr auto maxBones = std::numeric_limits<decltype(XModel::numBones)>::max();
|
||||||
@ -415,8 +292,10 @@ namespace
|
|||||||
ApplyBasePose(xmodel.baseMat[boneIndex], bone);
|
ApplyBasePose(xmodel.baseMat[boneIndex], bone);
|
||||||
CalculateBoneBounds(xmodel.boneInfo[boneIndex], boneIndex, common);
|
CalculateBoneBounds(xmodel.boneInfo[boneIndex], boneIndex, common);
|
||||||
|
|
||||||
|
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||||
// Other boneInfo data is filled when calculating bone bounds
|
// Other boneInfo data is filled when calculating bone bounds
|
||||||
xmodel.boneInfo[boneIndex].collmap = -1;
|
xmodel.boneInfo[boneIndex].collmap = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (xmodel.numRootBones <= boneIndex)
|
if (xmodel.numRootBones <= boneIndex)
|
||||||
{
|
{
|
||||||
@ -744,7 +623,7 @@ namespace
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto extension = fs::path(jLod.file).extension().string();
|
auto extension = std::filesystem::path(jLod.file).extension().string();
|
||||||
utils::MakeStringLowerCase(extension);
|
utils::MakeStringLowerCase(extension);
|
||||||
|
|
||||||
const auto common = LoadModelByExtension(*file.m_stream, extension);
|
const auto common = LoadModelByExtension(*file.m_stream, extension);
|
||||||
@ -821,17 +700,52 @@ namespace
|
|||||||
lodInfo.partBits[i] |= surface.partBits[i];
|
lodInfo.partBits[i] |= surface.partBits[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
auto* modelSurfs = m_memory.Alloc<XModelSurfs>();
|
||||||
|
const auto modelSurfsName = std::format("{}_lod{}", xmodel.name, lodNumber);
|
||||||
|
modelSurfs->name = m_memory.Dup(modelSurfsName.c_str());
|
||||||
|
|
||||||
|
static_assert(std::extent_v<decltype(XModelLodInfo::partBits)> == std::extent_v<decltype(XModelSurfs::partBits)>);
|
||||||
|
memcpy(modelSurfs->partBits, lodInfo.partBits, sizeof(XModelLodInfo::partBits));
|
||||||
|
|
||||||
|
modelSurfs->numsurfs = lodInfo.numsurfs;
|
||||||
|
modelSurfs->surfs = m_memory.Alloc<XSurface>(modelSurfs->numsurfs);
|
||||||
|
memcpy(modelSurfs->surfs, &m_surfaces[lodInfo.surfIndex], sizeof(XSurface) * modelSurfs->numsurfs);
|
||||||
|
|
||||||
|
m_manager.AddAsset<AssetXModelSurfs>(modelSurfsName, modelSurfs);
|
||||||
|
|
||||||
|
lodInfo.modelSurfs = modelSurfs;
|
||||||
|
lodInfo.surfs = modelSurfs->surfs;
|
||||||
|
|
||||||
|
lodInfo.lod = static_cast<decltype(XModelLodInfo::lod)>(lodNumber);
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CalculateModelBounds(XModel& xmodel)
|
static void CalculateModelBounds(XModel& xmodel)
|
||||||
{
|
{
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
if (!xmodel.lodInfo[0].modelSurfs || !xmodel.lodInfo[0].modelSurfs->surfs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto* surfs = xmodel.lodInfo[0].modelSurfs->surfs;
|
||||||
|
vec3_t minCoordinate, maxCoordinate;
|
||||||
|
#else
|
||||||
if (!xmodel.surfs)
|
if (!xmodel.surfs)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto& minCoordinate = xmodel.mins;
|
||||||
|
auto& maxCoordinate = xmodel.maxs;
|
||||||
|
#endif
|
||||||
|
|
||||||
for (auto surfaceIndex = 0u; surfaceIndex < xmodel.lodInfo[0].numsurfs; surfaceIndex++)
|
for (auto surfaceIndex = 0u; surfaceIndex < xmodel.lodInfo[0].numsurfs; surfaceIndex++)
|
||||||
{
|
{
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
const auto& surface = surfs[surfaceIndex];
|
||||||
|
#else
|
||||||
const auto& surface = xmodel.surfs[surfaceIndex + xmodel.lodInfo[0].surfIndex];
|
const auto& surface = xmodel.surfs[surfaceIndex + xmodel.lodInfo[0].surfIndex];
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!surface.verts0)
|
if (!surface.verts0)
|
||||||
continue;
|
continue;
|
||||||
@ -840,19 +754,35 @@ namespace
|
|||||||
{
|
{
|
||||||
const auto& vertex = surface.verts0[vertIndex];
|
const auto& vertex = surface.verts0[vertIndex];
|
||||||
|
|
||||||
xmodel.mins.x = std::min(xmodel.mins.x, vertex.xyz.v[0]);
|
minCoordinate.x = std::min(minCoordinate.x, vertex.xyz.v[0]);
|
||||||
xmodel.mins.y = std::min(xmodel.mins.y, vertex.xyz.v[1]);
|
minCoordinate.y = std::min(minCoordinate.y, vertex.xyz.v[1]);
|
||||||
xmodel.mins.z = std::min(xmodel.mins.z, vertex.xyz.v[2]);
|
minCoordinate.z = std::min(minCoordinate.z, vertex.xyz.v[2]);
|
||||||
xmodel.maxs.x = std::max(xmodel.maxs.x, vertex.xyz.v[0]);
|
maxCoordinate.x = std::max(maxCoordinate.x, vertex.xyz.v[0]);
|
||||||
xmodel.maxs.y = std::max(xmodel.maxs.y, vertex.xyz.v[1]);
|
maxCoordinate.y = std::max(maxCoordinate.y, vertex.xyz.v[1]);
|
||||||
xmodel.maxs.z = std::max(xmodel.maxs.z, vertex.xyz.v[2]);
|
maxCoordinate.z = std::max(maxCoordinate.z, vertex.xyz.v[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto maxX = std::max(std::abs(xmodel.mins.x), std::abs(xmodel.maxs.x));
|
#ifdef FEATURE_IW5
|
||||||
const auto maxY = std::max(std::abs(xmodel.mins.y), std::abs(xmodel.maxs.y));
|
const Eigen::Vector3f minEigen(minCoordinate.x, minCoordinate.y, minCoordinate.z);
|
||||||
const auto maxZ = std::max(std::abs(xmodel.mins.z), std::abs(xmodel.maxs.z));
|
const Eigen::Vector3f maxEigen(maxCoordinate.x, maxCoordinate.y, maxCoordinate.z);
|
||||||
|
const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f;
|
||||||
|
const Eigen::Vector3f halfSizeEigen = maxEigen - boundsCenter;
|
||||||
|
|
||||||
|
xmodel.bounds.halfSize.x = halfSizeEigen.x();
|
||||||
|
xmodel.bounds.halfSize.y = halfSizeEigen.y();
|
||||||
|
xmodel.bounds.halfSize.z = halfSizeEigen.z();
|
||||||
|
|
||||||
|
xmodel.bounds.midPoint.x = boundsCenter.x();
|
||||||
|
xmodel.bounds.midPoint.y = boundsCenter.y();
|
||||||
|
xmodel.bounds.midPoint.z = boundsCenter.z();
|
||||||
|
xmodel.radius = halfSizeEigen.norm();
|
||||||
|
#else
|
||||||
|
const auto maxX = std::max(std::abs(minCoordinate.x), std::abs(maxCoordinate.x));
|
||||||
|
const auto maxY = std::max(std::abs(minCoordinate.y), std::abs(maxCoordinate.y));
|
||||||
|
const auto maxZ = std::max(std::abs(minCoordinate.z), std::abs(maxCoordinate.z));
|
||||||
xmodel.radius = Eigen::Vector3f(maxX, maxY, maxZ).norm();
|
xmodel.radius = Eigen::Vector3f(maxX, maxY, maxZ).norm();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel)
|
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel)
|
||||||
@ -865,7 +795,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lodNumber = 0u;
|
auto lodNumber = 0u;
|
||||||
xmodel.numLods = static_cast<uint16_t>(jXModel.lods.size());
|
xmodel.numLods = static_cast<decltype(XModel::numLods)>(jXModel.lods.size());
|
||||||
for (const auto& jLod : jXModel.lods)
|
for (const auto& jLod : jXModel.lods)
|
||||||
{
|
{
|
||||||
if (!LoadLod(jLod, xmodel, lodNumber++))
|
if (!LoadLod(jLod, xmodel, lodNumber++))
|
||||||
@ -879,10 +809,14 @@ namespace
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
xmodel.numsurfs = static_cast<unsigned char>(m_surfaces.size());
|
xmodel.numsurfs = static_cast<decltype(XModel::numsurfs)>(m_surfaces.size());
|
||||||
|
|
||||||
|
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||||
xmodel.surfs = m_memory.Alloc<XSurface>(xmodel.numsurfs);
|
xmodel.surfs = m_memory.Alloc<XSurface>(xmodel.numsurfs);
|
||||||
xmodel.materialHandles = m_memory.Alloc<Material*>(xmodel.numsurfs);
|
|
||||||
memcpy(xmodel.surfs, m_surfaces.data(), sizeof(XSurface) * xmodel.numsurfs);
|
memcpy(xmodel.surfs, m_surfaces.data(), sizeof(XSurface) * xmodel.numsurfs);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xmodel.materialHandles = m_memory.Alloc<Material*>(xmodel.numsurfs);
|
||||||
memcpy(xmodel.materialHandles, m_materials.data(), sizeof(Material*) * xmodel.numsurfs);
|
memcpy(xmodel.materialHandles, m_materials.data(), sizeof(Material*) * xmodel.numsurfs);
|
||||||
|
|
||||||
CalculateModelBounds(xmodel);
|
CalculateModelBounds(xmodel);
|
||||||
@ -894,7 +828,7 @@ namespace
|
|||||||
PrintError(xmodel, "Collision lod is not a valid lod");
|
PrintError(xmodel, "Collision lod is not a valid lod");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
xmodel.collLod = static_cast<int16_t>(jXModel.collLod.value());
|
xmodel.collLod = static_cast<decltype(XModel::collLod)>(jXModel.collLod.value());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
xmodel.collLod = -1;
|
xmodel.collLod = -1;
|
||||||
@ -915,6 +849,25 @@ namespace
|
|||||||
xmodel.physPreset = nullptr;
|
xmodel.physPreset = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(FEATURE_IW5)
|
||||||
|
if (jXModel.physCollmap)
|
||||||
|
{
|
||||||
|
auto* physCollmap = m_manager.LoadDependency<AssetPhysCollMap>(jXModel.physCollmap.value());
|
||||||
|
if (!physCollmap)
|
||||||
|
{
|
||||||
|
PrintError(xmodel, "Could not find phys collmap");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dependencies.emplace(physCollmap);
|
||||||
|
xmodel.physCollmap = physCollmap->Asset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xmodel.physCollmap = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||||
if (jXModel.physConstraints)
|
if (jXModel.physConstraints)
|
||||||
{
|
{
|
||||||
auto* physConstraints = m_manager.LoadDependency<AssetPhysConstraints>(jXModel.physConstraints.value());
|
auto* physConstraints = m_manager.LoadDependency<AssetPhysConstraints>(jXModel.physConstraints.value());
|
||||||
@ -930,12 +883,16 @@ namespace
|
|||||||
{
|
{
|
||||||
xmodel.physConstraints = nullptr;
|
xmodel.physConstraints = nullptr;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
xmodel.flags = jXModel.flags;
|
xmodel.flags = jXModel.flags;
|
||||||
|
|
||||||
|
#ifdef FEATURE_T6
|
||||||
xmodel.lightingOriginOffset.x = jXModel.lightingOriginOffset.x;
|
xmodel.lightingOriginOffset.x = jXModel.lightingOriginOffset.x;
|
||||||
xmodel.lightingOriginOffset.y = jXModel.lightingOriginOffset.y;
|
xmodel.lightingOriginOffset.y = jXModel.lightingOriginOffset.y;
|
||||||
xmodel.lightingOriginOffset.z = jXModel.lightingOriginOffset.z;
|
xmodel.lightingOriginOffset.z = jXModel.lightingOriginOffset.z;
|
||||||
xmodel.lightingOriginRange = jXModel.lightingOriginRange;
|
xmodel.lightingOriginRange = jXModel.lightingOriginRange;
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -950,18 +907,14 @@ namespace
|
|||||||
PartClassificationState& m_part_classification_state;
|
PartClassificationState& m_part_classification_state;
|
||||||
std::set<XAssetInfoGeneric*>& m_dependencies;
|
std::set<XAssetInfoGeneric*>& m_dependencies;
|
||||||
};
|
};
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace T6
|
bool LoadXModel(std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
|
||||||
{
|
|
||||||
bool LoadXModelAsJson(
|
|
||||||
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
|
|
||||||
{
|
{
|
||||||
std::set<XAssetInfoGeneric*> dependenciesSet;
|
std::set<XAssetInfoGeneric*> dependenciesSet;
|
||||||
JsonLoader loader(stream, *memory, *manager, dependenciesSet);
|
XModelLoader loader(stream, *memory, *manager, dependenciesSet);
|
||||||
|
|
||||||
dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend());
|
dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend());
|
||||||
|
|
||||||
return loader.Load(xmodel);
|
return loader.Load(xmodel);
|
||||||
}
|
}
|
||||||
} // namespace T6
|
} // namespace GAME
|
@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "XModel/XModelCommon.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class XModelLoader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
XModelLoader() = default;
|
|
||||||
virtual ~XModelLoader() = default;
|
|
||||||
XModelLoader(const XModelLoader& other) = default;
|
|
||||||
XModelLoader(XModelLoader&& other) noexcept = default;
|
|
||||||
XModelLoader& operator=(const XModelLoader& other) = default;
|
|
||||||
XModelLoader& operator=(XModelLoader&& other) noexcept = default;
|
|
||||||
|
|
||||||
virtual std::unique_ptr<XModelCommon> Load() = 0;
|
|
||||||
};
|
|
19
src/ObjLoading/XModel/XModelLoader.h.template
Normal file
19
src/ObjLoading/XModel/XModelLoader.h.template
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#options GAME (IW5, T5, T6)
|
||||||
|
|
||||||
|
#filename "Game/" + GAME + "/XModel/XModelLoader" + GAME + ".h"
|
||||||
|
|
||||||
|
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AssetLoading/IAssetLoadingManager.h"
|
||||||
|
#include GAME_HEADER
|
||||||
|
#include "Utils/MemoryManager.h"
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace GAME
|
||||||
|
{
|
||||||
|
bool LoadXModel(std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies);
|
||||||
|
}
|
@ -51,6 +51,8 @@ function ObjWriting:project()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useSourceTemplating("ObjWriting")
|
||||||
|
|
||||||
self:include(includes)
|
self:include(includes)
|
||||||
Utils:include(includes)
|
Utils:include(includes)
|
||||||
minilzo:include(includes)
|
minilzo:include(includes)
|
||||||
|
48
src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.cpp
Normal file
48
src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "AssetDumperMaterial.h"
|
||||||
|
|
||||||
|
#include "Game/IW5/Material/JsonMaterialWriter.h"
|
||||||
|
#include "Game/IW5/Material/MaterialConstantZoneState.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <format>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
using namespace IW5;
|
||||||
|
|
||||||
|
std::string AssetDumperMaterial::GetFileNameForAsset(const std::string& assetName)
|
||||||
|
{
|
||||||
|
std::string sanitizedFileName(assetName);
|
||||||
|
if (sanitizedFileName[0] == '*')
|
||||||
|
{
|
||||||
|
std::ranges::replace(sanitizedFileName, '*', '_');
|
||||||
|
const auto parenthesisPos = sanitizedFileName.find('(');
|
||||||
|
if (parenthesisPos != std::string::npos)
|
||||||
|
sanitizedFileName.erase(parenthesisPos);
|
||||||
|
sanitizedFileName = "generated/" + sanitizedFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::format("materials/{}.json", sanitizedFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetDumperMaterial::ShouldDump(XAssetInfo<Material>* asset)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetDumperMaterial::DumpAsset(AssetDumpingContext& context, XAssetInfo<Material>* asset)
|
||||||
|
{
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForAsset(asset->m_name));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DumpMaterialAsJson(*assetFile, asset->Asset(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetDumperMaterial::DumpPool(AssetDumpingContext& context, AssetPool<Material>* pool)
|
||||||
|
{
|
||||||
|
auto* materialConstantState = context.GetZoneAssetDumperState<MaterialConstantZoneState>();
|
||||||
|
materialConstantState->ExtractNamesFromZone();
|
||||||
|
|
||||||
|
AbstractAssetDumper::DumpPool(context, pool);
|
||||||
|
}
|
21
src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.h
Normal file
21
src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Dumping/AbstractAssetDumper.h"
|
||||||
|
#include "Game/IW5/IW5.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
class AssetDumperMaterial final : public AbstractAssetDumper<Material>
|
||||||
|
{
|
||||||
|
static std::string GetFileNameForAsset(const std::string& assetName);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool ShouldDump(XAssetInfo<Material>* asset) override;
|
||||||
|
void DumpAsset(AssetDumpingContext& context, XAssetInfo<Material>* asset) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void DumpPool(AssetDumpingContext& context, AssetPool<Material>* pool) override;
|
||||||
|
};
|
||||||
|
} // namespace IW5
|
@ -545,8 +545,8 @@ namespace IW5
|
|||||||
for (auto i = 0u; i < originalKnotCount; i++)
|
for (auto i = 0u; i < originalKnotCount; i++)
|
||||||
{
|
{
|
||||||
auto& knot = graph.knots[i];
|
auto& knot = graph.knots[i];
|
||||||
knot.x = originalKnots[i][0];
|
knot.x = originalKnots[i].x;
|
||||||
knot.y = originalKnots[i][1];
|
knot.y = originalKnots[i].y;
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph;
|
return graph;
|
||||||
|
@ -1,506 +1,9 @@
|
|||||||
#include "AssetDumperXModel.h"
|
#include "AssetDumperXModel.h"
|
||||||
|
|
||||||
#include "Game/IW5/CommonIW5.h"
|
#include "Game/IW5/XModel/XModelDumperIW5.h"
|
||||||
#include "ObjWriting.h"
|
|
||||||
#include "Utils/DistinctMapper.h"
|
|
||||||
#include "Utils/HalfFloat.h"
|
|
||||||
#include "Utils/QuatInt16.h"
|
|
||||||
#include "XModel/Export/XModelExportWriter.h"
|
|
||||||
#include "XModel/Gltf/GltfBinOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfTextOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfWriter.h"
|
|
||||||
#include "XModel/Obj/ObjWriter.h"
|
|
||||||
#include "XModel/XModelWriter.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
using namespace IW5;
|
using namespace IW5;
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
GfxImage* GetMaterialColorMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_COLOR_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 'c' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialNormalMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_NORMAL_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 'n' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialSpecularMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_SPECULAR_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 's' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
|
|
||||||
{
|
|
||||||
XModelBone bone;
|
|
||||||
if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count())
|
|
||||||
bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]];
|
|
||||||
else
|
|
||||||
bone.name = "INVALID_BONE_NAME";
|
|
||||||
|
|
||||||
if (boneNum >= model->numRootBones)
|
|
||||||
bone.parentIndex = boneNum - static_cast<unsigned int>(model->parentList[boneNum - model->numRootBones]);
|
|
||||||
else
|
|
||||||
bone.parentIndex = std::nullopt;
|
|
||||||
|
|
||||||
bone.scale[0] = 1.0f;
|
|
||||||
bone.scale[1] = 1.0f;
|
|
||||||
bone.scale[2] = 1.0f;
|
|
||||||
|
|
||||||
bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
|
|
||||||
bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
|
|
||||||
bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
|
|
||||||
bone.globalRotation = {
|
|
||||||
model->baseMat[boneNum].quat[0],
|
|
||||||
model->baseMat[boneNum].quat[1],
|
|
||||||
model->baseMat[boneNum].quat[2],
|
|
||||||
model->baseMat[boneNum].quat[3],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (boneNum < model->numRootBones)
|
|
||||||
{
|
|
||||||
bone.localOffset[0] = 0;
|
|
||||||
bone.localOffset[1] = 0;
|
|
||||||
bone.localOffset[2] = 0;
|
|
||||||
bone.localRotation = {0, 0, 0, 1};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0];
|
|
||||||
bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1];
|
|
||||||
bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2];
|
|
||||||
bone.localRotation = {
|
|
||||||
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]),
|
|
||||||
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]),
|
|
||||||
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]),
|
|
||||||
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
out.m_bones.emplace_back(std::move(bone));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* AssetName(const char* input)
|
|
||||||
{
|
|
||||||
if (input && input[0] == ',')
|
|
||||||
return &input[1];
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
|
|
||||||
{
|
|
||||||
Material* material = model->materialHandles[surfaceMaterialNum];
|
|
||||||
if (materialMapper.Add(material))
|
|
||||||
{
|
|
||||||
XModelMaterial xMaterial;
|
|
||||||
xMaterial.ApplyDefaults();
|
|
||||||
|
|
||||||
xMaterial.name = AssetName(material->info.name);
|
|
||||||
const auto* colorMap = GetMaterialColorMap(material);
|
|
||||||
if (colorMap)
|
|
||||||
xMaterial.colorMapName = AssetName(colorMap->name);
|
|
||||||
|
|
||||||
const auto* normalMap = GetMaterialNormalMap(material);
|
|
||||||
if (normalMap)
|
|
||||||
xMaterial.normalMapName = AssetName(normalMap->name);
|
|
||||||
|
|
||||||
const auto* specularMap = GetMaterialSpecularMap(material);
|
|
||||||
if (specularMap)
|
|
||||||
xMaterial.specularMapName = AssetName(specularMap->name);
|
|
||||||
|
|
||||||
out.m_materials.emplace_back(std::move(xMaterial));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelObjects(XModelCommon& out, const XModelSurfs* modelSurfs, const DistinctMapper<Material*>& materialMapper, const int baseSurfaceIndex)
|
|
||||||
{
|
|
||||||
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
|
|
||||||
{
|
|
||||||
XModelObject object;
|
|
||||||
object.name = std::format("surf{}", surfIndex);
|
|
||||||
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
|
|
||||||
|
|
||||||
out.m_objects.emplace_back(std::move(object));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertices(XModelCommon& out, const XModelSurfs* modelSurfs)
|
|
||||||
{
|
|
||||||
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = modelSurfs->surfs[surfIndex];
|
|
||||||
|
|
||||||
for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++)
|
|
||||||
{
|
|
||||||
const auto& v = surface.verts0.packedVerts0[vertexIndex];
|
|
||||||
|
|
||||||
XModelVertex vertex{};
|
|
||||||
vertex.coordinates[0] = v.xyz[0];
|
|
||||||
vertex.coordinates[1] = v.xyz[1];
|
|
||||||
vertex.coordinates[2] = v.xyz[2];
|
|
||||||
Common::Vec3UnpackUnitVec(v.normal, vertex.normal);
|
|
||||||
Common::Vec4UnpackGfxColor(v.color, vertex.color);
|
|
||||||
Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv);
|
|
||||||
|
|
||||||
out.m_vertices.emplace_back(vertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection)
|
|
||||||
{
|
|
||||||
auto totalWeightCount = 0u;
|
|
||||||
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = modelSurfs->surfs[surfIndex];
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertListCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[0] * 1;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[1] * 2;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[2] * 3;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[3] * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
weightCollection.weights.resize(totalWeightCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
float BoneWeight16(const uint16_t value)
|
|
||||||
{
|
|
||||||
return static_cast<float>(value) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertexBoneWeights(XModelCommon& out, const XModelSurfs* modelSurfs)
|
|
||||||
{
|
|
||||||
auto& weightCollection = out.m_bone_weight_data;
|
|
||||||
size_t weightOffset = 0u;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = modelSurfs->surfs[surfIndex];
|
|
||||||
auto handledVertices = 0u;
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
|
|
||||||
{
|
|
||||||
const auto& vertList = surface.vertList[vertListIndex];
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{vertList.boneOffset / sizeof(DObjSkelMat), 1.0f};
|
|
||||||
|
|
||||||
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
handledVertices += vertList.vertCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto vertsBlendOffset = 0u;
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
// 1 bone weight
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f};
|
|
||||||
|
|
||||||
vertsBlendOffset += 1;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
|
|
||||||
vertsBlendOffset += 3;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
|
|
||||||
vertsBlendOffset += 5;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight3 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3};
|
|
||||||
|
|
||||||
vertsBlendOffset += 7;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
handledVertices +=
|
|
||||||
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; handledVertices < surface.vertCount; handledVertices++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelFaces(XModelCommon& out, const XModelSurfs* modelSurfs)
|
|
||||||
{
|
|
||||||
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = modelSurfs->surfs[surfIndex];
|
|
||||||
auto& object = out.m_objects[surfIndex];
|
|
||||||
object.m_faces.reserve(surface.triCount);
|
|
||||||
|
|
||||||
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
|
|
||||||
{
|
|
||||||
const auto& tri = surface.triIndices[triIndex];
|
|
||||||
|
|
||||||
XModelFace face{};
|
|
||||||
face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
|
|
||||||
object.m_faces.emplace_back(face);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
|
|
||||||
{
|
|
||||||
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
|
|
||||||
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
AllocateXModelBoneWeights(modelSurfs, out.m_bone_weight_data);
|
|
||||||
|
|
||||||
out.m_name = modelSurfs->name;
|
|
||||||
AddXModelBones(out, context, model);
|
|
||||||
AddXModelMaterials(out, materialMapper, model);
|
|
||||||
AddXModelObjects(out, modelSurfs, materialMapper, model->lodInfo[lod].surfIndex);
|
|
||||||
AddXModelVertices(out, modelSurfs);
|
|
||||||
AddXModelVertexBoneWeights(out, modelSurfs);
|
|
||||||
AddXModelFaces(out, modelSurfs);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
|
|
||||||
|
|
||||||
if (!mtlFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
|
|
||||||
|
|
||||||
if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.obj", modelSurfs->name));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer =
|
|
||||||
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
|
|
||||||
const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.XMODEL_EXPORT", modelSurfs->name));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void DumpGltfLod(
|
|
||||||
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod, const std::string& extension)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
|
|
||||||
const auto assetFile = context.OpenAssetFile(std::format("model_export/{}{}", modelSurfs->name, extension));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto output = std::make_unique<T>(*assetFile);
|
|
||||||
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
|
|
||||||
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
|
|
||||||
{
|
|
||||||
XModelCommon common;
|
|
||||||
PopulateXModelWriter(common, context, currentLod, asset->Asset());
|
|
||||||
|
|
||||||
switch (ObjWriting::Configuration.ModelOutputFormat)
|
|
||||||
{
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
|
||||||
DumpObjLod(common, context, asset, currentLod);
|
|
||||||
if (currentLod == 0u)
|
|
||||||
DumpObjMtl(common, context, asset);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
|
||||||
DumpXModelExportLod(common, context, asset, currentLod);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
|
||||||
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
|
||||||
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
||||||
@ -508,5 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
|||||||
|
|
||||||
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
DumpXModelSurfs(context, asset);
|
DumpXModel(context, asset);
|
||||||
}
|
}
|
||||||
|
293
src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.cpp
Normal file
293
src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.cpp
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
#include "JsonMaterialWriter.h"
|
||||||
|
|
||||||
|
#include "Game/IW5/CommonIW5.h"
|
||||||
|
#include "Game/IW5/Material/JsonMaterial.h"
|
||||||
|
#include "Impl/Base64.h"
|
||||||
|
#include "MaterialConstantZoneState.h"
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using namespace nlohmann;
|
||||||
|
using namespace IW5;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class JsonDumper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JsonDumper(AssetDumpingContext& context, std::ostream& stream)
|
||||||
|
: m_stream(stream),
|
||||||
|
m_material_constants(*context.GetZoneAssetDumperState<MaterialConstantZoneState>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dump(const Material* material) const
|
||||||
|
{
|
||||||
|
JsonMaterial jsonMaterial;
|
||||||
|
CreateJsonMaterial(jsonMaterial, *material);
|
||||||
|
json jRoot = jsonMaterial;
|
||||||
|
|
||||||
|
jRoot["_type"] = "material";
|
||||||
|
jRoot["_game"] = "iw5";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonGameFlags(JsonMaterial& jMaterial, const unsigned gameFlags)
|
||||||
|
{
|
||||||
|
jMaterial.gameFlags.clear();
|
||||||
|
for (auto i = 0u; i < sizeof(gameFlags) * 8u; i++)
|
||||||
|
{
|
||||||
|
const auto flag = static_cast<MaterialGameFlags>(1 << i);
|
||||||
|
|
||||||
|
if (gameFlags & flag)
|
||||||
|
jMaterial.gameFlags.emplace_back(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonSamplerState(JsonSamplerState& jSamplerState, const MaterialTextureDefSamplerState& samplerState)
|
||||||
|
{
|
||||||
|
jSamplerState.filter = static_cast<TextureFilter>(samplerState.filter);
|
||||||
|
jSamplerState.mipMap = static_cast<SamplerStateBitsMipMap_e>(samplerState.mipMap);
|
||||||
|
jSamplerState.clampU = samplerState.clampU;
|
||||||
|
jSamplerState.clampV = samplerState.clampV;
|
||||||
|
jSamplerState.clampW = samplerState.clampW;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonWater(JsonWater& jWater, const water_t& water)
|
||||||
|
{
|
||||||
|
jWater.floatTime = water.writable.floatTime;
|
||||||
|
jWater.m = water.M;
|
||||||
|
jWater.n = water.N;
|
||||||
|
jWater.lx = water.Lx;
|
||||||
|
jWater.lz = water.Lz;
|
||||||
|
jWater.gravity = water.gravity;
|
||||||
|
jWater.windvel = water.windvel;
|
||||||
|
jWater.winddir[0] = water.winddir[0];
|
||||||
|
jWater.winddir[1] = water.winddir[1];
|
||||||
|
jWater.amplitude = water.amplitude;
|
||||||
|
jWater.codeConstant[0] = water.codeConstant[0];
|
||||||
|
jWater.codeConstant[1] = water.codeConstant[1];
|
||||||
|
jWater.codeConstant[2] = water.codeConstant[2];
|
||||||
|
jWater.codeConstant[3] = water.codeConstant[3];
|
||||||
|
|
||||||
|
if (water.H0)
|
||||||
|
{
|
||||||
|
const auto count = water.M * water.N;
|
||||||
|
jWater.h0 = base64::EncodeBase64(water.H0, sizeof(complex_s) * count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (water.wTerm)
|
||||||
|
{
|
||||||
|
const auto count = water.M * water.N;
|
||||||
|
jWater.wTerm = base64::EncodeBase64(water.wTerm, sizeof(float) * count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateJsonTexture(JsonTexture& jTextureDef, const MaterialTextureDef& textureDef) const
|
||||||
|
{
|
||||||
|
std::string textureDefName;
|
||||||
|
if (m_material_constants.GetTextureDefName(textureDef.nameHash, textureDefName))
|
||||||
|
{
|
||||||
|
jTextureDef.name = textureDefName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jTextureDef.nameHash = textureDef.nameHash;
|
||||||
|
jTextureDef.nameStart = std::string(1u, textureDef.nameStart);
|
||||||
|
jTextureDef.nameEnd = std::string(1u, textureDef.nameEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
jTextureDef.semantic = static_cast<TextureSemantic>(textureDef.semantic);
|
||||||
|
|
||||||
|
CreateJsonSamplerState(jTextureDef.samplerState, textureDef.samplerState);
|
||||||
|
|
||||||
|
if (textureDef.semantic == TS_WATER_MAP)
|
||||||
|
{
|
||||||
|
if (textureDef.u.water)
|
||||||
|
{
|
||||||
|
const auto& water = *textureDef.u.water;
|
||||||
|
if (water.image && water.image->name)
|
||||||
|
jTextureDef.image = AssetName(water.image->name);
|
||||||
|
|
||||||
|
JsonWater jWater;
|
||||||
|
CreateJsonWater(jWater, water);
|
||||||
|
|
||||||
|
jTextureDef.water = std::move(jWater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (textureDef.u.image && textureDef.u.image->name)
|
||||||
|
jTextureDef.image = AssetName(textureDef.u.image->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateJsonConstant(JsonConstant& jConstantDef, const MaterialConstantDef& constantDef) const
|
||||||
|
{
|
||||||
|
const auto fragmentLength = strnlen(constantDef.name, std::extent_v<decltype(MaterialConstantDef::name)>);
|
||||||
|
const std::string nameFragment(constantDef.name, fragmentLength);
|
||||||
|
std::string knownConstantName;
|
||||||
|
|
||||||
|
if (fragmentLength < std::extent_v<decltype(MaterialConstantDef::name)> || Common::R_HashString(nameFragment.c_str(), 0) == constantDef.nameHash)
|
||||||
|
{
|
||||||
|
jConstantDef.name = nameFragment;
|
||||||
|
}
|
||||||
|
else if (m_material_constants.GetConstantName(constantDef.nameHash, knownConstantName))
|
||||||
|
{
|
||||||
|
jConstantDef.name = knownConstantName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jConstantDef.nameHash = constantDef.nameHash;
|
||||||
|
jConstantDef.nameFragment = nameFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
jConstantDef.literal = std::vector({
|
||||||
|
constantDef.literal.x,
|
||||||
|
constantDef.literal.y,
|
||||||
|
constantDef.literal.z,
|
||||||
|
constantDef.literal.w,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonStencil(JsonStencil& jStencil, const unsigned pass, const unsigned fail, const unsigned zFail, const unsigned func)
|
||||||
|
{
|
||||||
|
jStencil.pass = static_cast<GfxStencilOp>(pass);
|
||||||
|
jStencil.fail = static_cast<GfxStencilOp>(fail);
|
||||||
|
jStencil.zfail = static_cast<GfxStencilOp>(zFail);
|
||||||
|
jStencil.func = static_cast<GfxStencilFunc>(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonStateBitsTableEntry(JsonStateBitsTableEntry& jStateBitsTableEntry, const GfxStateBits& stateBitsTableEntry)
|
||||||
|
{
|
||||||
|
const auto& structured = stateBitsTableEntry.loadBits.structured;
|
||||||
|
|
||||||
|
jStateBitsTableEntry.srcBlendRgb = static_cast<GfxBlend>(structured.srcBlendRgb);
|
||||||
|
jStateBitsTableEntry.dstBlendRgb = static_cast<GfxBlend>(structured.dstBlendRgb);
|
||||||
|
jStateBitsTableEntry.blendOpRgb = static_cast<GfxBlendOp>(structured.blendOpRgb);
|
||||||
|
|
||||||
|
assert(structured.alphaTestDisabled || structured.alphaTest == GFXS_ALPHA_TEST_GT_0 || structured.alphaTest == GFXS_ALPHA_TEST_LT_128
|
||||||
|
|| structured.alphaTest == GFXS_ALPHA_TEST_GE_128);
|
||||||
|
if (structured.alphaTestDisabled)
|
||||||
|
jStateBitsTableEntry.alphaTest = JsonAlphaTest::DISABLED;
|
||||||
|
else if (structured.alphaTest == GFXS_ALPHA_TEST_GT_0)
|
||||||
|
jStateBitsTableEntry.alphaTest = JsonAlphaTest::GT0;
|
||||||
|
else if (structured.alphaTest == GFXS_ALPHA_TEST_LT_128)
|
||||||
|
jStateBitsTableEntry.alphaTest = JsonAlphaTest::LT128;
|
||||||
|
else if (structured.alphaTest == GFXS_ALPHA_TEST_GE_128)
|
||||||
|
jStateBitsTableEntry.alphaTest = JsonAlphaTest::GE128;
|
||||||
|
else
|
||||||
|
jStateBitsTableEntry.alphaTest = JsonAlphaTest::INVALID;
|
||||||
|
|
||||||
|
assert(structured.cullFace == GFXS_CULL_NONE || structured.cullFace == GFXS_CULL_BACK || structured.cullFace == GFXS_CULL_FRONT);
|
||||||
|
if (structured.cullFace == GFXS_CULL_NONE)
|
||||||
|
jStateBitsTableEntry.cullFace = JsonCullFace::NONE;
|
||||||
|
else if (structured.cullFace == GFXS_CULL_BACK)
|
||||||
|
jStateBitsTableEntry.cullFace = JsonCullFace::BACK;
|
||||||
|
else if (structured.cullFace == GFXS_CULL_FRONT)
|
||||||
|
jStateBitsTableEntry.cullFace = JsonCullFace::FRONT;
|
||||||
|
else
|
||||||
|
jStateBitsTableEntry.cullFace = JsonCullFace::INVALID;
|
||||||
|
|
||||||
|
jStateBitsTableEntry.srcBlendAlpha = static_cast<GfxBlend>(structured.srcBlendAlpha);
|
||||||
|
jStateBitsTableEntry.dstBlendAlpha = static_cast<GfxBlend>(structured.dstBlendAlpha);
|
||||||
|
jStateBitsTableEntry.blendOpAlpha = static_cast<GfxBlendOp>(structured.blendOpAlpha);
|
||||||
|
jStateBitsTableEntry.colorWriteRgb = structured.colorWriteRgb;
|
||||||
|
jStateBitsTableEntry.colorWriteAlpha = structured.colorWriteAlpha;
|
||||||
|
jStateBitsTableEntry.gammaWrite = structured.gammaWrite;
|
||||||
|
jStateBitsTableEntry.polymodeLine = structured.polymodeLine;
|
||||||
|
jStateBitsTableEntry.depthWrite = structured.depthWrite;
|
||||||
|
|
||||||
|
assert(structured.depthTestDisabled || structured.depthTest == GFXS_DEPTHTEST_ALWAYS || structured.depthTest == GFXS_DEPTHTEST_LESS
|
||||||
|
|| structured.depthTest == GFXS_DEPTHTEST_EQUAL || structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL);
|
||||||
|
if (structured.depthTestDisabled)
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::DISABLED;
|
||||||
|
else if (structured.depthTest == GFXS_DEPTHTEST_ALWAYS)
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::ALWAYS;
|
||||||
|
else if (structured.depthTest == GFXS_DEPTHTEST_LESS)
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::LESS;
|
||||||
|
else if (structured.depthTest == GFXS_DEPTHTEST_EQUAL)
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::EQUAL;
|
||||||
|
else if (structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL)
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::LESS_EQUAL;
|
||||||
|
else
|
||||||
|
jStateBitsTableEntry.depthTest = JsonDepthTest::INVALID;
|
||||||
|
|
||||||
|
jStateBitsTableEntry.polygonOffset = static_cast<GfxPolygonOffset_e>(structured.polygonOffset);
|
||||||
|
|
||||||
|
if (structured.stencilFrontEnabled)
|
||||||
|
{
|
||||||
|
JsonStencil jStencilFront;
|
||||||
|
CreateJsonStencil(
|
||||||
|
jStencilFront, structured.stencilFrontPass, structured.stencilFrontFail, structured.stencilFrontZFail, structured.stencilFrontFunc);
|
||||||
|
jStateBitsTableEntry.stencilFront = jStencilFront;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structured.stencilBackEnabled)
|
||||||
|
{
|
||||||
|
JsonStencil jStencilBack;
|
||||||
|
CreateJsonStencil(
|
||||||
|
jStencilBack, structured.stencilBackPass, structured.stencilBackFail, structured.stencilBackZFail, structured.stencilBackFunc);
|
||||||
|
jStateBitsTableEntry.stencilBack = jStencilBack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateJsonMaterial(JsonMaterial& jMaterial, const Material& material) const
|
||||||
|
{
|
||||||
|
CreateJsonGameFlags(jMaterial, material.info.gameFlags);
|
||||||
|
jMaterial.sortKey = material.info.sortKey;
|
||||||
|
|
||||||
|
jMaterial.textureAtlas = JsonTextureAtlas();
|
||||||
|
jMaterial.textureAtlas->rows = material.info.textureAtlasRowCount;
|
||||||
|
jMaterial.textureAtlas->columns = material.info.textureAtlasColumnCount;
|
||||||
|
|
||||||
|
jMaterial.surfaceTypeBits = material.info.surfaceTypeBits;
|
||||||
|
|
||||||
|
jMaterial.stateBitsEntry.resize(std::extent_v<decltype(Material::stateBitsEntry)>);
|
||||||
|
for (auto i = 0u; i < std::extent_v<decltype(Material::stateBitsEntry)>; i++)
|
||||||
|
jMaterial.stateBitsEntry[i] = material.stateBitsEntry[i];
|
||||||
|
|
||||||
|
jMaterial.stateFlags = material.stateFlags;
|
||||||
|
jMaterial.cameraRegion = static_cast<GfxCameraRegionType>(material.cameraRegion);
|
||||||
|
|
||||||
|
if (material.techniqueSet && material.techniqueSet->name)
|
||||||
|
jMaterial.techniqueSet = AssetName(material.techniqueSet->name);
|
||||||
|
|
||||||
|
jMaterial.textures.resize(material.textureCount);
|
||||||
|
for (auto i = 0u; i < material.textureCount; i++)
|
||||||
|
CreateJsonTexture(jMaterial.textures[i], material.textureTable[i]);
|
||||||
|
|
||||||
|
jMaterial.constants.resize(material.constantCount);
|
||||||
|
for (auto i = 0u; i < material.constantCount; i++)
|
||||||
|
CreateJsonConstant(jMaterial.constants[i], material.constantTable[i]);
|
||||||
|
|
||||||
|
jMaterial.stateBits.resize(material.stateBitsCount);
|
||||||
|
for (auto i = 0u; i < material.stateBitsCount; i++)
|
||||||
|
CreateJsonStateBitsTableEntry(jMaterial.stateBits[i], material.stateBitsTable[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& m_stream;
|
||||||
|
const MaterialConstantZoneState& m_material_constants;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
void DumpMaterialAsJson(std::ostream& stream, const Material* material, AssetDumpingContext& context)
|
||||||
|
{
|
||||||
|
const JsonDumper dumper(context, stream);
|
||||||
|
dumper.Dump(material);
|
||||||
|
}
|
||||||
|
} // namespace IW5
|
11
src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.h
Normal file
11
src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Dumping/AssetDumpingContext.h"
|
||||||
|
#include "Game/IW5/IW5.h"
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
void DumpMaterialAsJson(std::ostream& stream, const Material* material, AssetDumpingContext& context);
|
||||||
|
} // namespace IW5
|
236
src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.cpp
Normal file
236
src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.cpp
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
#include "MaterialConstantZoneState.h"
|
||||||
|
|
||||||
|
#include "Game/IW5/CommonIW5.h"
|
||||||
|
#include "Game/IW5/GameAssetPoolIW5.h"
|
||||||
|
#include "Game/IW5/GameIW5.h"
|
||||||
|
#include "ObjWriting.h"
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
const char* KNOWN_CONSTANT_NAMES[]{
|
||||||
|
"worldViewProjectionMatrix",
|
||||||
|
"worldViewMatrix2",
|
||||||
|
"worldViewMatrix1",
|
||||||
|
"worldViewMatrix",
|
||||||
|
"worldOutdoorLookupMatrix",
|
||||||
|
"worldMatrix",
|
||||||
|
"waterColor",
|
||||||
|
"viewportDimensions",
|
||||||
|
"viewProjectionMatrix",
|
||||||
|
"uvScale",
|
||||||
|
"uvAnimParms",
|
||||||
|
"thermalColorOffset",
|
||||||
|
"sunShadowmapPixelAdjust",
|
||||||
|
"ssaoParms",
|
||||||
|
"spotShadowmapPixelAdjust",
|
||||||
|
"shadowmapSwitchPartition",
|
||||||
|
"shadowmapScale",
|
||||||
|
"shadowmapPolygonOffset",
|
||||||
|
"shadowLookupMatrix",
|
||||||
|
"renderTargetSize",
|
||||||
|
"renderSourceSize",
|
||||||
|
"projectionMatrix",
|
||||||
|
"playlistPopulationParams",
|
||||||
|
"pixelCostFracs",
|
||||||
|
"pixelCostDecode",
|
||||||
|
"particleCloudSparkColor2",
|
||||||
|
"particleCloudSparkColor1",
|
||||||
|
"particleCloudSparkColor0",
|
||||||
|
"particleCloudMatrix2",
|
||||||
|
"particleCloudMatrix1",
|
||||||
|
"particleCloudMatrix",
|
||||||
|
"particleCloudColor",
|
||||||
|
"outdoorFeatherParms",
|
||||||
|
"oceanUVAnimParmPaintedFoam",
|
||||||
|
"oceanUVAnimParmOctave2",
|
||||||
|
"oceanUVAnimParmOctave1",
|
||||||
|
"oceanUVAnimParmOctave0",
|
||||||
|
"oceanUVAnimParmFoam",
|
||||||
|
"oceanUVAnimParmDetail1",
|
||||||
|
"oceanUVAnimParmDetail0",
|
||||||
|
"oceanScrollParms",
|
||||||
|
"oceanMiscParms",
|
||||||
|
"oceanFoamParms",
|
||||||
|
"oceanAmplitude",
|
||||||
|
"materialColor",
|
||||||
|
"lightprobeAmbient",
|
||||||
|
"lightingLookupScale",
|
||||||
|
"lightSpotFactors",
|
||||||
|
"lightSpotDir",
|
||||||
|
"lightSpecular",
|
||||||
|
"lightPosition",
|
||||||
|
"lightFalloffPlacement",
|
||||||
|
"lightDiffuse",
|
||||||
|
"inverseWorldViewMatrix",
|
||||||
|
"inverseViewProjectionMatrix",
|
||||||
|
"inverseTransposeWorldViewMatrix",
|
||||||
|
"heatMapDetail",
|
||||||
|
"glowSetup",
|
||||||
|
"glowApply",
|
||||||
|
"gameTime",
|
||||||
|
"fullscreenDistortion",
|
||||||
|
"fogSunDir",
|
||||||
|
"fogSunConsts",
|
||||||
|
"fogSunColorLinear",
|
||||||
|
"fogSunColorGamma",
|
||||||
|
"fogConsts",
|
||||||
|
"fogColorLinear",
|
||||||
|
"fogColorGamma",
|
||||||
|
"flagParms",
|
||||||
|
"filterTap",
|
||||||
|
"featherParms",
|
||||||
|
"falloffParms",
|
||||||
|
"falloffEndColor",
|
||||||
|
"falloffBeginColor",
|
||||||
|
"fadeEffect",
|
||||||
|
"eyeOffsetParms",
|
||||||
|
"eyeOffset",
|
||||||
|
"envMapParms",
|
||||||
|
"dustTint",
|
||||||
|
"dustParms",
|
||||||
|
"dustEyeParms",
|
||||||
|
"dofRowDelta",
|
||||||
|
"dofLerpScale",
|
||||||
|
"dofLerpBias",
|
||||||
|
"dofEquationViewModelAndFarBlur",
|
||||||
|
"dofEquationScene",
|
||||||
|
"distortionScale",
|
||||||
|
"detailScale",
|
||||||
|
"depthFromClip",
|
||||||
|
"debugBumpmap",
|
||||||
|
"colorTintQuadraticDelta",
|
||||||
|
"colorTintDelta",
|
||||||
|
"colorTintBase",
|
||||||
|
"colorSaturationR",
|
||||||
|
"colorSaturationG",
|
||||||
|
"colorSaturationB",
|
||||||
|
"colorObjMin",
|
||||||
|
"colorObjMax",
|
||||||
|
"colorMatrixR",
|
||||||
|
"colorMatrixG",
|
||||||
|
"colorMatrixB",
|
||||||
|
"colorBias",
|
||||||
|
"codeMeshArg",
|
||||||
|
"clipSpaceLookupScale",
|
||||||
|
"clipSpaceLookupOffset",
|
||||||
|
"baseLightingCoords",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* KNOWN_TEXTURE_DEF_NAMES[]{
|
||||||
|
"attenuation",
|
||||||
|
"attenuationSampler",
|
||||||
|
"cinematicA",
|
||||||
|
"cinematicASampler",
|
||||||
|
"cinematicCb",
|
||||||
|
"cinematicCbSampler",
|
||||||
|
"cinematicCr",
|
||||||
|
"cinematicCrSampler",
|
||||||
|
"cinematicY",
|
||||||
|
"cinematicYSampler",
|
||||||
|
"colorMap",
|
||||||
|
"colorMap1",
|
||||||
|
"colorMap2",
|
||||||
|
"colorMapPostSun",
|
||||||
|
"colorMapPostSunSampler",
|
||||||
|
"colorMapSampler",
|
||||||
|
"colorMapSampler1",
|
||||||
|
"colorMapSampler2",
|
||||||
|
"cucoloris",
|
||||||
|
"cucolorisSampler",
|
||||||
|
"detailMap",
|
||||||
|
"detailMapSampler",
|
||||||
|
"dust",
|
||||||
|
"dustSampler",
|
||||||
|
"fadeMap",
|
||||||
|
"fadeMapSampler",
|
||||||
|
"floatZ",
|
||||||
|
"floatZSampler",
|
||||||
|
"grainMap",
|
||||||
|
"grainMapSampler",
|
||||||
|
"halfParticleColor",
|
||||||
|
"halfParticleColorSampler",
|
||||||
|
"halfParticleDepth",
|
||||||
|
"halfParticleDepthSampler",
|
||||||
|
"heatmap",
|
||||||
|
"heatmapSampler",
|
||||||
|
"lightmapPrimary",
|
||||||
|
"lightmapSamplerPrimary",
|
||||||
|
"lightmapSamplerSecondary",
|
||||||
|
"lightmapSecondary",
|
||||||
|
"lookupMap",
|
||||||
|
"lookupMapSampler",
|
||||||
|
"modelLighting",
|
||||||
|
"modelLightingSampler",
|
||||||
|
"normalMap",
|
||||||
|
"normalMapSampler",
|
||||||
|
"oceanColorRamp",
|
||||||
|
"oceanColorRampSampler",
|
||||||
|
"oceanDetailNormal",
|
||||||
|
"oceanDetailNormalSampler",
|
||||||
|
"oceanDisplacement",
|
||||||
|
"oceanDisplacementSampler",
|
||||||
|
"oceanEnv",
|
||||||
|
"oceanEnvSampler",
|
||||||
|
"oceanFoam",
|
||||||
|
"oceanFoamSampler",
|
||||||
|
"oceanHeightNormal",
|
||||||
|
"oceanHeightNormalSampler",
|
||||||
|
"oceanPaintedFoam",
|
||||||
|
"oceanPaintedFoamSampler",
|
||||||
|
"outdoorMap",
|
||||||
|
"outdoorMapSampler",
|
||||||
|
"population",
|
||||||
|
"populationSampler",
|
||||||
|
"reflectionProbe",
|
||||||
|
"reflectionProbeSampler",
|
||||||
|
"shadowmapSamplerSpot",
|
||||||
|
"shadowmapSamplerSun",
|
||||||
|
"shadowmapSpot",
|
||||||
|
"shadowmapSun",
|
||||||
|
"skyMap",
|
||||||
|
"skyMapSampler",
|
||||||
|
"specularMap",
|
||||||
|
"specularMapSampler",
|
||||||
|
"ssao",
|
||||||
|
"ssaoSampler",
|
||||||
|
"worldMap",
|
||||||
|
"worldMapSampler",
|
||||||
|
};
|
||||||
|
|
||||||
|
void MaterialConstantZoneState::ExtractNamesFromZoneInternal()
|
||||||
|
{
|
||||||
|
for (const auto* zone : g_GameIW5.GetZones())
|
||||||
|
{
|
||||||
|
const auto* iw5AssetPools = dynamic_cast<const GameAssetPoolIW5*>(zone->m_pools.get());
|
||||||
|
if (!iw5AssetPools)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto* vertexShaderAsset : *iw5AssetPools->m_material_vertex_shader)
|
||||||
|
{
|
||||||
|
const auto* vertexShader = vertexShaderAsset->Asset();
|
||||||
|
if (ShouldDumpFromStruct(vertexShader))
|
||||||
|
ExtractNamesFromShader(vertexShader->prog.loadDef.program, static_cast<size_t>(vertexShader->prog.loadDef.programSize) * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto* pixelShaderAsset : *iw5AssetPools->m_material_pixel_shader)
|
||||||
|
{
|
||||||
|
const auto* pixelShader = pixelShaderAsset->Asset();
|
||||||
|
if (ShouldDumpFromStruct(pixelShader))
|
||||||
|
ExtractNamesFromShader(pixelShader->prog.loadDef.program, static_cast<size_t>(pixelShader->prog.loadDef.programSize) * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialConstantZoneState::AddStaticKnownNames()
|
||||||
|
{
|
||||||
|
for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES)
|
||||||
|
AddConstantName(knownConstantName);
|
||||||
|
for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES)
|
||||||
|
AddTextureDefName(knownTextureDefName);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned MaterialConstantZoneState::HashString(const std::string& str)
|
||||||
|
{
|
||||||
|
return Common::R_HashString(str.c_str());
|
||||||
|
}
|
||||||
|
} // namespace IW5
|
16
src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.h
Normal file
16
src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Material/AbstractMaterialConstantZoneState.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace IW5
|
||||||
|
{
|
||||||
|
class MaterialConstantZoneState final : public AbstractMaterialConstantZoneStateDx9
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void ExtractNamesFromZoneInternal() override;
|
||||||
|
void AddStaticKnownNames() override;
|
||||||
|
unsigned HashString(const std::string& str) override;
|
||||||
|
};
|
||||||
|
} // namespace IW5
|
@ -5,6 +5,7 @@
|
|||||||
#include "AssetDumpers/AssetDumperLeaderboardDef.h"
|
#include "AssetDumpers/AssetDumperLeaderboardDef.h"
|
||||||
#include "AssetDumpers/AssetDumperLoadedSound.h"
|
#include "AssetDumpers/AssetDumperLoadedSound.h"
|
||||||
#include "AssetDumpers/AssetDumperLocalizeEntry.h"
|
#include "AssetDumpers/AssetDumperLocalizeEntry.h"
|
||||||
|
#include "AssetDumpers/AssetDumperMaterial.h"
|
||||||
#include "AssetDumpers/AssetDumperMenuDef.h"
|
#include "AssetDumpers/AssetDumperMenuDef.h"
|
||||||
#include "AssetDumpers/AssetDumperMenuList.h"
|
#include "AssetDumpers/AssetDumperMenuList.h"
|
||||||
#include "AssetDumpers/AssetDumperRawFile.h"
|
#include "AssetDumpers/AssetDumperRawFile.h"
|
||||||
@ -39,7 +40,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const
|
|||||||
// DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts, ASSET_TYPE_XANIMPARTS)
|
// DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts, ASSET_TYPE_XANIMPARTS)
|
||||||
// DUMP_ASSET_POOL(AssetDumperXModelSurfs, m_xmodel_surfs, ASSET_TYPE_XMODEL_SURFS)
|
// DUMP_ASSET_POOL(AssetDumperXModelSurfs, m_xmodel_surfs, ASSET_TYPE_XMODEL_SURFS)
|
||||||
DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel, ASSET_TYPE_XMODEL)
|
DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel, ASSET_TYPE_XMODEL)
|
||||||
// DUMP_ASSET_POOL(AssetDumperMaterial, m_material, ASSET_TYPE_MATERIAL)
|
DUMP_ASSET_POOL(AssetDumperMaterial, m_material, ASSET_TYPE_MATERIAL)
|
||||||
// DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader, ASSET_TYPE_PIXELSHADER)
|
// DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader, ASSET_TYPE_PIXELSHADER)
|
||||||
// DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader, ASSET_TYPE_VERTEXSHADER)
|
// DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader, ASSET_TYPE_VERTEXSHADER)
|
||||||
// DUMP_ASSET_POOL(AssetDumperMaterialVertexDeclaration, m_material_vertex_decl, ASSET_TYPE_VERTEXDECL)
|
// DUMP_ASSET_POOL(AssetDumperMaterialVertexDeclaration, m_material_vertex_decl, ASSET_TYPE_VERTEXDECL)
|
||||||
|
@ -1,519 +1,9 @@
|
|||||||
#include "AssetDumperXModel.h"
|
#include "AssetDumperXModel.h"
|
||||||
|
|
||||||
#include "Game/T5/CommonT5.h"
|
#include "Game/T5/XModel/XModelDumperT5.h"
|
||||||
#include "ObjWriting.h"
|
|
||||||
#include "Utils/DistinctMapper.h"
|
|
||||||
#include "Utils/QuatInt16.h"
|
|
||||||
#include "XModel/Export/XModelExportWriter.h"
|
|
||||||
#include "XModel/Gltf/GltfBinOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfTextOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfWriter.h"
|
|
||||||
#include "XModel/Obj/ObjWriter.h"
|
|
||||||
#include "XModel/XModelWriter.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
using namespace T5;
|
using namespace T5;
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
|
|
||||||
{
|
|
||||||
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialColorMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 'c' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialNormalMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_NORMAL_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 'n' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialSpecularMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_SPECULAR_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 's' && def->nameEnd == 'p')
|
|
||||||
return def->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->u.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
|
|
||||||
{
|
|
||||||
XModelBone bone;
|
|
||||||
if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count())
|
|
||||||
bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]];
|
|
||||||
else
|
|
||||||
bone.name = "INVALID_BONE_NAME";
|
|
||||||
|
|
||||||
if (boneNum >= model->numRootBones)
|
|
||||||
bone.parentIndex = boneNum - static_cast<unsigned int>(model->parentList[boneNum - model->numRootBones]);
|
|
||||||
else
|
|
||||||
bone.parentIndex = std::nullopt;
|
|
||||||
|
|
||||||
bone.scale[0] = 1.0f;
|
|
||||||
bone.scale[1] = 1.0f;
|
|
||||||
bone.scale[2] = 1.0f;
|
|
||||||
|
|
||||||
bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
|
|
||||||
bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
|
|
||||||
bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
|
|
||||||
bone.globalRotation = {
|
|
||||||
model->baseMat[boneNum].quat[0],
|
|
||||||
model->baseMat[boneNum].quat[1],
|
|
||||||
model->baseMat[boneNum].quat[2],
|
|
||||||
model->baseMat[boneNum].quat[3],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (boneNum < model->numRootBones)
|
|
||||||
{
|
|
||||||
bone.localOffset[0] = 0;
|
|
||||||
bone.localOffset[1] = 0;
|
|
||||||
bone.localOffset[2] = 0;
|
|
||||||
bone.localRotation = {0, 0, 0, 1};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3];
|
|
||||||
bone.localOffset[0] = trans[0];
|
|
||||||
bone.localOffset[1] = trans[1];
|
|
||||||
bone.localOffset[2] = trans[2];
|
|
||||||
|
|
||||||
const auto& quat = model->quats[boneNum - model->numRootBones];
|
|
||||||
bone.localRotation = {
|
|
||||||
QuatInt16::ToFloat(quat.v[0]),
|
|
||||||
QuatInt16::ToFloat(quat.v[1]),
|
|
||||||
QuatInt16::ToFloat(quat.v[2]),
|
|
||||||
QuatInt16::ToFloat(quat.v[3]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
out.m_bones.emplace_back(std::move(bone));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* AssetName(const char* input)
|
|
||||||
{
|
|
||||||
if (input && input[0] == ',')
|
|
||||||
return &input[1];
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
|
|
||||||
{
|
|
||||||
Material* material = model->materialHandles[surfaceMaterialNum];
|
|
||||||
if (materialMapper.Add(material))
|
|
||||||
{
|
|
||||||
XModelMaterial xMaterial;
|
|
||||||
xMaterial.ApplyDefaults();
|
|
||||||
|
|
||||||
xMaterial.name = AssetName(material->info.name);
|
|
||||||
const auto* colorMap = GetMaterialColorMap(material);
|
|
||||||
if (colorMap)
|
|
||||||
xMaterial.colorMapName = AssetName(colorMap->name);
|
|
||||||
|
|
||||||
const auto* normalMap = GetMaterialNormalMap(material);
|
|
||||||
if (normalMap)
|
|
||||||
xMaterial.normalMapName = AssetName(normalMap->name);
|
|
||||||
|
|
||||||
const auto* specularMap = GetMaterialSpecularMap(material);
|
|
||||||
if (specularMap)
|
|
||||||
xMaterial.specularMapName = AssetName(specularMap->name);
|
|
||||||
|
|
||||||
out.m_materials.emplace_back(std::move(xMaterial));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
|
|
||||||
{
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
XModelObject object;
|
|
||||||
object.name = std::format("surf{}", surfIndex);
|
|
||||||
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
|
|
||||||
|
|
||||||
out.m_objects.emplace_back(std::move(object));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
|
|
||||||
for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++)
|
|
||||||
{
|
|
||||||
const auto& v = surface.verts0[vertexIndex];
|
|
||||||
|
|
||||||
XModelVertex vertex{};
|
|
||||||
vertex.coordinates[0] = v.xyz[0];
|
|
||||||
vertex.coordinates[1] = v.xyz[1];
|
|
||||||
vertex.coordinates[2] = v.xyz[2];
|
|
||||||
Common::Vec3UnpackUnitVec(v.normal, vertex.normal);
|
|
||||||
Common::Vec4UnpackGfxColor(v.color, vertex.color);
|
|
||||||
Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv);
|
|
||||||
|
|
||||||
out.m_vertices.emplace_back(vertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
auto totalWeightCount = 0u;
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertListCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[0] * 1;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[1] * 2;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[2] * 3;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[3] * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
weightCollection.weights.resize(totalWeightCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
float BoneWeight16(const uint16_t value)
|
|
||||||
{
|
|
||||||
return static_cast<float>(value) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
auto& weightCollection = out.m_bone_weight_data;
|
|
||||||
|
|
||||||
size_t weightOffset = 0u;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
auto handledVertices = 0u;
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
|
|
||||||
{
|
|
||||||
const auto& vertList = surface.vertList[vertListIndex];
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{vertList.boneOffset / sizeof(DObjSkelMat), 1.0f};
|
|
||||||
|
|
||||||
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
handledVertices += vertList.vertCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto vertsBlendOffset = 0u;
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
// 1 bone weight
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f};
|
|
||||||
|
|
||||||
vertsBlendOffset += 1;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
|
|
||||||
vertsBlendOffset += 3;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
|
|
||||||
vertsBlendOffset += 5;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight3 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3};
|
|
||||||
|
|
||||||
vertsBlendOffset += 7;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
handledVertices +=
|
|
||||||
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; handledVertices < surface.vertCount; handledVertices++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
auto& object = out.m_objects[surfIndex];
|
|
||||||
object.m_faces.reserve(surface.triCount);
|
|
||||||
|
|
||||||
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
|
|
||||||
{
|
|
||||||
const auto& tri = surface.triIndices[triIndex];
|
|
||||||
|
|
||||||
XModelFace face{};
|
|
||||||
face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
|
|
||||||
object.m_faces.emplace_back(face);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
|
|
||||||
{
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
|
|
||||||
|
|
||||||
out.m_name = std::format("{}_lod{}", model->name, lod);
|
|
||||||
AddXModelBones(out, context, model);
|
|
||||||
AddXModelMaterials(out, materialMapper, model);
|
|
||||||
AddXModelObjects(out, model, lod, materialMapper);
|
|
||||||
AddXModelVertices(out, model, lod);
|
|
||||||
AddXModelVertexBoneWeights(out, model, lod);
|
|
||||||
AddXModelFaces(out, model, lod);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
|
|
||||||
|
|
||||||
if (!mtlFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer =
|
|
||||||
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void DumpGltfLod(
|
|
||||||
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod, const std::string& extension)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto output = std::make_unique<T>(*assetFile);
|
|
||||||
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
|
|
||||||
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
|
|
||||||
{
|
|
||||||
XModelCommon common;
|
|
||||||
PopulateXModelWriter(common, context, currentLod, asset->Asset());
|
|
||||||
|
|
||||||
switch (ObjWriting::Configuration.ModelOutputFormat)
|
|
||||||
{
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
|
||||||
DumpObjLod(common, context, asset, currentLod);
|
|
||||||
if (currentLod == 0u)
|
|
||||||
DumpObjMtl(common, context, asset);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
|
||||||
DumpXModelExportLod(common, context, asset, currentLod);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
|
||||||
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
|
||||||
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
||||||
@ -521,5 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
|||||||
|
|
||||||
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
DumpXModelSurfs(context, asset);
|
DumpXModel(context, asset);
|
||||||
}
|
}
|
||||||
|
@ -1,600 +1,9 @@
|
|||||||
#include "AssetDumperXModel.h"
|
#include "AssetDumperXModel.h"
|
||||||
|
|
||||||
#include "Game/T6/CommonT6.h"
|
#include "Game/T6/XModel/XModelDumperT6.h"
|
||||||
#include "Game/T6/XModel/JsonXModelWriter.h"
|
|
||||||
#include "ObjWriting.h"
|
|
||||||
#include "Utils/DistinctMapper.h"
|
|
||||||
#include "Utils/QuatInt16.h"
|
|
||||||
#include "XModel/Export/XModelExportWriter.h"
|
|
||||||
#include "XModel/Gltf/GltfBinOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfTextOutput.h"
|
|
||||||
#include "XModel/Gltf/GltfWriter.h"
|
|
||||||
#include "XModel/Obj/ObjWriter.h"
|
|
||||||
#include "XModel/XModelWriter.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
using namespace T6;
|
using namespace T6;
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
|
|
||||||
{
|
|
||||||
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialColorMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p')
|
|
||||||
return def->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k')
|
|
||||||
return def->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p')
|
|
||||||
return def->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialNormalMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_NORMAL_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 'n' && def->nameEnd == 'p')
|
|
||||||
return def->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxImage* GetMaterialSpecularMap(const Material* material)
|
|
||||||
{
|
|
||||||
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
|
||||||
|
|
||||||
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
|
||||||
{
|
|
||||||
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
|
||||||
|
|
||||||
if (def->semantic == TS_SPECULAR_MAP)
|
|
||||||
potentialTextureDefs.push_back(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialTextureDefs.empty())
|
|
||||||
return nullptr;
|
|
||||||
if (potentialTextureDefs.size() == 1)
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
|
|
||||||
for (const auto* def : potentialTextureDefs)
|
|
||||||
{
|
|
||||||
if (def->nameStart == 's' && def->nameEnd == 'p')
|
|
||||||
return def->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTextureDefs[0]->image;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasDefaultArmature(const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
if (model->numRootBones != 1 || model->numBones != 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
if (!surfs)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
|
|
||||||
if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto& vertList = surface.vertList[0];
|
|
||||||
if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OmitDefaultArmature(XModelCommon& common)
|
|
||||||
{
|
|
||||||
common.m_bones.clear();
|
|
||||||
common.m_bone_weight_data.weights.clear();
|
|
||||||
common.m_vertex_bone_weights.resize(common.m_vertices.size());
|
|
||||||
for (auto& vertexWeights : common.m_vertex_bone_weights)
|
|
||||||
{
|
|
||||||
vertexWeights.weightOffset = 0u;
|
|
||||||
vertexWeights.weightCount = 0u;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
|
|
||||||
{
|
|
||||||
XModelBone bone;
|
|
||||||
if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count())
|
|
||||||
bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]];
|
|
||||||
else
|
|
||||||
bone.name = "INVALID_BONE_NAME";
|
|
||||||
|
|
||||||
if (boneNum >= model->numRootBones)
|
|
||||||
bone.parentIndex = static_cast<int>(boneNum - static_cast<unsigned int>(model->parentList[boneNum - model->numRootBones]));
|
|
||||||
else
|
|
||||||
bone.parentIndex = std::nullopt;
|
|
||||||
|
|
||||||
bone.scale[0] = 1.0f;
|
|
||||||
bone.scale[1] = 1.0f;
|
|
||||||
bone.scale[2] = 1.0f;
|
|
||||||
|
|
||||||
const auto& baseMat = model->baseMat[boneNum];
|
|
||||||
bone.globalOffset[0] = baseMat.trans.x;
|
|
||||||
bone.globalOffset[1] = baseMat.trans.y;
|
|
||||||
bone.globalOffset[2] = baseMat.trans.z;
|
|
||||||
bone.globalRotation = {
|
|
||||||
baseMat.quat.x,
|
|
||||||
baseMat.quat.y,
|
|
||||||
baseMat.quat.z,
|
|
||||||
baseMat.quat.w,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (boneNum < model->numRootBones)
|
|
||||||
{
|
|
||||||
bone.localOffset[0] = 0;
|
|
||||||
bone.localOffset[1] = 0;
|
|
||||||
bone.localOffset[2] = 0;
|
|
||||||
bone.localRotation = {0, 0, 0, 1};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3];
|
|
||||||
bone.localOffset[0] = trans[0];
|
|
||||||
bone.localOffset[1] = trans[1];
|
|
||||||
bone.localOffset[2] = trans[2];
|
|
||||||
|
|
||||||
const auto& quat = model->quats[boneNum - model->numRootBones];
|
|
||||||
bone.localRotation = {
|
|
||||||
QuatInt16::ToFloat(quat.v[0]),
|
|
||||||
QuatInt16::ToFloat(quat.v[1]),
|
|
||||||
QuatInt16::ToFloat(quat.v[2]),
|
|
||||||
QuatInt16::ToFloat(quat.v[3]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
out.m_bones.emplace_back(std::move(bone));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* AssetName(const char* input)
|
|
||||||
{
|
|
||||||
if (input && input[0] == ',')
|
|
||||||
return &input[1];
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
|
|
||||||
{
|
|
||||||
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
|
|
||||||
{
|
|
||||||
Material* material = model->materialHandles[surfaceMaterialNum];
|
|
||||||
if (materialMapper.Add(material))
|
|
||||||
{
|
|
||||||
XModelMaterial xMaterial;
|
|
||||||
xMaterial.ApplyDefaults();
|
|
||||||
|
|
||||||
xMaterial.name = AssetName(material->info.name);
|
|
||||||
const auto* colorMap = GetMaterialColorMap(material);
|
|
||||||
if (colorMap)
|
|
||||||
xMaterial.colorMapName = AssetName(colorMap->name);
|
|
||||||
|
|
||||||
const auto* normalMap = GetMaterialNormalMap(material);
|
|
||||||
if (normalMap)
|
|
||||||
xMaterial.normalMapName = AssetName(normalMap->name);
|
|
||||||
|
|
||||||
const auto* specularMap = GetMaterialSpecularMap(material);
|
|
||||||
if (specularMap)
|
|
||||||
xMaterial.specularMapName = AssetName(specularMap->name);
|
|
||||||
|
|
||||||
out.m_materials.emplace_back(std::move(xMaterial));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
|
|
||||||
{
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
XModelObject object;
|
|
||||||
object.name = std::format("surf{}", surfIndex);
|
|
||||||
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
|
|
||||||
|
|
||||||
out.m_objects.emplace_back(std::move(object));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
if (!surfs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
|
|
||||||
for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++)
|
|
||||||
{
|
|
||||||
const auto& v = surface.verts0[vertexIndex];
|
|
||||||
|
|
||||||
XModelVertex vertex{};
|
|
||||||
vertex.coordinates[0] = v.xyz.x;
|
|
||||||
vertex.coordinates[1] = v.xyz.y;
|
|
||||||
vertex.coordinates[2] = v.xyz.z;
|
|
||||||
Common::Vec3UnpackUnitVec(v.normal, vertex.normal);
|
|
||||||
Common::Vec4UnpackGfxColor(v.color, vertex.color);
|
|
||||||
Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv);
|
|
||||||
|
|
||||||
out.m_vertices.emplace_back(vertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
if (!surfs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto totalWeightCount = 0u;
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertListCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[0] * 1;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[1] * 2;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[2] * 3;
|
|
||||||
totalWeightCount += surface.vertInfo.vertCount[3] * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
weightCollection.weights.resize(totalWeightCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
float BoneWeight16(const uint16_t value)
|
|
||||||
{
|
|
||||||
return static_cast<float>(value) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
auto& weightCollection = out.m_bone_weight_data;
|
|
||||||
|
|
||||||
if (!surfs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
size_t weightOffset = 0u;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
auto handledVertices = 0u;
|
|
||||||
|
|
||||||
if (surface.vertList)
|
|
||||||
{
|
|
||||||
for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
|
|
||||||
{
|
|
||||||
const auto& vertList = surface.vertList[vertListIndex];
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{vertList.boneOffset / sizeof(DObjSkelMat), 1.0f};
|
|
||||||
|
|
||||||
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
handledVertices += vertList.vertCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto vertsBlendOffset = 0u;
|
|
||||||
if (surface.vertInfo.vertsBlend)
|
|
||||||
{
|
|
||||||
// 1 bone weight
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f};
|
|
||||||
|
|
||||||
vertsBlendOffset += 1;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
|
|
||||||
vertsBlendOffset += 3;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
|
|
||||||
vertsBlendOffset += 5;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4 bone weights
|
|
||||||
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++)
|
|
||||||
{
|
|
||||||
const auto boneWeightOffset = weightOffset;
|
|
||||||
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
|
||||||
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
|
||||||
const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat);
|
|
||||||
const auto boneWeight3 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]);
|
|
||||||
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3;
|
|
||||||
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
|
||||||
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3};
|
|
||||||
|
|
||||||
vertsBlendOffset += 7;
|
|
||||||
|
|
||||||
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
handledVertices +=
|
|
||||||
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; handledVertices < surface.vertCount; handledVertices++)
|
|
||||||
{
|
|
||||||
out.m_vertex_bone_weights.emplace_back(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
|
||||||
const auto surfCount = model->lodInfo[lod].numsurfs;
|
|
||||||
|
|
||||||
if (!surfs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
|
||||||
{
|
|
||||||
const auto& surface = surfs[surfIndex];
|
|
||||||
auto& object = out.m_objects[surfIndex];
|
|
||||||
object.m_faces.reserve(surface.triCount);
|
|
||||||
|
|
||||||
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
|
|
||||||
{
|
|
||||||
const auto& tri = surface.triIndices[triIndex];
|
|
||||||
|
|
||||||
XModelFace face{};
|
|
||||||
face.vertexIndex[0] = tri.i[0] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex;
|
|
||||||
face.vertexIndex[2] = tri.i[2] + surface.baseVertIndex;
|
|
||||||
object.m_faces.emplace_back(face);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
|
|
||||||
{
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
|
|
||||||
|
|
||||||
out.m_name = std::format("{}_lod{}", model->name, lod);
|
|
||||||
AddXModelMaterials(out, materialMapper, model);
|
|
||||||
AddXModelObjects(out, model, lod, materialMapper);
|
|
||||||
AddXModelVertices(out, model, lod);
|
|
||||||
AddXModelFaces(out, model, lod);
|
|
||||||
|
|
||||||
if (!HasDefaultArmature(model, lod))
|
|
||||||
{
|
|
||||||
AddXModelBones(out, context, model);
|
|
||||||
AddXModelVertexBoneWeights(out, model, lod);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OmitDefaultArmature(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
|
|
||||||
|
|
||||||
if (!mtlFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer =
|
|
||||||
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void DumpGltfLod(
|
|
||||||
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod, const std::string& extension)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension));
|
|
||||||
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto output = std::make_unique<T>(*assetFile);
|
|
||||||
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
|
||||||
|
|
||||||
writer->Write(common);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto* model = asset->Asset();
|
|
||||||
|
|
||||||
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
|
|
||||||
{
|
|
||||||
XModelCommon common;
|
|
||||||
PopulateXModelWriter(common, context, currentLod, asset->Asset());
|
|
||||||
|
|
||||||
switch (ObjWriting::Configuration.ModelOutputFormat)
|
|
||||||
{
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
|
||||||
DumpObjLod(common, context, asset, currentLod);
|
|
||||||
if (currentLod == 0u)
|
|
||||||
DumpObjMtl(common, context, asset);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
|
||||||
DumpXModelExportLod(common, context, asset, currentLod);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
|
||||||
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
|
||||||
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpXModel(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
|
||||||
{
|
|
||||||
const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name));
|
|
||||||
if (!assetFile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
DumpXModelAsJson(*assetFile, asset->Asset(), context);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
return !asset->m_name.empty() && asset->m_name[0] != ',';
|
||||||
@ -602,6 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
|
|||||||
|
|
||||||
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
{
|
{
|
||||||
DumpXModelSurfs(context, asset);
|
|
||||||
DumpXModel(context, asset);
|
DumpXModel(context, asset);
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,9 @@
|
|||||||
#include "Game/T6/GameAssetPoolT6.h"
|
#include "Game/T6/GameAssetPoolT6.h"
|
||||||
#include "Game/T6/GameT6.h"
|
#include "Game/T6/GameT6.h"
|
||||||
#include "ObjWriting.h"
|
#include "ObjWriting.h"
|
||||||
#include "Shader/D3D11ShaderAnalyser.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace T6
|
namespace T6
|
||||||
{
|
{
|
||||||
static constexpr const char* SAMPLER_STR = "Sampler";
|
|
||||||
static constexpr const char* GLOBALS_CBUFFER_NAME = "$Globals";
|
|
||||||
static constexpr const char* PER_OBJECT_CONSTS_CBUFFER_NAME = "PerObjectConsts";
|
|
||||||
|
|
||||||
const char* KNOWN_CONSTANT_NAMES[]{
|
const char* KNOWN_CONSTANT_NAMES[]{
|
||||||
"AngularVelocityScale",
|
"AngularVelocityScale",
|
||||||
"AnimSpeed",
|
"AnimSpeed",
|
||||||
@ -478,15 +471,8 @@ namespace T6
|
|||||||
"ui3dSampler",
|
"ui3dSampler",
|
||||||
};
|
};
|
||||||
|
|
||||||
void MaterialConstantZoneState::ExtractNamesFromZone()
|
void MaterialConstantZoneState::ExtractNamesFromZoneInternal()
|
||||||
{
|
{
|
||||||
if (ObjWriting::Configuration.Verbose)
|
|
||||||
std::cout << "Building material constant name lookup...\n";
|
|
||||||
|
|
||||||
const auto begin = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
AddStaticKnownNames();
|
|
||||||
|
|
||||||
for (const auto* zone : g_GameT6.GetZones())
|
for (const auto* zone : g_GameT6.GetZones())
|
||||||
{
|
{
|
||||||
const auto* t6AssetPools = dynamic_cast<const GameAssetPoolT6*>(zone->m_pools.get());
|
const auto* t6AssetPools = dynamic_cast<const GameAssetPoolT6*>(zone->m_pools.get());
|
||||||
@ -504,49 +490,18 @@ namespace T6
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
if (ObjWriting::Configuration.Verbose)
|
|
||||||
{
|
|
||||||
const auto durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
|
|
||||||
std::cout << "Built material constant name lookup in " << durationInMs.count() << "ms: " << m_constant_names_from_shaders.size()
|
|
||||||
<< " constant names; " << m_texture_def_names_from_shaders.size() << " texture def names\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MaterialConstantZoneState::GetConstantName(const unsigned hash, std::string& constantName) const
|
unsigned MaterialConstantZoneState::HashString(const std::string& str)
|
||||||
{
|
{
|
||||||
const auto existingConstantName = m_constant_names_from_shaders.find(hash);
|
return Common::R_HashString(str.c_str());
|
||||||
if (existingConstantName != m_constant_names_from_shaders.end())
|
|
||||||
{
|
|
||||||
constantName = existingConstantName->second;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MaterialConstantZoneState::GetTextureDefName(const unsigned hash, std::string& textureDefName) const
|
|
||||||
{
|
|
||||||
const auto existingTextureDefName = m_texture_def_names_from_shaders.find(hash);
|
|
||||||
if (existingTextureDefName != m_texture_def_names_from_shaders.end())
|
|
||||||
{
|
|
||||||
textureDefName = existingTextureDefName->second;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialConstantZoneState::ExtractNamesFromTechnique(const MaterialTechnique* technique)
|
void MaterialConstantZoneState::ExtractNamesFromTechnique(const MaterialTechnique* technique)
|
||||||
{
|
{
|
||||||
const auto existingTechnique = m_dumped_techniques.find(technique);
|
if (!ShouldDumpFromStruct(technique))
|
||||||
if (existingTechnique != m_dumped_techniques.end())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_dumped_techniques.emplace(technique);
|
|
||||||
|
|
||||||
for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++)
|
for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++)
|
||||||
{
|
{
|
||||||
const auto& pass = technique->passArray[passIndex];
|
const auto& pass = technique->passArray[passIndex];
|
||||||
@ -559,54 +514,6 @@ namespace T6
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialConstantZoneState::ExtractNamesFromShader(const char* shader, const size_t shaderSize)
|
|
||||||
{
|
|
||||||
const auto shaderInfo = d3d11::ShaderAnalyser::GetShaderInfo(reinterpret_cast<const uint8_t*>(shader), shaderSize);
|
|
||||||
if (!shaderInfo)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto globalsConstantBuffer = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers),
|
|
||||||
[](const d3d11::ConstantBuffer& constantBuffer)
|
|
||||||
{
|
|
||||||
return constantBuffer.m_name == GLOBALS_CBUFFER_NAME;
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto perObjectConsts = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers),
|
|
||||||
[](const d3d11::ConstantBuffer& constantBuffer)
|
|
||||||
{
|
|
||||||
return constantBuffer.m_name == PER_OBJECT_CONSTS_CBUFFER_NAME;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (globalsConstantBuffer != shaderInfo->m_constant_buffers.end())
|
|
||||||
{
|
|
||||||
for (const auto& variable : globalsConstantBuffer->m_variables)
|
|
||||||
AddConstantName(variable.m_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (perObjectConsts != shaderInfo->m_constant_buffers.end())
|
|
||||||
{
|
|
||||||
for (const auto& variable : perObjectConsts->m_variables)
|
|
||||||
AddConstantName(variable.m_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& boundResource : shaderInfo->m_bound_resources)
|
|
||||||
{
|
|
||||||
if (boundResource.m_type == d3d11::BoundResourceType::SAMPLER || boundResource.m_type == d3d11::BoundResourceType::TEXTURE)
|
|
||||||
{
|
|
||||||
if (AddTextureDefName(boundResource.m_name))
|
|
||||||
{
|
|
||||||
const auto samplerPos = boundResource.m_name.rfind(SAMPLER_STR);
|
|
||||||
if (samplerPos != std::string::npos)
|
|
||||||
{
|
|
||||||
auto nameWithoutSamplerStr = boundResource.m_name;
|
|
||||||
nameWithoutSamplerStr.erase(samplerPos, std::char_traits<char>::length(SAMPLER_STR));
|
|
||||||
AddTextureDefName(std::move(nameWithoutSamplerStr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaterialConstantZoneState::AddStaticKnownNames()
|
void MaterialConstantZoneState::AddStaticKnownNames()
|
||||||
{
|
{
|
||||||
for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES)
|
for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES)
|
||||||
@ -614,23 +521,4 @@ namespace T6
|
|||||||
for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES)
|
for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES)
|
||||||
AddTextureDefName(knownTextureDefName);
|
AddTextureDefName(knownTextureDefName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialConstantZoneState::AddConstantName(std::string constantName)
|
|
||||||
{
|
|
||||||
const auto hash = Common::R_HashString(constantName.c_str(), 0);
|
|
||||||
if (m_constant_names_from_shaders.contains(hash))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_constant_names_from_shaders.emplace(hash, std::move(constantName));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MaterialConstantZoneState::AddTextureDefName(std::string textureDefName)
|
|
||||||
{
|
|
||||||
const auto hash = Common::R_HashString(textureDefName.c_str(), 0);
|
|
||||||
if (m_texture_def_names_from_shaders.contains(hash))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_texture_def_names_from_shaders.emplace(hash, std::move(textureDefName));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace T6
|
} // namespace T6
|
||||||
|
@ -1,30 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Dumping/IZoneAssetDumperState.h"
|
|
||||||
#include "Game/T6/T6.h"
|
#include "Game/T6/T6.h"
|
||||||
|
#include "Material/AbstractMaterialConstantZoneState.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace T6
|
namespace T6
|
||||||
{
|
{
|
||||||
class MaterialConstantZoneState final : public IZoneAssetDumperState
|
class MaterialConstantZoneState final : public AbstractMaterialConstantZoneStateDx11
|
||||||
{
|
{
|
||||||
public:
|
protected:
|
||||||
void ExtractNamesFromZone();
|
void ExtractNamesFromZoneInternal() override;
|
||||||
bool GetConstantName(unsigned hash, std::string& constantName) const;
|
|
||||||
bool GetTextureDefName(unsigned hash, std::string& textureDefName) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void ExtractNamesFromTechnique(const MaterialTechnique* technique);
|
void ExtractNamesFromTechnique(const MaterialTechnique* technique);
|
||||||
void ExtractNamesFromShader(const char* shader, size_t shaderSize);
|
void AddStaticKnownNames() override;
|
||||||
void AddStaticKnownNames();
|
unsigned HashString(const std::string& str) override;
|
||||||
void AddConstantName(std::string constantName);
|
|
||||||
bool AddTextureDefName(std::string textureDefName);
|
|
||||||
|
|
||||||
std::unordered_set<const MaterialTechnique*> m_dumped_techniques;
|
|
||||||
std::unordered_map<unsigned, std::string> m_constant_names_from_shaders;
|
|
||||||
std::unordered_map<unsigned, std::string> m_texture_def_names_from_shaders;
|
|
||||||
};
|
};
|
||||||
} // namespace T6
|
} // namespace T6
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
#include "JsonXModelWriter.h"
|
|
||||||
|
|
||||||
#include "Game/T6/CommonT6.h"
|
|
||||||
#include "Game/T6/Json/JsonXModel.h"
|
|
||||||
#include "ObjWriting.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <format>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* GetExtensionForModelByConfig()
|
|
||||||
{
|
|
||||||
switch (ObjWriting::Configuration.ModelOutputFormat)
|
|
||||||
{
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
|
||||||
return ".XMODEL_EXPORT";
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
|
||||||
return ".OBJ";
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
|
||||||
return ".GLTF";
|
|
||||||
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
|
||||||
return ".GLB";
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel)
|
|
||||||
{
|
|
||||||
if (xmodel.collLod >= 0)
|
|
||||||
jXModel.collLod = xmodel.collLod;
|
|
||||||
|
|
||||||
for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++)
|
|
||||||
{
|
|
||||||
JsonXModelLod lod;
|
|
||||||
lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig());
|
|
||||||
lod.distance = xmodel.lodInfo[lodNumber].dist;
|
|
||||||
|
|
||||||
jXModel.lods.emplace_back(std::move(lod));
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
@ -1,11 +0,0 @@
|
|||||||
#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
|
|
164
src/ObjWriting/Material/AbstractMaterialConstantZoneState.cpp
Normal file
164
src/ObjWriting/Material/AbstractMaterialConstantZoneState.cpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include "AbstractMaterialConstantZoneState.h"
|
||||||
|
|
||||||
|
#include "ObjWriting.h"
|
||||||
|
#include "Shader/D3D11ShaderAnalyser.h"
|
||||||
|
#include "Shader/D3D9ShaderAnalyser.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr const char* SAMPLER_STR = "Sampler";
|
||||||
|
constexpr const char* GLOBALS_CBUFFER_NAME = "$Globals";
|
||||||
|
constexpr const char* PER_OBJECT_CONSTS_CBUFFER_NAME = "PerObjectConsts";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AbstractMaterialConstantZoneState::ExtractNamesFromZone()
|
||||||
|
{
|
||||||
|
if (ObjWriting::Configuration.Verbose)
|
||||||
|
std::cout << "Building material constant name lookup...\n";
|
||||||
|
|
||||||
|
const auto begin = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
AddStaticKnownNames();
|
||||||
|
|
||||||
|
ExtractNamesFromZoneInternal();
|
||||||
|
|
||||||
|
const auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
if (ObjWriting::Configuration.Verbose)
|
||||||
|
{
|
||||||
|
const auto durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
|
||||||
|
std::cout << std::format("Built material constant name lookup in {}ms: {} constant names; {} texture def names\n",
|
||||||
|
durationInMs.count(),
|
||||||
|
m_constant_names_from_shaders.size(),
|
||||||
|
m_texture_def_names_from_shaders.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractMaterialConstantZoneState::GetConstantName(const unsigned hash, std::string& constantName) const
|
||||||
|
{
|
||||||
|
const auto existingConstantName = m_constant_names_from_shaders.find(hash);
|
||||||
|
if (existingConstantName != m_constant_names_from_shaders.end())
|
||||||
|
{
|
||||||
|
constantName = existingConstantName->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractMaterialConstantZoneState::GetTextureDefName(const unsigned hash, std::string& textureDefName) const
|
||||||
|
{
|
||||||
|
const auto existingTextureDefName = m_texture_def_names_from_shaders.find(hash);
|
||||||
|
if (existingTextureDefName != m_texture_def_names_from_shaders.end())
|
||||||
|
{
|
||||||
|
textureDefName = existingTextureDefName->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractMaterialConstantZoneState::ShouldDumpFromStruct(const void* pStruct)
|
||||||
|
{
|
||||||
|
const auto existingTextureDefName = m_dumped_structs.find(pStruct);
|
||||||
|
if (existingTextureDefName != m_dumped_structs.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_dumped_structs.emplace(pStruct);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractMaterialConstantZoneState::AddConstantName(const std::string& constantName)
|
||||||
|
{
|
||||||
|
const auto hash = HashString(constantName);
|
||||||
|
if (m_constant_names_from_shaders.contains(hash))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_constant_names_from_shaders.emplace(hash, constantName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractMaterialConstantZoneState::AddTextureDefName(const std::string& textureDefName)
|
||||||
|
{
|
||||||
|
const auto hash = HashString(textureDefName);
|
||||||
|
if (m_texture_def_names_from_shaders.contains(hash))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_texture_def_names_from_shaders.emplace(hash, textureDefName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractMaterialConstantZoneStateDx9::ExtractNamesFromShader(const void* shader, const size_t shaderSize)
|
||||||
|
{
|
||||||
|
const auto shaderInfo = d3d9::ShaderAnalyser::GetShaderInfo(shader, shaderSize);
|
||||||
|
if (!shaderInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto& constant : shaderInfo->m_constants)
|
||||||
|
{
|
||||||
|
if (constant.m_register_set == d3d9::RegisterSet::SAMPLER)
|
||||||
|
{
|
||||||
|
if (AddTextureDefName(constant.m_name))
|
||||||
|
{
|
||||||
|
const auto samplerPos = constant.m_name.rfind(SAMPLER_STR);
|
||||||
|
if (samplerPos != std::string::npos)
|
||||||
|
{
|
||||||
|
auto nameWithoutSamplerStr = constant.m_name;
|
||||||
|
nameWithoutSamplerStr.erase(samplerPos, std::char_traits<char>::length(SAMPLER_STR));
|
||||||
|
AddTextureDefName(nameWithoutSamplerStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AddConstantName(constant.m_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractMaterialConstantZoneStateDx11::ExtractNamesFromShader(const void* shader, const size_t shaderSize)
|
||||||
|
{
|
||||||
|
const auto shaderInfo = d3d11::ShaderAnalyser::GetShaderInfo(static_cast<const uint8_t*>(shader), shaderSize);
|
||||||
|
if (!shaderInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto globalsConstantBuffer = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers),
|
||||||
|
[](const d3d11::ConstantBuffer& constantBuffer)
|
||||||
|
{
|
||||||
|
return constantBuffer.m_name == GLOBALS_CBUFFER_NAME;
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto perObjectConsts = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers),
|
||||||
|
[](const d3d11::ConstantBuffer& constantBuffer)
|
||||||
|
{
|
||||||
|
return constantBuffer.m_name == PER_OBJECT_CONSTS_CBUFFER_NAME;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (globalsConstantBuffer != shaderInfo->m_constant_buffers.end())
|
||||||
|
{
|
||||||
|
for (const auto& variable : globalsConstantBuffer->m_variables)
|
||||||
|
AddConstantName(variable.m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perObjectConsts != shaderInfo->m_constant_buffers.end())
|
||||||
|
{
|
||||||
|
for (const auto& variable : perObjectConsts->m_variables)
|
||||||
|
AddConstantName(variable.m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& boundResource : shaderInfo->m_bound_resources)
|
||||||
|
{
|
||||||
|
if (boundResource.m_type == d3d11::BoundResourceType::SAMPLER || boundResource.m_type == d3d11::BoundResourceType::TEXTURE)
|
||||||
|
{
|
||||||
|
if (AddTextureDefName(boundResource.m_name))
|
||||||
|
{
|
||||||
|
const auto samplerPos = boundResource.m_name.rfind(SAMPLER_STR);
|
||||||
|
if (samplerPos != std::string::npos)
|
||||||
|
{
|
||||||
|
auto nameWithoutSamplerStr = boundResource.m_name;
|
||||||
|
nameWithoutSamplerStr.erase(samplerPos, std::char_traits<char>::length(SAMPLER_STR));
|
||||||
|
AddTextureDefName(nameWithoutSamplerStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/ObjWriting/Material/AbstractMaterialConstantZoneState.h
Normal file
41
src/ObjWriting/Material/AbstractMaterialConstantZoneState.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Dumping/IZoneAssetDumperState.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
class AbstractMaterialConstantZoneState : public IZoneAssetDumperState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void ExtractNamesFromZone();
|
||||||
|
bool GetConstantName(unsigned hash, std::string& constantName) const;
|
||||||
|
bool GetTextureDefName(unsigned hash, std::string& textureDefName) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void ExtractNamesFromShader(const void* shader, size_t shaderSize) = 0;
|
||||||
|
virtual void ExtractNamesFromZoneInternal() = 0;
|
||||||
|
virtual void AddStaticKnownNames() = 0;
|
||||||
|
virtual unsigned HashString(const std::string& str) = 0;
|
||||||
|
|
||||||
|
bool ShouldDumpFromStruct(const void* pStruct);
|
||||||
|
void AddConstantName(const std::string& constantName);
|
||||||
|
bool AddTextureDefName(const std::string& textureDefName);
|
||||||
|
|
||||||
|
std::unordered_set<const void*> m_dumped_structs;
|
||||||
|
std::unordered_map<unsigned, std::string> m_constant_names_from_shaders;
|
||||||
|
std::unordered_map<unsigned, std::string> m_texture_def_names_from_shaders;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AbstractMaterialConstantZoneStateDx9 : public AbstractMaterialConstantZoneState
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void ExtractNamesFromShader(const void* shader, size_t shaderSize) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AbstractMaterialConstantZoneStateDx11 : public AbstractMaterialConstantZoneState
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void ExtractNamesFromShader(const void* shader, size_t shaderSize) override;
|
||||||
|
};
|
687
src/ObjWriting/XModel/GenericXModelDumper.inc.h
Normal file
687
src/ObjWriting/XModel/GenericXModelDumper.inc.h
Normal file
@ -0,0 +1,687 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef GAME_NAMESPACE
|
||||||
|
#error Must define GAME_NAMESPACE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Game/T6/CommonT6.h"
|
||||||
|
#include "ObjWriting.h"
|
||||||
|
#include "Utils/DistinctMapper.h"
|
||||||
|
#include "Utils/QuatInt16.h"
|
||||||
|
#include "XModel/Export/XModelExportWriter.h"
|
||||||
|
#include "XModel/Gltf/GltfBinOutput.h"
|
||||||
|
#include "XModel/Gltf/GltfTextOutput.h"
|
||||||
|
#include "XModel/Gltf/GltfWriter.h"
|
||||||
|
#include "XModel/Obj/ObjWriter.h"
|
||||||
|
#include "XModel/XModelWriter.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace GAME_NAMESPACE
|
||||||
|
{
|
||||||
|
inline std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
|
||||||
|
{
|
||||||
|
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GfxImage* GetImageFromTextureDef(const MaterialTextureDef& textureDef)
|
||||||
|
{
|
||||||
|
#ifdef FEATURE_T6
|
||||||
|
return textureDef.image;
|
||||||
|
#else
|
||||||
|
return textureDef.u.image;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GfxImage* GetMaterialColorMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GfxImage* GetMaterialNormalMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
if (def->semantic == TS_NORMAL_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (def->nameStart == 'n' && def->nameEnd == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GfxImage* GetMaterialSpecularMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
if (def->semantic == TS_SPECULAR_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (def->nameStart == 's' && def->nameEnd == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool HasDefaultArmature(const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
if (model->numRootBones != 1 || model->numBones != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
|
||||||
|
if (!surfs)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto& vertList = surface.vertList[0];
|
||||||
|
if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void OmitDefaultArmature(XModelCommon& common)
|
||||||
|
{
|
||||||
|
common.m_bones.clear();
|
||||||
|
common.m_bone_weight_data.weights.clear();
|
||||||
|
common.m_vertex_bone_weights.resize(common.m_vertices.size());
|
||||||
|
for (auto& vertexWeights : common.m_vertex_bone_weights)
|
||||||
|
{
|
||||||
|
vertexWeights.weightOffset = 0u;
|
||||||
|
vertexWeights.weightCount = 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
|
||||||
|
{
|
||||||
|
for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
|
||||||
|
{
|
||||||
|
XModelBone bone;
|
||||||
|
if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count())
|
||||||
|
bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]];
|
||||||
|
else
|
||||||
|
bone.name = "INVALID_BONE_NAME";
|
||||||
|
|
||||||
|
if (boneNum >= model->numRootBones)
|
||||||
|
bone.parentIndex = static_cast<int>(boneNum - static_cast<unsigned int>(model->parentList[boneNum - model->numRootBones]));
|
||||||
|
else
|
||||||
|
bone.parentIndex = std::nullopt;
|
||||||
|
|
||||||
|
bone.scale[0] = 1.0f;
|
||||||
|
bone.scale[1] = 1.0f;
|
||||||
|
bone.scale[2] = 1.0f;
|
||||||
|
|
||||||
|
const auto& baseMat = model->baseMat[boneNum];
|
||||||
|
bone.globalOffset[0] = baseMat.trans.x;
|
||||||
|
bone.globalOffset[1] = baseMat.trans.y;
|
||||||
|
bone.globalOffset[2] = baseMat.trans.z;
|
||||||
|
bone.globalRotation = {
|
||||||
|
baseMat.quat.x,
|
||||||
|
baseMat.quat.y,
|
||||||
|
baseMat.quat.z,
|
||||||
|
baseMat.quat.w,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (boneNum < model->numRootBones)
|
||||||
|
{
|
||||||
|
bone.localOffset[0] = 0;
|
||||||
|
bone.localOffset[1] = 0;
|
||||||
|
bone.localOffset[2] = 0;
|
||||||
|
bone.localRotation = {0, 0, 0, 1};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3];
|
||||||
|
bone.localOffset[0] = trans[0];
|
||||||
|
bone.localOffset[1] = trans[1];
|
||||||
|
bone.localOffset[2] = trans[2];
|
||||||
|
|
||||||
|
const auto& quat = model->quats[boneNum - model->numRootBones];
|
||||||
|
bone.localRotation = {
|
||||||
|
QuatInt16::ToFloat(quat.v[0]),
|
||||||
|
QuatInt16::ToFloat(quat.v[1]),
|
||||||
|
QuatInt16::ToFloat(quat.v[2]),
|
||||||
|
QuatInt16::ToFloat(quat.v[3]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.m_bones.emplace_back(std::move(bone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char* AssetName(const char* input)
|
||||||
|
{
|
||||||
|
if (input && input[0] == ',')
|
||||||
|
return &input[1];
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
|
||||||
|
{
|
||||||
|
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
|
||||||
|
{
|
||||||
|
Material* material = model->materialHandles[surfaceMaterialNum];
|
||||||
|
if (materialMapper.Add(material))
|
||||||
|
{
|
||||||
|
XModelMaterial xMaterial;
|
||||||
|
xMaterial.ApplyDefaults();
|
||||||
|
|
||||||
|
xMaterial.name = AssetName(material->info.name);
|
||||||
|
const auto* colorMap = GetMaterialColorMap(material);
|
||||||
|
if (colorMap)
|
||||||
|
xMaterial.colorMapName = AssetName(colorMap->name);
|
||||||
|
|
||||||
|
const auto* normalMap = GetMaterialNormalMap(material);
|
||||||
|
if (normalMap)
|
||||||
|
xMaterial.normalMapName = AssetName(normalMap->name);
|
||||||
|
|
||||||
|
const auto* specularMap = GetMaterialSpecularMap(material);
|
||||||
|
if (specularMap)
|
||||||
|
xMaterial.specularMapName = AssetName(specularMap->name);
|
||||||
|
|
||||||
|
out.m_materials.emplace_back(std::move(xMaterial));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
|
||||||
|
{
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
XModelObject object;
|
||||||
|
object.name = std::format("surf{}", surfIndex);
|
||||||
|
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
|
||||||
|
|
||||||
|
out.m_objects.emplace_back(std::move(object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
|
||||||
|
if (!surfs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++)
|
||||||
|
{
|
||||||
|
const auto& v = surface.verts0[vertexIndex];
|
||||||
|
|
||||||
|
XModelVertex vertex{};
|
||||||
|
vertex.coordinates[0] = v.xyz.x;
|
||||||
|
vertex.coordinates[1] = v.xyz.y;
|
||||||
|
vertex.coordinates[2] = v.xyz.z;
|
||||||
|
Common::Vec3UnpackUnitVec(v.normal, vertex.normal);
|
||||||
|
Common::Vec4UnpackGfxColor(v.color, vertex.color);
|
||||||
|
Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv);
|
||||||
|
|
||||||
|
out.m_vertices.emplace_back(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
|
||||||
|
{
|
||||||
|
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
|
||||||
|
if (!surfs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto totalWeightCount = 0u;
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
if (surface.vertList)
|
||||||
|
{
|
||||||
|
totalWeightCount += surface.vertListCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface.vertInfo.vertsBlend)
|
||||||
|
{
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[0] * 1;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[1] * 2;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[2] * 3;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[3] * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weightCollection.weights.resize(totalWeightCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float BoneWeight16(const uint16_t value)
|
||||||
|
{
|
||||||
|
return static_cast<float>(value) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
auto& weightCollection = out.m_bone_weight_data;
|
||||||
|
|
||||||
|
if (!surfs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t weightOffset = 0u;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
auto handledVertices = 0u;
|
||||||
|
|
||||||
|
if (surface.vertList)
|
||||||
|
{
|
||||||
|
for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
|
||||||
|
{
|
||||||
|
const auto& vertList = surface.vertList[vertListIndex];
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{vertList.boneOffset / sizeof(DObjSkelMat), 1.0f};
|
||||||
|
|
||||||
|
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
|
||||||
|
{
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
||||||
|
}
|
||||||
|
handledVertices += vertList.vertCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vertsBlendOffset = 0u;
|
||||||
|
if (surface.vertInfo.vertsBlend)
|
||||||
|
{
|
||||||
|
// 1 bone weight
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f};
|
||||||
|
|
||||||
|
vertsBlendOffset += 1;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
|
||||||
|
vertsBlendOffset += 3;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
||||||
|
|
||||||
|
vertsBlendOffset += 5;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
||||||
|
const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight3 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3};
|
||||||
|
|
||||||
|
vertsBlendOffset += 7;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
handledVertices +=
|
||||||
|
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; handledVertices < surface.vertCount; handledVertices++)
|
||||||
|
{
|
||||||
|
out.m_vertex_bone_weights.emplace_back(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
|
||||||
|
if (!surfs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
auto& object = out.m_objects[surfIndex];
|
||||||
|
object.m_faces.reserve(surface.triCount);
|
||||||
|
|
||||||
|
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
|
||||||
|
{
|
||||||
|
const auto& tri = surface.triIndices[triIndex];
|
||||||
|
|
||||||
|
XModelFace face{};
|
||||||
|
face.vertexIndex[0] = tri.i[0] + surface.baseVertIndex;
|
||||||
|
face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex;
|
||||||
|
face.vertexIndex[2] = tri.i[2] + surface.baseVertIndex;
|
||||||
|
object.m_faces.emplace_back(face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
|
||||||
|
{
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
|
||||||
|
|
||||||
|
out.m_name = std::format("{}_lod{}", model->name, lod);
|
||||||
|
AddXModelMaterials(out, materialMapper, model);
|
||||||
|
AddXModelObjects(out, model, lod, materialMapper);
|
||||||
|
AddXModelVertices(out, model, lod);
|
||||||
|
AddXModelFaces(out, model, lod);
|
||||||
|
|
||||||
|
if (!HasDefaultArmature(model, lod))
|
||||||
|
{
|
||||||
|
AddXModelBones(out, context, model);
|
||||||
|
AddXModelVertexBoneWeights(out, model, lod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OmitDefaultArmature(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
|
||||||
|
|
||||||
|
if (!mtlFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer =
|
||||||
|
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void DumpGltfLod(
|
||||||
|
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod, const std::string& extension)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto output = std::make_unique<T>(*assetFile);
|
||||||
|
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
|
||||||
|
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
|
||||||
|
{
|
||||||
|
XModelCommon common;
|
||||||
|
PopulateXModelWriter(common, context, currentLod, asset->Asset());
|
||||||
|
|
||||||
|
switch (ObjWriting::Configuration.ModelOutputFormat)
|
||||||
|
{
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
||||||
|
DumpObjLod(common, context, asset, currentLod);
|
||||||
|
if (currentLod == 0u)
|
||||||
|
DumpObjMtl(common, context, asset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
||||||
|
DumpXModelExportLod(common, context, asset, currentLod);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
||||||
|
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
||||||
|
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonDumper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JsonDumper(AssetDumpingContext& context, std::ostream& stream)
|
||||||
|
: m_stream(stream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dump(const XModel* xmodel) const
|
||||||
|
{
|
||||||
|
JsonXModel jsonXModel;
|
||||||
|
CreateJsonXModel(jsonXModel, *xmodel);
|
||||||
|
nlohmann::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* GetExtensionForModelByConfig()
|
||||||
|
{
|
||||||
|
switch (ObjWriting::Configuration.ModelOutputFormat)
|
||||||
|
{
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
||||||
|
return ".XMODEL_EXPORT";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
||||||
|
return ".OBJ";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
||||||
|
return ".GLTF";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
||||||
|
return ".GLB";
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel)
|
||||||
|
{
|
||||||
|
if (xmodel.collLod >= 0)
|
||||||
|
jXModel.collLod = xmodel.collLod;
|
||||||
|
|
||||||
|
for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++)
|
||||||
|
{
|
||||||
|
JsonXModelLod lod;
|
||||||
|
lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig());
|
||||||
|
lod.distance = xmodel.lodInfo[lodNumber].dist;
|
||||||
|
|
||||||
|
jXModel.lods.emplace_back(std::move(lod));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#ifdef FEATURE_T6
|
||||||
|
jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x;
|
||||||
|
jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y;
|
||||||
|
jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z;
|
||||||
|
jXModel.lightingOriginRange = xmodel.lightingOriginRange;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& m_stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void DumpXModelJson(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name));
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const JsonDumper dumper(context, *assetFile);
|
||||||
|
dumper.Dump(asset->Asset());
|
||||||
|
}
|
||||||
|
} // namespace GAME_NAMESPACE
|
@ -6,6 +6,8 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#define LTC_NO_PROTOTYPES
|
#define LTC_NO_PROTOTYPES
|
||||||
|
#include "Impl/Base64.h"
|
||||||
|
|
||||||
#include <tomcrypt.h>
|
#include <tomcrypt.h>
|
||||||
|
|
||||||
using namespace gltf;
|
using namespace gltf;
|
||||||
@ -17,18 +19,15 @@ TextOutput::TextOutput(std::ostream& stream)
|
|||||||
|
|
||||||
std::optional<std::string> TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const
|
std::optional<std::string> TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const
|
||||||
{
|
{
|
||||||
const auto base64Length = 4u * ((bufferSize + 2u) / 3u);
|
const auto base64Length = base64::GetBase64EncodeOutputLength(bufferSize);
|
||||||
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
|
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
|
||||||
|
|
||||||
std::string output(base64BufferSize, '\0');
|
std::string output(base64BufferSize, '\0');
|
||||||
|
|
||||||
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
|
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
|
||||||
|
|
||||||
unsigned long outLength = base64Length + 1u;
|
auto result = base64::EncodeBase64(buffer, bufferSize, &output[URI_PREFIX_LENGTH], base64Length + 1u);
|
||||||
const auto result = base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
|
assert(result);
|
||||||
|
|
||||||
assert(result == CRYPT_OK);
|
|
||||||
assert(outLength == base64Length);
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
733
src/ObjWriting/XModel/XModelDumper.cpp.template
Normal file
733
src/ObjWriting/XModel/XModelDumper.cpp.template
Normal file
@ -0,0 +1,733 @@
|
|||||||
|
#options GAME (IW5, T5, T6)
|
||||||
|
|
||||||
|
#filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".cpp"
|
||||||
|
|
||||||
|
#set DUMPER_HEADER "\"XModelDumper" + GAME + ".h\""
|
||||||
|
#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\""
|
||||||
|
#set JSON_HEADER "\"Game/" + GAME + "/XModel/JsonXModel" + GAME + ".h\""
|
||||||
|
|
||||||
|
#if GAME == "IW5"
|
||||||
|
#define FEATURE_IW5
|
||||||
|
#elif GAME == "T5"
|
||||||
|
#define FEATURE_T5
|
||||||
|
#elif GAME == "T6"
|
||||||
|
#define FEATURE_T6
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include DUMPER_HEADER
|
||||||
|
|
||||||
|
#include COMMON_HEADER
|
||||||
|
#include JSON_HEADER
|
||||||
|
|
||||||
|
#include "ObjWriting.h"
|
||||||
|
#include "Utils/DistinctMapper.h"
|
||||||
|
#include "Utils/QuatInt16.h"
|
||||||
|
#include "XModel/Export/XModelExportWriter.h"
|
||||||
|
#include "XModel/Gltf/GltfBinOutput.h"
|
||||||
|
#include "XModel/Gltf/GltfTextOutput.h"
|
||||||
|
#include "XModel/Gltf/GltfWriter.h"
|
||||||
|
#include "XModel/Obj/ObjWriter.h"
|
||||||
|
#include "XModel/XModelWriter.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace GAME
|
||||||
|
{
|
||||||
|
std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
|
||||||
|
{
|
||||||
|
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
GfxImage* GetImageFromTextureDef(const MaterialTextureDef& textureDef)
|
||||||
|
{
|
||||||
|
#ifdef FEATURE_T6
|
||||||
|
return textureDef.image;
|
||||||
|
#else
|
||||||
|
return textureDef.u.image;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
GfxImage* GetMaterialColorMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
if (def->semantic == TS_COLOR_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
#else
|
||||||
|
if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GfxImage* GetMaterialNormalMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
if (def->semantic == TS_NORMAL_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (def->nameStart == 'n' && def->nameEnd == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GfxImage* GetMaterialSpecularMap(const Material* material)
|
||||||
|
{
|
||||||
|
std::vector<MaterialTextureDef*> potentialTextureDefs;
|
||||||
|
|
||||||
|
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
|
||||||
|
{
|
||||||
|
MaterialTextureDef* def = &material->textureTable[textureIndex];
|
||||||
|
|
||||||
|
if (def->semantic == TS_SPECULAR_MAP)
|
||||||
|
potentialTextureDefs.push_back(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialTextureDefs.empty())
|
||||||
|
return nullptr;
|
||||||
|
if (potentialTextureDefs.size() == 1)
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
|
||||||
|
for (const auto* def : potentialTextureDefs)
|
||||||
|
{
|
||||||
|
if (def->nameStart == 's' && def->nameEnd == 'p')
|
||||||
|
return GetImageFromTextureDef(*def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetImageFromTextureDef(*potentialTextureDefs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetSurfaces(const XModel* model, const unsigned lod, XSurface*& surfs, unsigned& surfCount)
|
||||||
|
{
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
if (!model->lodInfo[lod].modelSurfs || !model->lodInfo[lod].modelSurfs->surfs)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
surfs = model->lodInfo[lod].modelSurfs->surfs;
|
||||||
|
surfCount = model->lodInfo[lod].modelSurfs->numsurfs;
|
||||||
|
#else
|
||||||
|
if (!model->surfs)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
surfs = &model->surfs[model->lodInfo[lod].surfIndex];
|
||||||
|
surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasDefaultArmature(const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
if (model->numRootBones != 1 || model->numBones != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
XSurface* surfs;
|
||||||
|
unsigned surfCount;
|
||||||
|
if (!GetSurfaces(model, lod, surfs, surfCount))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto& vertList = surface.vertList[0];
|
||||||
|
if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmitDefaultArmature(XModelCommon& common)
|
||||||
|
{
|
||||||
|
common.m_bones.clear();
|
||||||
|
common.m_bone_weight_data.weights.clear();
|
||||||
|
common.m_vertex_bone_weights.resize(common.m_vertices.size());
|
||||||
|
for (auto& vertexWeights : common.m_vertex_bone_weights)
|
||||||
|
{
|
||||||
|
vertexWeights.weightOffset = 0u;
|
||||||
|
vertexWeights.weightCount = 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
|
||||||
|
{
|
||||||
|
for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
|
||||||
|
{
|
||||||
|
XModelBone bone;
|
||||||
|
if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count())
|
||||||
|
bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]];
|
||||||
|
else
|
||||||
|
bone.name = "INVALID_BONE_NAME";
|
||||||
|
|
||||||
|
if (boneNum >= model->numRootBones)
|
||||||
|
bone.parentIndex = static_cast<int>(boneNum - static_cast<unsigned int>(model->parentList[boneNum - model->numRootBones]));
|
||||||
|
else
|
||||||
|
bone.parentIndex = std::nullopt;
|
||||||
|
|
||||||
|
bone.scale[0] = 1.0f;
|
||||||
|
bone.scale[1] = 1.0f;
|
||||||
|
bone.scale[2] = 1.0f;
|
||||||
|
|
||||||
|
const auto& baseMat = model->baseMat[boneNum];
|
||||||
|
bone.globalOffset[0] = baseMat.trans.x;
|
||||||
|
bone.globalOffset[1] = baseMat.trans.y;
|
||||||
|
bone.globalOffset[2] = baseMat.trans.z;
|
||||||
|
bone.globalRotation = {
|
||||||
|
baseMat.quat.x,
|
||||||
|
baseMat.quat.y,
|
||||||
|
baseMat.quat.z,
|
||||||
|
baseMat.quat.w,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (boneNum < model->numRootBones)
|
||||||
|
{
|
||||||
|
bone.localOffset[0] = 0;
|
||||||
|
bone.localOffset[1] = 0;
|
||||||
|
bone.localOffset[2] = 0;
|
||||||
|
bone.localRotation = {0, 0, 0, 1};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3];
|
||||||
|
bone.localOffset[0] = trans[0];
|
||||||
|
bone.localOffset[1] = trans[1];
|
||||||
|
bone.localOffset[2] = trans[2];
|
||||||
|
|
||||||
|
const auto& quat = model->quats[boneNum - model->numRootBones];
|
||||||
|
bone.localRotation = {
|
||||||
|
QuatInt16::ToFloat(quat.v[0]),
|
||||||
|
QuatInt16::ToFloat(quat.v[1]),
|
||||||
|
QuatInt16::ToFloat(quat.v[2]),
|
||||||
|
QuatInt16::ToFloat(quat.v[3]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.m_bones.emplace_back(std::move(bone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AssetName(const char* input)
|
||||||
|
{
|
||||||
|
if (input && input[0] == ',')
|
||||||
|
return &input[1];
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
|
||||||
|
{
|
||||||
|
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
|
||||||
|
{
|
||||||
|
Material* material = model->materialHandles[surfaceMaterialNum];
|
||||||
|
if (materialMapper.Add(material))
|
||||||
|
{
|
||||||
|
XModelMaterial xMaterial;
|
||||||
|
xMaterial.ApplyDefaults();
|
||||||
|
|
||||||
|
xMaterial.name = AssetName(material->info.name);
|
||||||
|
const auto* colorMap = GetMaterialColorMap(material);
|
||||||
|
if (colorMap)
|
||||||
|
xMaterial.colorMapName = AssetName(colorMap->name);
|
||||||
|
|
||||||
|
const auto* normalMap = GetMaterialNormalMap(material);
|
||||||
|
if (normalMap)
|
||||||
|
xMaterial.normalMapName = AssetName(normalMap->name);
|
||||||
|
|
||||||
|
const auto* specularMap = GetMaterialSpecularMap(material);
|
||||||
|
if (specularMap)
|
||||||
|
xMaterial.specularMapName = AssetName(specularMap->name);
|
||||||
|
|
||||||
|
out.m_materials.emplace_back(std::move(xMaterial));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
|
||||||
|
{
|
||||||
|
const auto surfCount = model->lodInfo[lod].numsurfs;
|
||||||
|
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
XModelObject object;
|
||||||
|
object.name = std::format("surf{}", surfIndex);
|
||||||
|
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
|
||||||
|
|
||||||
|
out.m_objects.emplace_back(std::move(object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
XSurface* surfs;
|
||||||
|
unsigned surfCount;
|
||||||
|
if (!GetSurfaces(model, lod, surfs, surfCount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++)
|
||||||
|
{
|
||||||
|
const auto& v = surface.verts0[vertexIndex];
|
||||||
|
|
||||||
|
XModelVertex vertex{};
|
||||||
|
vertex.coordinates[0] = v.xyz.x;
|
||||||
|
vertex.coordinates[1] = v.xyz.y;
|
||||||
|
vertex.coordinates[2] = v.xyz.z;
|
||||||
|
Common::Vec3UnpackUnitVec(v.normal, vertex.normal);
|
||||||
|
Common::Vec4UnpackGfxColor(v.color, vertex.color);
|
||||||
|
Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv);
|
||||||
|
|
||||||
|
out.m_vertices.emplace_back(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
|
||||||
|
{
|
||||||
|
XSurface* surfs;
|
||||||
|
unsigned surfCount;
|
||||||
|
if (!GetSurfaces(model, lod, surfs, surfCount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto totalWeightCount = 0u;
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
|
||||||
|
if (surface.vertList)
|
||||||
|
{
|
||||||
|
totalWeightCount += surface.vertListCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface.vertInfo.vertsBlend)
|
||||||
|
{
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[0] * 1;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[1] * 2;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[2] * 3;
|
||||||
|
totalWeightCount += surface.vertInfo.vertCount[3] * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weightCollection.weights.resize(totalWeightCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BoneWeight16(const uint16_t value)
|
||||||
|
{
|
||||||
|
return static_cast<float>(value) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
XSurface* surfs;
|
||||||
|
unsigned surfCount;
|
||||||
|
if (!GetSurfaces(model, lod, surfs, surfCount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& weightCollection = out.m_bone_weight_data;
|
||||||
|
size_t weightOffset = 0u;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
auto handledVertices = 0u;
|
||||||
|
|
||||||
|
if (surface.vertList)
|
||||||
|
{
|
||||||
|
for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
|
||||||
|
{
|
||||||
|
const auto& vertList = surface.vertList[vertListIndex];
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{vertList.boneOffset / sizeof(DObjSkelMat), 1.0f};
|
||||||
|
|
||||||
|
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
|
||||||
|
{
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
||||||
|
}
|
||||||
|
handledVertices += vertList.vertCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vertsBlendOffset = 0u;
|
||||||
|
if (surface.vertInfo.vertsBlend)
|
||||||
|
{
|
||||||
|
// 1 bone weight
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f};
|
||||||
|
|
||||||
|
vertsBlendOffset += 1;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
|
||||||
|
vertsBlendOffset += 3;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
||||||
|
|
||||||
|
vertsBlendOffset += 5;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bone weights
|
||||||
|
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++)
|
||||||
|
{
|
||||||
|
const auto boneWeightOffset = weightOffset;
|
||||||
|
const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]);
|
||||||
|
const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]);
|
||||||
|
const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat);
|
||||||
|
const auto boneWeight3 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]);
|
||||||
|
const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3;
|
||||||
|
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2};
|
||||||
|
weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3};
|
||||||
|
|
||||||
|
vertsBlendOffset += 7;
|
||||||
|
|
||||||
|
out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
handledVertices +=
|
||||||
|
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; handledVertices < surface.vertCount; handledVertices++)
|
||||||
|
{
|
||||||
|
out.m_vertex_bone_weights.emplace_back(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
|
||||||
|
{
|
||||||
|
XSurface* surfs;
|
||||||
|
unsigned surfCount;
|
||||||
|
if (!GetSurfaces(model, lod, surfs, surfCount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
|
||||||
|
{
|
||||||
|
const auto& surface = surfs[surfIndex];
|
||||||
|
auto& object = out.m_objects[surfIndex];
|
||||||
|
object.m_faces.reserve(surface.triCount);
|
||||||
|
|
||||||
|
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
|
||||||
|
{
|
||||||
|
const auto& tri = surface.triIndices[triIndex];
|
||||||
|
|
||||||
|
XModelFace face{};
|
||||||
|
face.vertexIndex[0] = tri.i[0] + surface.baseVertIndex;
|
||||||
|
face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex;
|
||||||
|
face.vertexIndex[2] = tri.i[2] + surface.baseVertIndex;
|
||||||
|
object.m_faces.emplace_back(face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
|
||||||
|
{
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
|
||||||
|
|
||||||
|
out.m_name = std::format("{}_lod{}", model->name, lod);
|
||||||
|
AddXModelMaterials(out, materialMapper, model);
|
||||||
|
AddXModelObjects(out, model, lod, materialMapper);
|
||||||
|
AddXModelVertices(out, model, lod);
|
||||||
|
AddXModelFaces(out, model, lod);
|
||||||
|
|
||||||
|
if (!HasDefaultArmature(model, lod))
|
||||||
|
{
|
||||||
|
AddXModelBones(out, context, model);
|
||||||
|
AddXModelVertexBoneWeights(out, model, lod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OmitDefaultArmature(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
|
||||||
|
|
||||||
|
if (!mtlFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer =
|
||||||
|
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
DistinctMapper<Material*> materialMapper(model->numsurfs);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void DumpGltfLod(
|
||||||
|
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod, const std::string& extension)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension));
|
||||||
|
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto output = std::make_unique<T>(*assetFile);
|
||||||
|
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
|
||||||
|
|
||||||
|
writer->Write(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto* model = asset->Asset();
|
||||||
|
|
||||||
|
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
|
||||||
|
{
|
||||||
|
XModelCommon common;
|
||||||
|
PopulateXModelWriter(common, context, currentLod, asset->Asset());
|
||||||
|
|
||||||
|
switch (ObjWriting::Configuration.ModelOutputFormat)
|
||||||
|
{
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
||||||
|
DumpObjLod(common, context, asset, currentLod);
|
||||||
|
if (currentLod == 0u)
|
||||||
|
DumpObjMtl(common, context, asset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
||||||
|
DumpXModelExportLod(common, context, asset, currentLod);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
||||||
|
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
||||||
|
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonDumper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JsonDumper(AssetDumpingContext& context, std::ostream& stream)
|
||||||
|
: m_stream(stream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dump(const XModel* xmodel) const
|
||||||
|
{
|
||||||
|
JsonXModel jsonXModel;
|
||||||
|
CreateJsonXModel(jsonXModel, *xmodel);
|
||||||
|
nlohmann::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* GetExtensionForModelByConfig()
|
||||||
|
{
|
||||||
|
switch (ObjWriting::Configuration.ModelOutputFormat)
|
||||||
|
{
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
|
||||||
|
return ".XMODEL_EXPORT";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
|
||||||
|
return ".OBJ";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
|
||||||
|
return ".GLTF";
|
||||||
|
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
|
||||||
|
return ".GLB";
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel)
|
||||||
|
{
|
||||||
|
if (xmodel.collLod >= 0)
|
||||||
|
jXModel.collLod = xmodel.collLod;
|
||||||
|
|
||||||
|
for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++)
|
||||||
|
{
|
||||||
|
JsonXModelLod lod;
|
||||||
|
lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig());
|
||||||
|
lod.distance = xmodel.lodInfo[lodNumber].dist;
|
||||||
|
|
||||||
|
jXModel.lods.emplace_back(std::move(lod));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmodel.physPreset && xmodel.physPreset->name)
|
||||||
|
jXModel.physPreset = AssetName(xmodel.physPreset->name);
|
||||||
|
|
||||||
|
#ifdef FEATURE_IW5
|
||||||
|
if (xmodel.physCollmap && xmodel.physCollmap->name)
|
||||||
|
jXModel.physCollmap = AssetName(xmodel.physCollmap->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||||
|
if (xmodel.physConstraints && xmodel.physConstraints->name)
|
||||||
|
jXModel.physConstraints = AssetName(xmodel.physConstraints->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
jXModel.flags = xmodel.flags;
|
||||||
|
|
||||||
|
#ifdef FEATURE_T6
|
||||||
|
jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x;
|
||||||
|
jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y;
|
||||||
|
jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z;
|
||||||
|
jXModel.lightingOriginRange = xmodel.lightingOriginRange;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& m_stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
void DumpXModelJson(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name));
|
||||||
|
if (!assetFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const JsonDumper dumper(context, *assetFile);
|
||||||
|
dumper.Dump(asset->Asset());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpXModel(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
|
||||||
|
{
|
||||||
|
DumpXModelJson(context, asset);
|
||||||
|
DumpXModelSurfs(context, asset);
|
||||||
|
}
|
||||||
|
} // namespace GAME
|
15
src/ObjWriting/XModel/XModelDumper.h.template
Normal file
15
src/ObjWriting/XModel/XModelDumper.h.template
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#options GAME (IW5, T5, T6)
|
||||||
|
|
||||||
|
#filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".h"
|
||||||
|
|
||||||
|
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Dumping/AssetDumpingContext.h"
|
||||||
|
#include GAME_HEADER
|
||||||
|
|
||||||
|
namespace GAME
|
||||||
|
{
|
||||||
|
void DumpXModel(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
|
||||||
|
}
|
@ -13,7 +13,7 @@ set count parentList numBones - numRootBones;
|
|||||||
set reusable quats;
|
set reusable quats;
|
||||||
set count quats numBones - numRootBones;
|
set count quats numBones - numRootBones;
|
||||||
set reusable trans;
|
set reusable trans;
|
||||||
set count trans numBones - numRootBones;
|
set count trans (numBones - numRootBones) * 3;
|
||||||
set reusable partClassification;
|
set reusable partClassification;
|
||||||
set count partClassification numBones;
|
set count partClassification numBones;
|
||||||
set reusable baseMat;
|
set reusable baseMat;
|
||||||
|
@ -14,6 +14,9 @@ set count vertList vertListCount;
|
|||||||
set reusable triIndices;
|
set reusable triIndices;
|
||||||
set block triIndices XFILE_BLOCK_INDEX;
|
set block triIndices XFILE_BLOCK_INDEX;
|
||||||
set count triIndices triCount;
|
set count triIndices triCount;
|
||||||
|
set reusable verts0;
|
||||||
|
set block verts0 XFILE_BLOCK_VERTEX;
|
||||||
|
set count verts0 XSurface::vertCount;
|
||||||
reorder:
|
reorder:
|
||||||
zoneHandle
|
zoneHandle
|
||||||
vertInfo
|
vertInfo
|
||||||
@ -29,15 +32,6 @@ set count vertsBlend vertCount[0]
|
|||||||
+ 5 * vertCount[2]
|
+ 5 * vertCount[2]
|
||||||
+ 7 * vertCount[3];
|
+ 7 * vertCount[3];
|
||||||
|
|
||||||
// GfxVertexUnion0
|
|
||||||
use GfxVertexUnion0;
|
|
||||||
set condition quantizedNoColorVerts0 never;
|
|
||||||
set condition quantizedVerts0 never;
|
|
||||||
set condition verts0 never;
|
|
||||||
set reusable packedVerts0;
|
|
||||||
set block packedVerts0 XFILE_BLOCK_VERTEX;
|
|
||||||
set count packedVerts0 XSurface::vertCount;
|
|
||||||
|
|
||||||
// XRigidVertList
|
// XRigidVertList
|
||||||
set reusable XRigidVertList::collisionTree;
|
set reusable XRigidVertList::collisionTree;
|
||||||
|
|
||||||
|
66
tools/scripts/source_templating.lua
Normal file
66
tools/scripts/source_templating.lua
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
function useSourceTemplating(projectName)
|
||||||
|
local projectFolder = path.join(ProjectFolder(), projectName)
|
||||||
|
local templateFiles = os.matchfiles(path.join(projectFolder, "**.template"))
|
||||||
|
|
||||||
|
local createdFiles = {}
|
||||||
|
|
||||||
|
for i = 1, #templateFiles do
|
||||||
|
local templateFile = templateFiles[i]
|
||||||
|
local relativeTemplatePath = path.getrelative(projectFolder, templateFile)
|
||||||
|
local relativeResultPath = path.replaceextension(relativeTemplatePath, "")
|
||||||
|
local resultExtension = path.getextension(relativeResultPath)
|
||||||
|
|
||||||
|
local data = io.readfile(templateFile)
|
||||||
|
local gameOptionsStart, gameOptionsCount = string.find(data, "#options%s+GAME%s*%(")
|
||||||
|
|
||||||
|
if gameOptionsStart == nil then
|
||||||
|
error("Source template " .. relativeTemplatePath .. " must define an option called GAME")
|
||||||
|
end
|
||||||
|
|
||||||
|
local gameOptionsPos, gameOptionsLenPlusOne = string.find(data, "[%a%d%s,]+%)", gameOptionsStart + gameOptionsCount)
|
||||||
|
|
||||||
|
if gameOptionsPos ~= gameOptionsStart + gameOptionsCount then
|
||||||
|
error("Source template " .. relativeTemplatePath .. " must define an option called GAME")
|
||||||
|
end
|
||||||
|
|
||||||
|
local gameOptions = string.sub(data, gameOptionsPos, gameOptionsLenPlusOne - 1)
|
||||||
|
local games = string.explode(gameOptions, ",%s*")
|
||||||
|
|
||||||
|
files {
|
||||||
|
templateFile
|
||||||
|
}
|
||||||
|
|
||||||
|
filter("files:" .. templateFile)
|
||||||
|
buildmessage("Templating source file " .. relativeTemplatePath)
|
||||||
|
buildinputs {
|
||||||
|
TargetDirectoryBuildTools .. "/" .. ExecutableByOs('RawTemplater')
|
||||||
|
}
|
||||||
|
buildcommands {
|
||||||
|
'"' .. TargetDirectoryBuildTools .. '/' .. ExecutableByOs('RawTemplater') .. '"'
|
||||||
|
.. ' -o "%{prj.location}/"'
|
||||||
|
.. " %{file.relpath}"
|
||||||
|
}
|
||||||
|
for i = 1, #games do
|
||||||
|
local gameName = games[i]
|
||||||
|
local outputFileName = path.replaceextension(path.replaceextension(relativeResultPath, "") .. gameName, resultExtension)
|
||||||
|
local outputFile = "%{prj.location}/Game/" .. gameName .. "/" .. outputFileName
|
||||||
|
|
||||||
|
table.insert(createdFiles, outputFile)
|
||||||
|
|
||||||
|
buildoutputs {
|
||||||
|
outputFile
|
||||||
|
}
|
||||||
|
end
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
includedirs {
|
||||||
|
"%{prj.location}"
|
||||||
|
}
|
||||||
|
|
||||||
|
files {
|
||||||
|
createdFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
RawTemplater:use()
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user