1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
#pragma once
/**
* @file structures.h
* @brief Data structures for MDX model components
*
* This file defines all the structure types used in MDX files, including:
* - Animation sequences
* - Textures and materials
* - Geometry (geosets)
* - Bones and hierarchy nodes
* - Lights and cameras
* - Particle systems and effects
*
* Each structure corresponds to a specific chunk type in the MDX format.
*/
#include <array>
#include "types.h"
namespace whiteout {
namespace mdx {
// Helper macro: defines bitwise operators and a hasFlag overload for a scoped
// flag enum backed by u32. Must be invoked at the same scope as the enum.
#define WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(EnumType) \
inline EnumType operator|(EnumType lhs, EnumType rhs) { \
return static_cast<EnumType>(static_cast<u32>(lhs) | static_cast<u32>(rhs)); \
} \
inline EnumType operator&(EnumType lhs, EnumType rhs) { \
return static_cast<EnumType>(static_cast<u32>(lhs) & static_cast<u32>(rhs)); \
} \
inline EnumType& operator|=(EnumType& lhs, EnumType rhs) { \
lhs = lhs | rhs; \
return lhs; \
} \
inline EnumType& operator&=(EnumType& lhs, EnumType rhs) { \
lhs = lhs & rhs; \
return lhs; \
} \
inline EnumType operator~(EnumType v) { \
return static_cast<EnumType>(~static_cast<u32>(v)); \
} \
inline bool hasFlag(EnumType flags, EnumType flag) { \
return (static_cast<u32>(flags) & static_cast<u32>(flag)) != 0; \
}
// ============================================================================
// Sequence
// ============================================================================
/**
* @brief Animation sequence definition
*
* Sequences define named animation clips with specific frame ranges.
* Examples: "Stand", "Walk", "Attack", "Death"
*/
struct Sequence {
/**
* @brief Sequence playback flags
*/
/// @bind
enum class Flag : u32 {
None = 0x0,
NonLooping = 0x1, ///< Sequence plays once instead of looping
};
std::string name; ///< Sequence name (e.g., "Stand", "Walk")
u32 intervalStart = 0; ///< Starting frame number
u32 intervalEnd = 0; ///< Ending frame number (exclusive)
f32 moveSpeed = 0.0f; ///< Movement speed during this animation
Flag flags = Flag::None; ///< Playback flags
f32 rarity = 0.0f; ///< Rarity factor for variation sequences
u32 syncPoint = 0; ///< Sync point for blending
Extent extent; ///< Bounding volume for this sequence
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(Sequence::Flag)
// ============================================================================
// Texture
// ============================================================================
/**
* @brief Texture definition
*
* Defines a texture file path and its properties.
*/
struct Texture {
/**
* @brief Texture wrap flags
*/
/// @bind
enum class Flag : u32 {
None = 0x0,
WrapWidth = 0x1, ///< Wrap texture horizontally
WrapHeight = 0x2, ///< Wrap texture vertically
};
u32 replaceableId =
0; ///< Replaceable texture ID (0 = not replaceable, 1 = team color, 2 = team glow, etc.)
std::string fileName; ///< Path to texture file (BLP, DDS or TGA)
Flag flags = Flag::None; ///< Texture wrap flags
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(Texture::Flag)
// ============================================================================
// Sound (Deprecated)
// ============================================================================
/**
* @brief Sound definition (deprecated, not used in shipped models)
*
* Sounds define audio file paths and playback properties.
* While readers for this chunk exist in the internal game code,
* no known shipped model contains SNDS data.
*
* Binary layout: 56 bytes per entry
*/
struct Sound {
std::string soundFile; ///< Path to sound file (44 bytes, null-terminated)
f32 maximumDistance = 0.0f; ///< Maximum audible distance
f32 minimumDistance = 0.0f; ///< Minimum distance (full volume)
u32 soundChannel = 0; ///< Sound channel identifier
};
// ============================================================================
// Node (Base for hierarchy)
// ============================================================================
/**
* @brief Base node in the model hierarchy
*
* Nodes form the skeleton and attachment system of the model. Each node
* can have a parent creating a transformation hierarchy. Nodes can be:
* - Bones (for skeletal animation)
* - Helpers (attachment points)
* - Lights
* - Particle emitters
* - And other special objects
*
* Nodes contain transformation animation tracks (translation, rotation, scaling).
*/
struct Node {
/**
* @brief Type of node in the hierarchy
*/
/// @bind
enum class NodeType : u32 {
Bone, ///< Skeleton bone
Light, ///< Light source
Helper, ///< Helper/attachment point
Attachment, ///< Equipment attachment
ParticleEmitter, ///< Particle emitter v1
ParticleEmitter2, ///< Particle emitter v2
RibbonEmitter, ///< Ribbon trail emitter
EventObject, ///< Event trigger
Camera, ///< Camera
CollisionShape, ///< Collision volume
FaceEffect, ///< Face effect (Reforged)
CornEmitter ///< PopcornFX emitter (Reforged)
};
/**
* @brief Flags controlling node behavior and rendering
*/
/// @bind
enum class NodeFlag : u32 {
None = 0x0,
DontInheritTranslation = 0x1, ///< Don't inherit parent translation
DontInheritScaling = 0x2, ///< Don't inherit parent scaling
DontInheritRotation = 0x4, ///< Don't inherit parent rotation
Billboarded = 0x8, ///< Always face camera
BillboardedLockX = 0x10, ///< Billboard but lock X axis
BillboardedLockY = 0x20, ///< Billboard but lock Y axis
BillboardedLockZ = 0x40, ///< Billboard but lock Z axis
CameraAnchored = 0x80, ///< Anchored to camera
Bone = 0x100, ///< This is a bone
Light = 0x200, ///< This is a light
EventObject = 0x400, ///< This is an event object
Attachment = 0x800, ///< This is an attachment
ParticleEmitter = 0x1000, ///< This is a particle emitter
CollisionShape = 0x2000, ///< This is a collision shape
RibbonEmitter = 0x4000, ///< This is a ribbon emitter
// Bits 0x8000..0x40000 carry different meanings depending on node type.
// Aliases below give each context its own readable name.
Unshaded = 0x8000, ///< Unshaded (PE2/Popcorn) / EmitterUsesMdl (PE)
EmitterUsesMdl = 0x8000, ///< Particle emitter uses MDL model
SortPrimitives = 0x10000, ///< Sort primitives (PE2/Popcorn)
SortPrimsFarZ = 0x10000, ///< Alias of SortPrimitives
EmitterUsesTga = 0x10000, ///< Particle emitter uses TGA (PE only)
LineEmitter = 0x20000, ///< Line-shaped emitter (PE2)
PopcornUnfogged = 0x20000, ///< Unfogged (Popcorn emitter)
Unfogged = 0x40000, ///< Not affected by fog (PE2)
PopcornScaling = 0x40000, ///< Particle scaling (Popcorn emitter)
ModelSpace = 0x80000, ///< Use model space coordinates
XYQuad = 0x100000 ///< XY quad billboarding
};
static constexpr u32 NO_PARENT =
0xFFFFFFFF; ///< @bind js_name=MdxNoParent — Value indicating no parent node
std::string name; ///< Node name (for debugging/reference)
u32 objectId = 0; ///< Unique ID for this node
u32 parentId = NO_PARENT; ///< Parent node ID or NO_PARENT
NodeFlag flags = NodeFlag::None; ///< Combination of NodeFlag values
NodeType type = NodeType::Helper; ///< Type of this node
u32 nodeFamilyId = 0; ///< Used to link related nodes of the same type
// Animation tracks
Track<Vector3f> translationTracks; ///< Position animation
Track<Quaternion> rotationTracks; ///< Rotation animation (quaternion XYZW)
Track<Vector3f> scalingTracks; ///< Scale animation
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(Node::NodeFlag)
// ============================================================================
// Sound Emitter (Deprecated)
// ============================================================================
/**
* @brief Sound emitter attached to the model hierarchy (deprecated)
*
* Sound emitters are positioned nodes that can emit sounds at specific
* animation frames via KSEK tracks. While readers exist in the internal
* game code, no known shipped model contains SNEM data.
*/
struct SoundEmitter {
Node node; ///< Base node data with transform
Track<u32> soundTrack; ///< KSEK - sound event track (u32 values)
};
// ============================================================================
// Layer
// ============================================================================
/**
* @brief Material rendering layer
*
* Materials can have multiple layers, each with its own texture and blending mode.
* Layers control how textures are combined and rendered.
*/
struct Layer {
/**
* @brief Blending/filter mode for the layer
*/
/// @bind
enum class FilterMode : u32 {
None = 0, ///< No blending
Transparent = 1, ///< Alpha test transparency
Blend = 2, ///< Alpha blending
Additive = 3, ///< Additive blending
AddAlpha = 4, ///< Additive alpha blending
Modulate = 5, ///< Modulate (multiply) blending
Modulate2x = 6, ///< Modulate 2x (brighten)
Count,
};
// For MDX < 1100 only a tiny subset is authorable.
// For MDX >= 1100 the layer's shaderIdMDL
// is a raw uint32, so any of these values can appear on disk.
/// @bind
enum class ShaderType : u32 {
SD = 0,
HD = 1,
SDOnHD = 2,
Terrain = 3,
Water = 4,
Fog = 5,
Foliage = 6,
FoliagePush = 7,
Sprite = 8,
DebugTexture = 9,
DepthOfField = 10,
BloomCombine = 11,
BloomExtract = 12,
GaussianBlur = 13,
Tonemap = 14,
Movie = 15,
FFXCMAAEdge0 = 16,
FFXCMAAEdge1 = 17,
FFXCMAAEdgeCombine = 18,
FFXCMAAProcessAndApply = 19,
PopcornFX = 20,
ConeIndicator = 21,
CliffBlightMiscTerrain = 22,
Distortion = 23,
Crystal = 24,
Imgui = 25,
};
/**
* @brief Shader and rendering flags
*/
/// @bind
enum class ShadingFlag : u32 {
None = 0,
Unshaded = 0x1, ///< Not affected by lighting
SphereEnvMap = 0x2, ///< Spherical environment mapping
WrapWidth = 0x4, ///< Texture U-wrap
WrapHeight = 0x8, ///< Texture V-wrap
TwoSided = 0x10, ///< Render both sides of polygons
Unfogged = 0x20, ///< Not affected by fog
NoDepthTest = 0x40, ///< Disable depth testing
NoDepthSet = 0x80, ///< Don't write to depth buffer
Unlit = 0x100, ///< Reforged: bypass lighting pipeline
};
/// @bind
enum class SlotType : u32 {
DiffuseMap = 0,
NormalMap = 1,
ORMMap = 2,
EmissiveMap = 3,
TeamColor = 4,
EnvironmentMap = 5,
Unknown
};
/**
* @brief Sub-texture definition (Reforged multi-texture)
*/
/// @bind
struct SubTexture {
u32 textureId = 0; ///< Index into texture array
SlotType slot = SlotType::DiffuseMap; ///< Texture slot number
Track<u32> tracks; ///< Texture ID animation
};
FilterMode filterMode = FilterMode::None; ///< Blending mode
ShadingFlag shadingFlags = ShadingFlag::None; ///< Rendering flags
u32 textureId = 0; ///< Texture index (versions 800-1100)
u32 textureAnimationId = 0; ///< Texture animation index
u32 coordId = 0; ///< Texture coordinate set index
f32 alpha = 1.0f; ///< Layer opacity
// Reforged PBR properties (version > 800)
f32 emissiveGain = 1.0f; ///< Emissive light intensity (default 1.0)
Vector3f fresnelColor = Vector3f(1, 1, 1); ///< Fresnel effect color
f32 fresnelOpacity = 0.0f; ///< Fresnel effect opacity
f32 fresnelTeamColor = 0.0f; ///< Fresnel team color factor
ShaderType shader = ShaderType::SD; ///< Shader to use for this layer
bool is_hd = false; ///< @bind rename=isHd — True if using Reforged HD shading
std::vector<SubTexture> subTextures; ///< Multi-texture support (version 1200+)
// Animation tracks
Track<u32> textureIdTracks; ///< Texture ID animation (versions 800-1100)
Track<f32> alphaTracks; ///< Alpha animation
Track<f32> emissiveGainTracks; ///< Emissive gain animation
Track<Vector3f> fresnelColorTracks; ///< Fresnel color animation
Track<f32> fresnelAlphaTracks; ///< Fresnel alpha animation
Track<f32> fresnelTeamColorTracks; ///< Fresnel team color animation
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(Layer::ShadingFlag)
// ============================================================================
// Material
// ============================================================================
/**
* @brief Material definition with rendering properties
*
* Materials define how surfaces are rendered. Each material contains one or more
* layers that specify textures and blending modes.
*/
struct Material {
/**
* @brief Material-level flags
*/
/// @bind
enum class Flag : u32 {
None = 0x0,
ConstantColor = 0x1, ///< Use constant color (no per-vertex tinting)
TwoSided = 0x2, ///< Render both sides of polygons
Unfogged = 0x4, ///< Not affected by fog
SortPrimsNearZ = 0x8, ///< Sort by near-Z
SortPrimsFarZ = 0x10, ///< Sort by far-Z; alias: SortPrimitives
SortPrimitives = 0x10, ///< Legacy alias for SortPrimsFarZ (HiveWorkshop name)
FullResolution = 0x20, ///< Force full-resolution textures
};
i32 priorityPlane = 0; ///< Rendering priority (higher = render last); signed
Flag flags = Flag::None; ///< Material flags
std::string shader; ///< Shader name (Reforged)
std::vector<Layer> layers; ///< Rendering layers
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(Material::Flag)
// ============================================================================
// Texture Animation
// ============================================================================
/**
* @brief UV coordinate animation
*
* Texture animations transform UV coordinates over time, creating effects
* like scrolling water, rotating symbols, etc.
*/
struct TextureAnimation {
Track<Vector3f> translationTracks; ///< UV translation animation
Track<Quaternion> rotationTracks; ///< UV rotation animation (quaternion XYZW)
Track<Vector3f> scalingTracks; ///< UV scaling animation
};
// ============================================================================
// Geoset
// ============================================================================
/**
* @brief Mesh geometry
*
* A geoset is a complete mesh with vertices, normals, faces, and skinning data.
* Models can have multiple geosets with different materials or LOD levels.
*/
struct Geoset {
std::vector<Vector3f> vertexPositions; ///< Vertex positions
std::vector<Vector3f> vertexNormals; ///< Vertex normals
std::vector<u32> faceTypeGroups; ///< Face type groups (4 = triangles)
std::vector<u32> faceGroups; ///< Number of indices per group
std::vector<u16> faces; ///< Vertex indices (triangles)
std::vector<u8> vertexGroups; ///< @bind array_with_view — Bone groups per vertex
std::vector<u32> matrixGroups; ///< Number of matrices per group
std::vector<u32> matrixIndices; ///< Bone indices
u32 materialId = 0; ///< Material index
u32 selectionGroup = 0; ///< Selection group (for editor)
u32 selectionFlags = 0; ///< Selection flags
u32 lod = 0; ///< Level of detail index
std::string lodName; ///< LOD name
Extent extent; ///< Bounding volume
std::vector<Extent> sequenceExtents; ///< Per-sequence bounding volumes
std::vector<Vector4f> tangents; ///< Tangent vectors (for normal mapping)
std::vector<u8> skinData; ///< @bind array_with_view — Bone indices and weights
std::vector<std::vector<Vector2f>> textureCoordinateSets; ///< UV coordinates (multiple sets)
};
// ============================================================================
// Geoset Animation
// ============================================================================
/**
* @brief Geoset visibility and color animation
*
* Geoset animations control the visibility and color tinting of meshes.
*/
struct GeosetAnimation {
/**
* @brief Geoset animation flags
*/
/// @bind
enum class Flag : u32 {
None = 0x0,
DropShadow = 0x1, ///< Geoset casts a drop shadow
Color = 0x2, ///< Use the per-geoset color/alpha values (else inherit)
};
f32 alpha = 1.0f; ///< Base alpha value
Flag flags = Flag::None; ///< Animation flags
Vector3f color = Vector3f(1, 1, 1); ///< Base color tint
u32 geosetId = 0; ///< Target geoset index
Track<f32> alphaTracks; ///< Alpha animation
Track<Vector3f> colorTracks; ///< Color animation
};
WHITEOUT_MDX_DEFINE_FLAG_OPERATORS(GeosetAnimation::Flag)
// ============================================================================
// Bone
// ============================================================================
/**
* @brief Skeleton bone for skinned animation
*
* Bones form the skeleton that deforms mesh geometry. Each bone is a node
* in the hierarchy and can affect one or more geosets.
*/
struct Bone {
static constexpr u32 MULTIPLE_GEOSETS =
0xFFFFFFFF; ///< @bind js_name=MdxMultipleGeosets — Bone affects all geosets
Node node; ///< Base node data with transform
u32 geosetId = MULTIPLE_GEOSETS; ///< Geoset this bone affects
u32 geosetAnimationId = MULTIPLE_GEOSETS; ///< Geoset animation index
};
// ============================================================================
// Light
// ============================================================================
/**
* @brief Light source
*
* Lights can be attached to bones to move with animations.
* They affect how the model is rendered in the game engine.
*/
struct Light {
/**
* @brief Type of light source
*/
/// @bind
enum class LightType : u32 {
Omni = 0, ///< Point light (radiates in all directions)
Directional = 1, ///< Directional light (like sunlight)
Ambient = 2 ///< Ambient light (affects everything equally)
};
Node node; ///< Base node data
LightType type = LightType::Omni; ///< Type of light
f32 attenuationStart = 0.0f; ///< Distance where attenuation begins
f32 attenuationEnd = 100.0f; ///< Distance where light reaches zero
Vector3f color = Vector3f(1, 1, 1); ///< Light color (RGB)
f32 intensity = 1.0f; ///< Light intensity
Vector3f ambientColor = Vector3f(0, 0, 0); ///< Ambient light color
f32 ambientIntensity = 0.0f; ///< Ambient intensity
f32 shadowIntensity = 0.4f; ///< Shadow darkness (Reforged)
// Animation tracks
Track<f32> attenuationStartTracks; ///< Attenuation start animation
Track<f32> attenuationEndTracks; ///< Attenuation end animation
Track<Vector3f> colorTracks; ///< Color animation
Track<f32> intensityTracks; ///< Intensity animation
Track<f32> ambientIntensityTracks; ///< Ambient intensity animation
Track<Vector3f> ambientColorTracks; ///< Ambient color animation
Track<f32> visibilityTracks; ///< Visibility animation
Track<f32> shadowIntensityTracks; ///< Shadow intensity animation (Reforged)
};
// ============================================================================
// Helper
// ============================================================================
/**
* @brief Helper node (attachment point)
*
* Helpers are simple nodes used as attachment points for effects,
* weapons, or other objects. They don't render anything themselves.
*/
struct Helper {
Node node; ///< Base node data with transform
};
// ============================================================================
// Attachment
// ============================================================================
/**
* @brief Attachment point for external models
*
* Attachments define points where other models (like weapons or shields)
* can be attached to this model.
*/
struct Attachment {
Node node; ///< Base node data
std::string path; ///< Path to attached model
u32 attachmentId = 0; ///< Attachment slot ID
Track<f32> visibilityTracks; ///< Visibility animation
};
// ============================================================================
// Particle Emitter
// ============================================================================
/**
* @brief Particle emitter version 1 (uses external model)
*
* Legacy particle emitter that spawns copies of an external model file.
* Used for effects like footprints, blood splatter, etc.
*/
struct ParticleEmitter {
Node node; ///< Base node data
f32 emissionRate = 0.0f; ///< Particles per second
f32 gravity = 0.0f; ///< Gravity force
f32 longitude = 0.0f; ///< Emission longitude angle
f32 latitude = 0.0f; ///< Emission latitude angle
std::string spawnModelFileName; ///< Model to spawn as particles
f32 lifespan = 0.0f; ///< Particle lifetime in seconds
f32 initialVelocity = 0.0f; ///< Initial particle speed
// Animation tracks
Track<f32> emissionRateTracks; ///< Emission rate animation
Track<f32> gravityTracks; ///< Gravity animation
Track<f32> longitudeTracks; ///< Longitude animation
Track<f32> latitudeTracks; ///< Latitude animation
Track<f32> lifespanTracks; ///< Lifespan animation
Track<f32> speedTracks; ///< Speed animation
Track<f32> visibilityTracks; ///< Visibility animation
};
// ============================================================================
// Particle Emitter 2
// ============================================================================
/**
* @brief Particle emitter version 2 (sprite-based)
*
* More advanced particle system that uses sprite textures. Supports various
* particle shapes, blending modes, and animation over the particle lifetime.
* Used for fire, smoke, magic effects, etc.
*/
struct ParticleEmitter2 {
Node node; ///< Base node data
f32 speed = 0.0f; ///< Particle speed
f32 variation = 0.0f; ///< Speed variation (randomness)
f32 latitude = 0.0f; ///< Emission cone latitude
f32 gravity = 0.0f; ///< Gravity acceleration
f32 lifespan = 0.0f; ///< Particle lifetime in seconds
f32 emissionRate = 0.0f; ///< Particles per second
f32 length = 0.0f; ///< Particle length (for tail effect)
f32 width = 0.0f; ///< Particle width
u32 filterMode = 0; ///< Blending mode
u32 rows = 1; ///< Texture atlas rows
u32 columns = 1; ///< Texture atlas columns
u32 headOrTail = 0; ///< Head/tail flags
f32 tailLength = 0.0f; ///< Tail particle length
f32 time = 0.0f; ///< Middle time for segment animation
std::array<Vector3f, 3> segmentColor{}; ///< Color at start/middle/end
std::array<u8, 3> segmentAlpha{}; ///< Alpha at start/middle/end
std::array<f32, 3> segmentScaling{}; ///< Scale at start/middle/end
std::array<u32, 3> headInterval{}; ///< Head lifetime intervals
std::array<u32, 3> headDecayInterval{}; ///< Head decay intervals
std::array<u32, 3> tailInterval{}; ///< Tail lifetime intervals
std::array<u32, 3> tailDecayInterval{}; ///< Tail decay intervals
u32 textureId = 0; ///< Texture index
u32 squirt = 0; ///< Squirt flag (burst mode)
i32 priorityPlane = 0; ///< Rendering priority; signed
u32 replaceableId = 0; ///< Replaceable texture ID
// Animation tracks
Track<f32> speedTracks; ///< Speed animation
Track<f32> variationTracks; ///< Variation animation
Track<f32> latitudeTracks; ///< Latitude animation
Track<f32> gravityTracks; ///< Gravity animation
Track<f32> emissionRateTracks; ///< Emission rate animation
Track<f32> lengthTracks; ///< Length animation
Track<f32> widthTracks; ///< Width animation
Track<f32> visibilityTracks; ///< Visibility animation
};
// ============================================================================
// Ribbon Emitter
// ============================================================================
/**
* @brief Ribbon/trail emitter
*
* Creates ribbon trails that follow the emitter's movement, like sword trails,
* missile contrails, etc.
*/
struct RibbonEmitter {
Node node; ///< Base node data
f32 heightAbove = 0.0f; ///< Height above attachment point
f32 heightBelow = 0.0f; ///< Height below attachment point
f32 alpha = 1.0f; ///< Ribbon opacity
Vector3f color = Vector3f(1, 1, 1); ///< Ribbon color
f32 lifespan = 0.0f; ///< Ribbon segment lifetime
u32 textureSlot = 0; ///< Texture slot in material
u32 emissionRate = 0; ///< Emission rate
u32 rows = 1; ///< Texture atlas rows
u32 columns = 1; ///< Texture atlas columns
u32 materialId = 0; ///< Material index
f32 gravity = 0.0f; ///< Gravity effect
// Animation tracks
Track<f32> heightAboveTracks; ///< Height above animation
Track<f32> heightBelowTracks; ///< Height below animation
Track<f32> alphaTracks; ///< Alpha animation
Track<Vector3f> colorTracks; ///< Color animation
Track<u32> textureSlotTracks; ///< Texture slot animation
Track<f32> visibilityTracks; ///< Visibility animation
};
// ============================================================================
// Event Object
// ============================================================================
/**
* @brief Animation event trigger
*
* Event objects fire events at specific animation frames, used to trigger
* sounds, spawn effects, etc. synchronized with animations.
*/
struct EventObject {
Node node; ///< Base node data
u32 globalSequenceId = 0xFFFFFFFF; ///< Global sequence if looping
std::vector<u32> eventTrackTimes; ///< Frame numbers when events fire
};
// ============================================================================
// Camera
// ============================================================================
/**
* @brief Camera definition
*
* Cameras define viewpoints that can be used for portrait renders or
* in-game cutscenes.
*/
struct Camera {
std::string name; ///< Camera name
Vector3f position; ///< Camera position
f32 fieldOfView = 0.0f; ///< Field of view angle in radians
f32 farClippingPlane = 100.0f; ///< Far clipping distance
f32 nearClippingPlane = 0.1f; ///< Near clipping distance
Vector3f targetPosition; ///< Look-at target position
// Animation tracks
Track<Vector3f> positionTracks; ///< Position animation
Track<f32> targetRotationTracks; ///< Target rotation animation
Track<Vector3f> targetPositionTracks; ///< Target position animation
};
// ============================================================================
// Collision Shape
// ============================================================================
/**
* @brief Collision volume for physics
*
* Collision shapes define simplified geometry for collision detection.
*/
struct CollisionShape {
/// @bind
enum class ShapeType : u32 { Box = 0, Plane = 1, Sphere = 2, Cylinder = 3 };
Node node; ///< Base node data
ShapeType type = ShapeType::Box; ///< Shape type
std::vector<Vector3f> vertices; ///< Shape vertices (box only)
f32 radius = 0.0f; ///< Radius (sphere/cylinder only)
};
// ============================================================================
// Face Effect (Reforged)
// ============================================================================
/**
* @brief Facial animation effect (Reforged)
*
* Holds a name and a path to the FaceFX setup file used for a character's face.
*/
struct FaceEffect {
std::string name; ///< Section name
std::string path; ///< Path to facial animation data
};
// ============================================================================
// Corn Emitter (PopcornFX - Reforged)
// ============================================================================
/**
* @brief PopcornFX particle emitter (Reforged)
*
* Advanced particle system using PopcornFX technology in Warcraft III: Reforged.
* Color (RGB) and alpha are stored as separate fields with independent
* animation tracks.
*/
struct CornEmitter {
Node node; ///< Base node data
f32 lifeSpan = 0.0f; ///< Particle lifetime (default)
f32 emissionRate = 0.0f; ///< Emission rate (default)
f32 speed = 0.0f; ///< Particle speed (default)
Vector3f color = Vector3f(1, 1, 1); ///< Particle color (RGB)
f32 alpha = 1.0f; ///< Particle alpha (default)
u32 replaceableId = 0; ///< Replaceable texture ID
std::string path; ///< Path to PopcornFX effect
std::string animVisibilityGuide; ///< Animation visibility guide
// Animation tracks
Track<f32> lifeSpanTracks; ///< Lifespan animation
Track<f32> emissionRateTracks; ///< Emission rate animation
Track<f32> speedTracks; ///< Speed animation
Track<Vector3f> colorTracks; ///< Color animation
Track<f32> alphaTracks; ///< Alpha animation
Track<f32> visibilityTracks; ///< Visibility animation
};
} // namespace mdx
} // namespace whiteout