feat: first draft of loading gltf models for t6

This commit is contained in:
Jan 2024-05-25 10:03:25 +02:00
parent f8b5734f86
commit 1f5050befa
No known key found for this signature in database
GPG Key ID: 44B581F78FF5C57C
40 changed files with 2459 additions and 67 deletions

View 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
1 J_Hip_RI right_leg_upper
2 J_Hip_LE left_leg_upper
3 J_Knee_RI right_leg_lower
4 J_SpineUpper torso_upper
5 J_Knee_LE left_leg_lower
6 J_Ankle_RI right_foot
7 J_Ankle_LE left_foot
8 J_Clavicle_RI torso_upper
9 J_Clavicle_LE torso_upper
10 J_Shoulder_RI right_arm_upper
11 J_Shoulder_LE left_arm_upper
12 J_Neck neck
13 J_Head head
14 J_Elbow_RI right_arm_lower
15 J_Elbow_LE left_arm_lower
16 J_Wrist_RI right_hand
17 J_Wrist_LE left_hand
18 J_MainRoot torso_lower
19 TAG_WEAPON_LEFT gun
20 TAG_WEAPON_RIGHT gun
21 J_Helmet helmet

View 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
1 J_Hip_RI right_leg_upper
2 J_Hip_LE left_leg_upper
3 J_Knee_RI right_leg_lower
4 J_SpineUpper torso_lower
5 J_SpineLower torso_lower
6 J_MainRoot torso_lower
7 J_Knee_LE left_leg_lower
8 J_Ankle_RI right_foot
9 J_Ankle_LE left_foot
10 J_Clavicle_RI torso_upper
11 J_Clavicle_LE torso_upper
12 J_Shoulder_RI right_arm_upper
13 J_Shoulder_LE left_arm_upper
14 J_Neck neck
15 J_Head head
16 J_Elbow_RI right_arm_lower
17 J_Elbow_LE left_arm_lower
18 J_Wrist_RI right_hand
19 J_Wrist_LE left_hand

View 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
1 J_Hip_RI right_leg_upper
2 J_Hip_LE left_leg_upper
3 J_Knee_RI right_leg_lower
4 J_SpineUpper torso_upper
5 J_Knee_LE left_leg_lower
6 J_Ankle_RI right_foot
7 J_Ankle_LE left_foot
8 J_Clavicle_RI torso_upper
9 J_Clavicle_LE torso_upper
10 J_Shoulder_RI right_arm_upper
11 J_Shoulder_LE left_arm_upper
12 J_Neck neck
13 J_Head head
14 J_Elbow_RI right_arm_lower
15 J_Elbow_LE left_arm_lower
16 J_Wrist_RI right_hand
17 J_Wrist_LE left_hand
18 J_MainRoot torso_lower
19 TAG_WEAPON_LEFT gun
20 TAG_WEAPON_RIGHT gun
21 J_Helmet helmet

View 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
1 J_Hip_RI right_leg_upper
2 J_Hip_LE left_leg_upper
3 J_Knee_RI right_leg_lower
4 J_SpineUpper torso_lower
5 J_SpineLower torso_lower
6 J_MainRoot torso_lower
7 J_Knee_LE left_leg_lower
8 J_Ankle_RI right_foot
9 J_Ankle_LE left_foot
10 J_Clavicle_RI torso_upper
11 J_Clavicle_LE torso_upper
12 J_Shoulder_RI right_arm_upper
13 J_Shoulder_LE left_arm_upper
14 J_Neck neck
15 J_Head head
16 J_Elbow_RI right_arm_lower
17 J_Elbow_LE left_arm_lower
18 J_Wrist_RI right_hand
19 J_Wrist_LE left_hand

View File

@ -0,0 +1,140 @@
j_helmet,helmet
head,02
j_brow_le,02
j_brow_ri,02
j_cheek_le,02
j_cheek_ri,02
j_cheekpuff_le,02
j_cheekpuff_ri,02
j_eye_lid_bot_le,02
j_eye_lid_bot_ri,02
j_eye_lid_top_le,02
j_eye_lid_top_ri,02
j_eyeball_le,02
j_eyeball_ri,02
j_eyebrow_top_le,02
j_eyebrow_top_ri,02
j_eyelid_bottom_le,02
j_eyelid_bottom_ri,02
j_eyelid_top_le,02
j_eyelid_top_ri,02
j_head,02
j_head_end,02
j_helmet,02
j_jaw,02
j_levator_le,02
j_levator_ri,02
j_lip_bot_le,02
j_lip_bot_ri,02
j_lip_top_le,02
j_lip_top_ri,02
j_mouth_le,02
j_mouth_ri,02
j_nose,02
le_ear,02
le_ear_end,02
ri_ear,02
ri_ear_end,02
tag_eye,02
tag_mouth_fx,02
j_neck,03
j_neck_end,03
neck,03
j_clavicle_le,04
j_clavicle_ri,04
back_mid,05
j_shoulderraise_le,05
j_shoulderraise_ri,05
j_spine4,05
j_spineupper,05
tag_inhand,05
tag_weapon_chest,05
torso_stabilizer,06
j_elbow_bulge_ri,07
j_shoulder_ri,07
j_shouldertwist_ri,07
shoulder,07
j_elbow_bulge_le,08
j_shoulder_le,08
j_shouldertwist_le,08
j_elbow_ri,09
j_sleave_reshape_top_ri_1,09
j_sleave_reshape_top_ri_2,09
j_sleave_reshape_top_ri_3,09
j_wristtwist_ri,09
j_elbow_le,10
j_sleave_reshape_bottom_le_1,10
j_sleave_reshape_bottom_le_2,10
j_sleave_reshape_top_le_1,10
j_sleave_reshape_top_le_2,10
j_sleave_reshape_top_le_3,10
j_wristtwist_le,10
j_index_ri_0,11
j_index_ri_1,11
j_index_ri_2,11
j_index_ri_3,11
j_mid_ri_0,11
j_mid_ri_1,11
j_mid_ri_2,11
j_mid_ri_3,11
j_palm_end_ri,11
j_palm_ri,11
j_pinky_ri_0,11
j_pinky_ri_1,11
j_pinky_ri_2,11
j_pinky_ri_3,11
j_pinkypalm_ri,11
j_ring_ri_0,11
j_ring_ri_1,11
j_ring_ri_2,11
j_ring_ri_3,11
j_ringpalm_ri,11
j_thumb_ri_0,11
j_thumb_ri_1,11
j_thumb_ri_2,11
j_thumb_ri_3,11
j_webbing_ri,11
j_wrist_ri,11
tag_weapon_right,11
j_index_le_0,12
j_index_le_1,12
j_index_le_2,12
j_index_le_3,12
j_mid_le_0,12
j_mid_le_1,12
j_mid_le_2,12
j_mid_le_3,12
j_palm_end_le,12
j_palm_le,12
j_pinky_le_0,12
j_pinky_le_1,12
j_pinky_le_2,12
j_pinky_le_3,12
j_pinkypalm_le,12
j_ring_le_0,12
j_ring_le_1,12
j_ring_le_2,12
j_ring_le_3,12
j_ringpalm_le,12
j_thumb_le_0,12
j_thumb_le_1,12
j_thumb_le_2,12
j_thumb_le_3,12
j_webbing_le,12
j_wrist_le,12
tag_knife_attach,12
j_hip_ri,13
j_hip_le,14
j_hiptwist_le,14
j_knee_bulge_ri,15
j_knee_ri,15
j_knee_bulge_le,16
j_knee_le,16
j_ankle_ri,17
j_ball_ri,17
j_toe_ri,17
j_ankle_le,18
j_ball_le,18
j_toe_le,18
tag_stowed_back,20
tag_weapon_left,20
1 j_helmet helmet
2 head 02
3 j_brow_le 02
4 j_brow_ri 02
5 j_cheek_le 02
6 j_cheek_ri 02
7 j_cheekpuff_le 02
8 j_cheekpuff_ri 02
9 j_eye_lid_bot_le 02
10 j_eye_lid_bot_ri 02
11 j_eye_lid_top_le 02
12 j_eye_lid_top_ri 02
13 j_eyeball_le 02
14 j_eyeball_ri 02
15 j_eyebrow_top_le 02
16 j_eyebrow_top_ri 02
17 j_eyelid_bottom_le 02
18 j_eyelid_bottom_ri 02
19 j_eyelid_top_le 02
20 j_eyelid_top_ri 02
21 j_head 02
22 j_head_end 02
23 j_helmet 02
24 j_jaw 02
25 j_levator_le 02
26 j_levator_ri 02
27 j_lip_bot_le 02
28 j_lip_bot_ri 02
29 j_lip_top_le 02
30 j_lip_top_ri 02
31 j_mouth_le 02
32 j_mouth_ri 02
33 j_nose 02
34 le_ear 02
35 le_ear_end 02
36 ri_ear 02
37 ri_ear_end 02
38 tag_eye 02
39 tag_mouth_fx 02
40 j_neck 03
41 j_neck_end 03
42 neck 03
43 j_clavicle_le 04
44 j_clavicle_ri 04
45 back_mid 05
46 j_shoulderraise_le 05
47 j_shoulderraise_ri 05
48 j_spine4 05
49 j_spineupper 05
50 tag_inhand 05
51 tag_weapon_chest 05
52 torso_stabilizer 06
53 j_elbow_bulge_ri 07
54 j_shoulder_ri 07
55 j_shouldertwist_ri 07
56 shoulder 07
57 j_elbow_bulge_le 08
58 j_shoulder_le 08
59 j_shouldertwist_le 08
60 j_elbow_ri 09
61 j_sleave_reshape_top_ri_1 09
62 j_sleave_reshape_top_ri_2 09
63 j_sleave_reshape_top_ri_3 09
64 j_wristtwist_ri 09
65 j_elbow_le 10
66 j_sleave_reshape_bottom_le_1 10
67 j_sleave_reshape_bottom_le_2 10
68 j_sleave_reshape_top_le_1 10
69 j_sleave_reshape_top_le_2 10
70 j_sleave_reshape_top_le_3 10
71 j_wristtwist_le 10
72 j_index_ri_0 11
73 j_index_ri_1 11
74 j_index_ri_2 11
75 j_index_ri_3 11
76 j_mid_ri_0 11
77 j_mid_ri_1 11
78 j_mid_ri_2 11
79 j_mid_ri_3 11
80 j_palm_end_ri 11
81 j_palm_ri 11
82 j_pinky_ri_0 11
83 j_pinky_ri_1 11
84 j_pinky_ri_2 11
85 j_pinky_ri_3 11
86 j_pinkypalm_ri 11
87 j_ring_ri_0 11
88 j_ring_ri_1 11
89 j_ring_ri_2 11
90 j_ring_ri_3 11
91 j_ringpalm_ri 11
92 j_thumb_ri_0 11
93 j_thumb_ri_1 11
94 j_thumb_ri_2 11
95 j_thumb_ri_3 11
96 j_webbing_ri 11
97 j_wrist_ri 11
98 tag_weapon_right 11
99 j_index_le_0 12
100 j_index_le_1 12
101 j_index_le_2 12
102 j_index_le_3 12
103 j_mid_le_0 12
104 j_mid_le_1 12
105 j_mid_le_2 12
106 j_mid_le_3 12
107 j_palm_end_le 12
108 j_palm_le 12
109 j_pinky_le_0 12
110 j_pinky_le_1 12
111 j_pinky_le_2 12
112 j_pinky_le_3 12
113 j_pinkypalm_le 12
114 j_ring_le_0 12
115 j_ring_le_1 12
116 j_ring_le_2 12
117 j_ring_le_3 12
118 j_ringpalm_le 12
119 j_thumb_le_0 12
120 j_thumb_le_1 12
121 j_thumb_le_2 12
122 j_thumb_le_3 12
123 j_webbing_le 12
124 j_wrist_le 12
125 tag_knife_attach 12
126 j_hip_ri 13
127 j_hip_le 14
128 j_hiptwist_le 14
129 j_knee_bulge_ri 15
130 j_knee_ri 15
131 j_knee_bulge_le 16
132 j_knee_le 16
133 j_ankle_ri 17
134 j_ball_ri 17
135 j_toe_ri 17
136 j_ankle_le 18
137 j_ball_le 18
138 j_toe_le 18
139 tag_stowed_back 20
140 tag_weapon_left 20

