code wiki / (root) / nx_autorig.nx

nx_autorig.nx source

↩ module page · 153 lines · 6343 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" 11import "nx_vecmath.nx" 12 13const AR_MAXP: i64 = 32 14 15func ar_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 16 17func ar_np(base: i64) -> i64 { 18 let npp: *i64 = (base + O_NPART) as *i64 19 var np: i64 = npp[0] 20 if np <= 0 { np = NPART } 21 if np > AR_MAXP { np = AR_MAXP } 22 return np 23} 24// support radius of part i along direction (dx,dy,dz) whose length is dist (ellipsoid support function) 25func ar_support(base: i64, i: i64, dx: i64, dy: i64, dz: i64, dist: i64) -> i64 { 26 let p: *i64 = (base + O_PARTS) as *i64 27 if dist < 1 { return p[i * 6 + 3] } 28 let ax: i64 = p[i * 6 + 3] * dx 29 let ay: i64 = p[i * 6 + 4] * dy 30 let az: i64 = p[i * 6 + 5] * dz 31 return ar_isqrt(ax * ax + ay * ay + az * az) / dist 32} 33// SURFACE GAP between two parts along their center line: dist - support_i - support_j. 34// Negative = overlapping (deeply fused); the SMALLEST gap is the anatomically-correct attachment. 35func ar_gap(base: i64, i: i64, j: i64) -> i64 { 36 let p: *i64 = (base + O_PARTS) as *i64 37 let dx: i64 = p[j * 6] - p[i * 6] 38 let dy: i64 = p[j * 6 + 1] - p[i * 6 + 1] 39 let dz: i64 = p[j * 6 + 2] - p[i * 6 + 2] 40 let dist: i64 = ar_isqrt(dx * dx + dy * dy + dz * dz) 41 let si: i64 = ar_support(base, i, dx, dy, dz, dist) 42 let sj: i64 = ar_support(base, j, dx, dy, dz, dist) 43 return dist - si - sj 44} 45func ar_vol(base: i64, i: i64) -> i64 { 46 let p: *i64 = (base + O_PARTS) as *i64 47 return p[i * 6 + 3] * p[i * 6 + 4] * p[i * 6 + 5] 48} 49 50// derive the rig. bones = (parent,child) pairs; masses/soft per part. 51// out[0]=nparts out[1]=nbones out[2]=root out[3]=forced_joins (0 expected -- honest counter) 52func ar_build(base: i64, bones: *i64, masses: *i64, soft: *i64, out: *i64) -> i64 { 53 let np: i64 = ar_np(base) 54 let kp: *i64 = (base + O_KBLEND) as *i64 55 var slack: i64 = kp[0] 56 if slack <= 0 { slack = 130 } 57 slack = slack + 40 58 let p: *i64 = (base + O_PARTS) as *i64 59 var root: i64 = 0 60 var bestv: i64 = 0 - 1 61 var i: i64 = 0 62 while i < np { 63 let v: i64 = ar_vol(base, i) 64 masses[i] = v / 1000 65 if masses[i] < 1 { masses[i] = 1 } 66 soft[i] = (p[i * 6 + 3] + p[i * 6 + 4] + p[i * 6 + 5]) / 3 67 if v > bestv { bestv = v; root = i } 68 i = i + 1 69 } 70 // MINIMUM-SPANNING-TREE by SURFACE GAP (Prim): each step attaches the unattached part with the SMALLEST 71 // gap to any attached part -> chains follow anatomy (a forearm's min gap is the upper arm, NOT a fat 72 // root's long support reach -- the fix for BFS star-bias). Deterministic (first-found tie-break). 73 let seen: *i64 = sys_mmap(AR_MAXP * 8) as *i64 74 i = 0 75 while i < np { seen[i] = 0; i = i + 1 } 76 seen[root] = 1 77 var nb: i64 = 0 78 var forced: i64 = 0 79 var step: i64 = 0 80 while step < np - 1 { 81 var bestgap: i64 = 0 82 var besta: i64 = 0 - 1 83 var bestb: i64 = 0 - 1 84 var a: i64 = 0 85 while a < np { 86 if seen[a] == 1 { 87 var b: i64 = 0 88 while b < np { 89 if seen[b] == 0 { 90 let g: i64 = ar_gap(base, a, b) 91 if besta < 0 { bestgap = g; besta = a; bestb = b } 92 else { if g < bestgap { bestgap = g; besta = a; bestb = b } } 93 } 94 b = b + 1 95 } 96 } 97 a = a + 1 98 } 99 seen[bestb] = 1 100 bones[nb * 2] = besta 101 bones[nb * 2 + 1] = bestb 102 nb = nb + 1 103 if bestgap > slack { forced = forced + 1 } // attached beyond touch range = a disconnect finding 104 step = step + 1 105 } 106 out[0] = np 107 out[1] = nb 108 out[2] = root 109 out[3] = forced 110 return 0 111} 112 113// ---- 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} ---- 114func ar_pack(base: i64, bones: *i64, masses: *i64, soft: *i64, out: *i64, buf: *u8) -> i64 { 115 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 116 buf[5] = 0 as u8; buf[6] = 0 as u8; buf[7] = 0 as u8 117 let np: i64 = out[0] 118 let nb: i64 = out[1] 119 let w: *i64 = (buf as i64 + 8) as *i64 120 let p: *i64 = (base + O_PARTS) as *i64 121 w[0] = np; w[1] = nb; w[2] = out[2] 122 var i: i64 = 0 123 while i < np { 124 let o: i64 = 3 + i * 8 125 w[o] = p[i * 6]; w[o + 1] = p[i * 6 + 1]; w[o + 2] = p[i * 6 + 2] 126 w[o + 3] = p[i * 6 + 3]; w[o + 4] = p[i * 6 + 4]; w[o + 5] = p[i * 6 + 5] 127 w[o + 6] = masses[i]; w[o + 7] = soft[i] 128 i = i + 1 129 } 130 let b0: i64 = 3 + np * 8 131 i = 0 132 while i < nb { w[b0 + i * 2] = bones[i * 2]; w[b0 + i * 2 + 1] = bones[i * 2 + 1]; i = i + 1 } 133 return 8 + (3 + np * 8 + nb * 2) * 8 134} 135// validate a pack: magic + sane counts + root in range (corrupt-reject) 136func ar_unpack_ok(buf: *u8, len: i64) -> i64 { 137 if len < 32 { return 0 } 138 if buf[0] != (78 as u8) { return 0 } 139 if buf[1] != (88 as u8) { return 0 } 140 if buf[2] != (82 as u8) { return 0 } 141 if buf[3] != (71 as u8) { return 0 } 142 if buf[4] != (49 as u8) { return 0 } 143 let w: *i64 = (buf as i64 + 8) as *i64 144 let np: i64 = w[0] 145 let nb: i64 = w[1] 146 if np < 1 { return 0 } 147 if np > AR_MAXP { return 0 } 148 if nb != np - 1 { return 0 } 149 if w[2] < 0 { return 0 } 150 if w[2] >= np { return 0 } 151 if len < 8 + (3 + np * 8 + nb * 2) * 8 { return 0 } 152 return 1 153}