code wiki / (root) / nx_animation_timeline.nx

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}