View File

@ -4082,7 +4082,7 @@ namespace IW4
HITLOC_GUN = 0x12, HITLOC_GUN = 0x12,
HITLOC_SHIELD = 0x13, HITLOC_SHIELD = 0x13,
HITLOC_NUM, HITLOC_COUNT,
}; };
struct snd_alias_list_name struct snd_alias_list_name

View File

@ -3452,7 +3452,7 @@ namespace IW5
HITLOC_GUN = 0x12, HITLOC_GUN = 0x12,
HITLOC_SHIELD = 0x13, HITLOC_SHIELD = 0x13,
HITLOC_NUM HITLOC_COUNT
}; };
enum materialSurfType_t enum materialSurfType_t

View File

@ -108,9 +108,49 @@ namespace T5
MAX_XFILE_COUNT MAX_XFILE_COUNT
}; };
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;
};
};
union XAssetHeader union XAssetHeader
{ {
@ -166,8 +206,6 @@ namespace T5
typedef tdef_align(128) float float_align128; typedef tdef_align(128) float float_align128;
typedef char cbrushedge_t; typedef char cbrushedge_t;
typedef float vec2_t[2];
typedef float vec3_t[3];
typedef tdef_align(128) unsigned int raw_uint128; typedef tdef_align(128) unsigned int raw_uint128;
struct PhysPreset struct PhysPreset
@ -619,6 +657,11 @@ namespace T5
PhysGeomList* geomList; PhysGeomList* geomList;
}; };
struct XModelQuat
{
int16_t v[4];
};
struct XModel struct XModel
{ {
const char* name; const char* name;
@ -628,8 +671,8 @@ namespace T5
char lodRampType; char lodRampType;
uint16_t* boneNames; uint16_t* boneNames;
char* parentList; char* parentList;
int16_t (*quats)[4]; XModelQuat* quats;
float (*trans)[4]; vec4_t* trans;
char* partClassification; char* partClassification;
DObjAnimMat* baseMat; DObjAnimMat* baseMat;
XSurface* surfs; XSurface* surfs;
@ -3288,7 +3331,7 @@ namespace T5
HITLOC_L_FOOT = 0x11, HITLOC_L_FOOT = 0x11,
HITLOC_GUN = 0x12, HITLOC_GUN = 0x12,
HITLOC_NUM HITLOC_COUNT
}; };
struct flameTable struct flameTable

View File

@ -601,6 +601,11 @@ namespace T6
XMODEL_LOD_RAMP_COUNT XMODEL_LOD_RAMP_COUNT
}; };
struct XModelQuat
{
int16_t v[4];
};
struct XModel struct XModel
{ {
const char* name; const char* name;
@ -610,9 +615,9 @@ namespace T6
XModelLodRampType lodRampType; XModelLodRampType lodRampType;
ScriptString* boneNames; ScriptString* boneNames;
unsigned char* parentList; unsigned char* parentList;
uint16_t (*quats)[4]; XModelQuat* quats;
float (*trans)[4]; vec4_t* trans;
char* partClassification; unsigned char* partClassification;
DObjAnimMat* baseMat; DObjAnimMat* baseMat;
XSurface* surfs; XSurface* surfs;
Material** materialHandles; Material** materialHandles;
@ -625,7 +630,7 @@ namespace T6
vec3_t mins; vec3_t mins;
vec3_t maxs; vec3_t maxs;
uint16_t numLods; uint16_t numLods;
uint16_t collLod; int16_t collLod;
float* himipInvSqRadii; float* himipInvSqRadii;
int memUsage; int memUsage;
unsigned int flags; unsigned int flags;
@ -4324,7 +4329,8 @@ namespace T6
MISSILE_GUIDANCE_TVGUIDED = 0x6, MISSILE_GUIDANCE_TVGUIDED = 0x6,
MISSILE_GUIDANCE_DRONE = 0x7, MISSILE_GUIDANCE_DRONE = 0x7,
MISSILE_GUIDANCE_HEATSEEKING = 0x8, MISSILE_GUIDANCE_HEATSEEKING = 0x8,
MISSILE_GUIDANCE_COUNT = 0x9,
MISSILE_GUIDANCE_COUNT
}; };
enum hitLocation_t enum hitLocation_t
@ -4351,7 +4357,7 @@ namespace T6
HITLOC_GUN = 0x13, HITLOC_GUN = 0x13,
HITLOC_SHIELD = 0x14, HITLOC_SHIELD = 0x14,
HITLOC_NUM, HITLOC_COUNT,
}; };
struct WeaponDef struct WeaponDef

View File

