-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmdx_bindings.cpp
More file actions
966 lines (860 loc) · 69.7 KB
/
Copy pathmdx_bindings.cpp
File metadata and controls
966 lines (860 loc) · 69.7 KB
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
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
//
// AUTOGENERATED by tools/codegen — do not edit by hand.
// Regenerate via: python -m tools.codegen.codegen mdx --backend pybind11
//
// Source headers and `@bind` annotations live under include/whiteout/.
//
// Include order matters here:
// 1. pybind11/pybind11.h sets up the base library
// 2. project headers define whiteout::u32 et al used inside MAKE_OPAQUE
// 3. PYBIND11_MAKE_OPAQUE opts out of stl.h's auto-conversion for our vectors
// 4. pybind11/stl.h, stl_bind.h honor the opaque declarations
#include <pybind11/pybind11.h>
#include <array>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <whiteout/vector_types.h>
#include <whiteout/models/mdx/types.h>
#include <whiteout/models/mdx/structures.h>
#include <whiteout/models/mdx/parser.h>
#include <whiteout/models/mdx/writer.h>
#include <whiteout/interfaces.h>
PYBIND11_MAKE_OPAQUE(std::vector<std::string>);
PYBIND11_MAKE_OPAQUE(std::vector<std::vector<whiteout::Vector2f>>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u32>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u8>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u16>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::Quaternion>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::Vector2f>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::Vector3f>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::Vector4f>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::f32>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Attachment>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Bone>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Camera>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::CollisionShape>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::CornEmitter>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::EventObject>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Extent>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::FaceEffect>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Geoset>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::GeosetAnimation>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Helper>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Layer::SubTexture>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Layer>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Light>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Material>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::ParticleEmitter2>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::ParticleEmitter>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::RibbonEmitter>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Sequence>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Sound>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::SoundEmitter>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::Texture>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::mdx::TextureAnimation>);
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/operators.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
namespace {
// Buffer-protocol vector wrapper for std::vector<Elem> where Elem is laid
// out as `Components` contiguous Scalars (Vector3f, Quaternion, ColorBGRA…).
//
// pybind11's bind_vector<> wires up py::buffer_protocol() automatically,
// but stl_bind.h's auto-buffer-info path static-asserts on element types
// lacking a format_descriptor — Vector3f/Quaternion don't have one.
// We provide the same Python surface (append, extend, clear, __getitem__,
// __setitem__, __iter__, __len__, __bool__) plus a 2D buffer view.
template <typename Elem, typename Scalar, py::ssize_t Components>
auto bindBufferVector(py::module_& m, const char* name) {
using Vec = std::vector<Elem>;
py::class_<Vec> cls(m, name, py::buffer_protocol());
cls.def(py::init<>());
cls.def("__len__", [](const Vec& v) { return v.size(); });
cls.def("__bool__", [](const Vec& v) { return !v.empty(); });
cls.def("__getitem__", [](const Vec& v, std::size_t i) -> Elem {
if (i >= v.size()) throw py::index_error();
return v[i];
});
cls.def("__setitem__", [](Vec& v, std::size_t i, const Elem& val) {
if (i >= v.size()) throw py::index_error();
v[i] = val;
});
cls.def("__iter__", [](Vec& v) {
return py::make_iterator(v.begin(), v.end());
}, py::keep_alive<0, 1>());
cls.def("append", [](Vec& v, const Elem& val) { v.push_back(val); });
cls.def("extend", [](Vec& v, const Vec& o) {
v.insert(v.end(), o.begin(), o.end());
});
cls.def("clear", &Vec::clear);
cls.def_buffer([](Vec& v) -> py::buffer_info {
if constexpr (Components == 1) {
return py::buffer_info(
v.data(),
static_cast<py::ssize_t>(sizeof(Scalar)),
py::format_descriptor<Scalar>::format(),
1,
{ static_cast<py::ssize_t>(v.size()) },
{ static_cast<py::ssize_t>(sizeof(Scalar)) });
} else {
return py::buffer_info(
v.data(),
static_cast<py::ssize_t>(sizeof(Scalar)),
py::format_descriptor<Scalar>::format(),
2,
{ static_cast<py::ssize_t>(v.size()), Components },
{ static_cast<py::ssize_t>(sizeof(Elem)),
static_cast<py::ssize_t>(sizeof(Scalar)) });
}
});
return cls;
}
} // namespace
void bind_mdx_1(py::module_& m);
void bind_mdx(py::module_& m) {
py::enum_<whiteout::mdx::InterpolationType>(m, "InterpolationType")
.value("NONE", whiteout::mdx::InterpolationType::None)
.value("LINEAR", whiteout::mdx::InterpolationType::Linear)
.value("HERMITE", whiteout::mdx::InterpolationType::Hermite)
.value("BEZIER", whiteout::mdx::InterpolationType::Bezier)
;
py::enum_<whiteout::mdx::Sequence::Flag>(m, "SequenceFlag", R"doc(Sequence playback flags)doc")
.value("NONE", whiteout::mdx::Sequence::Flag::None)
.value("NON_LOOPING", whiteout::mdx::Sequence::Flag::NonLooping, R"doc(Sequence plays once instead of looping)doc")
;
py::enum_<whiteout::mdx::Texture::Flag>(m, "TextureFlag", R"doc(Texture wrap flags)doc")
.value("NONE", whiteout::mdx::Texture::Flag::None)
.value("WRAP_WIDTH", whiteout::mdx::Texture::Flag::WrapWidth, R"doc(Wrap texture horizontally)doc")
.value("WRAP_HEIGHT", whiteout::mdx::Texture::Flag::WrapHeight, R"doc(Wrap texture vertically)doc")
;
py::enum_<whiteout::mdx::Node::NodeType>(m, "NodeType", R"doc(Type of node in the hierarchy)doc")
.value("BONE", whiteout::mdx::Node::NodeType::Bone, R"doc(Skeleton bone)doc")
.value("LIGHT", whiteout::mdx::Node::NodeType::Light, R"doc(Light source)doc")
.value("HELPER", whiteout::mdx::Node::NodeType::Helper, R"doc(Helper/attachment point)doc")
.value("ATTACHMENT", whiteout::mdx::Node::NodeType::Attachment, R"doc(Equipment attachment)doc")
.value("PARTICLE_EMITTER", whiteout::mdx::Node::NodeType::ParticleEmitter, R"doc(Particle emitter v1)doc")
.value("PARTICLE_EMITTER2", whiteout::mdx::Node::NodeType::ParticleEmitter2, R"doc(Particle emitter v2)doc")
.value("RIBBON_EMITTER", whiteout::mdx::Node::NodeType::RibbonEmitter, R"doc(Ribbon trail emitter)doc")
.value("EVENT_OBJECT", whiteout::mdx::Node::NodeType::EventObject, R"doc(Event trigger)doc")
.value("CAMERA", whiteout::mdx::Node::NodeType::Camera, R"doc(Camera)doc")
.value("COLLISION_SHAPE", whiteout::mdx::Node::NodeType::CollisionShape, R"doc(Collision volume)doc")
.value("FACE_EFFECT", whiteout::mdx::Node::NodeType::FaceEffect, R"doc(Face effect (Reforged))doc")
.value("CORN_EMITTER", whiteout::mdx::Node::NodeType::CornEmitter, R"doc(PopcornFX emitter (Reforged))doc")
;
py::enum_<whiteout::mdx::Node::NodeFlag>(m, "NodeFlag", R"doc(Flags controlling node behavior and rendering)doc")
.value("NONE", whiteout::mdx::Node::NodeFlag::None)
.value("DONT_INHERIT_TRANSLATION", whiteout::mdx::Node::NodeFlag::DontInheritTranslation, R"doc(Don't inherit parent translation)doc")
.value("DONT_INHERIT_SCALING", whiteout::mdx::Node::NodeFlag::DontInheritScaling, R"doc(Don't inherit parent scaling)doc")
.value("DONT_INHERIT_ROTATION", whiteout::mdx::Node::NodeFlag::DontInheritRotation, R"doc(Don't inherit parent rotation)doc")
.value("BILLBOARDED", whiteout::mdx::Node::NodeFlag::Billboarded, R"doc(Always face camera)doc")
.value("BILLBOARDED_LOCK_X", whiteout::mdx::Node::NodeFlag::BillboardedLockX, R"doc(Billboard but lock X axis)doc")
.value("BILLBOARDED_LOCK_Y", whiteout::mdx::Node::NodeFlag::BillboardedLockY, R"doc(Billboard but lock Y axis)doc")
.value("BILLBOARDED_LOCK_Z", whiteout::mdx::Node::NodeFlag::BillboardedLockZ, R"doc(Billboard but lock Z axis)doc")
.value("CAMERA_ANCHORED", whiteout::mdx::Node::NodeFlag::CameraAnchored, R"doc(Anchored to camera)doc")
.value("BONE", whiteout::mdx::Node::NodeFlag::Bone, R"doc(This is a bone)doc")
.value("LIGHT", whiteout::mdx::Node::NodeFlag::Light, R"doc(This is a light)doc")
.value("EVENT_OBJECT", whiteout::mdx::Node::NodeFlag::EventObject, R"doc(This is an event object)doc")
.value("ATTACHMENT", whiteout::mdx::Node::NodeFlag::Attachment, R"doc(This is an attachment)doc")
.value("PARTICLE_EMITTER", whiteout::mdx::Node::NodeFlag::ParticleEmitter, R"doc(This is a particle emitter)doc")
.value("COLLISION_SHAPE", whiteout::mdx::Node::NodeFlag::CollisionShape, R"doc(This is a collision shape)doc")
.value("RIBBON_EMITTER", whiteout::mdx::Node::NodeFlag::RibbonEmitter, R"doc(This is a ribbon emitter)doc")
.value("UNSHADED", whiteout::mdx::Node::NodeFlag::Unshaded, R"doc(Unshaded (PE2/Popcorn) / EmitterUsesMdl (PE))doc")
.value("EMITTER_USES_MDL", whiteout::mdx::Node::NodeFlag::EmitterUsesMdl, R"doc(Particle emitter uses MDL model)doc")
.value("SORT_PRIMITIVES", whiteout::mdx::Node::NodeFlag::SortPrimitives, R"doc(Sort primitives (PE2/Popcorn))doc")
.value("SORT_PRIMS_FAR_Z", whiteout::mdx::Node::NodeFlag::SortPrimsFarZ, R"doc(Alias of SortPrimitives)doc")
.value("EMITTER_USES_TGA", whiteout::mdx::Node::NodeFlag::EmitterUsesTga, R"doc(Particle emitter uses TGA (PE only))doc")
.value("LINE_EMITTER", whiteout::mdx::Node::NodeFlag::LineEmitter, R"doc(Line-shaped emitter (PE2))doc")
.value("POPCORN_UNFOGGED", whiteout::mdx::Node::NodeFlag::PopcornUnfogged, R"doc(Unfogged (Popcorn emitter))doc")
.value("UNFOGGED", whiteout::mdx::Node::NodeFlag::Unfogged, R"doc(Not affected by fog (PE2))doc")
.value("POPCORN_SCALING", whiteout::mdx::Node::NodeFlag::PopcornScaling, R"doc(Particle scaling (Popcorn emitter))doc")
.value("MODEL_SPACE", whiteout::mdx::Node::NodeFlag::ModelSpace, R"doc(Use model space coordinates)doc")
.value("XY_QUAD", whiteout::mdx::Node::NodeFlag::XYQuad, R"doc(XY quad billboarding)doc")
;
py::enum_<whiteout::mdx::Layer::FilterMode>(m, "LayerFilterMode", R"doc(Blending/filter mode for the layer)doc")
.value("NONE", whiteout::mdx::Layer::FilterMode::None, R"doc(No blending)doc")
.value("TRANSPARENT", whiteout::mdx::Layer::FilterMode::Transparent, R"doc(Alpha test transparency)doc")
.value("BLEND", whiteout::mdx::Layer::FilterMode::Blend, R"doc(Alpha blending)doc")
.value("ADDITIVE", whiteout::mdx::Layer::FilterMode::Additive, R"doc(Additive blending)doc")
.value("ADD_ALPHA", whiteout::mdx::Layer::FilterMode::AddAlpha, R"doc(Additive alpha blending)doc")
.value("MODULATE", whiteout::mdx::Layer::FilterMode::Modulate, R"doc(Modulate (multiply) blending)doc")
.value("MODULATE2X", whiteout::mdx::Layer::FilterMode::Modulate2x, R"doc(Modulate 2x (brighten))doc")
.value("COUNT", whiteout::mdx::Layer::FilterMode::Count)
;
py::enum_<whiteout::mdx::Layer::ShaderType>(m, "LayerShaderType")
.value("SD", whiteout::mdx::Layer::ShaderType::SD)
.value("HD", whiteout::mdx::Layer::ShaderType::HD)
.value("SD_ON_HD", whiteout::mdx::Layer::ShaderType::SDOnHD)
.value("TERRAIN", whiteout::mdx::Layer::ShaderType::Terrain)
.value("WATER", whiteout::mdx::Layer::ShaderType::Water)
.value("FOG", whiteout::mdx::Layer::ShaderType::Fog)
.value("FOLIAGE", whiteout::mdx::Layer::ShaderType::Foliage)
.value("FOLIAGE_PUSH", whiteout::mdx::Layer::ShaderType::FoliagePush)
.value("SPRITE", whiteout::mdx::Layer::ShaderType::Sprite)
.value("DEBUG_TEXTURE", whiteout::mdx::Layer::ShaderType::DebugTexture)
.value("DEPTH_OF_FIELD", whiteout::mdx::Layer::ShaderType::DepthOfField)
.value("BLOOM_COMBINE", whiteout::mdx::Layer::ShaderType::BloomCombine)
.value("BLOOM_EXTRACT", whiteout::mdx::Layer::ShaderType::BloomExtract)
.value("GAUSSIAN_BLUR", whiteout::mdx::Layer::ShaderType::GaussianBlur)
.value("TONEMAP", whiteout::mdx::Layer::ShaderType::Tonemap)
.value("MOVIE", whiteout::mdx::Layer::ShaderType::Movie)
.value("FFXCMAA_EDGE0", whiteout::mdx::Layer::ShaderType::FFXCMAAEdge0)
.value("FFXCMAA_EDGE1", whiteout::mdx::Layer::ShaderType::FFXCMAAEdge1)
.value("FFXCMAA_EDGE_COMBINE", whiteout::mdx::Layer::ShaderType::FFXCMAAEdgeCombine)
.value("FFXCMAA_PROCESS_AND_APPLY", whiteout::mdx::Layer::ShaderType::FFXCMAAProcessAndApply)
.value("POPCORN_FX", whiteout::mdx::Layer::ShaderType::PopcornFX)
.value("CONE_INDICATOR", whiteout::mdx::Layer::ShaderType::ConeIndicator)
.value("CLIFF_BLIGHT_MISC_TERRAIN", whiteout::mdx::Layer::ShaderType::CliffBlightMiscTerrain)
.value("DISTORTION", whiteout::mdx::Layer::ShaderType::Distortion)
.value("CRYSTAL", whiteout::mdx::Layer::ShaderType::Crystal)
.value("IMGUI", whiteout::mdx::Layer::ShaderType::Imgui)
;
py::enum_<whiteout::mdx::Layer::ShadingFlag>(m, "LayerShadingFlag", R"doc(Shader and rendering flags)doc")
.value("NONE", whiteout::mdx::Layer::ShadingFlag::None)
.value("UNSHADED", whiteout::mdx::Layer::ShadingFlag::Unshaded, R"doc(Not affected by lighting)doc")
.value("SPHERE_ENV_MAP", whiteout::mdx::Layer::ShadingFlag::SphereEnvMap, R"doc(Spherical environment mapping)doc")
.value("WRAP_WIDTH", whiteout::mdx::Layer::ShadingFlag::WrapWidth, R"doc(Texture U-wrap)doc")
.value("WRAP_HEIGHT", whiteout::mdx::Layer::ShadingFlag::WrapHeight, R"doc(Texture V-wrap)doc")
.value("TWO_SIDED", whiteout::mdx::Layer::ShadingFlag::TwoSided, R"doc(Render both sides of polygons)doc")
.value("UNFOGGED", whiteout::mdx::Layer::ShadingFlag::Unfogged, R"doc(Not affected by fog)doc")
.value("NO_DEPTH_TEST", whiteout::mdx::Layer::ShadingFlag::NoDepthTest, R"doc(Disable depth testing)doc")
.value("NO_DEPTH_SET", whiteout::mdx::Layer::ShadingFlag::NoDepthSet, R"doc(Don't write to depth buffer)doc")
.value("UNLIT", whiteout::mdx::Layer::ShadingFlag::Unlit, R"doc(Reforged: bypass lighting pipeline)doc")
.value("BACK_FACES_FOR_SHADOWS", whiteout::mdx::Layer::ShadingFlag::BackFacesForShadows, R"doc(Render back faces into the shadow map)doc")
.value("AMBIENT_OCCLUSION", whiteout::mdx::Layer::ShadingFlag::AmbientOcclusion, R"doc(HD: sample baked occlusion (ORM red) at UV set 1)doc")
;
py::enum_<whiteout::mdx::Layer::SlotType>(m, "LayerSlotType")
.value("DIFFUSE_MAP", whiteout::mdx::Layer::SlotType::DiffuseMap)
.value("NORMAL_MAP", whiteout::mdx::Layer::SlotType::NormalMap)
.value("ORM_MAP", whiteout::mdx::Layer::SlotType::ORMMap)
.value("EMISSIVE_MAP", whiteout::mdx::Layer::SlotType::EmissiveMap)
.value("TEAM_COLOR", whiteout::mdx::Layer::SlotType::TeamColor)
.value("ENVIRONMENT_MAP", whiteout::mdx::Layer::SlotType::EnvironmentMap)
.value("UNKNOWN", whiteout::mdx::Layer::SlotType::Unknown)
;
py::enum_<whiteout::mdx::Material::Flag>(m, "MaterialFlag", R"doc(Material-level flags)doc")
.value("NONE", whiteout::mdx::Material::Flag::None)
.value("CONSTANT_COLOR", whiteout::mdx::Material::Flag::ConstantColor, R"doc(Use constant color (no per-vertex tinting))doc")
.value("TWO_SIDED", whiteout::mdx::Material::Flag::TwoSided, R"doc(Render both sides of polygons)doc")
.value("UNFOGGED", whiteout::mdx::Material::Flag::Unfogged, R"doc(Not affected by fog)doc")
.value("SORT_PRIMS_NEAR_Z", whiteout::mdx::Material::Flag::SortPrimsNearZ, R"doc(Sort by near-Z)doc")
.value("SORT_PRIMS_FAR_Z", whiteout::mdx::Material::Flag::SortPrimsFarZ, R"doc(Sort by far-Z; alias: SortPrimitives)doc")
.value("SORT_PRIMITIVES", whiteout::mdx::Material::Flag::SortPrimitives, R"doc(Legacy alias for SortPrimsFarZ (HiveWorkshop name))doc")
.value("FULL_RESOLUTION", whiteout::mdx::Material::Flag::FullResolution, R"doc(Force full-resolution textures)doc")
;
py::enum_<whiteout::mdx::GeosetAnimation::Flag>(m, "GeosetAnimationFlag", R"doc(Geoset animation flags)doc")
.value("NONE", whiteout::mdx::GeosetAnimation::Flag::None)
.value("DROP_SHADOW", whiteout::mdx::GeosetAnimation::Flag::DropShadow, R"doc(Geoset casts a drop shadow)doc")
.value("COLOR", whiteout::mdx::GeosetAnimation::Flag::Color, R"doc(Use the per-geoset color/alpha values (else inherit))doc")
;
py::enum_<whiteout::mdx::Light::LightType>(m, "LightType", R"doc(Type of light source)doc")
.value("OMNI", whiteout::mdx::Light::LightType::Omni, R"doc(Point light (radiates in all directions))doc")
.value("DIRECTIONAL", whiteout::mdx::Light::LightType::Directional, R"doc(Directional light (like sunlight))doc")
.value("AMBIENT", whiteout::mdx::Light::LightType::Ambient, R"doc(Ambient light (affects everything equally))doc")
;
py::enum_<whiteout::mdx::CollisionShape::ShapeType>(m, "CollisionShapeShapeType")
.value("BOX", whiteout::mdx::CollisionShape::ShapeType::Box)
.value("PLANE", whiteout::mdx::CollisionShape::ShapeType::Plane)
.value("SPHERE", whiteout::mdx::CollisionShape::ShapeType::Sphere)
.value("CYLINDER", whiteout::mdx::CollisionShape::ShapeType::Cylinder)
;
py::enum_<whiteout::mdx::MDLXFormat>(m, "MDLXFormat", R"doc(Source format selector for buffer-based parsing.)doc")
.value("MDX", whiteout::mdx::MDLXFormat::MDX, R"doc(Binary MDX format)doc")
.value("MDL", whiteout::mdx::MDLXFormat::MDL, R"doc(Text MDL format)doc")
;
py::enum_<whiteout::mdx::Parser::UpgradeMode>(m, "UpgradeMode", R"doc(Version handling mode)doc")
.value("UPGRADE_OLD_VERSIONS", whiteout::mdx::Parser::UpgradeMode::UpgradeOldVersions, R"doc(Automatically upgrade older versions to latest format)doc")
.value("PRESERVE_ORIGINAL", whiteout::mdx::Parser::UpgradeMode::PreserveOriginal, R"doc(Keep original version and structure as-is)doc")
;
py::enum_<whiteout::mdx::MdlFormat>(m, "MdlFormat", R"doc(Selects which MDL text dialect Writer / writeModelToMdl emits.
Only affects the .mdl text output path; MDX binary is unchanged.)doc")
.value("WARCRAFT_III", whiteout::mdx::MdlFormat::WarcraftIII, R"doc(Engine-faithful dialect — matches what Warcraft III's MDL writer produces. HD layers use a per-layer `Shader "name",` directive and `static TextureID id <= slot,` for sub-textures. Files written in this dialect parse cleanly through the in-game MDL reader. (Default.))doc")
.value("HIVEWORKSHOP", whiteout::mdx::MdlFormat::Hiveworkshop, R"doc(HiveWorkshop community dialect — uses `ShaderTypeId N,` to mark HD layers and named slot properties (`NormalTextureID`, `ORMTextureID`, `EmissiveTextureID`, `TeamColorTextureID`, `ReflectionsTextureID`). Compatible with HiveWorkshop tools (Retera Model Studio, Magos, MdlVis, etc.).)doc")
;
py::class_<whiteout::Vector2f>(m, "Vector2f")
.def(py::init<>())
.def(py::init([](whiteout::f32 x, whiteout::f32 y) {
return whiteout::Vector2f{x, y};
}), py::arg("x"), py::arg("y"))
.def("__repr__", [](const whiteout::Vector2f& self) {
std::ostringstream oss;
oss << "Vector2f(";
oss << "x=" << self.x << ", ";
oss << "y=" << self.y << ")";
return oss.str();
})
.def_readwrite("x", &whiteout::Vector2f::x)
.def_readwrite("y", &whiteout::Vector2f::y)
;
py::class_<whiteout::Vector3f>(m, "Vector3f")
.def(py::init<>())
.def(py::init([](whiteout::f32 x, whiteout::f32 y, whiteout::f32 z) {
return whiteout::Vector3f{x, y, z};
}), py::arg("x"), py::arg("y"), py::arg("z"))
.def("__repr__", [](const whiteout::Vector3f& self) {
std::ostringstream oss;
oss << "Vector3f(";
oss << "x=" << self.x << ", ";
oss << "y=" << self.y << ", ";
oss << "z=" << self.z << ")";
return oss.str();
})
.def_readwrite("x", &whiteout::Vector3f::x)
.def_readwrite("y", &whiteout::Vector3f::y)
.def_readwrite("z", &whiteout::Vector3f::z)
;
py::class_<whiteout::Vector4f>(m, "Vector4f")
.def(py::init<>())
.def(py::init([](whiteout::f32 x, whiteout::f32 y, whiteout::f32 z, whiteout::f32 w) {
return whiteout::Vector4f{x, y, z, w};
}), py::arg("x"), py::arg("y"), py::arg("z"), py::arg("w"))
.def("__repr__", [](const whiteout::Vector4f& self) {
std::ostringstream oss;
oss << "Vector4f(";
oss << "x=" << self.x << ", ";
oss << "y=" << self.y << ", ";
oss << "z=" << self.z << ", ";
oss << "w=" << self.w << ")";
return oss.str();
})
.def_readwrite("x", &whiteout::Vector4f::x)
.def_readwrite("y", &whiteout::Vector4f::y)
.def_readwrite("z", &whiteout::Vector4f::z)
.def_readwrite("w", &whiteout::Vector4f::w)
;
py::class_<whiteout::Quaternion>(m, "Quaternion")
.def(py::init<>())
.def(py::init([](whiteout::f32 x, whiteout::f32 y, whiteout::f32 z, whiteout::f32 w) {
return whiteout::Quaternion{x, y, z, w};
}), py::arg("x"), py::arg("y"), py::arg("z"), py::arg("w"))
.def("__repr__", [](const whiteout::Quaternion& self) {
std::ostringstream oss;
oss << "Quaternion(";
oss << "x=" << self.x << ", ";
oss << "y=" << self.y << ", ";
oss << "z=" << self.z << ", ";
oss << "w=" << self.w << ")";
return oss.str();
})
.def_readwrite("x", &whiteout::Quaternion::x)
.def_readwrite("y", &whiteout::Quaternion::y)
.def_readwrite("z", &whiteout::Quaternion::z)
.def_readwrite("w", &whiteout::Quaternion::w)
;
py::class_<whiteout::mdx::Extent>(m, "Extent")
.def(py::init<>())
.def_readwrite("bounds_radius", &whiteout::mdx::Extent::boundsRadius)
.def_readwrite("minimum", &whiteout::mdx::Extent::minimum)
.def_readwrite("maximum", &whiteout::mdx::Extent::maximum)
;
py::class_<whiteout::mdx::Model>(m, "Model", R"doc(Complete MDX model file structure
This structure represents an entire MDX model, including all geometry, materials, animations, and metadata. It can represent models from Warcraft III Classic (version 800) through Reforged (version 1200).
The structure is organized into chunks, each containing a specific type of data. Not all chunks are required - optional chunks can have empty vectors.)doc")
.def(py::init<>())
.def_readwrite("version", &whiteout::mdx::Model::version, R"doc(MDX version: 800 (Classic), 900, 1000, 1100, 1200 (Reforged))doc")
.def_readwrite("model_name", &whiteout::mdx::Model::modelName, R"doc(Name of the model)doc")
.def_readwrite("animation_file_name", &whiteout::mdx::Model::animationFileName, R"doc(Path to external animation file (rarely used))doc")
.def_readwrite("model_extent", &whiteout::mdx::Model::modelExtent, R"doc(Bounding volume for the entire model)doc")
.def_readwrite("blend_time", &whiteout::mdx::Model::blendTime, R"doc(Time in milliseconds to blend between animations)doc")
.def_readwrite("global_sequences", &whiteout::mdx::Model::globalSequences, R"doc(Global looping animation durations)doc")
.def_readwrite("sequences", &whiteout::mdx::Model::sequences, R"doc(Named animation sequences)doc")
.def_readwrite("textures", &whiteout::mdx::Model::textures, R"doc(Texture paths and settings)doc")
.def_readwrite("sounds", &whiteout::mdx::Model::sounds, R"doc(Sounds (deprecated))doc")
.def_readwrite("sound_emitters", &whiteout::mdx::Model::soundEmitters, R"doc(Sound emitters (deprecated))doc")
.def_readwrite("materials", &whiteout::mdx::Model::materials, R"doc(Material definitions)doc")
.def_readwrite("texture_animations", &whiteout::mdx::Model::textureAnimations, R"doc(UV animation data)doc")
.def_readwrite("geosets", &whiteout::mdx::Model::geosets, R"doc(Mesh geometry)doc")
.def_readwrite("geoset_animations", &whiteout::mdx::Model::geosetAnimations, R"doc(Mesh visibility/color animation)doc")
.def_readwrite("bones", &whiteout::mdx::Model::bones, R"doc(Skeleton bones)doc")
.def_readwrite("helpers", &whiteout::mdx::Model::helpers, R"doc(Helper/attachment nodes)doc")
.def_readwrite("attachments", &whiteout::mdx::Model::attachments, R"doc(Equipment attachment points)doc")
.def_readwrite("pivot_points", &whiteout::mdx::Model::pivotPoints, R"doc(Pivot points for nodes)doc")
.def_readwrite("lights", &whiteout::mdx::Model::lights, R"doc(Light sources)doc")
.def_readwrite("particle_emitters", &whiteout::mdx::Model::particleEmitters, R"doc(Particle emitters v1)doc")
.def_readwrite("particle_emitters2", &whiteout::mdx::Model::particleEmitters2, R"doc(Particle emitters v2)doc")
.def_readwrite("ribbon_emitters", &whiteout::mdx::Model::ribbonEmitters, R"doc(Ribbon trail effects)doc")
.def_readwrite("corn_emitters", &whiteout::mdx::Model::cornEmitters, R"doc(PopcornFX emitters (Reforged))doc")
.def_readwrite("event_objects", &whiteout::mdx::Model::eventObjects, R"doc(Animation event triggers)doc")
.def_readwrite("cameras", &whiteout::mdx::Model::cameras, R"doc(Camera definitions)doc")
.def_readwrite("collision_shapes", &whiteout::mdx::Model::collisionShapes, R"doc(Collision geometry)doc")
.def_readwrite("face_effects", &whiteout::mdx::Model::faceEffects, R"doc(Facial effects (Reforged))doc")
;
py::class_<whiteout::mdx::Sequence>(m, "Sequence", R"doc(Animation sequence definition
Sequences define named animation clips with specific frame ranges. Examples: "Stand", "Walk", "Attack", "Death")doc")
.def(py::init<>())
.def_readwrite("name", &whiteout::mdx::Sequence::name, R"doc(Sequence name (e.g., "Stand", "Walk"))doc")
.def_readwrite("interval_start", &whiteout::mdx::Sequence::intervalStart, R"doc(Starting frame number)doc")
.def_readwrite("interval_end", &whiteout::mdx::Sequence::intervalEnd, R"doc(Ending frame number (exclusive))doc")
.def_readwrite("move_speed", &whiteout::mdx::Sequence::moveSpeed, R"doc(Movement speed during this animation)doc")
.def_readwrite("flags", &whiteout::mdx::Sequence::flags, R"doc(Playback flags)doc")
.def_readwrite("rarity", &whiteout::mdx::Sequence::rarity, R"doc(Rarity factor for variation sequences)doc")
.def_readwrite("sync_point", &whiteout::mdx::Sequence::syncPoint, R"doc(Sync point for blending)doc")
.def_readwrite("extent", &whiteout::mdx::Sequence::extent, R"doc(Bounding volume for this sequence)doc")
;
py::class_<whiteout::mdx::Texture>(m, "Texture", R"doc(Texture definition
Defines a texture file path and its properties.)doc")
.def(py::init<>())
.def_readwrite("replaceable_id", &whiteout::mdx::Texture::replaceableId)
.def_readwrite("file_name", &whiteout::mdx::Texture::fileName, R"doc(Path to texture file (BLP, DDS or TGA))doc")
.def_readwrite("flags", &whiteout::mdx::Texture::flags, R"doc(Texture wrap flags)doc")
;
py::class_<whiteout::mdx::Sound>(m, "Sound", R"doc(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)doc")
.def(py::init<>())
.def_readwrite("sound_file", &whiteout::mdx::Sound::soundFile, R"doc(Path to sound file (44 bytes, null-terminated))doc")
.def_readwrite("maximum_distance", &whiteout::mdx::Sound::maximumDistance, R"doc(Maximum audible distance)doc")
.def_readwrite("minimum_distance", &whiteout::mdx::Sound::minimumDistance, R"doc(Minimum distance (full volume))doc")
.def_readwrite("sound_channel", &whiteout::mdx::Sound::soundChannel, R"doc(Sound channel identifier)doc")
;
py::class_<whiteout::mdx::Node>(m, "Node", R"doc(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).)doc")
.def(py::init<>())
.def_readwrite("name", &whiteout::mdx::Node::name, R"doc(Node name (for debugging/reference))doc")
.def_readwrite("object_id", &whiteout::mdx::Node::objectId, R"doc(Unique ID for this node)doc")
.def_readwrite("parent_id", &whiteout::mdx::Node::parentId, R"doc(Parent node ID or NO_PARENT)doc")
.def_readwrite("flags", &whiteout::mdx::Node::flags, R"doc(Combination of NodeFlag values)doc")
.def_readwrite("type", &whiteout::mdx::Node::type, R"doc(Type of this node)doc")
.def_readwrite("node_family_id", &whiteout::mdx::Node::nodeFamilyId, R"doc(Used to link related nodes of the same type)doc")
.def_readwrite("translation_tracks", &whiteout::mdx::Node::translationTracks, R"doc(Position animation)doc")
.def_readwrite("rotation_tracks", &whiteout::mdx::Node::rotationTracks, R"doc(Rotation animation (quaternion XYZW))doc")
.def_readwrite("scaling_tracks", &whiteout::mdx::Node::scalingTracks, R"doc(Scale animation)doc")
;
py::class_<whiteout::mdx::SoundEmitter>(m, "SoundEmitter", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::SoundEmitter::node, R"doc(Base node data with transform)doc")
.def_readwrite("sound_track", &whiteout::mdx::SoundEmitter::soundTrack, R"doc(KSEK - sound event track (u32 values))doc")
;
py::class_<whiteout::mdx::Layer>(m, "Layer", R"doc(Material rendering layer
Materials can have multiple layers, each with its own texture and blending mode. Layers control how textures are combined and rendered.)doc")
.def(py::init<>())
.def_readwrite("filter_mode", &whiteout::mdx::Layer::filterMode, R"doc(Blending mode)doc")
.def_readwrite("shading_flags", &whiteout::mdx::Layer::shadingFlags, R"doc(Rendering flags)doc")
.def_readwrite("texture_id", &whiteout::mdx::Layer::textureId, R"doc(Texture index (versions 800-1100))doc")
.def_readwrite("texture_animation_id", &whiteout::mdx::Layer::textureAnimationId, R"doc(Texture animation index)doc")
.def_readwrite("coord_id", &whiteout::mdx::Layer::coordId, R"doc(Texture coordinate set index)doc")
.def_readwrite("alpha", &whiteout::mdx::Layer::alpha, R"doc(Layer opacity)doc")
.def_readwrite("emissive_gain", &whiteout::mdx::Layer::emissiveGain, R"doc(Emissive light intensity (default 1.0))doc")
.def_readwrite("fresnel_color", &whiteout::mdx::Layer::fresnelColor, R"doc(Fresnel effect color)doc")
.def_readwrite("fresnel_opacity", &whiteout::mdx::Layer::fresnelOpacity, R"doc(Fresnel effect opacity)doc")
.def_readwrite("fresnel_team_color", &whiteout::mdx::Layer::fresnelTeamColor, R"doc(Fresnel team color factor)doc")
.def_readwrite("shader", &whiteout::mdx::Layer::shader, R"doc(Shader to use for this layer)doc")
.def_readwrite("is_hd", &whiteout::mdx::Layer::is_hd, R"doc(True if using Reforged HD shading)doc")
.def_readwrite("sub_textures", &whiteout::mdx::Layer::subTextures, R"doc(Multi-texture support (version 1200+))doc")
.def_readwrite("texture_id_tracks", &whiteout::mdx::Layer::textureIdTracks, R"doc(Texture ID animation (versions 800-1100))doc")
.def_readwrite("alpha_tracks", &whiteout::mdx::Layer::alphaTracks, R"doc(Alpha animation)doc")
.def_readwrite("emissive_gain_tracks", &whiteout::mdx::Layer::emissiveGainTracks, R"doc(Emissive gain animation)doc")
.def_readwrite("fresnel_color_tracks", &whiteout::mdx::Layer::fresnelColorTracks, R"doc(Fresnel color animation)doc")
.def_readwrite("fresnel_alpha_tracks", &whiteout::mdx::Layer::fresnelAlphaTracks, R"doc(Fresnel alpha animation)doc")
.def_readwrite("fresnel_team_color_tracks", &whiteout::mdx::Layer::fresnelTeamColorTracks, R"doc(Fresnel team color animation)doc")
;
py::class_<whiteout::mdx::Layer::SubTexture>(m, "LayerSubTexture", R"doc(Sub-texture definition (Reforged multi-texture))doc")
.def(py::init<>())
.def_readwrite("texture_id", &whiteout::mdx::Layer::SubTexture::textureId, R"doc(Index into texture array)doc")
.def_readwrite("slot", &whiteout::mdx::Layer::SubTexture::slot, R"doc(Texture slot number)doc")
.def_readwrite("tracks", &whiteout::mdx::Layer::SubTexture::tracks, R"doc(Texture ID animation)doc")
;
py::class_<whiteout::mdx::Material>(m, "Material", R"doc(Material definition with rendering properties
Materials define how surfaces are rendered. Each material contains one or more layers that specify textures and blending modes.)doc")
.def(py::init<>())
.def_readwrite("priority_plane", &whiteout::mdx::Material::priorityPlane, R"doc(Rendering priority (higher = render last); signed)doc")
.def_readwrite("flags", &whiteout::mdx::Material::flags, R"doc(Material flags)doc")
.def_readwrite("shader", &whiteout::mdx::Material::shader, R"doc(Shader name (Reforged))doc")
.def_readwrite("layers", &whiteout::mdx::Material::layers, R"doc(Rendering layers)doc")
;
py::class_<whiteout::mdx::TextureAnimation>(m, "TextureAnimation", R"doc(UV coordinate animation
Texture animations transform UV coordinates over time, creating effects like scrolling water, rotating symbols, etc.)doc")
.def(py::init<>())
.def_readwrite("translation_tracks", &whiteout::mdx::TextureAnimation::translationTracks, R"doc(UV translation animation)doc")
.def_readwrite("rotation_tracks", &whiteout::mdx::TextureAnimation::rotationTracks, R"doc(UV rotation animation (quaternion XYZW))doc")
.def_readwrite("scaling_tracks", &whiteout::mdx::TextureAnimation::scalingTracks, R"doc(UV scaling animation)doc")
;
py::class_<whiteout::mdx::Geoset>(m, "Geoset", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("vertex_positions", &whiteout::mdx::Geoset::vertexPositions, R"doc(Vertex positions)doc")
.def_readwrite("vertex_normals", &whiteout::mdx::Geoset::vertexNormals, R"doc(Vertex normals)doc")
.def_readwrite("face_type_groups", &whiteout::mdx::Geoset::faceTypeGroups, R"doc(Face type groups (4 = triangles))doc")
.def_readwrite("face_groups", &whiteout::mdx::Geoset::faceGroups, R"doc(Number of indices per group)doc")
.def_readwrite("faces", &whiteout::mdx::Geoset::faces, R"doc(Vertex indices (triangles))doc")
.def_readwrite("vertex_groups", &whiteout::mdx::Geoset::vertexGroups, R"doc(Bone groups per vertex)doc")
.def("vertex_groups_view",
[](const whiteout::mdx::Geoset& self) {
return py::memoryview::from_memory(
static_cast<const void*>(self.vertexGroups.data()),
static_cast<py::ssize_t>(self.vertexGroups.size()));
})
.def_readwrite("matrix_groups", &whiteout::mdx::Geoset::matrixGroups, R"doc(Number of matrices per group)doc")
.def_readwrite("matrix_indices", &whiteout::mdx::Geoset::matrixIndices, R"doc(Bone indices)doc")
.def_readwrite("material_id", &whiteout::mdx::Geoset::materialId, R"doc(Material index)doc")
.def_readwrite("selection_group", &whiteout::mdx::Geoset::selectionGroup, R"doc(Selection group (for editor))doc")
.def_readwrite("selection_flags", &whiteout::mdx::Geoset::selectionFlags, R"doc(Selection flags)doc")
.def_readwrite("lod", &whiteout::mdx::Geoset::lod, R"doc(Level of detail index)doc")
.def_readwrite("lod_name", &whiteout::mdx::Geoset::lodName, R"doc(LOD name)doc")
.def_readwrite("extent", &whiteout::mdx::Geoset::extent, R"doc(Bounding volume)doc")
.def_readwrite("sequence_extents", &whiteout::mdx::Geoset::sequenceExtents, R"doc(Per-sequence bounding volumes)doc")
.def_readwrite("tangents", &whiteout::mdx::Geoset::tangents, R"doc(Tangent vectors (for normal mapping))doc")
.def_readwrite("skin_data", &whiteout::mdx::Geoset::skinData, R"doc(Four bone indices then four weights (summing to 255) per vertex. u16 because v1400+ stores them that way and cinematics index past 255 bones; files below 1400 store bytes.)doc")
.def_readwrite("texture_coordinate_sets", &whiteout::mdx::Geoset::textureCoordinateSets, R"doc(UV coordinates (multiple sets))doc")
;
py::class_<whiteout::mdx::GeosetAnimation>(m, "GeosetAnimation", R"doc(Geoset visibility and color animation
Geoset animations control the visibility and color tinting of meshes.)doc")
.def(py::init<>())
.def_readwrite("alpha", &whiteout::mdx::GeosetAnimation::alpha, R"doc(Base alpha value)doc")
.def_readwrite("flags", &whiteout::mdx::GeosetAnimation::flags, R"doc(Animation flags)doc")
.def_readwrite("color", &whiteout::mdx::GeosetAnimation::color, R"doc(Base color tint)doc")
.def_readwrite("geoset_id", &whiteout::mdx::GeosetAnimation::geosetId, R"doc(Target geoset index)doc")
.def_readwrite("alpha_tracks", &whiteout::mdx::GeosetAnimation::alphaTracks, R"doc(Alpha animation)doc")
.def_readwrite("color_tracks", &whiteout::mdx::GeosetAnimation::colorTracks, R"doc(Color animation)doc")
;
py::class_<whiteout::mdx::Bone>(m, "Bone", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::Bone::node, R"doc(Base node data with transform)doc")
.def_readwrite("geoset_id", &whiteout::mdx::Bone::geosetId, R"doc(Geoset this bone affects)doc")
.def_readwrite("geoset_animation_id", &whiteout::mdx::Bone::geosetAnimationId, R"doc(Geoset animation index)doc")
;
py::class_<whiteout::mdx::Light>(m, "Light", R"doc(Light source
Lights can be attached to bones to move with animations. They affect how the model is rendered in the game engine.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::Light::node, R"doc(Base node data)doc")
.def_readwrite("type", &whiteout::mdx::Light::type, R"doc(Type of light)doc")
.def_readwrite("attenuation_start", &whiteout::mdx::Light::attenuationStart, R"doc(Distance where attenuation begins)doc")
.def_readwrite("attenuation_end", &whiteout::mdx::Light::attenuationEnd, R"doc(Distance where light reaches zero)doc")
.def_readwrite("color", &whiteout::mdx::Light::color, R"doc(Light color (RGB))doc")
.def_readwrite("intensity", &whiteout::mdx::Light::intensity, R"doc(Light intensity)doc")
.def_readwrite("ambient_color", &whiteout::mdx::Light::ambientColor, R"doc(Ambient light color)doc")
.def_readwrite("ambient_intensity", &whiteout::mdx::Light::ambientIntensity, R"doc(Ambient intensity)doc")
.def_readwrite("shadow_intensity", &whiteout::mdx::Light::shadowIntensity, R"doc(Shadow darkness (Reforged))doc")
.def_readwrite("shadow_casting", &whiteout::mdx::Light::shadowCasting, R"doc(v1300+, stored as a u32 on disk)doc")
.def_readwrite("shadow_casting_start", &whiteout::mdx::Light::shadowCastingStart, R"doc(v1300+)doc")
.def_readwrite("shadow_casting_end", &whiteout::mdx::Light::shadowCastingEnd, R"doc(v1300+)doc")
.def_readwrite("quadratic_falloff", &whiteout::mdx::Light::quadraticFalloff, R"doc(v1600+)doc")
.def_readwrite("linear_falloff", &whiteout::mdx::Light::linearFalloff, R"doc(v1600+)doc")
.def_readwrite("damping", &whiteout::mdx::Light::damping, R"doc(v1600+)doc")
.def_readwrite("attenuation_start_tracks", &whiteout::mdx::Light::attenuationStartTracks, R"doc(Attenuation start animation)doc")
.def_readwrite("attenuation_end_tracks", &whiteout::mdx::Light::attenuationEndTracks, R"doc(Attenuation end animation)doc")
.def_readwrite("color_tracks", &whiteout::mdx::Light::colorTracks, R"doc(Color animation)doc")
.def_readwrite("intensity_tracks", &whiteout::mdx::Light::intensityTracks, R"doc(Intensity animation)doc")
.def_readwrite("ambient_intensity_tracks", &whiteout::mdx::Light::ambientIntensityTracks, R"doc(Ambient intensity animation)doc")
.def_readwrite("ambient_color_tracks", &whiteout::mdx::Light::ambientColorTracks, R"doc(Ambient color animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::Light::visibilityTracks, R"doc(Visibility animation)doc")
.def_readwrite("shadow_intensity_tracks", &whiteout::mdx::Light::shadowIntensityTracks, R"doc(Shadow intensity animation (Reforged))doc")
.def_readwrite("shadow_casting_start_tracks", &whiteout::mdx::Light::shadowCastingStartTracks, R"doc(KLSS, v1300+)doc")
.def_readwrite("shadow_casting_end_tracks", &whiteout::mdx::Light::shadowCastingEndTracks, R"doc(KLSE, v1300+)doc")
.def_readwrite("quadratic_falloff_tracks", &whiteout::mdx::Light::quadraticFalloffTracks, R"doc(KLQF, v1600+)doc")
.def_readwrite("linear_falloff_tracks", &whiteout::mdx::Light::linearFalloffTracks, R"doc(KLLF, v1600+)doc")
.def_readwrite("damping_tracks", &whiteout::mdx::Light::dampingTracks, R"doc(KLDA, v1600+)doc")
;
py::class_<whiteout::mdx::Helper>(m, "Helper", R"doc(Helper node (attachment point)
Helpers are simple nodes used as attachment points for effects, weapons, or other objects. They don't render anything themselves.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::Helper::node, R"doc(Base node data with transform)doc")
;
py::class_<whiteout::mdx::Attachment>(m, "Attachment", R"doc(Attachment point for external models
Attachments define points where other models (like weapons or shields) can be attached to this model.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::Attachment::node, R"doc(Base node data)doc")
.def_readwrite("path", &whiteout::mdx::Attachment::path, R"doc(Path to attached model)doc")
.def_readwrite("attachment_id", &whiteout::mdx::Attachment::attachmentId, R"doc(Attachment slot ID)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::Attachment::visibilityTracks, R"doc(Visibility animation)doc")
;
py::class_<whiteout::mdx::ParticleEmitter>(m, "ParticleEmitter", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::ParticleEmitter::node, R"doc(Base node data)doc")
.def_readwrite("emission_rate", &whiteout::mdx::ParticleEmitter::emissionRate, R"doc(Particles per second)doc")
.def_readwrite("gravity", &whiteout::mdx::ParticleEmitter::gravity, R"doc(Gravity force)doc")
.def_readwrite("longitude", &whiteout::mdx::ParticleEmitter::longitude, R"doc(Emission longitude angle)doc")
.def_readwrite("latitude", &whiteout::mdx::ParticleEmitter::latitude, R"doc(Emission latitude angle)doc")
.def_readwrite("spawn_model_file_name", &whiteout::mdx::ParticleEmitter::spawnModelFileName, R"doc(Model to spawn as particles)doc")
.def_readwrite("lifespan", &whiteout::mdx::ParticleEmitter::lifespan, R"doc(Particle lifetime in seconds)doc")
.def_readwrite("initial_velocity", &whiteout::mdx::ParticleEmitter::initialVelocity, R"doc(Initial particle speed)doc")
.def_readwrite("emission_rate_tracks", &whiteout::mdx::ParticleEmitter::emissionRateTracks, R"doc(Emission rate animation)doc")
.def_readwrite("gravity_tracks", &whiteout::mdx::ParticleEmitter::gravityTracks, R"doc(Gravity animation)doc")
.def_readwrite("longitude_tracks", &whiteout::mdx::ParticleEmitter::longitudeTracks, R"doc(Longitude animation)doc")
.def_readwrite("latitude_tracks", &whiteout::mdx::ParticleEmitter::latitudeTracks, R"doc(Latitude animation)doc")
.def_readwrite("lifespan_tracks", &whiteout::mdx::ParticleEmitter::lifespanTracks, R"doc(Lifespan animation)doc")
.def_readwrite("speed_tracks", &whiteout::mdx::ParticleEmitter::speedTracks, R"doc(Speed animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::ParticleEmitter::visibilityTracks, R"doc(Visibility animation)doc")
;
py::class_<whiteout::mdx::ParticleEmitter2>(m, "ParticleEmitter2", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::ParticleEmitter2::node, R"doc(Base node data)doc")
.def_readwrite("speed", &whiteout::mdx::ParticleEmitter2::speed, R"doc(Particle speed)doc")
.def_readwrite("variation", &whiteout::mdx::ParticleEmitter2::variation, R"doc(Speed variation (randomness))doc")
.def_readwrite("latitude", &whiteout::mdx::ParticleEmitter2::latitude, R"doc(Emission cone latitude)doc")
.def_readwrite("gravity", &whiteout::mdx::ParticleEmitter2::gravity, R"doc(Gravity acceleration)doc")
.def_readwrite("lifespan", &whiteout::mdx::ParticleEmitter2::lifespan, R"doc(Particle lifetime in seconds)doc")
.def_readwrite("emission_rate", &whiteout::mdx::ParticleEmitter2::emissionRate, R"doc(Particles per second)doc")
.def_readwrite("length", &whiteout::mdx::ParticleEmitter2::length, R"doc(Particle length (for tail effect))doc")
.def_readwrite("width", &whiteout::mdx::ParticleEmitter2::width, R"doc(Particle width)doc")
.def_readwrite("filter_mode", &whiteout::mdx::ParticleEmitter2::filterMode, R"doc(Blending mode)doc")
.def_readwrite("rows", &whiteout::mdx::ParticleEmitter2::rows, R"doc(Texture atlas rows)doc")
.def_readwrite("columns", &whiteout::mdx::ParticleEmitter2::columns, R"doc(Texture atlas columns)doc")
.def_readwrite("head_or_tail", &whiteout::mdx::ParticleEmitter2::headOrTail, R"doc(Head/tail flags)doc")
.def_readwrite("tail_length", &whiteout::mdx::ParticleEmitter2::tailLength, R"doc(Tail particle length)doc")
.def_readwrite("time", &whiteout::mdx::ParticleEmitter2::time, R"doc(Middle time for segment animation)doc")
.def_readwrite("texture_id", &whiteout::mdx::ParticleEmitter2::textureId, R"doc(Texture index)doc")
.def_readwrite("squirt", &whiteout::mdx::ParticleEmitter2::squirt, R"doc(Squirt flag (burst mode))doc")
.def_readwrite("priority_plane", &whiteout::mdx::ParticleEmitter2::priorityPlane, R"doc(Rendering priority; signed)doc")
.def_readwrite("replaceable_id", &whiteout::mdx::ParticleEmitter2::replaceableId, R"doc(Replaceable texture ID)doc")
.def_readwrite("speed_tracks", &whiteout::mdx::ParticleEmitter2::speedTracks, R"doc(Speed animation)doc")
.def_readwrite("variation_tracks", &whiteout::mdx::ParticleEmitter2::variationTracks, R"doc(Variation animation)doc")
.def_readwrite("latitude_tracks", &whiteout::mdx::ParticleEmitter2::latitudeTracks, R"doc(Latitude animation)doc")
.def_readwrite("gravity_tracks", &whiteout::mdx::ParticleEmitter2::gravityTracks, R"doc(Gravity animation)doc")
.def_readwrite("emission_rate_tracks", &whiteout::mdx::ParticleEmitter2::emissionRateTracks, R"doc(Emission rate animation)doc")
.def_readwrite("length_tracks", &whiteout::mdx::ParticleEmitter2::lengthTracks, R"doc(Length animation)doc")
.def_readwrite("width_tracks", &whiteout::mdx::ParticleEmitter2::widthTracks, R"doc(Width animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::ParticleEmitter2::visibilityTracks, R"doc(Visibility animation)doc")
.def("get_segment_color",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::Vector3f>(self.segmentColor.begin(), self.segmentColor.end());
})
.def("set_segment_color",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::Vector3f>& v) {
if (v.size() != self.segmentColor.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.segmentColor.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.segmentColor[i] = v[i];
})
.def("get_segment_alpha",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::u8>(self.segmentAlpha.begin(), self.segmentAlpha.end());
})
.def("set_segment_alpha",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::u8>& v) {
if (v.size() != self.segmentAlpha.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.segmentAlpha.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.segmentAlpha[i] = v[i];
})
.def("get_segment_scaling",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::f32>(self.segmentScaling.begin(), self.segmentScaling.end());
})
.def("set_segment_scaling",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::f32>& v) {
if (v.size() != self.segmentScaling.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.segmentScaling.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.segmentScaling[i] = v[i];
})
.def("get_head_interval",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::u32>(self.headInterval.begin(), self.headInterval.end());
})
.def("set_head_interval",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::u32>& v) {
if (v.size() != self.headInterval.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.headInterval.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.headInterval[i] = v[i];
})
.def("get_head_decay_interval",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::u32>(self.headDecayInterval.begin(), self.headDecayInterval.end());
})
.def("set_head_decay_interval",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::u32>& v) {
if (v.size() != self.headDecayInterval.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.headDecayInterval.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.headDecayInterval[i] = v[i];
})
.def("get_tail_interval",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::u32>(self.tailInterval.begin(), self.tailInterval.end());
})
.def("set_tail_interval",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::u32>& v) {
if (v.size() != self.tailInterval.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.tailInterval.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.tailInterval[i] = v[i];
})
.def("get_tail_decay_interval",
[](const whiteout::mdx::ParticleEmitter2& self) {
return std::vector<whiteout::u32>(self.tailDecayInterval.begin(), self.tailDecayInterval.end());
})
.def("set_tail_decay_interval",
[](whiteout::mdx::ParticleEmitter2& self, const std::vector<whiteout::u32>& v) {
if (v.size() != self.tailDecayInterval.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.tailDecayInterval.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.tailDecayInterval[i] = v[i];
})
;
py::class_<whiteout::mdx::RibbonEmitter>(m, "RibbonEmitter", R"doc(Ribbon/trail emitter
Creates ribbon trails that follow the emitter's movement, like sword trails, missile contrails, etc.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::RibbonEmitter::node, R"doc(Base node data)doc")
.def_readwrite("height_above", &whiteout::mdx::RibbonEmitter::heightAbove, R"doc(Height above attachment point)doc")
.def_readwrite("height_below", &whiteout::mdx::RibbonEmitter::heightBelow, R"doc(Height below attachment point)doc")
.def_readwrite("alpha", &whiteout::mdx::RibbonEmitter::alpha, R"doc(Ribbon opacity)doc")
.def_readwrite("color", &whiteout::mdx::RibbonEmitter::color, R"doc(Ribbon color)doc")
.def_readwrite("lifespan", &whiteout::mdx::RibbonEmitter::lifespan, R"doc(Ribbon segment lifetime)doc")
.def_readwrite("texture_slot", &whiteout::mdx::RibbonEmitter::textureSlot, R"doc(Texture slot in material)doc")
.def_readwrite("emission_rate", &whiteout::mdx::RibbonEmitter::emissionRate, R"doc(Emission rate)doc")
.def_readwrite("rows", &whiteout::mdx::RibbonEmitter::rows, R"doc(Texture atlas rows)doc")
.def_readwrite("columns", &whiteout::mdx::RibbonEmitter::columns, R"doc(Texture atlas columns)doc")
.def_readwrite("material_id", &whiteout::mdx::RibbonEmitter::materialId, R"doc(Material index)doc")
.def_readwrite("gravity", &whiteout::mdx::RibbonEmitter::gravity, R"doc(Gravity effect)doc")
.def_readwrite("height_above_tracks", &whiteout::mdx::RibbonEmitter::heightAboveTracks, R"doc(Height above animation)doc")
.def_readwrite("height_below_tracks", &whiteout::mdx::RibbonEmitter::heightBelowTracks, R"doc(Height below animation)doc")
.def_readwrite("alpha_tracks", &whiteout::mdx::RibbonEmitter::alphaTracks, R"doc(Alpha animation)doc")
.def_readwrite("color_tracks", &whiteout::mdx::RibbonEmitter::colorTracks, R"doc(Color animation)doc")
.def_readwrite("texture_slot_tracks", &whiteout::mdx::RibbonEmitter::textureSlotTracks, R"doc(Texture slot animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::RibbonEmitter::visibilityTracks, R"doc(Visibility animation)doc")
;
py::class_<whiteout::mdx::EventObject>(m, "EventObject", R"doc(Animation event trigger
Event objects fire events at specific animation frames, used to trigger sounds, spawn effects, etc. synchronized with animations.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::EventObject::node, R"doc(Base node data)doc")
.def_readwrite("global_sequence_id", &whiteout::mdx::EventObject::globalSequenceId, R"doc(Global sequence if looping)doc")
.def_readwrite("event_track_times", &whiteout::mdx::EventObject::eventTrackTimes, R"doc(Frame numbers when events fire)doc")
;
py::class_<whiteout::mdx::Camera>(m, "Camera", R"doc(Camera definition
Cameras define viewpoints that can be used for portrait renders or in-game cutscenes.)doc")
.def(py::init<>())
.def_readwrite("name", &whiteout::mdx::Camera::name, R"doc(Camera name)doc")
.def_readwrite("position", &whiteout::mdx::Camera::position, R"doc(Camera position)doc")
.def_readwrite("field_of_view", &whiteout::mdx::Camera::fieldOfView, R"doc(Field of view angle in radians)doc")
.def_readwrite("far_clipping_plane", &whiteout::mdx::Camera::farClippingPlane, R"doc(Far clipping distance)doc")
.def_readwrite("near_clipping_plane", &whiteout::mdx::Camera::nearClippingPlane, R"doc(Near clipping distance)doc")
.def_readwrite("target_position", &whiteout::mdx::Camera::targetPosition, R"doc(Look-at target position)doc")
.def_readwrite("position_tracks", &whiteout::mdx::Camera::positionTracks, R"doc(Position animation)doc")
.def_readwrite("target_rotation_tracks", &whiteout::mdx::Camera::targetRotationTracks, R"doc(Target rotation animation)doc")
.def_readwrite("target_position_tracks", &whiteout::mdx::Camera::targetPositionTracks, R"doc(Target position animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::Camera::visibilityTracks, R"doc(KCVS)doc")
.def_readwrite("focus_distance_tracks", &whiteout::mdx::Camera::focusDistanceTracks, R"doc(IDUF)doc")
.def_readwrite("focal_length_tracks", &whiteout::mdx::Camera::focalLengthTracks, R"doc(ELAF)doc")
.def_readwrite("f_stop_tracks", &whiteout::mdx::Camera::fStopTracks, R"doc(PTSF)doc")
;
py::class_<whiteout::mdx::CollisionShape>(m, "CollisionShape", R"doc(Collision volume for physics
Collision shapes define simplified geometry for collision detection.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::CollisionShape::node, R"doc(Base node data)doc")
.def_readwrite("type", &whiteout::mdx::CollisionShape::type, R"doc(Shape type)doc")
.def_readwrite("vertices", &whiteout::mdx::CollisionShape::vertices, R"doc(Shape vertices (box only))doc")
.def_readwrite("radius", &whiteout::mdx::CollisionShape::radius, R"doc(Radius (sphere/cylinder only))doc")
;
py::class_<whiteout::mdx::FaceEffect>(m, "FaceEffect", R"doc(Facial animation effect (Reforged)
Holds a name and a path to the FaceFX setup file used for a character's face.)doc")
.def(py::init<>())
.def_readwrite("name", &whiteout::mdx::FaceEffect::name, R"doc(Section name)doc")
.def_readwrite("path", &whiteout::mdx::FaceEffect::path, R"doc(Path to facial animation data)doc")
;
py::class_<whiteout::mdx::CornEmitter>(m, "CornEmitter", R"doc(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.)doc")
.def(py::init<>())
.def_readwrite("node", &whiteout::mdx::CornEmitter::node, R"doc(Base node data)doc")
.def_readwrite("life_span", &whiteout::mdx::CornEmitter::lifeSpan, R"doc(Particle lifetime (default))doc")
.def_readwrite("emission_rate", &whiteout::mdx::CornEmitter::emissionRate, R"doc(Emission rate (default))doc")
.def_readwrite("speed", &whiteout::mdx::CornEmitter::speed, R"doc(Particle speed (default))doc")
.def_readwrite("color", &whiteout::mdx::CornEmitter::color, R"doc(Particle color (RGB))doc")
.def_readwrite("alpha", &whiteout::mdx::CornEmitter::alpha, R"doc(Particle alpha (default))doc")
.def_readwrite("replaceable_id", &whiteout::mdx::CornEmitter::replaceableId, R"doc(Replaceable texture ID)doc")
.def_readwrite("path", &whiteout::mdx::CornEmitter::path, R"doc(Path to PopcornFX effect)doc")
.def_readwrite("anim_visibility_guide", &whiteout::mdx::CornEmitter::animVisibilityGuide, R"doc(Animation visibility guide)doc")
.def_readwrite("life_span_tracks", &whiteout::mdx::CornEmitter::lifeSpanTracks, R"doc(Lifespan animation)doc")
.def_readwrite("emission_rate_tracks", &whiteout::mdx::CornEmitter::emissionRateTracks, R"doc(Emission rate animation)doc")
.def_readwrite("speed_tracks", &whiteout::mdx::CornEmitter::speedTracks, R"doc(Speed animation)doc")
.def_readwrite("color_tracks", &whiteout::mdx::CornEmitter::colorTracks, R"doc(Color animation)doc")
.def_readwrite("alpha_tracks", &whiteout::mdx::CornEmitter::alphaTracks, R"doc(Alpha animation)doc")
.def_readwrite("visibility_tracks", &whiteout::mdx::CornEmitter::visibilityTracks, R"doc(Visibility animation)doc")
;
py::class_<whiteout::mdx::Parser>(m, "Parser", R"doc(Parser for MDX model files
The Parser reads binary MDX files and converts them into the Model structure. It supports multiple parsing modes and can handle version differences.
Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.)doc")
.def(py::init<>())
.def(py::init<whiteout::mdx::Parser::UpgradeMode>(), py::arg("upgrade_mode"))
.def("parse", py::overload_cast<const std::string&>(&whiteout::mdx::Parser::parse), py::arg("filePath"), R"doc(Parse an MDX file from disk
The format is detected from the file extension: `.mdl` for text MDL format, `.mdx` (or any other extension) for binary MDX format.
@param filePath Path to the MDX/MDL file @return Parsed MDX file data @throws std::runtime_error If file cannot be opened or parsing fails in strict mode)doc")
.def("parse",
[](whiteout::mdx::Parser& self, py::bytes __py_bytes_0, whiteout::mdx::MDLXFormat format) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer, format);
}, py::arg("buffer"), py::arg("format") = whiteout::mdx::MDLXFormat{}, R"doc(Parse an MDX/MDL file from memory buffer @param buffer Memory buffer containing MDX/MDL data @param format Source format (MDX binary or MDL text) @return Parsed MDX file data @throws std::runtime_error If parsing fails in strict mode)doc")
.def("has_issues", &whiteout::mdx::Parser::hasIssues, R"doc(Check if parsing encountered any issues @return True if there were warnings or recoverable errors)doc")
.def("get_issues", &whiteout::mdx::Parser::getIssues, R"doc(Get list of issues encountered during parsing @return Vector of issue description strings)doc")
;
py::class_<whiteout::mdx::Writer>(m, "Writer", R"doc(Writer for MDX/MDL model files
The Writer takes a Model structure and writes it to disk in binary MDX format or text MDL format. It automatically handles chunk serialization and size calculation for MDX, and text formatting for MDL.
The format is selected either by file extension (.mdl → text, .mdx → binary) or by an explicit MDLXFormat parameter.
Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.)doc")
.def(py::init<>())
.def("write", py::overload_cast<const std::string&, const whiteout::mdx::Model&, whiteout::mdx::MdlFormat>(&whiteout::mdx::Writer::write), py::arg("filePath"), py::arg("mdlx"), py::arg("mdlFormat") = whiteout::mdx::MdlFormat{}, R"doc(Write a model file to disk
The format is detected from the file extension: `.mdl` for text MDL format, `.mdx` (or any other extension) for binary MDX format.
@param filePath Path where the file should be written @param mdlx Model data to write @param mdlFormat MDL text dialect (only used when writing .mdl); defaults to WarcraftIII (engine-faithful). @throws std::runtime_error If file cannot be created or written)doc")
.def("write",
[](whiteout::mdx::Writer& self, const whiteout::mdx::Model& mdx, whiteout::mdx::MDLXFormat format, whiteout::mdx::MdlFormat mdlFormat) {
auto __v = self.write(mdx, format, mdlFormat);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("mdx"), py::arg("format") = whiteout::mdx::MDLXFormat{}, py::arg("mdlFormat") = whiteout::mdx::MdlFormat{}, R"doc(Write a model to a byte buffer @param mdx Model data to write @param format Output format (MDX binary or MDL text, defaults to MDX) @param mdlFormat MDL text dialect (only used when format == MDL); defaults to WarcraftIII (engine-faithful). @return Vector containing the binary MDX data or UTF-8 MDL text)doc")
;
py::class_<whiteout::mdx::Track<whiteout::Vector3f>>(m, "TrackVector3f", R"doc(Generic animation track with keyframe data
Tracks store animation data as a series of keyframes. The interpolation type determines how values between keyframes are calculated.
@tparam T The value type (f32, Vector3f, Vector4f, u32, etc.))doc")
.def(py::init<>())
.def_readwrite("is_used", &whiteout::mdx::Track<whiteout::Vector3f>::isUsed, R"doc(Whether this track contains animation data)doc")
.def_readwrite("interpolation_type", &whiteout::mdx::Track<whiteout::Vector3f>::interpolationType, R"doc(Interpolation type)doc")
.def_readwrite("global_sequence_id", &whiteout::mdx::Track<whiteout::Vector3f>::globalSequenceId, R"doc(Global sequence ID if applicable)doc")
.def_readwrite("key_count", &whiteout::mdx::Track<whiteout::Vector3f>::keyCount, R"doc(Number of keyframes)doc")
.def_readwrite("timestamps", &whiteout::mdx::Track<whiteout::Vector3f>::timestamps, R"doc(Keyframe timestamps (in frames))doc")
.def_readwrite("keys", &whiteout::mdx::Track<whiteout::Vector3f>::keys_data, R"doc(Raw keyframe data)doc")
;
py::class_<whiteout::mdx::Track<whiteout::Quaternion>>(m, "TrackQuaternion", R"doc(Generic animation track with keyframe data
Tracks store animation data as a series of keyframes. The interpolation type determines how values between keyframes are calculated.
@tparam T The value type (f32, Vector3f, Vector4f, u32, etc.))doc")
.def(py::init<>())
.def_readwrite("is_used", &whiteout::mdx::Track<whiteout::Quaternion>::isUsed, R"doc(Whether this track contains animation data)doc")
.def_readwrite("interpolation_type", &whiteout::mdx::Track<whiteout::Quaternion>::interpolationType, R"doc(Interpolation type)doc")
.def_readwrite("global_sequence_id", &whiteout::mdx::Track<whiteout::Quaternion>::globalSequenceId, R"doc(Global sequence ID if applicable)doc")
.def_readwrite("key_count", &whiteout::mdx::Track<whiteout::Quaternion>::keyCount, R"doc(Number of keyframes)doc")
.def_readwrite("timestamps", &whiteout::mdx::Track<whiteout::Quaternion>::timestamps, R"doc(Keyframe timestamps (in frames))doc")
.def_readwrite("keys", &whiteout::mdx::Track<whiteout::Quaternion>::keys_data, R"doc(Raw keyframe data)doc")
;
py::class_<whiteout::mdx::Track<whiteout::u32>>(m, "TrackU32", R"doc(Generic animation track with keyframe data
Tracks store animation data as a series of keyframes. The interpolation type determines how values between keyframes are calculated.
@tparam T The value type (f32, Vector3f, Vector4f, u32, etc.))doc")
.def(py::init<>())
.def_readwrite("is_used", &whiteout::mdx::Track<whiteout::u32>::isUsed, R"doc(Whether this track contains animation data)doc")
.def_readwrite("interpolation_type", &whiteout::mdx::Track<whiteout::u32>::interpolationType, R"doc(Interpolation type)doc")
.def_readwrite("global_sequence_id", &whiteout::mdx::Track<whiteout::u32>::globalSequenceId, R"doc(Global sequence ID if applicable)doc")
.def_readwrite("key_count", &whiteout::mdx::Track<whiteout::u32>::keyCount, R"doc(Number of keyframes)doc")
.def_readwrite("timestamps", &whiteout::mdx::Track<whiteout::u32>::timestamps, R"doc(Keyframe timestamps (in frames))doc")
.def_readwrite("keys", &whiteout::mdx::Track<whiteout::u32>::keys_data, R"doc(Raw keyframe data)doc")
;
py::class_<whiteout::mdx::Track<whiteout::f32>>(m, "TrackF32", R"doc(Generic animation track with keyframe data
Tracks store animation data as a series of keyframes. The interpolation type determines how values between keyframes are calculated.
@tparam T The value type (f32, Vector3f, Vector4f, u32, etc.))doc")
.def(py::init<>())
.def_readwrite("is_used", &whiteout::mdx::Track<whiteout::f32>::isUsed, R"doc(Whether this track contains animation data)doc")
.def_readwrite("interpolation_type", &whiteout::mdx::Track<whiteout::f32>::interpolationType, R"doc(Interpolation type)doc")
.def_readwrite("global_sequence_id", &whiteout::mdx::Track<whiteout::f32>::globalSequenceId, R"doc(Global sequence ID if applicable)doc")
.def_readwrite("key_count", &whiteout::mdx::Track<whiteout::f32>::keyCount, R"doc(Number of keyframes)doc")
.def_readwrite("timestamps", &whiteout::mdx::Track<whiteout::f32>::timestamps, R"doc(Keyframe timestamps (in frames))doc")
.def_readwrite("keys", &whiteout::mdx::Track<whiteout::f32>::keys_data, R"doc(Raw keyframe data)doc")
;
py::bind_vector<std::vector<std::vector<whiteout::Vector2f>>>(m, "VectorVectorVector2f");
py::bind_vector<std::vector<whiteout::u32>>(m, "VectorU32", py::buffer_protocol());
py::bind_vector<std::vector<whiteout::u16>>(m, "VectorU16", py::buffer_protocol());
bindBufferVector<whiteout::Quaternion, float, 4>(m, "VectorQuaternion");
bindBufferVector<whiteout::Vector2f, float, 2>(m, "VectorVector2f");
bindBufferVector<whiteout::Vector3f, float, 3>(m, "VectorVector3f");
bindBufferVector<whiteout::Vector4f, float, 4>(m, "VectorVector4f");
bind_mdx_1(m);
}