nx_figure_render.nx source
↩ module page · 240 lines · 10501 B
1// nx_figure_render.nx -- the ELARA RIG (17-joint humanoid skeleton, bones-as-data) + a fast software renderer
2// that draws the POSED skeleton as a shaded humanoid (tapered capsules between joints + a head), with a per-pixel
3// Z-BUFFER so limbs occlude correctly regardless of draw order. This is the MOTION-v0 render surface (swap+motion
4// census SCOPE B): forward-kinematics from nx_skeleton give correct joint motion for ANY authored move; the
5// figure is a mannequin preview -- skinning it with the photoreal SDF body / a rigged mesh is the deep rung (S5/S6).
6// ALL INTEGER (deterministic, VM-vettable). Rig consts are shared by nx_movelib (which poses the bones) and this
7// renderer (which draws them). Reuses nx_skeleton FK + nx_itrig. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_skeleton.nx"
10import "nx_itrig.nx"
11const RIG_MAGIC_65536: i64 = 65536
12const RIG_MAGIC_4096: i64 = 4096
13const RIG_MAGIC_2000000000: i64 = 2000000000
14const RIG_MAGIC_6000: i64 = 6000
15const RIG_MAGIC_1024: i64 = 1024
16
17func fr_dbg(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
18
19// ---- the rig: 17 joints, parent-first (a joint's parent index is always < its own) --------------------
20const RIG_NB: i64 = 17
21const RG_PELVIS: i64 = 0
22const RG_SPINE: i64 = 1
23const RG_CHEST: i64 = 2
24const RG_NECK: i64 = 3
25const RG_HEAD: i64 = 4
26const RG_SHL: i64 = 5
27const RG_ELL: i64 = 6
28const RG_WRL: i64 = 7
29const RG_SHR: i64 = 8
30const RG_ELR: i64 = 9
31const RG_WRR: i64 = 10
32const RG_HIPL: i64 = 11
33const RG_KNL: i64 = 12
34const RG_ANL: i64 = 13
35const RG_HIPR: i64 = 14
36const RG_KNR: i64 = 15
37const RG_ANR: i64 = 16
38
39// build the rest rig into a skeleton arena (offsets are child_rest - parent_rest, in body world units;
40// arms carry a small +z so they render in FRONT of the torso in a 3/4 view). parent-before-child order.
41func rig_build(sk: i64) -> i64 {
42 sk_init(sk)
43 sk_add_bone(sk, 0 - 1, 0, 80, 0) // 0 PELVIS root
44 sk_add_bone(sk, RG_PELVIS, 0, 420, 0) // 1 SPINE
45 sk_add_bone(sk, RG_SPINE, 0, 280, 0) // 2 CHEST
46 sk_add_bone(sk, RG_CHEST, 0, 230, 0) // 3 NECK
47 sk_add_bone(sk, RG_NECK, 0, 240, 0) // 4 HEAD
48 sk_add_bone(sk, RG_CHEST, 0-330, 20, 80) // 5 SHOULDER_L
49 sk_add_bone(sk, RG_SHL, 0-230, 0-330, 0) // 6 ELBOW_L
50 sk_add_bone(sk, RG_ELL, 0-130, 0-320, 0) // 7 WRIST_L
51 sk_add_bone(sk, RG_CHEST, 330, 20, 80) // 8 SHOULDER_R
52 sk_add_bone(sk, RG_SHR, 230, 0-330, 0) // 9 ELBOW_R
53 sk_add_bone(sk, RG_ELR, 130, 0-320, 0) // 10 WRIST_R
54 sk_add_bone(sk, RG_PELVIS, 0-195, 0-40, 0) // 11 HIP_L
55 sk_add_bone(sk, RG_HIPL, 0, 0-560, 0) // 12 KNEE_L
56 sk_add_bone(sk, RG_KNL, 0, 0-600, 0) // 13 ANKLE_L
57 sk_add_bone(sk, RG_PELVIS, 195, 0-40, 0) // 14 HIP_R
58 sk_add_bone(sk, RG_HIPR, 0, 0-560, 0) // 15 KNEE_R
59 sk_add_bone(sk, RG_KNR, 0, 0-600, 0) // 16 ANKLE_R
60 return 0
61}
62
63// per-bone draw radii (screen-space before scale): r at the PARENT end, r at THIS end. Bone i drawn parent->i.
64func rig_r0(i: i64) -> i64 {
65 if i == RG_SPINE { return 205 }
66 if i == RG_CHEST { return 175 }
67 if i == RG_NECK { return 120 }
68 if i == RG_HEAD { return 70 }
69 if i == RG_SHL { return 100 }
70 if i == RG_SHR { return 100 }
71 if i == RG_ELL { return 72 }
72 if i == RG_ELR { return 72 }
73 if i == RG_WRL { return 55 }
74 if i == RG_WRR { return 55 }
75 if i == RG_HIPL { return 150 }
76 if i == RG_HIPR { return 150 }
77 if i == RG_KNL { return 118 }
78 if i == RG_KNR { return 118 }
79 if i == RG_ANL { return 92 }
80 if i == RG_ANR { return 92 }
81 return 80
82}
83func rig_r1(i: i64) -> i64 {
84 if i == RG_SPINE { return 175 }
85 if i == RG_CHEST { return 192 }
86 if i == RG_NECK { return 72 }
87 if i == RG_HEAD { return 60 }
88 if i == RG_SHL { return 72 }
89 if i == RG_SHR { return 72 }
90 if i == RG_ELL { return 55 }
91 if i == RG_ELR { return 55 }
92 if i == RG_WRL { return 42 }
93 if i == RG_WRR { return 42 }
94 if i == RG_HIPL { return 120 }
95 if i == RG_HIPR { return 120 }
96 if i == RG_KNL { return 95 }
97 if i == RG_KNR { return 95 }
98 if i == RG_ANL { return 70 }
99 if i == RG_ANR { return 70 }
100 return 70
101}
102
103func fr_abs(a: i64) -> i64 { if a < 0 { return 0 - a } return a }
104func fr_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v < lo { return lo } if v > hi { return hi } return v }
105
106// skin base color
107const SK_R: i64 = 236
108const SK_G: i64 = 182
109const SK_B: i64 = 158
110
111// stamp one shaded disc (a sphere-ish blob) into fb with z-test. c[0..3] = {cx,cy,cr,cd} (packed to keep the
112// arg count <=5 -- NishiLang mis-passes calls with >~6 args, which segfaulted the 8-arg form).
113func fr_disc(fb: *i64, zb: *i64, w: i64, h: i64, c: *i64) -> i64 {
114 let cx: i64 = c[0]; let cy: i64 = c[1]; let cr: i64 = c[2]; let cd: i64 = c[3]
115 if cr < 1 { return 0 }
116 let rr: i64 = cr * cr
117 var py: i64 = cy - cr
118 let pymax: i64 = cy + cr
119 while py <= pymax {
120 if py >= 0 { if py < h {
121 let dy: i64 = py - cy
122 var px: i64 = cx - cr
123 let pxmax: i64 = cx + cr
124 while px <= pxmax {
125 if px >= 0 { if px < w {
126 let dx: i64 = px - cx
127 let dd: i64 = dx * dx + dy * dy
128 if dd <= rr {
129 let ff: i64 = (rr - dd) * 256 / rr // 256 center .. 0 edge (round falloff)
130 let dir: i64 = ((0 - dx) * 70 + (0 - dy) * 95) / cr // light upper-left
131 var lit: i64 = 66 + ff * 128 / 256 + 78 + dir
132 lit = fr_clamp(lit, 26, 255)
133 let depth: i64 = cd * 16 + ff / 8 // cd dominates -> adjacent discs blend (less beading)
134 let idx: i64 = py * w + px
135 if depth > zb[idx] {
136 zb[idx] = depth
137 var r: i64 = SK_R * lit / 255
138 var g: i64 = SK_G * lit / 255
139 var b: i64 = SK_B * lit / 255
140 if r > 255 { r = 255 }
141 if g > 255 { g = 255 }
142 if b > 255 { b = 255 }
143 fb[idx] = r + g * 256 + b * RIG_MAGIC_65536
144 }
145 }
146 } }
147 px = px + 1
148 }
149 } }
150 py = py + 1
151 }
152 return 0
153}
154
155// draw a tapered capsule between two projected endpoints by stamping overlapping discs along the segment.
156// endpoints packed in e[0..7] = {sx0,sy0,d0,r0, sx1,sy1,d1,r1} -> keeps the arg count <=8 (NishiLang ABI:
157// functions with >8 args mis-pass on the stack -> segfault; the 12-arg form crashed here).
158func fr_capsule(fb: *i64, zb: *i64, w: i64, h: i64, e: *i64) -> i64 {
159 let sx0: i64 = e[0]; let sy0: i64 = e[1]; let d0: i64 = e[2]; let r0: i64 = e[3]
160 let sx1: i64 = e[4]; let sy1: i64 = e[5]; let d1: i64 = e[6]; let r1: i64 = e[7]
161 let dxl: i64 = sx1 - sx0
162 let dyl: i64 = sy1 - sy0
163 var l: i64 = fr_abs(dxl)
164 if fr_abs(dyl) > l { l = fr_abs(dyl) }
165 var n: i64 = l / 2 + 1 // tight disc spacing -> overlapping discs read as a smooth limb
166 if n > 900 { n = 900 }
167 let c: *i64 = sys_mmap(4 * 8) as *i64
168 var s: i64 = 0
169 while s <= n {
170 let t: i64 = s * RIG_MAGIC_4096 / n
171 c[0] = sx0 + dxl * t / RIG_MAGIC_4096
172 c[1] = sy0 + dyl * t / RIG_MAGIC_4096
173 c[2] = r0 + (r1 - r0) * t / RIG_MAGIC_4096
174 c[3] = d0 + (d1 - d0) * t / RIG_MAGIC_4096
175 fr_disc(fb, zb, w, h, c)
176 s = s + 1
177 }
178 return 0
179}
180
181// project + draw the whole posed skeleton. Call after sk_update(sk). Clears fb (bg gradient) + zbuf first.
182// camyaw = orbit angle (rad*4096), scale = body-units->px numerator (screen px = worldunit*scale/1024).
183// zb = caller-allocated w*h i64 depth buffer (passed, not global -- keeps the renderer reentrant).
184func rig_draw(sk: i64, fb: *i64, zb: *i64, w: i64, h: i64, camyaw: i64, scale: i64) -> i64 {
185 let cy4: i64 = it_cos4096(camyaw)
186 let sy4: i64 = it_sin4096(camyaw)
187 let midy: i64 = h * 52 / 100
188 let hw: i64 = w / 2
189 // clear
190 var i: i64 = 0
191 let npx: i64 = w * h
192 while i < npx {
193 let yy: i64 = i / w
194 fb[i] = (20 + yy * 26 / h) + (24 + yy * 24 / h) * 256 + (38 + yy * 30 / h) * RIG_MAGIC_65536
195 zb[i] = 0 - RIG_MAGIC_2000000000
196 i = i + 1
197 }
198 // draw each bone parent->child (skip the root, which has no parent segment)
199 let sx: *i64 = sys_mmap(RIG_NB * 8) as *i64
200 let sy: *i64 = sys_mmap(RIG_NB * 8) as *i64
201 let sd: *i64 = sys_mmap(RIG_NB * 8) as *i64
202 var b: i64 = 0
203 while b < RIG_NB {
204 let bp: *i64 = sk_bone(sk, b)
205 // clamp world coords: the deep FK chain in fx256 can hit a worst-case matmul blow-up at some angle
206 // alignments; clamp keeps any such joint at the frame edge instead of streaking off to infinity
207 // (render stays clean). Higher-precision (fx1024) or renormalized FK is the refinement rung.
208 let wx: i64 = fr_clamp(bp[15], 0 - RIG_MAGIC_6000, RIG_MAGIC_6000)
209 let wy: i64 = fr_clamp(bp[16], 0 - RIG_MAGIC_6000, RIG_MAGIC_6000)
210 let wz: i64 = fr_clamp(bp[17], 0 - RIG_MAGIC_6000, RIG_MAGIC_6000)
211 let rx: i64 = (wx * cy4 + wz * sy4) / RIG_MAGIC_4096
212 let rz: i64 = (0 - wx * sy4 + wz * cy4) / RIG_MAGIC_4096
213 sx[b] = hw + rx * scale / RIG_MAGIC_1024
214 sy[b] = midy - wy * scale / RIG_MAGIC_1024
215 sd[b] = rz
216 b = b + 1
217 }
218 let e: *i64 = sys_mmap(8 * 8) as *i64
219 var j: i64 = 1
220 while j < RIG_NB {
221 let bpj: *i64 = sk_bone(sk, j)
222 let par: i64 = bpj[0]
223 let r0v: i64 = rig_r0(j) * scale / RIG_MAGIC_1024
224 let r1v: i64 = rig_r1(j) * scale / RIG_MAGIC_1024
225 let spar: i64 = sx[par]
226 let ypar: i64 = sy[par]
227 let dpar: i64 = sd[par]
228 let sj: i64 = sx[j]
229 let yj: i64 = sy[j]
230 let dj: i64 = sd[j]
231 e[0] = spar; e[1] = ypar; e[2] = dpar; e[3] = r0v
232 e[4] = sj; e[5] = yj; e[6] = dj; e[7] = r1v
233 fr_capsule(fb, zb, w, h, e)
234 j = j + 1
235 }
236 // head blob (bigger sphere at the head joint)
237 e[0] = sx[RG_HEAD]; e[1] = sy[RG_HEAD]; e[2] = 200 * scale / RIG_MAGIC_1024; e[3] = sd[RG_HEAD]
238 fr_disc(fb, zb, w, h, e)
239 return 0
240}