@ -24,7 +24,7 @@ namespace T6
{ {
public: public:
std::vector<JsonXModelLod> lods; std::vector<JsonXModelLod> lods;
unsigned collLod; std::optional<int> collLod;
std::optional<std::string> physPreset; std::optional<std::string> physPreset;
std::optional<std::string> physConstraints; std::optional<std::string> physConstraints;
unsigned flags; unsigned flags;

View File

@ -3,6 +3,7 @@
#include "Utils/FileUtils.h" #include "Utils/FileUtils.h"
#include <cstdint> #include <cstdint>
#include <string>
namespace gltf namespace gltf
{ {
@ -18,6 +19,7 @@ namespace gltf
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u; constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,"; constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,";
constexpr auto URI_PREFIX_LENGTH = std::char_traits<char>::length(GLTF_DATA_URI_PREFIX);
constexpr auto GLTF_ATTRIBUTE_POSITION = "POSITION"; constexpr auto GLTF_ATTRIBUTE_POSITION = "POSITION";
constexpr auto GLTF_ATTRIBUTE_NORMAL = "NORMAL"; constexpr auto GLTF_ATTRIBUTE_NORMAL = "NORMAL";

View File

@ -258,6 +258,7 @@ namespace gltf
public: public:
std::optional<unsigned> POSITION; std::optional<unsigned> POSITION;
std::optional<unsigned> NORMAL; std::optional<unsigned> NORMAL;
std::optional<unsigned> COLOR_0;
std::optional<unsigned> TEXCOORD_0; std::optional<unsigned> TEXCOORD_0;
std::optional<unsigned> JOINTS_0; std::optional<unsigned> JOINTS_0;
std::optional<unsigned> WEIGHTS_0; std::optional<unsigned> WEIGHTS_0;

View File

@ -18,6 +18,7 @@ function ObjLoading:link(links)
links:linkto(minilzo) links:linkto(minilzo)
links:linkto(minizip) links:linkto(minizip)
links:linkto(zlib) links:linkto(zlib)
links:linkto(libtomcrypt)
end end
function ObjLoading:use() function ObjLoading:use()
@ -55,6 +56,7 @@ function ObjLoading:project()
minilzo:include(includes) minilzo:include(includes)
minizip:include(includes) minizip:include(includes)
zlib:include(includes) zlib:include(includes)
json:include(includes)
eigen:include(includes) eigen:include(includes)
json:include(includes)
libtomcrypt:include(includes)
end end

View File

@ -38,7 +38,7 @@ bool AssetLoaderXModel::LoadFromRaw(
if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies)) if (LoadXModelAsJson(*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 << "Failed to load xmodel \"" << assetName << "\"\n"; std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName);
return true; return true;
} }

View File

@ -19,6 +19,7 @@
#include "AssetLoaders/AssetLoaderWeaponAttachment.h" #include "AssetLoaders/AssetLoaderWeaponAttachment.h"
#include "AssetLoaders/AssetLoaderWeaponAttachmentUnique.h" #include "AssetLoaders/AssetLoaderWeaponAttachmentUnique.h"
#include "AssetLoaders/AssetLoaderWeaponCamo.h" #include "AssetLoaders/AssetLoaderWeaponCamo.h"
#include "AssetLoaders/AssetLoaderXModel.h"
#include "AssetLoaders/AssetLoaderZBarrier.h" #include "AssetLoaders/AssetLoaderZBarrier.h"
#include "AssetLoading/AssetLoadingManager.h" #include "AssetLoading/AssetLoadingManager.h"
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
@ -50,7 +51,7 @@ namespace T6
REGISTER_ASSET_LOADER(AssetLoaderPhysConstraints) REGISTER_ASSET_LOADER(AssetLoaderPhysConstraints)
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(AssetLoaderMaterial) REGISTER_ASSET_LOADER(AssetLoaderMaterial)
REGISTER_ASSET_LOADER(BasicAssetLoader<AssetTechniqueSet>) REGISTER_ASSET_LOADER(BasicAssetLoader<AssetTechniqueSet>)
REGISTER_ASSET_LOADER(AssetLoaderGfxImage) REGISTER_ASSET_LOADER(AssetLoaderGfxImage)

View File

@ -1,8 +1,22 @@
#include "JsonXModelLoader.h" #include "JsonXModelLoader.h"
#include "Csv/CsvStream.h"
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h" #include "Game/T6/Json/JsonXModel.h"
#include "ObjLoading.h"
#include "Utils/QuatInt16.h"
#include "Utils/StringUtils.h"
#include "XModel/Gltf/GltfBinInput.h"
#include "XModel/Gltf/GltfLoader.h"
#include "XModel/Gltf/GltfTextInput.h"
#include "XModel/XModelCommon.h"
#pragma warning(push, 0)
#include <Eigen>
#pragma warning(pop)
#include <algorithm>
#include <filesystem>
#include <format> #include <format>
#include <iostream> #include <iostream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -11,21 +25,110 @@
using namespace nlohmann; using namespace nlohmann;
using namespace T6; using namespace T6;
namespace fs = std::filesystem;
namespace namespace
{ {
const char* HITLOC_NAMES[]{
"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",
};
static_assert(std::extent_v<decltype(HITLOC_NAMES)> == HITLOC_COUNT);
class PartClassificationState final : public IZoneAssetLoaderState
{
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 JsonLoader class JsonLoader
{ {
public: public:
JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set<XAssetInfoGeneric*>& dependencies) 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_manager(manager), m_manager(manager),
m_part_classification_state(*m_manager.GetAssetLoadingContext()->GetZoneAssetLoaderState<PartClassificationState>()),
m_dependencies(dependencies) m_dependencies(dependencies)
{ {
} }
bool Load(XModel& xmodel) const bool Load(XModel& xmodel)
{ {
const auto jRoot = json::parse(m_stream); const auto jRoot = json::parse(m_stream);
std::string type; std::string type;
@ -36,7 +139,7 @@ namespace
if (type != "xmodel" || version != 1u) if (type != "xmodel" || version != 1u)
{ {
std::cerr << "Tried to load xmodel \"" << xmodel.name << "\" but did not find expected type material of version 1\n"; std::cerr << std::format("Tried to load xmodel \"{}\" but did not find expected type material of version 1\n", xmodel.name);
return false; return false;
} }
@ -56,12 +159,344 @@ namespace
private: private:
static void PrintError(const XModel& xmodel, const std::string& message) static void PrintError(const XModel& xmodel, const std::string& message)
{ {
std::cerr << "Cannot load xmodel \"" << xmodel.name << "\": " << message << "\n"; std::cerr << std::format("Cannot load xmodel \"{}\": {}\n", xmodel.name, message);
} }
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel) const static std::unique_ptr<XModelCommon> LoadModelByExtension(std::istream& stream, const std::string& extension)
{ {
xmodel.collLod = static_cast<uint16_t>(jXModel.collLod); if (extension == ".glb")
{
gltf::BinInput input;
if (!input.ReadGltfData(stream))
return nullptr;
const auto loader = gltf::Loader::CreateLoader(&input);
return loader->Load();
}
if (extension == ".gltf")
{
gltf::TextInput input;
if (!input.ReadGltfData(stream))
return nullptr;
const auto loader = gltf::Loader::CreateLoader(&input);
return loader->Load();
}
return nullptr;
}
static void ApplyBasePose(DObjAnimMat& baseMat, const XModelBone& bone)
{
baseMat.trans.x = bone.globalOffset[0];
baseMat.trans.y = bone.globalOffset[1];
baseMat.trans.z = bone.globalOffset[2];
baseMat.quat.x = bone.globalRotation.x;
baseMat.quat.y = bone.globalRotation.y;
baseMat.quat.z = bone.globalRotation.z;
baseMat.quat.w = bone.globalRotation.w;
const auto quatNormSquared = Eigen::Quaternionf(baseMat.quat.w, baseMat.quat.x, baseMat.quat.y, baseMat.quat.z).squaredNorm();
if (std::abs(quatNormSquared) < std::numeric_limits<float>::epsilon())
{
baseMat.quat.w = 1.0f;
baseMat.transWeight = 2.0f;
}
else
{
baseMat.transWeight = 2.0f / quatNormSquared;
}
}
static void CalculateBoneBounds(XBoneInfo& info, const unsigned boneIndex, const XModelCommon& common)
{
if (common.m_vertex_bone_weights.empty())
return;
info.bounds[0].x = 0.0f;
info.bounds[0].y = 0.0f;
info.bounds[0].z = 0.0f;
info.bounds[1].x = 0.0f;
info.bounds[1].y = 0.0f;
info.bounds[1].z = 0.0f;
info.offset.x = 0.0f;
info.offset.y = 0.0f;
info.offset.z = 0.0f;
info.radiusSquared = 0.0f;
const auto vertexCount = common.m_vertex_bone_weights.size();
for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++)
{
const auto& vertex = common.m_vertices[vertexIndex];
const auto& vertexWeights = common.m_vertex_bone_weights[vertexIndex];
const auto* weights = &common.m_bone_weight_data.weights[vertexWeights.weightOffset];
for (auto weightIndex = 0u; weightIndex < vertexWeights.weightCount; weightIndex++)
{
const auto& weight = weights[weightIndex];
if (weight.boneIndex != boneIndex)
continue;
info.bounds[0].x = std::min(info.bounds[0].x, vertex.coordinates[0]);
info.bounds[0].y = std::min(info.bounds[0].y, vertex.coordinates[1]);
info.bounds[0].z = std::min(info.bounds[0].z, vertex.coordinates[2]);
info.bounds[1].x = std::max(info.bounds[1].x, vertex.coordinates[0]);
info.bounds[1].y = std::max(info.bounds[1].y, vertex.coordinates[1]);
info.bounds[1].z = std::max(info.bounds[1].z, vertex.coordinates[2]);
}
}
const Eigen::Vector3f minEigen(info.bounds[0].x, info.bounds[0].y, info.bounds[0].z);
const Eigen::Vector3f maxEigen(info.bounds[1].x, info.bounds[1].y, info.bounds[1].z);
const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f;
info.offset.x = boundsCenter.x();
info.offset.y = boundsCenter.y();
info.offset.z = boundsCenter.z();
info.radiusSquared = Eigen::Vector3f(maxEigen - boundsCenter).squaredNorm();
}
bool ApplyCommonBonesToXModel(const JsonXModelLod& jLod, XModel& xmodel, unsigned lodNumber, const XModelCommon& common) const
{
if (common.m_bones.empty())
return true;
m_part_classification_state.Load(m_manager);
const auto boneCount = common.m_bones.size();
constexpr auto maxBones = std::numeric_limits<decltype(XModel::numBones)>::max();
if (boneCount > maxBones)
{
PrintError(xmodel, std::format("Model \"{}\" for lod {} contains too many bones ({} -> max={})", jLod.file, lodNumber, boneCount, maxBones));
return false;
}
xmodel.numRootBones = 0u;
xmodel.numBones = 0u;
for (const auto& bone : common.m_bones)
{
if (!bone.parentIndex)
{
// Make sure root bones are at the beginning
assert(xmodel.numRootBones == xmodel.numBones);
xmodel.numRootBones++;
}
xmodel.numBones++;
}
xmodel.boneNames = m_memory.Alloc<ScriptString>(xmodel.numBones);
xmodel.partClassification = m_memory.Alloc<unsigned char>(xmodel.numBones);
xmodel.baseMat = m_memory.Alloc<DObjAnimMat>(xmodel.numBones);
xmodel.boneInfo = m_memory.Alloc<XBoneInfo>(xmodel.numBones);
if (xmodel.numBones > xmodel.numRootBones)
{
xmodel.parentList = m_memory.Alloc<unsigned char>(xmodel.numBones - xmodel.numRootBones);
xmodel.trans = m_memory.Alloc<vec4_t>(xmodel.numBones - xmodel.numRootBones);
xmodel.quats = m_memory.Alloc<XModelQuat>(xmodel.numBones - xmodel.numRootBones);
}
else
{
xmodel.parentList = nullptr;
xmodel.trans = nullptr;
xmodel.quats = nullptr;
}
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
{
const auto& bone = common.m_bones[boneIndex];
xmodel.boneNames[boneIndex] = m_script_strings.AddOrGetScriptString(bone.name);
xmodel.partClassification[boneIndex] = static_cast<unsigned char>(m_part_classification_state.GetPartClassificationForBoneName(bone.name));
ApplyBasePose(xmodel.baseMat[boneIndex], bone);
CalculateBoneBounds(xmodel.boneInfo[boneIndex], boneIndex, common);
// Other boneInfo data is filled when calculating bone bounds
xmodel.boneInfo[boneIndex].collmap = -1;
if (xmodel.numRootBones < boneIndex)
{
const auto nonRootIndex = boneIndex - xmodel.numRootBones;
xmodel.parentList[nonRootIndex] = static_cast<unsigned char>(bone.parentIndex.value_or(0u));
auto& trans = xmodel.trans[nonRootIndex];
trans.x = bone.localOffset[0];
trans.y = bone.localOffset[1];
trans.z = bone.localOffset[2];
auto& quats = xmodel.quats[nonRootIndex];
quats.v[0] = QuatInt16::ToInt16(bone.localRotation.x);
quats.v[1] = QuatInt16::ToInt16(bone.localRotation.y);
quats.v[2] = QuatInt16::ToInt16(bone.localRotation.z);
quats.v[3] = QuatInt16::ToInt16(bone.localRotation.w);
}
}
return true;
}
[[nodiscard]] bool VerifyBones(const JsonXModelLod& jLod, const XModel& xmodel, unsigned lodNumber, const XModelCommon& common) const
{
// This method currently only checks names
// This does not necessarily verify correctness entirely.
// It is most likely enough to catch accidental errors, however.
const auto commonBoneCount = common.m_bones.size();
if (xmodel.numBones != commonBoneCount)
{
PrintError(xmodel,
std::format(R"(Model "{}" for lod "{}" has different bone count compared to lod 0 ({} != {}))",
jLod.file,
lodNumber,
xmodel.numBones,
commonBoneCount));
return false;
}
for (auto boneIndex = 0u; boneIndex < commonBoneCount; boneIndex++)
{
const auto& commonBone = common.m_bones[boneIndex];
const auto& boneName = m_script_strings[xmodel.boneNames[boneIndex]];
if (commonBone.name != boneName)
{
PrintError(xmodel,
std::format(R"(Model "{}" for lod "{}" has different bone names compared to lod 0 (Index {}: {} != {}))",
jLod.file,
lodNumber,
boneIndex,
boneName,
commonBone.name));
return false;
}
}
return true;
}
static void CreateVertex(GfxPackedVertex& vertex, const XModelVertex& commonVertex)
{
constexpr float wrongTangent[]{1, 0, 0};
vertex.xyz.x = commonVertex.coordinates[0];
vertex.xyz.y = commonVertex.coordinates[1];
vertex.xyz.z = commonVertex.coordinates[2];
vertex.binormalSign = 1.0f; // TODO: Fill with actual value
vertex.color = Common::Vec4PackGfxColor(commonVertex.color);
vertex.texCoord = Common::Vec2PackTexCoords(commonVertex.uv);
vertex.normal = Common::Vec3PackUnitVec(commonVertex.normal);
vertex.tangent = Common::Vec3PackUnitVec(wrongTangent); // TODO: Fill with actual value
}
bool CreateXSurface(XSurface& surface, const XModelObject& commonObject, const XModelCommon& common)
{
std::vector<GfxPackedVertex> verts;
std::unordered_map<size_t, size_t> usedVertices;
surface.triCount = static_cast<uint16_t>(commonObject.m_faces.size());
surface.triIndices = m_memory.Alloc<r_index16_t[3]>(commonObject.m_faces.size());
const auto faceCount = commonObject.m_faces.size();
for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++)
{
const auto& face = commonObject.m_faces[faceIndex];
auto& tris = surface.triIndices[faceIndex];
for (auto faceVertexIndex = 0u; faceVertexIndex < std::extent_v<decltype(XModelFace::vertexIndex)>; faceVertexIndex++)
{
const auto vertexIndex = face.vertexIndex[faceVertexIndex];
const auto existingVertex = usedVertices.find(vertexIndex);
if (existingVertex == usedVertices.end())
{
const auto newVertexIndex = verts.size();
tris[faceVertexIndex] = static_cast<r_index16_t>(newVertexIndex);
const auto& commonVertex = common.m_vertices[vertexIndex];
GfxPackedVertex vertex{};
CreateVertex(vertex, commonVertex);
verts.emplace_back(vertex);
usedVertices.emplace(vertexIndex, newVertexIndex);
}
else
tris[faceVertexIndex] = static_cast<r_index16_t>(existingVertex->second);
}
}
surface.vertCount = static_cast<uint16_t>(verts.size());
surface.verts0 = m_memory.Alloc<GfxPackedVertex>(verts.size());
memcpy(surface.verts0, verts.data(), sizeof(GfxPackedVertex) * verts.size());
return true;
}
bool LoadLod(const JsonXModelLod& jLod, XModel& xmodel, unsigned lodNumber)
{
const auto file = m_manager.GetAssetLoadingContext()->m_raw_search_path->Open(jLod.file);
if (!file.IsOpen())
{
PrintError(xmodel, std::format("Failed to open file for lod {}: \"{}\"", lodNumber, jLod.file));
return false;
}
auto extension = fs::path(jLod.file).extension().string();
utils::MakeStringLowerCase(extension);
const auto common = LoadModelByExtension(*file.m_stream, extension);
if (!common)
{
PrintError(xmodel, std::format("Failure while trying to load model for lod {}: \"{}\"", lodNumber, jLod.file));
return false;
}
if (lodNumber == 0u)
{
if (!ApplyCommonBonesToXModel(jLod, xmodel, lodNumber, *common))
return false;
}
else
{
if (!VerifyBones(jLod, xmodel, lodNumber, *common))
return false;
}
xmodel.lodInfo[lodNumber].surfIndex = static_cast<uint16_t>(m_surfaces.size());
xmodel.lodInfo[lodNumber].numsurfs = static_cast<uint16_t>(common->m_objects.size());
return std::ranges::all_of(common->m_objects,
[this, &common](const XModelObject& commonObject)
{
XSurface surface{};
if (!CreateXSurface(surface, commonObject, *common))
return false;
m_surfaces.emplace_back(surface);
return true;
});
}
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel)
{
auto lodNumber = 0u;
for (const auto& jLod : jXModel.lods)
LoadLod(jLod, xmodel, lodNumber++);
xmodel.numsurfs = static_cast<unsigned char>(m_surfaces.size());
xmodel.surfs = m_memory.Alloc<XSurface>(xmodel.numsurfs);
memcpy(xmodel.surfs, m_surfaces.data(), sizeof(XSurface) * xmodel.numsurfs);
if (jXModel.collLod && jXModel.collLod.value() >= 0)
{
if (static_cast<unsigned>(jXModel.collLod.value()) >= jXModel.lods.size())
{
PrintError(xmodel, "Collision lod is not a valid lod");
return false;
}
xmodel.collLod = static_cast<int16_t>(jXModel.collLod.value());
}
else
xmodel.collLod = -1;
if (jXModel.physPreset) if (jXModel.physPreset)
{ {
@ -104,9 +539,13 @@ namespace
return true; return true;
} }
std::vector<XSurface> m_surfaces;
std::istream& m_stream; std::istream& m_stream;
MemoryManager& m_memory; MemoryManager& m_memory;
ZoneScriptStrings& m_script_strings;
IAssetLoadingManager& m_manager; IAssetLoadingManager& m_manager;
PartClassificationState& m_part_classification_state;
std::set<XAssetInfoGeneric*>& m_dependencies; std::set<XAssetInfoGeneric*>& m_dependencies;
}; };
} // namespace } // namespace
@ -117,7 +556,7 @@ namespace T6
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies) std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
{ {
std::set<XAssetInfoGeneric*> dependenciesSet; std::set<XAssetInfoGeneric*> dependenciesSet;
const JsonLoader loader(stream, *memory, *manager, dependenciesSet); JsonLoader loader(stream, *memory, *manager, dependenciesSet);
dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend()); dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend());

View File

@ -0,0 +1,128 @@
#include "GltfBinInput.h"
#include "XModel/Gltf/GltfConstants.h"
#include <exception>
#include <iostream>
#include <nlohmann/json.hpp>
using namespace gltf;
BinInput::BinInput()
: m_buffer_size(0u)
{
}
bool BinInput::GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const
{
if (!m_buffer || !m_buffer_size)
return false;
buffer = m_buffer.get();
bufferSize = m_buffer_size;
return true;
}
const nlohmann::json& BinInput::GetJson() const
{
if (!m_json)
throw std::exception();
return *m_json;
}
bool BinInput::ReadGltfData(std::istream& stream)
{
uint32_t magic;
if (!Read(stream, &magic, sizeof(magic), "magic"))
return false;
if (magic != GLTF_MAGIC)
{
std::cerr << "Invalid magic when trying to read GLB\n";
return false;
}
uint32_t version;
if (!Read(stream, &version, sizeof(version), "version"))
return false;
if (version != GLTF_VERSION)
{
std::cerr << std::format("Unsupported version {} when trying to read GLB: Expected version {}\n", version, GLTF_VERSION);
return false;
}
uint32_t fileLength;
if (!Read(stream, &fileLength, sizeof(fileLength), "file length"))
return false;
while (true)
{
uint32_t chunkLength;
uint32_t chunkMagic;
if (!Read(stream, &chunkLength, sizeof(chunkLength), "chunk length", false))
break;
if (!Read(stream, &chunkMagic, sizeof(chunkMagic), "chunk magic"))
return false;
if (chunkMagic == CHUNK_MAGIC_JSON)
{
const auto jsonBuffer = std::make_unique<uint8_t[]>(chunkLength + 1);
if (!Read(stream, jsonBuffer.get(), chunkLength, "json"))
return false;
jsonBuffer[chunkLength] = 0u;
try
{
m_json = std::make_unique<nlohmann::json>(nlohmann::json::parse(jsonBuffer.get(), &jsonBuffer[chunkLength]));
}
catch (const nlohmann::json::exception& e)
{
std::cerr << std::format("Failed trying to parse JSON of GLB: {}\n", e.what());
return false;
}
}
else if (chunkMagic == CHUNK_MAGIC_BIN)
{
m_buffer = std::make_unique<uint8_t[]>(chunkLength);
m_buffer_size = chunkLength;
if (!Read(stream, m_buffer.get(), m_buffer_size, "bin buffer"))
return false;
}
else
Skip(stream, chunkLength);
if (chunkLength % 4u > 0)
Skip(stream, 4u - (chunkLength % 4u));
}
if (!m_json)
{
std::cerr << "Failed to load GLB due to missing JSON\n";
return false;
}
return true;
}
bool BinInput::Read(std::istream& stream, void* dest, const size_t dataSize, const char* readTypeName, const bool errorWhenFailed)
{
stream.read(static_cast<char*>(dest), dataSize);
if (stream.gcount() != dataSize)
{
if (errorWhenFailed)
std::cerr << std::format("Unexpected EOF while reading GLB {}\n", readTypeName);
return false;
}
return true;
}
void BinInput::Skip(std::istream& stream, const size_t skipLength)
{
stream.seekg(skipLength, std::ios::cur);
}

View File

@ -0,0 +1,26 @@
#pragma once
#include "GltfInput.h"
#include <istream>
namespace gltf
{
class BinInput final : public Input
{
public:
BinInput();
bool ReadGltfData(std::istream& stream) override;
bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const override;
[[nodiscard]] const nlohmann::json& GetJson() const override;
private:
static bool Read(std::istream& stream, void* dest, size_t dataSize, const char* readTypeName, bool errorWhenFailed = true);
static void Skip(std::istream& stream, size_t skipLength);
std::unique_ptr<nlohmann::json> m_json;
std::unique_ptr<uint8_t[]> m_buffer;
size_t m_buffer_size;
};
} // namespace gltf

View File

@ -0,0 +1,24 @@
#pragma once
#include <cstdlib>
#include <nlohmann/json_fwd.hpp>
namespace gltf
{
class Input
{
protected:
Input() = default;
public:
virtual ~Input() = default;
Input(const Input& other) = default;
Input(Input&& other) noexcept = default;
Input& operator=(const Input& other) = default;
Input& operator=(Input&& other) noexcept = default;
virtual bool ReadGltfData(std::istream& stream) = 0;
virtual bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const = 0;
[[nodiscard]] virtual const nlohmann::json& GetJson() const = 0;
};
} // namespace gltf

View File

@ -0,0 +1,688 @@
#include "GltfLoader.h"
#include "Internal/GltfAccessor.h"
#include "Internal/GltfBuffer.h"
#include "Internal/GltfBufferView.h"
#pragma warning(push, 0)
#include <Eigen>
#pragma warning(pop)
#include <deque>
#include <exception>
#include <format>
#include <iostream>
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
using namespace gltf;
namespace
{
struct AccessorsForVertex
{
unsigned positionAccessor;
std::optional<unsigned> normalAccessor;
std::optional<unsigned> colorAccessor;
std::optional<unsigned> uvAccessor;
std::optional<unsigned> jointsAccessor;
std::optional<unsigned> weightsAccessor;
friend bool operator==(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs)
{
return lhs.positionAccessor == rhs.positionAccessor && lhs.normalAccessor == rhs.normalAccessor && lhs.colorAccessor == rhs.colorAccessor
&& lhs.uvAccessor == rhs.uvAccessor && lhs.jointsAccessor == rhs.jointsAccessor && lhs.weightsAccessor == rhs.weightsAccessor;
}
friend bool operator!=(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs)
{
return !(lhs == rhs);
}
};
} // namespace
template<> struct std::hash<AccessorsForVertex>
{
std::size_t operator()(const AccessorsForVertex& v) const noexcept
{
std::size_t seed = 0x7E42C0E6;
seed ^= (seed << 6) + (seed >> 2) + 0x47B15429 + static_cast<std::size_t>(v.positionAccessor);
seed ^= (seed << 6) + (seed >> 2) + 0x66847B5C + std::hash<std::optional<unsigned>>()(v.normalAccessor);
seed ^= (seed << 6) + (seed >> 2) + 0x77399D60 + std::hash<std::optional<unsigned>>()(v.colorAccessor);
seed ^= (seed << 6) + (seed >> 2) + 0x477AF9AB + std::hash<std::optional<unsigned>>()(v.uvAccessor);
seed ^= (seed << 6) + (seed >> 2) + 0x4421B4D9 + std::hash<std::optional<unsigned>>()(v.jointsAccessor);
seed ^= (seed << 6) + (seed >> 2) + 0x13C2EBA1 + std::hash<std::optional<unsigned>>()(v.weightsAccessor);
return seed;
}
};
namespace
{
class GltfLoadException final : std::exception
{
public:
explicit GltfLoadException(std::string message)
: m_message(std::move(message))
{
}
[[nodiscard]] const std::string& Str() const
{
return m_message;
}
[[nodiscard]] const char* what() const override
{
return m_message.c_str();
}
private:
std::string m_message;
};
struct ObjectToLoad
{
unsigned meshIndex;
std::optional<unsigned> skinIndex;
ObjectToLoad(const unsigned meshIndex, const std::optional<unsigned> skinIndex)
: meshIndex(meshIndex),
skinIndex(skinIndex)
{
}
};
class GltfLoaderImpl final : public Loader
{
public:
explicit GltfLoaderImpl(const Input* input)
: m_input(input)
{
}
static std::vector<unsigned> GetRootNodes(const JsonRoot& jRoot)
{
if (!jRoot.nodes || jRoot.nodes->empty())
return {};
const auto nodeCount = jRoot.nodes->size();
std::vector<unsigned> rootNodes;
std::vector<bool> isChild(nodeCount);
for (const auto& node : jRoot.nodes.value())
{
if (!node.children)
continue;
for (const auto childIndex : node.children.value())
{
if (childIndex >= nodeCount)
throw GltfLoadException("Illegal child index");
if (isChild[childIndex])
throw GltfLoadException("Node hierarchy is not a set of disjoint strict trees");
isChild[childIndex] = true;
}
}
for (auto nodeIndex = 0u; nodeIndex < nodeCount; nodeIndex++)
{
if (!isChild[nodeIndex])
rootNodes.emplace_back(nodeIndex);
}
return rootNodes;
}
void TraverseNodes(const JsonRoot& jRoot)
{
// Make sure there are any nodes to traverse
if (!jRoot.nodes || jRoot.nodes->empty())
return;
std::deque<unsigned> nodeQueue;
std::vector<unsigned> rootNodes = GetRootNodes(jRoot);
for (const auto rootNode : rootNodes)
nodeQueue.emplace_back(rootNode);
while (!nodeQueue.empty())
{
const auto& node = jRoot.nodes.value()[nodeQueue.front()];
nodeQueue.pop_front();
if (node.children)
{
for (const auto childIndex : *node.children)
nodeQueue.emplace_back(childIndex);
}
if (node.mesh)
m_load_objects.emplace_back(*node.mesh, node.skin);
}
}
std::optional<Accessor*> GetAccessorForIndex(const char* attributeName,
const std::optional<unsigned> index,
std::initializer_list<JsonAccessorType> allowedAccessorTypes,
std::initializer_list<JsonAccessorComponentType> allowedAccessorComponentTypes) const
{
if (!index)
return std::nullopt;
if (*index > m_accessors.size())
throw GltfLoadException(std::format("Index for {} accessor out of bounds", attributeName));
auto* accessor = m_accessors[*index].get();
const auto maybeType = accessor->GetType();
if (maybeType)
{
if (std::ranges::find(allowedAccessorTypes, *maybeType) == allowedAccessorTypes.end())
throw GltfLoadException(std::format("Accessor for {} has unsupported type {}", attributeName, static_cast<unsigned>(*maybeType)));
}
const auto maybeComponentType = accessor->GetComponentType();
if (maybeComponentType)
{
if (std::ranges::find(allowedAccessorComponentTypes, *maybeComponentType) == allowedAccessorComponentTypes.end())
throw GltfLoadException(
std::format("Accessor for {} has unsupported component type {}", attributeName, static_cast<unsigned>(*maybeComponentType)));
}
return accessor;
}
static void VerifyAccessorVertexCount(const char* accessorType, const Accessor* accessor, const size_t vertexCount)
{
if (accessor->GetCount() != vertexCount)
throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount));
}
unsigned CreateVertices(XModelCommon& common, const AccessorsForVertex& accessorsForVertex)
{
// clang-format off
auto* positionAccessor = GetAccessorForIndex(
"POSITION",
accessorsForVertex.positionAccessor,
{JsonAccessorType::VEC3},
{JsonAccessorComponentType::FLOAT}
).value_or(nullptr);
// clang-format on
const auto vertexCount = positionAccessor->GetCount();
NullAccessor nullAccessor(vertexCount);
// clang-format off
auto* normalAccessor = GetAccessorForIndex(
"NORMAL",
accessorsForVertex.normalAccessor,
{JsonAccessorType::VEC3},
{JsonAccessorComponentType::FLOAT}
).value_or(&nullAccessor);
VerifyAccessorVertexCount("NORMAL", normalAccessor, vertexCount);
auto* uvAccessor = GetAccessorForIndex(
"TEXCOORD_0",
accessorsForVertex.uvAccessor,
{JsonAccessorType::VEC2},
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
).value_or(&nullAccessor);
VerifyAccessorVertexCount("TEXCOORD_0", uvAccessor, vertexCount);
auto* colorAccessor = GetAccessorForIndex(
"COLOR_0",
accessorsForVertex.colorAccessor,
{JsonAccessorType::VEC3, JsonAccessorType::VEC4},
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
).value_or(&nullAccessor);
VerifyAccessorVertexCount("COLOR_0", colorAccessor, vertexCount);
auto* jointsAccessor = GetAccessorForIndex(
"JOINTS_0",
accessorsForVertex.jointsAccessor,
{JsonAccessorType::VEC4},
{JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
).value_or(&nullAccessor);
VerifyAccessorVertexCount("JOINTS_0", jointsAccessor, vertexCount);
auto* weightsAccessor = GetAccessorForIndex(
"WEIGHTS_0",
accessorsForVertex.weightsAccessor,
{JsonAccessorType::VEC4},
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
).value_or(&nullAccessor);
VerifyAccessorVertexCount("WEIGHTS_0", weightsAccessor, vertexCount);
// clang-format on
const auto vertexOffset = common.m_vertices.size();
common.m_vertices.reserve(vertexOffset + vertexCount);
common.m_vertex_bone_weights.reserve(vertexOffset + vertexCount);
for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++)
{
XModelVertex vertex;
unsigned joints[4];
float weights[4];
if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.coordinates) || !normalAccessor->GetFloatVec3(vertexIndex, vertex.normal)
|| !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !colorAccessor->GetFloatVec2(vertexIndex, vertex.uv)
|| !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights))
{
return false;
}
common.m_vertices.emplace_back(vertex);
XModelVertexBoneWeights vertexWeights{common.m_bone_weight_data.weights.size(), 0u};
for (auto i = 0u; i < std::extent_v<decltype(joints)>; i++)
{
if (std::abs(weights[i]) < std::numeric_limits<float>::epsilon())
continue;
common.m_bone_weight_data.weights.emplace_back(joints[i], weights[i]);
vertexWeights.weightCount++;
}
common.m_vertex_bone_weights.emplace_back(vertexWeights);
}
m_vertex_offset_for_accessors.emplace(accessorsForVertex, vertexOffset);
return vertexOffset;
}
bool ConvertObject(const JsonRoot& jRoot, const JsonMeshPrimitives& primitives, XModelCommon& common, XModelObject& object)
{
if (!primitives.indices)
throw GltfLoadException("Requires primitives indices");
if (!primitives.material)
throw GltfLoadException("Requires primitives material");
if (primitives.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES)
throw GltfLoadException("Only triangles are supported");
if (!primitives.attributes.POSITION)
throw GltfLoadException("Requires primitives attribute POSITION");
AccessorsForVertex accessorsForVertex{
*primitives.attributes.POSITION,
primitives.attributes.NORMAL,
primitives.attributes.COLOR_0,
primitives.attributes.TEXCOORD_0,
primitives.attributes.JOINTS_0,
primitives.attributes.WEIGHTS_0,
};
const auto existingVertices = m_vertex_offset_for_accessors.find(accessorsForVertex);
const auto vertexOffset =
existingVertices == m_vertex_offset_for_accessors.end() ? CreateVertices(common, accessorsForVertex) : existingVertices->second;
// clang-format off
auto* indexAccessor = GetAccessorForIndex(
"INDICES",
primitives.indices,
{JsonAccessorType::SCALAR},
{JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT, JsonAccessorComponentType::UNSIGNED_INT}
).value_or(nullptr);
// clang-format on
const auto indexCount = indexAccessor->GetCount();
if (indexCount % 3 != 0)
throw GltfLoadException("Index count must be dividable by 3 for triangles");
const auto faceCount = indexCount / 3u;
object.m_faces.reserve(faceCount);
for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++)
{
unsigned indices[3];
if (!indexAccessor->GetUnsigned(faceIndex * 3u + 0u, indices[0]) || !indexAccessor->GetUnsigned(faceIndex * 3u + 1u, indices[1])
|| !indexAccessor->GetUnsigned(faceIndex * 3u + 2u, indices[2]))
{
return false;
}
object.m_faces.emplace_back(XModelFace{
vertexOffset + indices[0],
vertexOffset + indices[1],
vertexOffset + indices[2],
});
}
if (!jRoot.materials || *primitives.material >= jRoot.materials->size())
throw GltfLoadException("Invalid material index");
object.materialIndex = static_cast<int>(*primitives.material);
return true;
}
static std::optional<unsigned> GetRootNodeForSkin(const JsonRoot& jRoot, const JsonSkin& skin)
{
if (!jRoot.nodes || skin.joints.empty())
return std::nullopt;
const auto jointCount = skin.joints.size();
auto rootCount = jointCount;
std::vector<bool> isRoot(jointCount, true);
for (const auto joint : skin.joints)
{
if (jRoot.nodes->size() <= joint)
throw GltfLoadException("Invalid joint index");
const auto& node = jRoot.nodes.value()[joint];
if (node.children)
{
for (const auto child : *node.children)
{
const auto foundChildJoint = std::ranges::find(skin.joints, child);
if (foundChildJoint != skin.joints.end())
{
const auto foundChildJointIndex = std::distance(skin.joints.begin(), foundChildJoint);
if (isRoot[foundChildJointIndex])
{
isRoot[foundChildJointIndex] = false;
rootCount--;
}
}
}
}
}
if (rootCount != 1)
throw GltfLoadException("Skins must have exactly one common root node");
for (auto index = 0u; index < jointCount; index++)
{
if (isRoot[index])
return skin.joints[index];
}
return std::nullopt;
}
static bool ConvertJoint(const JsonRoot& jRoot,
XModelCommon& common,
const unsigned nodeIndex,
const std::optional<unsigned> parentIndex,
const float (&parentOffset)[3],
const XModelQuaternion& parentRotation,
const float (&parentScale)[3])
{
if (!jRoot.nodes || nodeIndex >= jRoot.nodes->size())
return false;
const auto& node = jRoot.nodes.value()[nodeIndex];
XModelBone bone;
bone.name = node.name.value_or(std::string());
bone.parentIndex = parentIndex;
if (node.scale)
{
bone.scale[0] = parentScale[0] * (*node.scale)[0];
bone.scale[1] = parentScale[1] * (*node.scale)[1];
bone.scale[2] = parentScale[2] * (*node.scale)[2];
}
else
{
bone.scale[0] = parentScale[0];
bone.scale[1] = parentScale[1];
bone.scale[2] = parentScale[2];
}
if (node.translation)
{
bone.localOffset[0] = (*node.translation)[0];
bone.localOffset[1] = -(*node.translation)[2];
bone.localOffset[2] = (*node.translation)[1];
}
else
{
bone.localOffset[0] = 0.0f;
bone.localOffset[1] = 0.0f;
bone.localOffset[2] = 0.0f;
}
bone.globalOffset[0] = bone.localOffset[0] + parentOffset[0];
bone.globalOffset[1] = bone.localOffset[1] + parentOffset[1];
bone.globalOffset[2] = bone.localOffset[2] + parentOffset[2];
if (node.rotation)
{
bone.localRotation.x = (*node.rotation)[0];
bone.localRotation.y = (*node.rotation)[2];
bone.localRotation.z = -(*node.rotation)[1];
bone.localRotation.w = (*node.rotation)[3];
}
else
{
bone.localRotation.x = 0.0f;
bone.localRotation.y = 0.0f;
bone.localRotation.z = 0.0f;
bone.localRotation.w = 1.0f;
}
const auto localRotationEigen = Eigen::Quaternionf(bone.localRotation.w, bone.localRotation.x, bone.localRotation.y, bone.localRotation.z);
const auto parentRotationEigen = Eigen::Quaternionf(parentRotation.w, parentRotation.x, parentRotation.y, parentRotation.z);
const auto globalRotationEigen = localRotationEigen * parentRotationEigen;
bone.globalRotation.x = globalRotationEigen.x();
bone.globalRotation.y = globalRotationEigen.y();
bone.globalRotation.z = globalRotationEigen.z();
bone.globalRotation.w = globalRotationEigen.w();
const auto commonIndex = common.m_bones.size();
common.m_bones.emplace_back(std::move(bone));
if (node.children)
{
for (const auto childIndex : *node.children)
{
if (!ConvertJoint(jRoot, common, childIndex, commonIndex, bone.globalOffset, bone.globalRotation, bone.scale))
return false;
}
}
return true;
}
static bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common)
{
if (skin.joints.empty())
return true;
if (!jRoot.nodes)
return false;
if (!common.m_bones.empty())
throw GltfLoadException("Only scenes with at most one skin are supported");
const auto rootNode = GetRootNodeForSkin(jRoot, skin).value_or(skin.joints[0]);
constexpr float defaultTranslation[3]{0.0f, 0.0f, 0.0f};
constexpr XModelQuaternion defaultRotation{0.0f, 0.0f, 0.0f, 1.0f};
constexpr float defaultScale[3]{1.0f, 1.0f, 1.0f};
return ConvertJoint(jRoot, common, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale);
}
void ConvertObjects(const JsonRoot& jRoot, XModelCommon& common)
{
if (!jRoot.meshes)
return;
for (const auto& loadObject : m_load_objects)
{
if (loadObject.skinIndex && jRoot.skins)
{
const auto& skin = jRoot.skins.value()[*loadObject.skinIndex];
if (!ConvertSkin(jRoot, skin, common))
return;
}
const auto& mesh = jRoot.meshes.value()[loadObject.meshIndex];
common.m_objects.reserve(common.m_objects.size() + mesh.primitives.size());
for (const auto& primitives : mesh.primitives)
{
XModelObject object;
if (!ConvertObject(jRoot, primitives, common, object))
return;
common.m_objects.emplace_back(std::move(object));
}
}
}
static void ConvertMaterials(const JsonRoot& jRoot, XModelCommon& common)
{
if (!jRoot.materials)
return;
common.m_materials.reserve(jRoot.materials->size());
for (const auto& jMaterial : *jRoot.materials)
{
XModelMaterial material;
material.ApplyDefaults();
if (jMaterial.name)
material.name = *jMaterial.name;
else
throw GltfLoadException("Materials must have a name");
common.m_materials.emplace_back(std::move(material));
}
}
void CreateBuffers(const JsonRoot& jRoot)
{
if (!jRoot.buffers)
return;
m_buffers.reserve(jRoot.buffers->size());
for (const auto& jBuffer : *jRoot.buffers)
{
if (!jBuffer.uri)
{
const void* embeddedBufferPtr = nullptr;
size_t embeddedBufferSize = 0u;
if (!m_input->GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u)
throw GltfLoadException("Buffer tried to access embedded data when there is none");
m_buffers.emplace_back(std::make_unique<EmbeddedBuffer>(embeddedBufferPtr, embeddedBufferSize));
}
else if (DataUriBuffer::IsDataUri(*jBuffer.uri))
{
auto dataUriBuffer = std::make_unique<DataUriBuffer>();
if (!dataUriBuffer->ReadDataFromUri(*jBuffer.uri))
throw GltfLoadException("Buffer has invalid data uri");
m_buffers.emplace_back(std::move(dataUriBuffer));
}
else
{
throw GltfLoadException("File buffers are not supported");
}
}
}
void CreateBufferViews(const JsonRoot& jRoot)
{
if (!jRoot.bufferViews)
return;
m_buffer_views.reserve(jRoot.bufferViews->size());
for (const auto& jBufferView : *jRoot.bufferViews)
{
if (jBufferView.buffer >= m_buffers.size())
throw GltfLoadException("Buffer view references invalid buffer");
const auto* buffer = m_buffers[jBufferView.buffer].get();
const auto offset = jBufferView.byteOffset.value_or(0u);
const auto length = jBufferView.byteLength;
const auto stride = jBufferView.byteStride.value_or(0u);
if (offset + length > buffer->GetSize())
throw GltfLoadException("Buffer view is defined larger as underlying buffer");
m_buffer_views.emplace_back(std::make_unique<BufferView>(buffer, offset, length, stride));
}
}
void CreateAccessors(const JsonRoot& jRoot)
{
if (!jRoot.accessors)
return;
m_accessors.reserve(jRoot.accessors->size());
for (const auto& jAccessor : *jRoot.accessors)
{
if (!jAccessor.bufferView)
{
m_accessors.emplace_back(std::make_unique<NullAccessor>(jAccessor.count));
continue;
}
if (*jAccessor.bufferView >= m_buffer_views.size())
throw GltfLoadException("Accessor references invalid buffer view");
const auto* bufferView = m_buffer_views[*jAccessor.bufferView].get();
if (jAccessor.componentType == JsonAccessorComponentType::FLOAT)
m_accessors.emplace_back(std::make_unique<FloatAccessor>(bufferView, jAccessor.type, jAccessor.count));
else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_BYTE)
m_accessors.emplace_back(std::make_unique<UnsignedByteAccessor>(bufferView, jAccessor.type, jAccessor.count));
else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_SHORT)
m_accessors.emplace_back(std::make_unique<UnsignedShortAccessor>(bufferView, jAccessor.type, jAccessor.count));
else
throw GltfLoadException(std::format("Accessor has unsupported component type {}", static_cast<unsigned>(jAccessor.componentType)));
}
}
std::unique_ptr<XModelCommon> Load() override
{
JsonRoot jRoot;
try
{
jRoot = m_input->GetJson().get<JsonRoot>();
}
catch (const nlohmann::json::exception& e)
{
std::cerr << std::format("Failed to parse GLTF JSON: {}\n", e.what());
return nullptr;
}
try
{
CreateBuffers(jRoot);
CreateBufferViews(jRoot);
CreateAccessors(jRoot);
TraverseNodes(jRoot);
auto common = std::make_unique<XModelCommon>();
ConvertObjects(jRoot, *common);
ConvertMaterials(jRoot, *common);
return common;
}
catch (const GltfLoadException& e)
{
std::cerr << std::format("Failed to load GLTF: {}\n", e.Str());
return nullptr;
}
}
private:
const Input* m_input;
std::vector<ObjectToLoad> m_load_objects;
std::unordered_map<AccessorsForVertex, unsigned> m_vertex_offset_for_accessors;
std::vector<std::unique_ptr<Accessor>> m_accessors;
std::vector<std::unique_ptr<BufferView>> m_buffer_views;
std::vector<std::unique_ptr<Buffer>> m_buffers;
};
} // namespace
std::unique_ptr<Loader> Loader::CreateLoader(const Input* input)
{
return std::make_unique<GltfLoaderImpl>(input);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "GltfInput.h"
#include "XModel/Gltf/JsonGltf.h"
#include "XModel/XModelLoader.h"
#include <memory>
#include <ostream>
namespace gltf
{
class Loader : public XModelLoader
{
public:
Loader() = default;
~Loader() override = default;
Loader(const Loader& other) = default;
Loader(Loader&& other) noexcept = default;
Loader& operator=(const Loader& other) = default;
Loader& operator=(Loader&& other) noexcept = default;
static std::unique_ptr<Loader> CreateLoader(const Input* input);
};
} // namespace gltf

View File

@ -0,0 +1,48 @@
#include "GltfTextInput.h"
#include <exception>
#include <format>
#include <iostream>
#include <nlohmann/json.hpp>
using namespace gltf;
TextInput::TextInput()
: m_buffer_size(0u)
{
}
bool TextInput::GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const
{
if (!m_buffer || !m_buffer_size)
return false;
buffer = m_buffer.get();
bufferSize = m_buffer_size;
return true;
}
const nlohmann::json& TextInput::GetJson() const
{
if (!m_json)
throw std::exception();
return *m_json;
}
bool TextInput::ReadGltfData(std::istream& stream)
{
try
{
m_json = std::make_unique<nlohmann::json>(nlohmann::json::parse(stream));
return true;
}
catch (nlohmann::json::exception& e)
{
std::cerr << std::format("Failed to parse json of GLTF: {}", e.what());
}
return false;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "GltfInput.h"
#include <istream>
namespace gltf
{
class TextInput final : public Input
{
public:
TextInput();
bool ReadGltfData(std::istream& stream) override;
bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const override;
[[nodiscard]] const nlohmann::json& GetJson() const override;
private:
std::unique_ptr<nlohmann::json> m_json;
std::unique_ptr<uint8_t[]> m_buffer;
size_t m_buffer_size;
};
} // namespace gltf

View File

@ -0,0 +1,355 @@
#include "GltfAccessor.h"
using namespace gltf;
NullAccessor::NullAccessor(const size_t count)
: m_count(count)
{
}
bool NullAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
{
if (index >= m_count)
return false;
out[0] = 0.0f;
out[1] = 0.0f;
return true;
}
bool NullAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
{
if (index >= m_count)
return false;
out[0] = 0.0f;
out[1] = 0.0f;
out[2] = 0.0f;
return true;
}
bool NullAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
{
if (index >= m_count)
return false;
out[0] = 0.0f;
out[1] = 0.0f;
out[2] = 0.0f;
out[3] = 0.0f;
return true;
}
bool NullAccessor::GetUnsigned(const size_t index, unsigned& out) const
{
if (index >= m_count)
return false;
out = 0u;
return true;
}
bool NullAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
{
if (index >= m_count)
return false;
out[0] = 0u;
out[1] = 0u;
out[2] = 0u;
out[3] = 0u;
return true;
}
std::optional<JsonAccessorComponentType> NullAccessor::GetComponentType() const
{
return std::nullopt;
}
std::optional<JsonAccessorType> NullAccessor::GetType() const
{
return std::nullopt;
}
size_t NullAccessor::GetCount() const
{
return m_count;
}
FloatAccessor::FloatAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
: m_buffer_view(bufferView),
m_type(type),
m_count(count)
{
}
std::optional<JsonAccessorType> FloatAccessor::GetType() const
{
return m_type;
}
std::optional<JsonAccessorComponentType> FloatAccessor::GetComponentType() const
{
return JsonAccessorComponentType::FLOAT;
}
size_t FloatAccessor::GetCount() const
{
return m_count;
}
bool FloatAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
{
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
return m_buffer_view->ReadElement(&out, index, sizeof(float[2]));
}
bool FloatAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
{
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
return m_buffer_view->ReadElement(&out, index, sizeof(float[3]));
}
bool FloatAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
{
assert(m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
return m_buffer_view->ReadElement(&out, index, sizeof(float[4]));
}
bool FloatAccessor::GetUnsigned(size_t index, unsigned& out) const
{
return false;
}
bool FloatAccessor::GetUnsignedVec4(size_t index, unsigned (&out)[4]) const
{
return false;
}
UnsignedByteAccessor::UnsignedByteAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
: m_buffer_view(bufferView),
m_type(type),
m_count(count)
{
}
std::optional<JsonAccessorType> UnsignedByteAccessor::GetType() const
{
return m_type;
}
std::optional<JsonAccessorComponentType> UnsignedByteAccessor::GetComponentType() const
{
return JsonAccessorComponentType::UNSIGNED_BYTE;
}
size_t UnsignedByteAccessor::GetCount() const
{
return m_count;
}
bool UnsignedByteAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
{
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint8_t temp[2];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[2])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
return true;
}
bool UnsignedByteAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
{
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint8_t temp[3];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[3])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
return true;
}
bool UnsignedByteAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
{
assert(m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint8_t temp[4];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
out[3] = static_cast<float>(temp[3]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
return true;
}
bool UnsignedByteAccessor::GetUnsigned(const size_t index, unsigned& out) const
{
if (index >= m_count)
return false;
uint8_t temp;
if (!m_buffer_view->ReadElement(&temp, index, sizeof(uint8_t)))
return false;
out = temp;
return true;
}
bool UnsignedByteAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
{
assert(m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint8_t temp[4];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
return false;
out[0] = static_cast<unsigned>(temp[0]);
out[1] = static_cast<unsigned>(temp[1]);
out[2] = static_cast<unsigned>(temp[2]);
out[3] = static_cast<unsigned>(temp[3]);
return true;
}
UnsignedShortAccessor::UnsignedShortAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
: m_buffer_view(bufferView),
m_type(type),
m_count(count)
{
}
std::optional<JsonAccessorType> UnsignedShortAccessor::GetType() const
{
return m_type;
}
std::optional<JsonAccessorComponentType> UnsignedShortAccessor::GetComponentType() const
{
return JsonAccessorComponentType::UNSIGNED_SHORT;
}
size_t UnsignedShortAccessor::GetCount() const
{
return m_count;
}
bool UnsignedShortAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
{
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint16_t temp[2];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[2])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
return true;
}
bool UnsignedShortAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
{
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint16_t temp[3];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[3])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
return true;
}
bool UnsignedShortAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
{
assert(m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint16_t temp[4];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[4])))
return false;
// Return as normalized value between 0 and 1
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
out[3] = static_cast<float>(temp[3]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
return true;
}
bool UnsignedShortAccessor::GetUnsigned(const size_t index, unsigned& out) const
{
if (index >= m_count)
return false;
uint16_t temp;
if (!m_buffer_view->ReadElement(&temp, index, sizeof(uint16_t)))
return false;
out = temp;
return true;
}
bool UnsignedShortAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
{
assert(m_type == JsonAccessorType::VEC4);
if (index >= m_count)
return false;
uint16_t temp[4];
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
return false;
out[0] = static_cast<unsigned>(temp[0]);
out[1] = static_cast<unsigned>(temp[1]);
out[2] = static_cast<unsigned>(temp[2]);
out[3] = static_cast<unsigned>(temp[3]);
return true;
}

View File

@ -0,0 +1,111 @@
#pragma once
#include "GltfBufferView.h"
#include "XModel/Gltf/JsonGltf.h"
namespace gltf
{
class Accessor
{
protected:
Accessor() = default;
public:
virtual ~Accessor() = default;
Accessor(const Accessor& other) = default;
Accessor(Accessor&& other) noexcept = default;
Accessor& operator=(const Accessor& other) = default;
Accessor& operator=(Accessor&& other) noexcept = default;
[[nodiscard]] virtual std::optional<JsonAccessorType> GetType() const = 0;
[[nodiscard]] virtual std::optional<JsonAccessorComponentType> GetComponentType() const = 0;
[[nodiscard]] virtual size_t GetCount() const = 0;
[[nodiscard]] virtual bool GetFloatVec2(size_t index, float (&out)[2]) const = 0;
[[nodiscard]] virtual bool GetFloatVec3(size_t index, float (&out)[3]) const = 0;
[[nodiscard]] virtual bool GetFloatVec4(size_t index, float (&out)[4]) const = 0;
[[nodiscard]] virtual bool GetUnsigned(size_t index, unsigned& out) const = 0;
[[nodiscard]] virtual bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const = 0;
};
class NullAccessor final : public Accessor
{
public:
explicit NullAccessor(size_t count);
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
[[nodiscard]] size_t GetCount() const override;
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
private:
size_t m_count;
};
class FloatAccessor final : public Accessor
{
public:
FloatAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
[[nodiscard]] size_t GetCount() const override;
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
private:
const BufferView* m_buffer_view;
JsonAccessorType m_type;
size_t m_count;
};
class UnsignedByteAccessor final : public Accessor
{
public:
UnsignedByteAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
[[nodiscard]] size_t GetCount() const override;
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
private:
const BufferView* m_buffer_view;
JsonAccessorType m_type;
size_t m_count;
};
class UnsignedShortAccessor final : public Accessor
{
public:
UnsignedShortAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
[[nodiscard]] size_t GetCount() const override;
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
private:
const BufferView* m_buffer_view;
JsonAccessorType m_type;
size_t m_count;
};
} // namespace gltf

View File

@ -0,0 +1,80 @@
#include "GltfBuffer.h"
#include "XModel/Gltf/GltfConstants.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#define LTC_NO_PROTOTYPES
#include <tomcrypt.h>
using namespace gltf;
EmbeddedBuffer::EmbeddedBuffer(const void* data, const size_t dataSize)
: m_data(static_cast<const uint8_t*>(data)),
m_data_size(dataSize)
{
}
bool EmbeddedBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
{
assert(m_data);
assert(m_data_size > 0u);
if (offset + count > m_data_size)
return false;
std::memcpy(dest, &m_data[offset], count);
return true;
}
size_t EmbeddedBuffer::GetSize() const
{
return m_data_size;
}
DataUriBuffer::DataUriBuffer()
: m_data(nullptr),
m_data_size(0u)
{
}
bool DataUriBuffer::IsDataUri(const std::string& uri)
{
return uri.starts_with(GLTF_DATA_URI_PREFIX) && uri.size() > URI_PREFIX_LENGTH;
}
bool DataUriBuffer::ReadDataFromUri(const std::string& uri)
{
if (!IsDataUri(uri))
return false;
const auto base64DataLength = uri.size() - URI_PREFIX_LENGTH;
unsigned long outLength = base64DataLength / 4u;
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 false;
}
bool DataUriBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
{
assert(m_data);
assert(m_data_size > 0u);
if (offset + count > m_data_size)
return false;
std::memcpy(dest, &m_data[offset], count);
return true;
}
size_t DataUriBuffer::GetSize() const
{
return m_data_size;
}

View File

@ -0,0 +1,54 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
namespace gltf
{
class Buffer
{
protected:
Buffer() = default;
public:
virtual ~Buffer() = default;
Buffer(const Buffer& other) = default;
Buffer(Buffer&& other) noexcept = default;
Buffer& operator=(const Buffer& other) = default;
Buffer& operator=(Buffer&& other) noexcept = default;
virtual bool ReadData(void* dest, size_t offset, size_t count) const = 0;
[[nodiscard]] virtual size_t GetSize() const = 0;
};
class EmbeddedBuffer final : public Buffer
{
public:
EmbeddedBuffer(const void* data, size_t dataSize);
bool ReadData(void* dest, size_t offset, size_t count) const override;
[[nodiscard]] size_t GetSize() const override;
private:
const uint8_t* m_data;
size_t m_data_size;
};
class DataUriBuffer final : public Buffer
{
public:
DataUriBuffer();
static bool IsDataUri(const std::string& uri);
bool ReadDataFromUri(const std::string& uri);
bool ReadData(void* dest, size_t offset, size_t count) const override;
[[nodiscard]] size_t GetSize() const override;
private:
std::unique_ptr<uint8_t[]> m_data;
size_t m_data_size;
};
} // namespace gltf

View File

@ -0,0 +1,23 @@
#include "GltfBufferView.h"
using namespace gltf;
BufferView::BufferView(const Buffer* buffer, const size_t offset, const size_t length, const size_t stride)
: m_buffer(buffer),
m_offset(offset),
m_length(length),
m_stride(stride)
{
}
bool BufferView::ReadElement(void* dest, const size_t elementIndex, const size_t elementSize) const
{
const auto stride = std::max(elementSize, m_stride);
const auto bufferViewOffset = elementIndex * stride;
if (bufferViewOffset + elementSize > m_length)
return false;
const auto bufferOffset = m_offset + bufferViewOffset;
return m_buffer->ReadData(dest, bufferOffset, elementSize);
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "GltfBuffer.h"
namespace gltf
{
class BufferView
{
public:
BufferView(const Buffer* buffer, size_t offset, size_t length, size_t stride);
bool ReadElement(void* dest, size_t elementIndex, size_t elementSize) const;
private:
const Buffer* m_buffer;
size_t m_offset;
size_t m_length;
size_t m_stride;
};
} // namespace gltf

View File

@ -0,0 +1,18 @@
#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;
};

View File

@ -652,7 +652,7 @@ void AssetDumperWeapon::CopyToFullDef(const WeaponCompleteDef* weapon, WeaponFul
if (fullDef->weapDef.locationDamageMultipliers) if (fullDef->weapDef.locationDamageMultipliers)
{ {
static_assert(std::extent_v<decltype(WeaponFullDef::locationDamageMultipliers)> == HITLOC_NUM); static_assert(std::extent_v<decltype(WeaponFullDef::locationDamageMultipliers)> == HITLOC_COUNT);
assert(sizeof(WeaponFullDef::locationDamageMultipliers) >= sizeof(float) * std::extent_v<decltype(WeaponFullDef::locationDamageMultipliers)>); assert(sizeof(WeaponFullDef::locationDamageMultipliers) >= sizeof(float) * std::extent_v<decltype(WeaponFullDef::locationDamageMultipliers)>);
memcpy(fullDef->locationDamageMultipliers, memcpy(fullDef->locationDamageMultipliers,
fullDef->weapDef.locationDamageMultipliers, fullDef->weapDef.locationDamageMultipliers,

View File

@ -139,14 +139,17 @@ namespace
} }
else else
{ {
bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; const auto& trans = model->trans[boneNum - model->numRootBones];
bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; bone.localOffset[0] = trans.x;
bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; bone.localOffset[1] = trans.y;
bone.localOffset[2] = trans.z;
const auto& quat = model->quats[boneNum - model->numRootBones];
bone.localRotation = { bone.localRotation = {
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), QuatInt16::ToFloat(quat.v[0]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), QuatInt16::ToFloat(quat.v[1]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), QuatInt16::ToFloat(quat.v[2]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]), QuatInt16::ToFloat(quat.v[3]),
}; };
} }
@ -229,15 +232,15 @@ namespace
vertex.coordinates[0] = v.xyz[0]; vertex.coordinates[0] = v.xyz[0];
vertex.coordinates[1] = v.xyz[1]; vertex.coordinates[1] = v.xyz[1];
vertex.coordinates[2] = v.xyz[2]; vertex.coordinates[2] = v.xyz[2];
vertex.normal[0] = normalVec[0]; vertex.normal[0] = normalVec.x;
vertex.normal[1] = normalVec[1]; vertex.normal[1] = normalVec.y;
vertex.normal[2] = normalVec[2]; vertex.normal[2] = normalVec.z;
vertex.color[0] = color[0]; vertex.color[0] = color.r;
vertex.color[1] = color[1]; vertex.color[1] = color.g;
vertex.color[2] = color[2]; vertex.color[2] = color.b;
vertex.color[3] = color[3]; vertex.color[3] = color.a;
vertex.uv[0] = uv[0]; vertex.uv[0] = uv.x;
vertex.uv[1] = uv[1]; vertex.uv[1] = uv.y;
out.m_vertices.emplace_back(vertex); out.m_vertices.emplace_back(vertex);
} }

