code wiki / (root) / nx_skeleton.nx

nx_skeleton.nx source

↩ module page · 154 lines · 6111 B

1// nx_skeleton.nx -- SKELETON + LINEAR-BLEND SKINNING (the character-foundation rung everything Elara needs 2// hangs off: morphs, IK, jiggle-region binding, posing -- VAMX ladder R3, rides nx_mesh3 + nx_itrig per the 3// reuse law). BONES ARE DATA: a rig is rows {parent, local offset, yaw, pitch} a mod can ship. World pose = 4// parent-composed integer 3x3 rotations (fx256) + offsets (fx256). Skinned vertex = sum of per-bone weighted 5// transforms of the vertex's BONE-LOCAL position (LBS with 2 influences, weights fx256 summing 256). 6// ALL INTEGER => deterministic, VM-vettable, wasm-ready (base-relative). license_tier: ORIGINAL 7import "nx_syscalls.nx" 8import "nx_itrig.nx" 9 10// bone record: 32 slots at 64+i*256 11// [0]=parent(-1 root) [1..3]=local offset fx256 [4]=yaw4096 [5]=pitch4096 12// [6..14]=world rotation 3x3 fx256 (row-major) [15..17]=world position fx256 [18]=valid 13const SK_MAXB: i64 = 32 14// skin vertex record: 8 slots: [0]=bone0 [1]=w0 [2]=bone1 [3]=w1 [4..6]=bone-local pos (in bone0/bone1 local 15// frames -- v1 authors verts in EACH bone's local frame identically: local-of-bone = offset from that bone) 16// (v1 simplification: ONE local pos used for both bones -- valid when bind poses align, as in tube limbs) 17const SK_MAXV: i64 = 2048 18 19// header 256B (32 slots): [0]=nbones [1]=nverts [2..10]=rot scratch [11..13]=vec scratch (in-arena scratch = 20// wasm-ready: sys_mmap does not exist inside the wasm; base-relative organs use ONLY the caller's arena) 21func sk_hdr(base: i64) -> *i64 { return base as *i64 } 22func sk_lr(base: i64) -> *i64 { return (base + 16) as *i64 } 23func sk_vs(base: i64) -> *i64 { return (base + 88) as *i64 } 24func sk_bone(base: i64, i: i64) -> *i64 { return (base + 256 + i * 256) as *i64 } 25func sk_vert(base: i64, i: i64) -> *i64 { return (base + 256 + SK_MAXB * 256 + i * 64) as *i64 } 26func sk_out(base: i64, i: i64) -> *i64 { return (base + 256 + SK_MAXB * 256 + SK_MAXV * 64 + i * 24) as *i64 } 27func sk_bytes() -> i64 { return 256 + SK_MAXB * 256 + SK_MAXV * 64 + SK_MAXV * 24 + 64 } 28 29func sk_init(base: i64) -> i64 { 30 let h: *i64 = sk_hdr(base) 31 h[0] = 0 32 h[1] = 0 33 return 0 34} 35func sk_add_bone(base: i64, parent: i64, ox: i64, oy: i64, oz: i64) -> i64 { 36 let h: *i64 = sk_hdr(base) 37 if h[0] >= SK_MAXB { return 0 - 1 } 38 let b: *i64 = sk_bone(base, h[0]) 39 b[0] = parent 40 b[1] = ox; b[2] = oy; b[3] = oz 41 b[4] = 0; b[5] = 0 42 b[18] = 0 43 h[0] = h[0] + 1 44 return h[0] - 1 45} 46func sk_pose(base: i64, bone: i64, yaw: i64, pitch: i64) -> i64 { 47 let b: *i64 = sk_bone(base, bone) 48 b[4] = yaw 49 b[5] = pitch 50 return 0 51} 52func sk_add_vert(base: i64, b0: i64, w0: i64, b1: i64, w1: i64, lx: i64, ly: i64, lz: i64) -> i64 { 53 let h: *i64 = sk_hdr(base) 54 if h[1] >= SK_MAXV { return 0 - 1 } 55 let v: *i64 = sk_vert(base, h[1]) 56 v[0] = b0; v[1] = w0; v[2] = b1; v[3] = w1 57 v[4] = lx; v[5] = ly; v[6] = lz 58 h[1] = h[1] + 1 59 return h[1] - 1 60} 61 62// local rotation R = Ry(yaw)*Rz(elev), integer fx256 (trig fx4096 -> /16 to fx256). 63// ⚠ELEVATION IS Rz, NOT Rx: bones point along their +x offset, and Rx leaves the x-axis INVARIANT -- a 64// pitch(Rx) bone rotation could never raise a +x bone (caught during IK design). Ry*Rz covers the sphere: 65// +x -> (cy*ce, se, -sy*ce). Yaw-only poses are IDENTICAL to the old basis (Rz(0)=I) -- prior gates unaffected. 66func sk_localrot(yaw: i64, elev: i64, out: *i64) -> i64 { 67 let cy: i64 = it_cos4096(yaw) / 16 68 let sy: i64 = it_sin4096(yaw) / 16 69 let ce: i64 = it_cos4096(elev) / 16 70 let se: i64 = it_sin4096(elev) / 16 71 out[0] = cy * ce / 256 72 out[1] = 0 - cy * se / 256 73 out[2] = sy 74 out[3] = se 75 out[4] = ce 76 out[5] = 0 77 out[6] = 0 - sy * ce / 256 78 out[7] = sy * se / 256 79 out[8] = cy 80 return 0 81} 82func sk_matmul(a: *i64, b: *i64, out: *i64) -> i64 { 83 var r: i64 = 0 84 while r < 3 { 85 var c: i64 = 0 86 while c < 3 { 87 out[r * 3 + c] = (a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c]) / 256 88 c = c + 1 89 } 90 r = r + 1 91 } 92 return 0 93} 94func sk_matvec(m: *i64, x: i64, y: i64, z: i64, out: *i64) -> i64 { 95 out[0] = (m[0] * x + m[1] * y + m[2] * z) / 256 96 out[1] = (m[3] * x + m[4] * y + m[5] * z) / 256 97 out[2] = (m[6] * x + m[7] * y + m[8] * z) / 256 98 return 0 99} 100 101// compose world transforms parent-first (bones MUST be added parent-before-child; enforced by the add order) 102func sk_update(base: i64) -> i64 { 103 let h: *i64 = sk_hdr(base) 104 let scratch: *i64 = sk_vs(base) 105 let lr: *i64 = sk_lr(base) 106 var i: i64 = 0 107 while i < h[0] { 108 let b: *i64 = sk_bone(base, i) 109 sk_localrot(b[4], b[5], lr) 110 if b[0] < 0 { 111 var k: i64 = 0 112 while k < 9 { b[6 + k] = lr[k]; k = k + 1 } 113 b[15] = b[1]; b[16] = b[2]; b[17] = b[3] 114 } else { 115 let p: *i64 = sk_bone(base, b[0]) 116 sk_matmul((p as i64 + 48) as *i64, lr, (b as i64 + 48) as *i64) 117 sk_matvec((p as i64 + 48) as *i64, b[1], b[2], b[3], scratch) 118 b[15] = p[15] + scratch[0] 119 b[16] = p[16] + scratch[1] 120 b[17] = p[17] + scratch[2] 121 } 122 b[18] = 1 123 i = i + 1 124 } 125 return 0 126} 127 128// LBS: out[i] = sum_b w_b * (worldR_b * vlocal + worldpos_b) 129func sk_skin(base: i64) -> i64 { 130 let h: *i64 = sk_hdr(base) 131 let tmp: *i64 = sk_vs(base) 132 var i: i64 = 0 133 while i < h[1] { 134 let v: *i64 = sk_vert(base, i) 135 let o: *i64 = sk_out(base, i) 136 o[0] = 0; o[1] = 0; o[2] = 0 137 var k: i64 = 0 138 while k < 2 { 139 var bi: i64 = v[0] 140 var w: i64 = v[1] 141 if k == 1 { bi = v[2]; w = v[3] } 142 if w > 0 { 143 let b: *i64 = sk_bone(base, bi) 144 sk_matvec((b as i64 + 48) as *i64, v[4], v[5], v[6], tmp) 145 o[0] = o[0] + w * (tmp[0] + b[15]) / 256 146 o[1] = o[1] + w * (tmp[1] + b[16]) / 256 147 o[2] = o[2] + w * (tmp[2] + b[17]) / 256 148 } 149 k = k + 1 150 } 151 i = i + 1 152 } 153 return 0 154}