code wiki / (root) / nx_autorig.nx

nx_autorig.nx source

↩ module page · 152 lines · 6418 B

1// nx_autorig.nx -- AUTO-RIG + PHYSICS-PARAM EMIT (generator-path R5, operator: the ecosystem emits rigged, 2// physics-ready objects -- not hand-authored per object). Given ANY generated SDF-part object (t2mesh output, 3// the pioneer body, ...), DERIVE from its geometry: (1) a SKELETON -- parts whose ellipsoids touch (support- 4// distance test along the center line, + the object's smin blend as slack) form a graph; a BFS spanning tree 5// from the largest-volume part (the natural pelvis-style root) = the bones; (2) MASS per part (volume rx*ry*rz); 6// (3) COLLISION proxy per part (the ellipsoid itself); (4) a SOFT param per part (mean radius -- bigger part = 7// softer/jigglier; v0 heuristic, data-driven refinement later). Serializes as an NXRG1 data pack (magic + 8// corrupt-reject) per the pack economy (NXM1/NXCH1/...). Integer, deterministic. license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_sdfrender.nx" 11 12const AR_MAXP: i64 = 32 13 14func ar_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x + 1) / 2; while y < x { x = y; y = (x + v / x) / 2 } return x } 15 16func ar_np(base: i64) -> i64 { 17 let npp: *i64 = (base + O_NPART) as *i64 18 var np: i64 = npp[0] 19 if np <= 0 { np = NPART } 20 if np > AR_MAXP { np = AR_MAXP } 21 return np 22} 23// support radius of part i along direction (dx,dy,dz) whose length is dist (ellipsoid support function) 24func ar_support(base: i64, i: i64, dx: i64, dy: i64, dz: i64, dist: i64) -> i64 { 25 let p: *i64 = (base + O_PARTS) as *i64 26 if dist < 1 { return p[i * 6 + 3] } 27 let ax: i64 = p[i * 6 + 3] * dx 28 let ay: i64 = p[i * 6 + 4] * dy 29 let az: i64 = p[i * 6 + 5] * dz 30 return ar_isqrt(ax * ax + ay * ay + az * az) / dist 31} 32// SURFACE GAP between two parts along their center line: dist - support_i - support_j. 33// Negative = overlapping (deeply fused); the SMALLEST gap is the anatomically-correct attachment. 34func ar_gap(base: i64, i: i64, j: i64) -> i64 { 35 let p: *i64 = (base + O_PARTS) as *i64 36 let dx: i64 = p[j * 6] - p[i * 6] 37 let dy: i64 = p[j * 6 + 1] - p[i * 6 + 1] 38 let dz: i64 = p[j * 6 + 2] - p[i * 6 + 2] 39 let dist: i64 = ar_isqrt(dx * dx + dy * dy + dz * dz) 40 let si: i64 = ar_support(base, i, dx, dy, dz, dist) 41 let sj: i64 = ar_support(base, j, dx, dy, dz, dist) 42 return dist - si - sj 43} 44func ar_vol(base: i64, i: i64) -> i64 { 45 let p: *i64 = (base + O_PARTS) as *i64 46 return p[i * 6 + 3] * p[i * 6 + 4] * p[i * 6 + 5] 47} 48 49// derive the rig. bones = (parent,child) pairs; masses/soft per part. 50// out[0]=nparts out[1]=nbones out[2]=root out[3]=forced_joins (0 expected -- honest counter) 51func ar_build(base: i64, bones: *i64, masses: *i64, soft: *i64, out: *i64) -> i64 { 52 let np: i64 = ar_np(base) 53 let kp: *i64 = (base + O_KBLEND) as *i64 54 var slack: i64 = kp[0] 55 if slack <= 0 { slack = 130 } 56 slack = slack + 40 57 let p: *i64 = (base + O_PARTS) as *i64 58 var root: i64 = 0 59 var bestv: i64 = 0 - 1 60 var i: i64 = 0 61 while i < np { 62 let v: i64 = ar_vol(base, i) 63 masses[i] = v / 1000 64 if masses[i] < 1 { masses[i] = 1 } 65 soft[i] = (p[i * 6 + 3] + p[i * 6 + 4] + p[i * 6 + 5]) / 3 66 if v > bestv { bestv = v; root = i } 67 i = i + 1 68 } 69 // MINIMUM-SPANNING-TREE by SURFACE GAP (Prim): each step attaches the unattached part with the SMALLEST 70 // gap to any attached part -> chains follow anatomy (a forearm's min gap is the upper arm, NOT a fat 71 // root's long support reach -- the fix for BFS star-bias). Deterministic (first-found tie-break). 72 let seen: *i64 = sys_mmap(AR_MAXP * 8) as *i64 73 i = 0 74 while i < np { seen[i] = 0; i = i + 1 } 75 seen[root] = 1 76 var nb: i64 = 0 77 var forced: i64 = 0 78 var step: i64 = 0 79 while step < np - 1 { 80 var bestgap: i64 = 0 81 var besta: i64 = 0 - 1 82 var bestb: i64 = 0 - 1 83 var a: i64 = 0 84 while a < np { 85 if seen[a] == 1 { 86 var b: i64 = 0 87 while b < np { 88 if seen[b] == 0 { 89 let g: i64 = ar_gap(base, a, b) 90 if besta < 0 { bestgap = g; besta = a; bestb = b } 91 else { if g < bestgap { bestgap = g; besta = a; bestb = b } } 92 } 93 b = b + 1 94 } 95 } 96 a = a + 1 97 } 98 seen[bestb] = 1 99 bones[nb * 2] = besta 100 bones[nb * 2 + 1] = bestb 101 nb = nb + 1 102 if bestgap > slack { forced = forced + 1 } // attached beyond touch range = a disconnect finding 103 step = step + 1 104 } 105 out[0] = np 106 out[1] = nb 107 out[2] = root 108 out[3] = forced 109 return 0 110} 111 112// ---- NXRG1 data pack: 8-byte magic 'NXRG1' + i64s {np, nb, root} + per-part {cx,cy,cz,rx,ry,rz,mass,soft} + per-bone {a,b} ---- 113func ar_pack(base: i64, bones: *i64, masses: *i64, soft: *i64, out: *i64, buf: *u8) -> i64 { 114 buf[0] = 78 as u8; buf[1] = 88 as u8; buf[2] = 82 as u8; buf[3] = 71 as u8; buf[4] = 49 as u8 115 buf[5] = 0 as u8; buf[6] = 0 as u8; buf[7] = 0 as u8 116 let np: i64 = out[0] 117 let nb: i64 = out[1] 118 let w: *i64 = (buf as i64 + 8) as *i64 119 let p: *i64 = (base + O_PARTS) as *i64 120 w[0] = np; w[1] = nb; w[2] = out[2] 121 var i: i64 = 0 122 while i < np { 123 let o: i64 = 3 + i * 8 124 w[o] = p[i * 6]; w[o + 1] = p[i * 6 + 1]; w[o + 2] = p[i * 6 + 2] 125 w[o + 3] = p[i * 6 + 3]; w[o + 4] = p[i * 6 + 4]; w[o + 5] = p[i * 6 + 5] 126 w[o + 6] = masses[i]; w[o + 7] = soft[i] 127 i = i + 1 128 } 129 let b0: i64 = 3 + np * 8 130 i = 0 131 while i < nb { w[b0 + i * 2] = bones[i * 2]; w[b0 + i * 2 + 1] = bones[i * 2 + 1]; i = i + 1 } 132 return 8 + (3 + np * 8 + nb * 2) * 8 133} 134// validate a pack: magic + sane counts + root in range (corrupt-reject) 135func ar_unpack_ok(buf: *u8, len: i64) -> i64 { 136 if len < 32 { return 0 } 137 if buf[0] != (78 as u8) { return 0 } 138 if buf[1] != (88 as u8) { return 0 } 139 if buf[2] != (82 as u8) { return 0 } 140 if buf[3] != (71 as u8) { return 0 } 141 if buf[4] != (49 as u8) { return 0 } 142 let w: *i64 = (buf as i64 + 8) as *i64 143 let np: i64 = w[0] 144 let nb: i64 = w[1] 145 if np < 1 { return 0 } 146 if np > AR_MAXP { return 0 } 147 if nb != np - 1 { return 0 } 148 if w[2] < 0 { return 0 } 149 if w[2] >= np { return 0 } 150 if len < 8 + (3 + np * 8 + nb * 2) * 8 { return 0 } 151 return 1 152}