wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
track.hpp
Go to the documentation of this file.
1#pragma once
2
16
17#include <algorithm>
18#include <span>
19#include <cstdint>
20#include <format>
21#include <utility>
22#include <vector>
23
24#include <welder/vocabulary.hpp>
25
26#include <wowlib/core/lang.hpp>
28#include <wowlib/core/error.hpp>
32
38 template <typename T>
39 struct [[
40 =welder::weld,
42 =welder::doc(R"(
43 An animation track family for one value type, abstract over the
44 client version; the per-version classes are subclasses.)")
46 // The concretes default operator== — the base must be comparable too.
47 bool operator==(const M2TrackFamilyBase&) const = default;
48 };
49
51 Bindings-only, like every *Base: it gives the per-version classes a
52 common welded supertype, so the family surface hoists their shared
53 members and containers of them carry a base-typed live view. */
54 struct [[
55 =welder::weld,
56 =welder::weld_as("M2EventTrack"),
58 =welder::doc(R"(
59 A timestamp-only event track (every key fires). Abstract over the client version; construct a concrete
60 version with M2EventTrack.ForVersion / for_version.)")
62 // The concretes default operator== — the base must be comparable too.
63 bool operator==(const M2EventTrackBase&) const = default;
64 };
66 struct [[
67 =welder::weld,
68 =welder::doc("An inclusive u32 range: pre-WotLK track interpolation "
69 "ranges, sequence replay bounds.")
70 ]] M2Range {
71 [[=welder::doc("The inclusive lower bound.")]]
72 std::uint32_t minimum = 0;
74 [[=welder::doc("The inclusive upper bound.")]]
75 std::uint32_t maximum = 0;
76
77 bool operator==(const M2Range&) const = default;
78 };
79
80 static_assert(sizeof(M2Range) == 8);
81
82 struct [[
83 =welder::weld,
84 =welder::doc("A bounding volume: axis-aligned extent plus sphere radius.")
85 ]] M2Bounds {
86 [[=welder::doc("The axis-aligned extent.")]]
87 CAaBox extent{};
89 [[=welder::doc("The bounding-sphere radius.")]]
90 float radius = 0;
91
92 bool operator==(const M2Bounds&) const = default;
93 };
94
95 static_assert(sizeof(M2Bounds) == 28);
97 struct [[
98 =welder::weld,
99 =welder::doc(
100 "A global-loop entry: the timestamp a global sequence wraps at.")
102 [[=welder::doc("The timestamp the global sequence wraps at.")]]
103 std::uint32_t timestamp = 0;
105 bool operator==(const M2Loop&) const = default;
106 };
108 static_assert(sizeof(M2Loop) == 4);
110 struct [[
111 =welder::weld,
112 =welder::doc("A quaternion compressed to i16 x, y, z, w (TBC+ bone "
113 "rotations); decompress as (v < 0 ? v + 32768 : v - 32767) / 32767.")
114 ]] M2CompQuat {
115 [[=welder::doc("The compressed x component.")]]
116 std::int16_t x = 32767;
118 [[=welder::doc("The compressed y component.")]]
119 std::int16_t y = 32767;
121 [[=welder::doc("The compressed z component.")]]
122 std::int16_t z = 32767;
123
124 [[=welder::doc("The compressed w component (identity stores 65535).")]]
125 std::int16_t w = -1; // 65535 as the client stores identity w
126
127 bool operator==(const M2CompQuat&) const = default;
128 };
130 static_assert(sizeof(M2CompQuat) == 8);
132 struct [[
133 =welder::weld,
134 =welder::doc("A model-space box: minimum and maximum corner vectors.")
135 ]] M2Box {
136 [[=welder::doc("The minimum corner.")]]
137 C3Vector minimum{};
138
139 [[=welder::doc("The maximum corner.")]]
140 C3Vector maximum{};
141
142 bool operator==(const M2Box&) const = default;
143 };
144
145 static_assert(sizeof(M2Box) == 24);
146
147 template <typename T>
148 struct [[
149 =welder::weld,
150 =welder::doc(
151 "A spline keyframe: the value plus incoming/outgoing tangents "
152 "(bezier/hermite camera tracks).")
153 ]] M2SplineKey {
154 [[=welder::doc("The keyframe value.")]]
155 T value{};
156
157 [[=welder::doc("The incoming tangent.")]]
158 T inTan{};
159
160 [[=welder::doc("The outgoing tangent.")]]
161 T outTan{};
162
163 bool operator==(const M2SplineKey&) const = default;
164 };
165
166 static_assert(sizeof(M2SplineKey<float>) == 12);
167 static_assert(sizeof(M2SplineKey<C3Vector>) == 36);
168
169 namespace detail {
170 // The annotated era layouts; instantiate through the canonicalizing
171 // aliases below, never directly.
177 template <typename T, ClientVersion V>
178 struct M2Track;
179
180 template <typename T, ClientVersion V> requires (V < M2PerSequenceTimelines)
181 struct [[
182 =welder::weld,
183 =welder::doc(
184 "An animation track, pre-WotLK layout: one global timeline with "
185 "per-sequence interpolation ranges.")
187 [[=welder::doc("Interpolation: 0 none, 1 linear, 2 bezier, 3 hermite "
188 "(spline types only valid for spline-key tracks).")]]
189 std::uint16_t interpolationType = 0;
190
191 [[=welder::doc("The global sequence driving this track; -1: none.")]]
192 std::uint16_t globalSequence = 0xFFFF;
193
194 [[=welder::doc("Per-sequence [first, last] key-index ranges into the "
195 "global timeline.")]]
196 std::vector<M2Range> interpolationRanges;
197
198 [[=welder::doc("The global timeline's keyframe timestamps.")]]
199 std::vector<std::uint32_t> timestamps;
200
201 [[
202 =formats::countMatches("timestamps"),
203 =welder::doc("The keyframe values, one per timestamp.")]]
204 std::vector<T> values;
205
206 // --- the version-agnostic TIMELINE surface -----------------------------
207 // Keys live on TIMELINES: here (pre-WotLK) one shared global array,
208 // sliced per sequence by interpolationRanges; a global-sequence or
209 // rangeless track exposes the whole array as its single timeline 0.
210 // WotLK+ stores one array per sequence outright - same accessors,
211 // different backing, so version-agnostic code never branches.
212
213 [[=welder::doc("The number of timelines this track carries: one per "
214 "interpolation range, or a single timeline 0 for a "
215 "global-sequence-driven or rangeless track (0 when the "
216 "track is empty).")]]
217 std::size_t timelineCount() const {
218 if (globalSequence != 0xFFFF || interpolationRanges.empty()) return timestamps.empty()
219 ? std::size_t{0}
220 : std::size_t{1};
221 return interpolationRanges.size();
222 }
223
224 [[nodiscard]]
225 [[=welder::doc("The number of keys on one timeline."),
226 =welder::returns(
227 "the key count; errors when timeline is out of range")]
228 ]
229 Result<std::size_t> keyCount(std::size_t timeline
230 [[=welder::doc("the timeline index")]]) const {
231 return _timelineSlice(timeline).transform([](auto s) {
232 return s.second;
233 });
234 }
235
236 [[nodiscard]]
237 [[=welder::doc("One timeline's keyframe timestamps, as a copy."),
238 =welder::returns("the timestamps; errors when timeline is out of range")
239 ]]
240 Result<std::vector<std::uint32_t>> timelineTimestamps(
241 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
242 return _timelineSlice(timeline).transform([this](auto s) {
243 const auto window = std::span(timestamps).subspan(s.first, s.second);
244 return std::vector<std::uint32_t>(window.begin(), window.end());
245 });
246 }
247
248 [[nodiscard]]
249 [[=welder::doc("One timeline's keyframe values, as a copy."),
250 =welder::returns("the values; errors when timeline is out of range")]]
251 Result<std::vector<T>> timelineValues(
252 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
253 return _timelineSlice(timeline).transform([this](auto s) {
254 const std::size_t maxFirst = std::min(s.first, values.size());
255 const std::size_t count = std::min(s.second,
256 values.size() - maxFirst);
257 const auto window = std::span(values).subspan(maxFirst, count);
258 return std::vector<T>(window.begin(), window.end());
259 });
260 }
261
262 private:
269 std::size_t timeline) const {
270 if (timeline >= timelineCount())
272 std::format(
273 "timeline {} out of range ({} timelines)",
274 timeline, timelineCount()));
275 if (globalSequence != 0xFFFF || interpolationRanges.empty())
276 return std::pair<std::size_t, std::size_t>{0, timestamps.size()};
277 const M2Range& r = interpolationRanges[timeline];
278 const std::size_t first = std::min<std::size_t>(
279 r.minimum, timestamps.size());
280 const std::size_t end =
281 std::min<std::size_t>(std::size_t{r.maximum} + 1, timestamps.size());
282 return std::pair<std::size_t, std::size_t>{
283 first,
284 end < first ? 0 : end - first
285 };
286 }
287
288 public:
289 bool operator==(const M2Track&) const = default;
290 };
291
292 template <typename T, ClientVersion V>
294 struct [[
295 =welder::weld,
296 =welder::doc(
297 "An animation track, WotLK+ layout: one timestamp/value array per "
298 "sequence; an external sequence keeps its arrays in the .anim file.")
300 [[=welder::doc("Interpolation: 0 none, 1 linear, 2 bezier, 3 hermite "
301 "(spline types only valid for spline-key tracks).")]]
302 std::uint16_t interpolationType = 0;
303
304 [[=welder::doc("The global sequence driving this track; -1: none.")]]
305 std::uint16_t globalSequence = 0xFFFF;
306
307 [[
309 =welder::doc("Keyframe timestamps, one array per sequence (an external "
310 "sequence keeps its arrays in the .anim file).")]]
311 std::vector<std::vector<std::uint32_t>> timestamps;
312
313 [[
315 =formats::countMatches("timestamps"),
316 =welder::doc("Keyframe values, per sequence, parallel to timestamps.")]]
317 std::vector<std::vector<T>> values;
318
319 // --- the version-agnostic TIMELINE surface -----------------------------
320 // Same accessors as the pre-WotLK layout, over the native per-sequence
321 // arrays - version-agnostic code never branches on the era.
322
323 [[=welder::doc("The number of timelines this track carries: one per "
324 "sequence (a global-sequence-driven track stores a "
325 "single timeline; an external sequence's timeline is "
326 "empty until its .anim data is loaded).")]]
327 std::size_t timelineCount() const { return timestamps.size(); }
328
329 [[nodiscard]]
330 [[=welder::doc("The number of keys on one timeline."),
331 =welder::returns(
332 "the key count; errors when timeline is out of range")]
333 ]
334 Result<std::size_t> keyCount(std::size_t timeline
335 [[=welder::doc("the timeline index")]]) const {
336 if (timeline >= timestamps.size())
338 std::format(
339 "timeline {} out of range ({} timelines)",
340 timeline, timestamps.size()));
341 return timestamps[timeline].size();
342 }
344 [[nodiscard]]
345 [[=welder::doc("One timeline's keyframe timestamps, as a copy."),
346 =welder::returns("the timestamps; errors when timeline is out of range")
347 ]]
348 Result<std::vector<std::uint32_t>> timelineTimestamps(
349 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
350 if (timeline >= timestamps.size())
352 std::format(
353 "timeline {} out of range ({} timelines)",
354 timeline, timestamps.size()));
355 return timestamps[timeline];
356 }
358 [[nodiscard]]
359 [[=welder::doc("One timeline's keyframe values, as a copy."),
360 =welder::returns("the values; errors when timeline is out of range")]]
361 Result<std::vector<T>> timelineValues(
362 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
363 if (timeline >= values.size())
365 std::format(
366 "timeline {} out of range ({} timelines)",
367 timeline, values.size()));
368 return values[timeline];
369 }
370
371 bool operator==(const M2Track&) const = default;
372 };
376 template <ClientVersion V>
377 struct M2TrackBase;
378
379 template <ClientVersion V>
380 requires (V < M2PerSequenceTimelines)
381 struct [[
382 =welder::weld,
383 =welder::doc(
384 "A timestamp-only event track, pre-WotLK layout (every key fires).")
386 [[=welder::doc("Interpolation: 0 none, 1 linear (keys fire, no value to "
387 "interpolate).")]]
388 std::uint16_t interpolationType = 0;
389
390 [[=welder::doc("The global sequence driving this track; -1: none.")]]
391 std::uint16_t globalSequence = 0xFFFF;
392
393 [[=welder::doc("Per-sequence [first, last] key-index ranges into the "
394 "global timeline.")]]
395 std::vector<M2Range> interpolationRanges;
396
397 [[=welder::doc("The global timeline's trigger timestamps.")]]
398 std::vector<std::uint32_t> timestamps;
399
400 // The version-agnostic TIMELINE surface (see M2Track): the event
401 // track's timestamps only.
402
403 [[=welder::doc("The number of timelines this track carries: one per "
404 "interpolation range, or a single timeline 0 for a "
405 "global-sequence-driven or rangeless track (0 when the "
406 "track is empty).")]]
407 std::size_t timelineCount() const {
408 if (globalSequence != 0xFFFF || interpolationRanges.empty())
409 return timestamps.empty() ? std::size_t{0} : std::size_t{1};
410 return interpolationRanges.size();
411 }
412
413 [[nodiscard]]
414 [[=welder::doc("The number of trigger keys on one timeline."),
415 =welder::returns(
416 "the key count; errors when timeline is out of range")]
417 ]
418 Result<std::size_t> keyCount(std::size_t timeline
419 [[=welder::doc("the timeline index")]]) const {
420 return _timelineSlice(timeline).transform([](auto s) {
421 return s.second;
422 });
423 }
424
425 [[nodiscard]]
426 [[=welder::doc("One timeline's trigger timestamps, as a copy."),
427 =welder::returns("the timestamps; errors when timeline is out of range")
428 ]]
429 Result<std::vector<std::uint32_t>> timelineTimestamps(
430 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
431 return _timelineSlice(timeline).transform([this](auto s) {
432 const auto window = std::span(timestamps).subspan(s.first, s.second);
433 return std::vector<std::uint32_t>(window.begin(), window.end());
434 });
435 }
436
437 private:
443 std::size_t timeline) const {
444 if (timeline >= timelineCount())
446 std::format(
447 "timeline {} out of range ({} timelines)",
448 timeline, timelineCount()));
449 if (globalSequence != 0xFFFF || interpolationRanges.empty())
450 return std::pair<std::size_t, std::size_t>{0, timestamps.size()};
451 const M2Range& r = interpolationRanges[timeline];
452 const std::size_t first = std::min<std::size_t>(
453 r.minimum, timestamps.size());
454 const std::size_t end =
455 std::min<std::size_t>(std::size_t{r.maximum} + 1, timestamps.size());
456 return std::pair<std::size_t, std::size_t>{
457 first,
458 end < first ? 0 : end - first
459 };
460 }
461
462 public:
463 bool operator==(const M2TrackBase&) const = default;
464 };
465
466 template <ClientVersion V>
467 requires (V >= M2PerSequenceTimelines)
468 struct [[
469 =welder::weld,
470 =welder::doc(
471 "A timestamp-only event track, WotLK+ layout (every key fires).")
473 [[=welder::doc("Interpolation: 0 none, 1 linear (keys fire, no value to "
474 "interpolate).")]]
475 std::uint16_t interpolationType = 0;
476
477 [[=welder::doc("The global sequence driving this track; -1: none.")]]
478 std::uint16_t globalSequence = 0xFFFF;
479
480 [[
482 =welder::doc("Trigger timestamps, one array per sequence (an external "
483 "sequence keeps its arrays in the .anim file).")]]
484 std::vector<std::vector<std::uint32_t>> timestamps;
485
486 // The version-agnostic TIMELINE surface (see M2Track): the event
487 // track's timestamps only, over the native per-sequence arrays.
488
489 [[=welder::doc("The number of timelines this track carries: one per "
490 "sequence (an external sequence's timeline is empty "
491 "until its .anim data is loaded).")]]
492 std::size_t timelineCount() const { return timestamps.size(); }
493
494 [[nodiscard]]
495 [[=welder::doc("The number of trigger keys on one timeline."),
496 =welder::returns(
497 "the key count; errors when timeline is out of range")]
499 Result<std::size_t> keyCount(std::size_t timeline
500 [[=welder::doc("the timeline index")]]) const {
501 if (timeline >= timestamps.size())
503 std::format(
504 "timeline {} out of range ({} timelines)",
505 timeline, timestamps.size()));
506 return timestamps[timeline].size();
507 }
508
509 [[nodiscard]]
510 [[=welder::doc("One timeline's trigger timestamps, as a copy."),
511 =welder::returns("the timestamps; errors when timeline is out of range")
512 ]]
513 Result<std::vector<std::uint32_t>> timelineTimestamps(
514 std::size_t timeline [[=welder::doc("the timeline index")]]) const {
515 if (timeline >= timestamps.size())
517 std::format(
518 "timeline {} out of range ({} timelines)",
519 timeline, timestamps.size()));
520 return timestamps[timeline];
522
523 bool operator==(const M2TrackBase&) const = default;
524 };
525 }
526
527 /** An animation track for value type @a T — the canonicalizing face of
528 detail::M2Track: every client version maps to its range's first grid
529 version (M2TrackPivots), so one instantiation serves the whole range.
530 See the detail primary for the era semantics. */
531 template <typename T, ClientVersion V>
532 using M2Track =
537 template <ClientVersion V>
538 using M2TrackBase =
540
542 template <typename T>
543 struct [[
544 =welder::weld,
545 =welder::doc(
546 "The header-less 'fake' animation block: sequence-independent u16 "
547 "timestamps plus keys (WotLK+ particle ramps).")
548 ]] FBlock {
549 [[=welder::doc("Sequence-independent keyframe timestamps.")]]
550 std::vector<std::uint16_t> timestamps;
551
552 [[
553 =formats::countMatches("timestamps"),
554 =welder::doc("The keys, one per timestamp.")]]
555 std::vector<T> keys;
556
557 bool operator==(const FBlock&) const = default;
558 };
559
560 template <typename T>
561 struct [[
562 =welder::weld,
563 =welder::doc(
564 "A partial track: normalized fixed16 times plus values (Legion+ EXP2 "
565 "alpha cutoffs).")
566 ]] M2PartTrack {
567 [[=welder::doc("Normalized fixed16 key times.")]]
568 std::vector<fixed16> times;
569
570 [[
571 =formats::countMatches("times"),
572 =welder::doc("The values, one per time.")]]
573 std::vector<T> values;
574
575 bool operator==(const M2PartTrack&) const = default;
576 };
577}
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
The chunk annotation vocabulary format entities declare their binary mapping with.
Binding-language identities welder's core does not name.
#define WOWLIB_CS_FAMILY_SURFACE
The C# rod's family-surface opt-in, spellable in every build.
Definition lang.hpp:44
The M2 version vocabulary: the layout pivots record/entity partial specializations key on,...
M2 body record structs (sequences, bones, tracks, textures, cameras, emitters) and their flag enums.
Definition bone.hpp:17
detail::M2TrackBase< canonicalVersion(V, M2TrackPivots, M2Versions)> M2TrackBase
A timestamp-only event track — the canonicalizing face of detail::M2TrackBase (same two eras and pivo...
Definition track.hpp:504
detail::M2Track< T, canonicalVersion(V, M2TrackPivots, M2Versions)> M2Track
An animation track for value type T — the canonicalizing face of detail::M2Track: every client versio...
Definition track.hpp:498
constexpr std::array M2TrackPivots
Track-shaped records (M2Track, M2TrackBase, and every record whose only version axis is the tracks it...
constexpr ClientVersion M2PerSequenceTimelines
WotLK (v264): every M2Track nests one timestamp/value array per sequence (the vanilla single timeline...
constexpr std::array M2Versions
The versions M2 is instantiated (and welded) for: every targeted last-minor-of-major release,...
constexpr ClientVersion canonicalVersion(ClientVersion v, std::span< const ClientVersion > pivots, std::span< const ClientVersion > grid)
The canonical version v collapses to: the FIRST grid version in v's range.
consteval detail::CountMatchesSpec countMatches(std::string_view name, std::uint32_t scale=1)
Declare a companion-count contract: when this member is engaged (non-empty), its element count times ...
constexpr detail::SequenceDataSpec SequenceData
Mark an offset-entity member (a nested std::vector<std::vector<T>>, one inner array per animation seq...
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
Definition error.hpp:107
@ InvalidEntityState
An entity's members disagree (e.g.
Definition error.hpp:40
A 3D float vector — the workhorse: positions, normals, rotations-as-Euler-degrees in placements.
Definition types.hpp:49
An axis-aligned bounding box: minimum and maximum corners.
Definition types.hpp:134
std::vector< std::uint16_t > timestamps
Definition track.hpp:516
bool operator==(const FBlock &) const =default
A bounding volume: axis-aligned extent plus sphere radius.
Definition track.hpp:73
CAaBox extent
The axis-aligned extent.
Definition track.hpp:75
bool operator==(const M2Bounds &) const =default
float radius
The bounding-sphere radius.
Definition track.hpp:78
A model-space box: minimum and maximum corner vectors.
Definition track.hpp:115
bool operator==(const M2Box &) const =default
C3Vector maximum
The maximum corner.
Definition track.hpp:120
C3Vector minimum
The minimum corner.
Definition track.hpp:117
A quaternion compressed to i16 x, y, z, w (TBC+ bone rotations); decompress as (v < 0 ?
Definition track.hpp:96
std::int16_t x
The compressed x component.
Definition track.hpp:98
std::int16_t z
The compressed z component.
Definition track.hpp:104
std::int16_t y
The compressed y component.
Definition track.hpp:101
bool operator==(const M2CompQuat &) const =default
std::int16_t w
The compressed w component (identity stores 65535).
Definition track.hpp:107
The version-agnostic base of every M2EventTrack<V> (welded as "M2EventTrack").
Definition track.hpp:54
bool operator==(const M2EventTrackBase &) const =default
A global-loop entry: the timestamp a global sequence wraps at.
Definition track.hpp:86
std::uint32_t timestamp
The timestamp the global sequence wraps at.
Definition track.hpp:88
bool operator==(const M2Loop &) const =default
bool operator==(const M2PartTrack &) const =default
An inclusive u32 range: pre-WotLK track interpolation ranges, sequence replay bounds.
Definition track.hpp:60
std::uint32_t minimum
The inclusive lower bound.
Definition track.hpp:62
bool operator==(const M2Range &) const =default
std::uint32_t maximum
The inclusive upper bound.
Definition track.hpp:65
A spline keyframe: the value plus incoming/outgoing tangents (bezier/hermite camera tracks).
Definition track.hpp:129
bool operator==(const M2SplineKey &) const =default
The version-agnostic base of every M2Track<T, V> VALUE FAMILY, one base per value type T (welded per ...
Definition track.hpp:42
bool operator==(const M2TrackFamilyBase &) const =default
A timestamp-only track (event triggers: every key is an implicit "firenow").
Definition track.hpp:343
std::vector< T > values
The keyframe values, one per timestamp.
Definition track.hpp:173
std::vector< M2Range > interpolationRanges
Per-sequence [first, last] key-index ranges into the global timeline.
Definition track.hpp:166
std::vector< std::uint32_t > timestamps
The global timeline's keyframe timestamps.
Definition track.hpp:169
std::uint16_t globalSequence
The global sequence driving this track; -1: none.
Definition track.hpp:163
std::uint16_t interpolationType
Interpolation: 0 none, 1 linear, 2 bezier, 3 hermite (spline types only valid for spline-key tracks).
Definition track.hpp:160
std::size_t timelineCount() const
The number of timelines this track carries: one per interpolation range, or a single timeline 0 for a...
Definition track.hpp:183
An animation track for value type T, laid out for client version V.
Definition track.hpp:154
The binary-level math and color primitives shared across WoW file formats (wowdev....