nx_animation_timeline.nx source
↩ module page · 125 lines · 4153 B
1// nx_animation_timeline.nx -- keyframe animation primitive.
2//
3// Holds N (ts_us, value_q14) keyframes; samples a value at any
4// timestamp via linear interpolation between adjacent keyframes.
5// Pre-first-keyframe samples return the first value; post-last
6// samples return the last (no extrapolation). Composes with all
7// game-render primitives that need time-varying values.
8//
9// V1 ships:
10// - struct NxKeyframe + NxTimeline
11// - add_keyframe (must be added in ts order; substrate enforces)
12// - sample(ts_us) returns interpolated Q14 value
13// - loop sample (modulo at duration_us)
14
15import "nx_syscalls.nx"
16import "nx_tier.nx"
17
18const NX_AT_OK: nx_int = 0
19const NX_AT_ERR_FULL: nx_int = 1
20const NX_AT_ERR_OUT_OF_ORDER: nx_int = 2
21const NX_AT_ERR_EMPTY: nx_int = 3
22
23// ===== Struct: NxKeyframe ==========================================
24
25struct NxKeyframe {
26 ts_us: nx_size,
27 value_q14: nx_int,
28}
29
30// ===== Struct: NxTimeline ==========================================
31
32struct NxTimeline {
33 keyframes: *NxKeyframe,
34 capacity: nx_size,
35 count: nx_size,
36 last_ts_us: nx_size,
37}
38
39const NX_AT_KEY_BYTES: nx_size = 16
40
41func nx_timeline_new(capacity: nx_size) -> *NxTimeline {
42 let t: *NxTimeline = (sys_mmap(32)) as *NxTimeline
43 let bytes: nx_size = capacity * NX_AT_KEY_BYTES
44 t.keyframes = (sys_mmap(bytes)) as *NxKeyframe
45 t.capacity = capacity
46 t.count = 0
47 t.last_ts_us = 0
48 return t
49}
50
51func _at_at(t: *NxTimeline, idx: nx_size) -> *NxKeyframe {
52 return (t.keyframes as i64 + (idx as i64) * NX_AT_KEY_BYTES) as *NxKeyframe
53}
54
55// ===== nx_timeline_add_keyframe ====================================
56//
57// Append a (ts, value) keyframe. ts MUST be >= last_ts (timeline is
58// non-decreasing). Returns OK / FULL / OUT_OF_ORDER.
59
60func nx_timeline_add_keyframe(t: *NxTimeline, ts_us: nx_size, value_q14: nx_int) -> nx_int {
61 if t.count >= t.capacity { return NX_AT_ERR_FULL }
62 if t.count > 0 {
63 if ts_us < t.last_ts_us { return NX_AT_ERR_OUT_OF_ORDER }
64 }
65 let k: *NxKeyframe = _at_at(t, t.count)
66 k.ts_us = ts_us
67 k.value_q14 = value_q14
68 t.count = t.count + 1
69 t.last_ts_us = ts_us
70 return NX_AT_OK
71}
72
73// ===== nx_timeline_sample ==========================================
74//
75// Sample the animated value at ts_us. Linear interp between adjacent
76// keyframes. Before first keyframe -> first value; after last -> last.
77// Empty timeline -> 0.
78
79func nx_timeline_sample(t: *NxTimeline, ts_us: nx_size) -> nx_int {
80 if t.count == 0 { return 0 }
81 let first: *NxKeyframe = _at_at(t, 0)
82 if ts_us <= first.ts_us { return first.value_q14 }
83 let last: *NxKeyframe = _at_at(t, t.count - 1)
84 if ts_us >= last.ts_us { return last.value_q14 }
85
86 // Walk to find bracketing keyframes
87 var i: nx_size = 0
88 while i + 1 < t.count {
89 let a: *NxKeyframe = _at_at(t, i)
90 let b: *NxKeyframe = _at_at(t, i + 1)
91 if ts_us >= a.ts_us {
92 if ts_us <= b.ts_us {
93 if b.ts_us == a.ts_us { return a.value_q14 }
94 let dt: nx_size = ts_us - a.ts_us
95 let span: nx_size = b.ts_us - a.ts_us
96 let dv: nx_int = b.value_q14 - a.value_q14
97 return a.value_q14 + ((dv * (dt as i64)) / (span as i64)) as i64
98 }
99 }
100 i = i + 1
101 }
102 return last.value_q14
103}
104
105// ===== nx_timeline_sample_loop =====================================
106//
107// Sample with modulo at duration_us — animation loops indefinitely.
108
109func nx_timeline_sample_loop(t: *NxTimeline, now_us: nx_size, duration_us: nx_size) -> nx_int {
110 if duration_us == 0 { return nx_timeline_sample(t, now_us) }
111 let cycles: nx_size = now_us / duration_us
112 let modulo_ts: nx_size = now_us - cycles * duration_us
113 return nx_timeline_sample(t, modulo_ts)
114}
115
116func nx_timeline_count(t: *NxTimeline) -> nx_size {
117 return t.count
118}
119
120func nx_timeline_duration_us(t: *NxTimeline) -> nx_size {
121 if t.count <= 1 { return 0 }
122 let first: *NxKeyframe = _at_at(t, 0)
123 let last: *NxKeyframe = _at_at(t, t.count - 1)
124 return last.ts_us - first.ts_us
125}