24#include <welder/vocabulary.hpp>
42 using namespace wowlib::formats::m2::root::record;
45 inline constexpr std::uint32_t
Md20Magic = 0x3032444D;
51 =welder::doc(
"M2 global flags: tilt behavior, the texture-combiner-combo "
52 "gate, physics participation and exporter-era markers.")
54 TiltX [[=welder::doc(
"Tilt the model over X (flying mounts).")]] = 0x1,
55 TiltY [[=welder::doc(
"Tilt the model over Y.")]] = 0x2,
57 "block trails the header (TBC+).")]] = 0x8,
58 LoadPhysData [[=welder::doc(
"Request the .phys file (MoP+).")]] = 0x20,
59 Unk0x80 [[=welder::doc(
"Unset stops demon-hunter tattoos glowing (WoD+).")]] = 0x80,
60 CameraRelated [[=welder::doc(
"Camera related (WoD+).")]] = 0x100,
62 "layout even below v272.")]] = 0x200,
64 [[=welder::doc(
"Texture transforms animate on the bone's sequence "
65 "(Legion+).")]] = 0x800,
66 ChunkedAnimFiles [[=welder::doc(
"The .anim files are chunked (Legion+).")]] = 0x2000
80 =welder::weld_as(
"M2Root"),
83 An MD20 model body, abstract over the client version. Construct a
84 concrete version with M2Root.for_version(expansion); the per-version
85 M2Root* classes are subclasses. See https://wowdev.wiki/M2.)")
97 template <ClientVersion V>
102 =welder::mark::no_reassign,
103 =welder::doc(
"Playable-animation fallbacks, one per AnimationData.dbc "
110 =welder::mark::no_reassign,
111 =welder::doc(
"The skin profiles (LOD views), embedded in the model "
112 "pre-WotLK; WotLK+ moves them to .skin files.")]]
118 =welder::mark::no_reassign,
119 =welder::doc(
"Texture flipbooks (pre-WotLK; never seen engaged).")]]
122 [[=welder::mark::exclude]]
132 =welder::mark::exclude,
133 =welder::doc(
"How many .skin files (LOD views) belong to the model "
134 "(WotLK+). A derived layout field: the M2 assembly's "
135 "skins vector is the source of truth — its write stamps "
136 "this from skins.size(), and the bindings hide it.")]]
139 [[=welder::mark::exclude]]
140 bool operator==(
const DataWotlk&)
const =
default;
149 =welder::mark::no_reassign,
150 =welder::doc(
"Second-texture material override combos; present in the "
151 "layout only under global flag 0x8 (TBC+).")]]
154 [[=welder::mark::exclude]]
155 bool operator==(
const DataTbc&)
const =
default;
167 template <ClientVersion V>
171 An MD20 model body for one client version: header scalars plus every
172 offset-addressed block, decoded. A version's class carries ONLY the
173 members that version defines. See https://wowdev.wiki/M2.)")
184 [[=welder::mark::exclude,
185 =welder::doc(
"The leading magic, 'MD20' — constant on every model; "
186 "hidden from the bindings.")]]
189 [[=welder::doc(
"The MD20 format version (256 vanilla .. 274 Legion+).")]]
192 [[=welder::doc(
"The model's internal name; empty in 9.2+ files.")]]
195 [[=welder::doc(
"Global flags, see GlobalFlags.")]]
198 [[=welder::mark::no_reassign,
199 =welder::doc(
"Global-sequence loop lengths (timestamps).")]]
200 std::vector<M2Loop> globalLoops;
202 [[=welder::mark::no_reassign,
203 =welder::doc(
"The animation sequences.")]]
204 std::vector<M2Sequence<V>> sequences;
206 [[=welder::mark::no_reassign,
208 =welder::doc(
"Animation-id hash table: AnimationData.dbc id -> sequence "
209 "index, quadratic probing, -1 empty.")]]
210 std::vector<std::int16_t> sequenceLookups;
212 [[=welder::mark::no_reassign,
213 =welder::doc(
"The bones (MAX_BONES nominally 256).")]]
214 std::vector<M2CompBone<V>> bones;
220 [[=welder::mark::no_reassign,
222 "Key-bone lookup: key bone slot -> bone index, -1 if none.")]
224 std::vector<std::int16_t> keyBoneLookup;
226 [[=welder::mark::no_reassign,
227 =welder::doc(
"The global vertex list (Z-up model space).")]]
228 std::vector<M2Vertex> vertices;
230 [[=welder::mark::no_reassign,
232 "Color and alpha animations, referenced from skin batches.")]
234 std::vector<M2Color<V>> colors;
236 [[=welder::mark::no_reassign,
237 =welder::doc(
"The texture definitions.")]]
238 std::vector<M2Texture> textures;
240 [[=welder::mark::no_reassign,
241 =welder::doc(
"Global transparency weights.")]]
242 std::vector<M2TextureWeight<V>> textureWeights;
244 [[=welder::mark::no_reassign,
245 =welder::doc(
"UV animations.")]]
246 std::vector<M2TextureTransform<V>> textureTransforms;
248 [[=welder::mark::no_reassign,
250 =welder::doc(
"Replacable-texture reverse lookup: replacable id -> "
251 "texture index or -1.")]]
252 std::vector<std::int16_t> replacableTextureLookup;
254 [[=welder::mark::no_reassign,
255 =welder::doc(
"Materials: render flags + blending modes.")]]
256 std::vector<M2Material> materials;
259 [[=welder::mark::no_reassign,
260 =welder::doc(
"Bone lookup: skin sections select bone subsets through it.")
262 std::vector<std::uint16_t> boneLookupTable;
264 [[=welder::mark::no_reassign,
266 =welder::doc(
"Texture lookup: batches select textures through it.")]]
267 std::vector<std::uint16_t> textureLookupTable;
269 [[=welder::mark::no_reassign,
270 =welder::doc(
"Texture-mapping lookup: -1 environment, 0 first UV set, "
271 "1 second (unused since Cata).")]]
272 std::vector<std::int16_t> textureMappingLookupTable;
274 [[=welder::mark::no_reassign,
276 =welder::doc(
"Transparency lookup: batches select texture weights "
278 std::vector<std::uint16_t> transparencyLookupTable;
280 [[=welder::mark::no_reassign,
282 =welder::doc(
"Texture-transform lookup: batches select UV animations "
283 "through it, -1 static.")]]
284 std::vector<std::int16_t> textureTransformsLookupTable;
286 [[=welder::doc(
"The render bounds.")]]
289 [[=welder::doc(
"The render bounding-sphere radius.")]]
290 float boundingSphereRadius = 0;
292 [[=welder::doc(
"The collision bounds.")]]
295 [[=welder::doc(
"The collision bounding-sphere radius.")]]
296 float collisionSphereRadius = 0;
298 [[=welder::mark::no_reassign,
301 =welder::doc(
"Collision-hull triangle indices (3 per face).")]]
302 std::vector<std::uint16_t> collisionTriangles;
304 [[=welder::mark::no_reassign,
305 =welder::doc(
"Collision-hull vertices.")]]
306 std::vector<C3Vector> collisionVertices;
308 [[=welder::mark::no_reassign,
310 =welder::doc(
"Collision-hull per-face normals.")]]
311 std::vector<C3Vector> collisionNormals;
313 [[=welder::mark::no_reassign,
314 =welder::doc(
"Attachment points (weapons, effects, name plates).")]]
315 std::vector<M2Attachment<V>> attachments;
317 [[=welder::mark::no_reassign,
319 =welder::doc(
"Attachment lookup: attachment id -> index.")]]
320 std::vector<std::uint16_t> attachmentLookupTable;
322 [[=welder::mark::no_reassign,
323 =welder::doc(
"Timed events (sounds, footsteps, death thud).")]]
324 std::vector<M2Event<V>> events;
326 [[=welder::mark::no_reassign,
327 =welder::doc(
"Model lights.")]]
328 std::vector<M2Light<V>> lights;
330 [[=welder::mark::no_reassign,
331 =welder::doc(
"Cameras (portrait, character info, flyby).")]]
332 std::vector<M2Camera<V>> cameras;
334 [[=welder::mark::no_reassign,
336 =welder::doc(
"Camera lookup: camera type -> index.")]]
337 std::vector<std::uint16_t> cameraLookupTable;
339 [[=welder::mark::no_reassign,
340 =welder::doc(
"Ribbon (trail) emitters.")]]
341 std::vector<M2Ribbon<V>> ribbonEmitters;
343 [[=welder::mark::no_reassign,
344 =welder::doc(
"Particle emitters.")]]
345 std::vector<M2Particle<V>> particleEmitters;
354 [[=welder::mark::exclude]]
358 for (std::size_t i = 0; i < bones.size(); ++i) {
359 const std::int16_t parent = bones[i].parentBone;
362 if (
static_cast<std::size_t
>(parent) >= bones.size())
363 report.
addError(std::format(
"bones[{}]", i),
364 std::format(
"parent_bone {} out of range: {} bones",
367 else if (
static_cast<std::size_t
>(parent) >= i)
368 report.
addError(std::format(
"bones[{}]", i),
370 "parent_bone {} does not precede the child",
376 for (std::size_t i = 0; i < sequences.size(); ++i) {
377 if (!sequences[i].isAlias())
380 std::size_t steps = 0;
381 while (steps++ <= sequences.size()) {
382 const std::size_t next = sequences[at].aliasNext;
383 if (next >= sequences.size()) {
384 report.
addError(std::format(
"sequences[{}]", at),
386 "alias_next {} out of range: {} sequences", next,
391 report.
addError(std::format(
"sequences[{}]", at),
392 "alias_next points at itself");
396 if (!sequences[at].isAlias())
399 if (steps > sequences.size())
400 report.
addError(std::format(
"sequences[{}]", i),
401 "alias chain does not reach a non-alias sequence (cycle)");
405 bool operator==(
const M2Root&)
const =
default;
413 template <ClientVersion V>
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
M2 effect-emitter records (namespace wowlib::formats::m2::root::record): ribbon emitters and the part...
Binding-language identities welder's core does not name.
#define WOWLIB_CS_FAMILY_SURFACE
The C# rod's family-surface opt-in, spellable in every build.
The M2 version vocabulary: the layout pivots record/entity partial specializations key on,...
M2 geometry, texture and material records (namespace wowlib::formats::m2::root::record): the global v...
M2 skin-profile records (namespace wowlib::formats::m2::skin): the LOD view onto the model — local ve...
The offset-format storage vocabulary and serializer engine for the M2 family, in one header.
M2 skeleton records (namespace wowlib::formats::m2::root::record): M2CompBone across its eras.
M2 scene records (namespace wowlib::formats::m2::root::record): attachments, events,...
M2 animation-sequence records (namespace wowlib::formats::m2::root::record): M2Sequence across its th...
The M2 animation vocabulary (namespace wowlib::formats::m2::root::record): the small fixed-size primi...
The binary-level math and color primitives shared across WoW file formats (wowdev....
The conditional-base mechanism that gives a versioned chunked entity exactly the fields its client ve...