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}