View File

@ -397,8 +397,8 @@ void AssetDumperWeapon::CopyToFullDef(const WeaponVariantDef* weapon, WeaponFull
if (fullDef->weapDef.locationDamageMultipliers) if (fullDef->weapDef.locationDamageMultipliers)
{ {
assert(sizeof(WeaponFullDef::locationDamageMultipliers) >= sizeof(float) * HITLOC_NUM); assert(sizeof(WeaponFullDef::locationDamageMultipliers) >= sizeof(float) * HITLOC_COUNT);
memcpy(fullDef->locationDamageMultipliers, fullDef->weapDef.locationDamageMultipliers, sizeof(float) * HITLOC_NUM); memcpy(fullDef->locationDamageMultipliers, fullDef->weapDef.locationDamageMultipliers, sizeof(float) * HITLOC_COUNT);
fullDef->weapDef.locationDamageMultipliers = fullDef->locationDamageMultipliers; fullDef->weapDef.locationDamageMultipliers = fullDef->locationDamageMultipliers;
} }

View File

@ -101,8 +101,8 @@ void AssetDumperWeaponAttachmentUnique::CopyToFullDef(const WeaponAttachmentUniq
if (attachment->locationDamageMultipliers) if (attachment->locationDamageMultipliers)
{ {
assert(sizeof(WeaponAttachmentUniqueFull::locationDamageMultipliers) >= sizeof(float) * HITLOC_NUM); assert(sizeof(WeaponAttachmentUniqueFull::locationDamageMultipliers) >= sizeof(float) * HITLOC_COUNT);
memcpy(fullDef->locationDamageMultipliers, attachment->locationDamageMultipliers, sizeof(float) * HITLOC_NUM); memcpy(fullDef->locationDamageMultipliers, attachment->locationDamageMultipliers, sizeof(float) * HITLOC_COUNT);
fullDef->attachment.locationDamageMultipliers = fullDef->locationDamageMultipliers; fullDef->attachment.locationDamageMultipliers = fullDef->locationDamageMultipliers;
} }
} }

