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}