View File

@ -1,6 +1,7 @@
#include "AssetDumperXModel.h" #include "AssetDumperXModel.h"
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
#include "Game/T6/XModel/JsonXModelWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h" #include "Utils/DistinctMapper.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.h"
@ -132,14 +133,24 @@ namespace
bone.scale[1] = 1.0f; bone.scale[1] = 1.0f;
bone.scale[2] = 1.0f; bone.scale[2] = 1.0f;
bone.globalOffset[0] = model->baseMat[boneNum].trans.x; if (model->partClassification[boneNum])
bone.globalOffset[1] = model->baseMat[boneNum].trans.y; {
bone.globalOffset[2] = model->baseMat[boneNum].trans.z; if (boneNum < model->numRootBones
|| model->partClassification[model->parentList[boneNum - model->numRootBones]] != model->partClassification[boneNum])
{
std::cerr << std::format("Part: {:02} = {}\n", model->partClassification[boneNum], bone.name);
}
}
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 = { bone.globalRotation = {
model->baseMat[boneNum].quat.x, baseMat.quat.x,
model->baseMat[boneNum].quat.y, baseMat.quat.y,
model->baseMat[boneNum].quat.z, baseMat.quat.z,
model->baseMat[boneNum].quat.w, baseMat.quat.w,
}; };
if (boneNum < model->numRootBones) if (boneNum < model->numRootBones)
@ -151,14 +162,17 @@ namespace
} }
else else
{ {
bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; const auto& trans = model->trans[boneNum - model->numRootBones];
bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; bone.localOffset[0] = trans.x;
bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; bone.localOffset[1] = trans.y;
bone.localOffset[2] = trans.z;
const auto& quat = model->quats[boneNum - model->numRootBones];
bone.localRotation = { bone.localRotation = {
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), QuatInt16::ToFloat(quat.v[0]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), QuatInt16::ToFloat(quat.v[1]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), QuatInt16::ToFloat(quat.v[2]),
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]), QuatInt16::ToFloat(quat.v[3]),
}; };
} }
@ -234,10 +248,12 @@ namespace
const auto& v = surface.verts0[vertexIndex]; const auto& v = surface.verts0[vertexIndex];
vec2_t uv{}; vec2_t uv{};
vec3_t normalVec{}; vec3_t normalVec{};
vec3_t tangentVec{};
vec4_t color{}; vec4_t color{};
Common::Vec2UnpackTexCoords(v.texCoord, &uv); Common::Vec2UnpackTexCoords(v.texCoord, &uv);
Common::Vec3UnpackUnitVec(v.normal, &normalVec); Common::Vec3UnpackUnitVec(v.normal, &normalVec);
Common::Vec3UnpackUnitVec(v.tangent, &tangentVec);
Common::Vec4UnpackGfxColor(v.color, &color); Common::Vec4UnpackGfxColor(v.color, &color);
XModelVertex vertex{}; XModelVertex vertex{};
@ -546,6 +562,15 @@ namespace
} }
} }
} }
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 } // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset) bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
@ -556,4 +581,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); DumpXModelSurfs(context, asset);
DumpXModel(context, asset);
} }

View File

@ -2,7 +2,10 @@
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h" #include "Game/T6/Json/JsonXModel.h"
#include "ObjWriting.h"
#include <cassert>
#include <format>
#include <iomanip> #include <iomanip>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -40,9 +43,36 @@ namespace
return input; 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 "";
}
}
void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) const void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) const
{ {
jXModel.collLod = xmodel.collLod; 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());
jXModel.lods.emplace_back(std::move(lod));
}
if (xmodel.physPreset && xmodel.physPreset->name) if (xmodel.physPreset && xmodel.physPreset->name)
jXModel.physPreset = AssetName(xmodel.physPreset->name); jXModel.physPreset = AssetName(xmodel.physPreset->name);

View File

@ -1,6 +1,5 @@
#include "GltfTextOutput.h" #include "GltfTextOutput.h"
#include "Utils/Alignment.h"
#include "XModel/Gltf/GltfConstants.h" #include "XModel/Gltf/GltfConstants.h"
#include <iomanip> #include <iomanip>
@ -18,7 +17,6 @@ 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
{ {
static constexpr auto URI_PREFIX_LENGTH = std::char_traits<char>::length(GLTF_DATA_URI_PREFIX);
const auto base64Length = 4u * ((bufferSize + 2u) / 3u); const auto base64Length = 4u * ((bufferSize + 2u) / 3u);
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length; const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;

View File

@ -101,7 +101,7 @@ set string szUseHintString;
set string dropHintString; set string dropHintString;
set string szScript; set string szScript;
set reusable locationDamageMultipliers; set reusable locationDamageMultipliers;
set count locationDamageMultipliers HITLOC_NUM; set count locationDamageMultipliers HITLOC_COUNT;
set string fireRumble; set string fireRumble;
set string meleeImpactRumble; set string meleeImpactRumble;
set string turretBarrelSpinRumble; set string turretBarrelSpinRumble;

View File

@ -139,7 +139,7 @@ set count originalAiVsPlayerAccuracyGraphKnots aiVsPlayerAccuracyGraphKnotCount;
set string szUseHintString; set string szUseHintString;
set string dropHintString; set string dropHintString;
set string szScript; set string szScript;
set count locationDamageMultipliers HITLOC_NUM; set count locationDamageMultipliers HITLOC_COUNT;
set reusable locationDamageMultipliers; set reusable locationDamageMultipliers;
set string fireRumble; set string fireRumble;
set string meleeImpactRumble; set string meleeImpactRumble;

View File

@ -160,7 +160,7 @@ set count originalAiVsPlayerAccuracyGraphKnots aiVsPlayerAccuracyGraphKnotCount;
set string szUseHintString; set string szUseHintString;
set string dropHintString; set string dropHintString;
set string szScript; set string szScript;
set count locationDamageMultipliers HITLOC_NUM; set count locationDamageMultipliers HITLOC_COUNT;
set reusable locationDamageMultipliers; set reusable locationDamageMultipliers;
set string fireRumble; set string fireRumble;
set string meleeImpactRumble; set string meleeImpactRumble;