code wiki / (root) / nx_wasm_charlab.nx

nx_wasm_charlab.nx source

↩ module page · 354 lines · 15617 B

1// nx_wasm_charlab.nx -- CHARLAB: the COMPOSED character surface (Elara bar E2+E3+E4+E5 in ONE interactive 2// page, the physlab pattern). A torso skeleton BREATHES (chest elevation sine); TWO soft-body regions with 3// DIFFERENT data profiles ride the chest bone (nx_bodybind); a 2-bone IK ARM chases the MOUSE (nx_ik CCD); 4// the arm's HAND is a live capsule collider in both regions -- push it into the flesh and the contact side 5// DENTS while the far side BULGES (the occlusion law, interactive). ALL logic in-wasm (JS = mouse + blit); 6// ALL INTEGER => the entire surface VM-vets byte-for-byte. Neutral tech demo; the anatomy-detailed content 7// rides the gated membership surfaces. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_itrig.nx" 10import "nx_skeleton.nx" 11import "nx_softbody_region.nx" 12import "nx_bodybind.nx" 13import "nx_ik.nx" 14import "nx_scene.nx" 15import "nx_character.nx" 16const O_MAGIC_2600: i64 = 2600 17const O_MAGIC_1400: i64 = 1400 18const O_MAGIC_1200: i64 = 1200 19const O_MAGIC_2050: i64 = 2050 20const O_MAGIC_99999: i64 = 99999 21const O_MAGIC_65536: i64 = 65536 22 23const W: i64 = 320 24const H: i64 = 240 25const HW: i64 = 160 26const GROUNDY: i64 = 225 27const SCALE: i64 = 18 28const O_FB: i64 = 0 29const O_SK: i64 = 614400 // sk_bytes 188736 30const O_SRL: i64 = 803200 // sr_bytes 6848 31const O_SRR: i64 = 810112 32const O_BB: i64 = 817024 // bb_bytes 1088 33const O_SC: i64 = 818176 // scene arena (sc_bytes ~132k) 34const O_ST: i64 = 950272 // [0]=tick [1]=btn [2]=mx [3]=my [4]=breathe [5]=scene-play [6]=scene-t [7]=preset 35// scene channel target ids 36const CH_ARM_X: i64 = 100 37const CH_ARM_Y: i64 = 101 38const CH_SOFT_R: i64 = 300 39func st_ptr(base: i64) -> *i64 { return (base + O_ST) as *i64 } 40func chr_ptr(base: i64) -> *i64 { return (base + O_ST + 64) as *i64 } // char record in st slots [8..23] 41func mem_bytes() -> i64 { return O_ST + 256 } 42 43// build the built-in demo scene (a "position" as data -- reaches across then presses into the right region) 44func scene_build(base: i64) -> i64 { 45 let s: i64 = base + O_SC 46 sc_init(s) 47 let a: i64 = sc_channel(s, CH_ARM_X) 48 sc_key_add(s, a, 0, O_MAGIC_2600) 49 sc_key_add(s, a, 120, 200) 50 sc_key_add(s, a, 240, 900) 51 sc_key_add(s, a, 360, O_MAGIC_2600) 52 let b: i64 = sc_channel(s, CH_ARM_Y) 53 sc_key_add(s, b, 0, O_MAGIC_1400) 54 sc_key_add(s, b, 120, 1000) 55 sc_key_add(s, b, 240, 700) 56 sc_key_add(s, b, 360, O_MAGIC_1400) 57 let c: i64 = sc_channel(s, CH_SOFT_R) 58 sc_key_add(s, c, 0, 24) 59 sc_key_add(s, c, 120, 12) 60 sc_key_add(s, c, 360, 24) 61 return 0 62} 63 64// bones: 0 pelvis(root) 1 chest(parent 0, +y 900) 2 upperarm(parent 1, offset (250,150,0)) 3 forearm(parent 2, +x 500) 65func world_init(base: i64) -> i64 { 66 sk_init(base + O_SK) 67 sk_add_bone(base + O_SK, 0 - 1, 0, O_MAGIC_1200, 0) 68 sk_add_bone(base + O_SK, 0, 0, 650, 0) // 1 chest (0,O_MAGIC_2050) 69 sk_add_bone(base + O_SK, 1, 0, 680, 0) // 2 head (longer neck -> less top-heavy) 70 sk_add_bone(base + O_SK, 1, 0 - 430, 240, 0) // 3 L-upperarm 71 sk_add_bone(base + O_SK, 3, 0 - 80, 0 - 640, 0) // 4 L-forearm (longer) 72 sk_add_bone(base + O_SK, 1, 430, 240, 0) // 5 R-upperarm (IK base) 73 sk_add_bone(base + O_SK, 5, 560, 0, 0) // 6 R-forearm (IK tip; effector local 560,0,0) 74 sk_add_bone(base + O_SK, 0, 0 - 230, 0 - 140, 0) // 7 L-thigh 75 sk_add_bone(base + O_SK, 7, 0 - 20, 0 - 820, 0) // 8 L-shin (longer legs) 76 sk_add_bone(base + O_SK, 0, 230, 0 - 140, 0) // 9 R-thigh 77 sk_add_bone(base + O_SK, 9, 20, 0 - 820, 0) // 10 R-shin 78 // load the active character preset (a CHARACTER as DATA sets the two breast profiles, skin, proportions) 79 let rec: *i64 = chr_ptr(base) 80 chr_preset(rec, st_ptr(base)[7]) 81 // two chest soft regions sized/profiled by the character record 82 sr_init(base + O_SRL, 0, 0, 0) 83 sr_ring(base + O_SRL, 16, rec[1]) 84 sr_param(base + O_SRL, 6, rec[2]); sr_param(base + O_SRL, 7, rec[2] + 8); sr_param(base + O_SRL, 9, rec[3]) 85 sr_init(base + O_SRR, 0, 0, 0) 86 sr_ring(base + O_SRR, 16, rec[4]) 87 sr_param(base + O_SRR, 6, rec[5]); sr_param(base + O_SRR, 7, rec[5] + 8); sr_param(base + O_SRR, 9, rec[6]) 88 sr_add_capsule(base + O_SRL, O_MAGIC_99999, 0, 0, O_MAGIC_99999, 100, 0, 170) 89 sr_add_capsule(base + O_SRR, O_MAGIC_99999, 0, 0, O_MAGIC_99999, 100, 0, 170) 90 bb_init(base + O_BB) 91 bb_add(base + O_BB, 1, 0 - 230, 150, 0, O_SRL) // breasts on the chest, front 92 bb_add(base + O_BB, 1, 230, 150, 0, O_SRR) 93 scene_build(base) 94 let st: *i64 = st_ptr(base) 95 st[0] = 0 96 st[1] = 0 97 st[2] = 200 98 st[3] = 80 99 st[4] = 1 100 st[5] = 0 101 st[6] = 0 102 return 0 103} 104func start_at(base: i64) -> i64 { let st: *i64 = st_ptr(base); st[7] = 0; return world_init(base) } 105 106func tick_at(base: i64, mx: i64, my: i64, buttons: i64) -> i64 { 107 let st: *i64 = st_ptr(base) 108 if (buttons & 2) != 0 { world_init(base); return 0 } 109 if (buttons & 1) != 0 { if (st[1] & 1) == 0 { st[4] = 1 - st[4] } } // click toggles breathing 110 if (buttons & 4) != 0 { if (st[1] & 4) == 0 { st[5] = 1 - st[5]; st[6] = 0 } } // key P: play/stop scene 111 if (buttons & 8) != 0 { if (st[1] & 8) == 0 { st[7] = (st[7] + 1) % 3; world_init(base) } } // key C: cycle character 112 st[1] = buttons 113 st[2] = mx 114 st[3] = my 115 // breathing: chest elevation micro-sine (drives the bound regions through bb) 116 if st[4] == 1 { sk_pose(base + O_SK, 1, 0, it_sin4096(st[0] * chr_ptr(base)[8]) / 24 ) } 117 // R-arm target: the SCENE drives it when playing (a "position" as DATA); else the mouse 118 var wx: i64 = (mx - HW) * SCALE 119 var wy: i64 = (GROUNDY - my) * SCALE 120 if st[5] == 1 { 121 wx = sc_sample(base + O_SC, 0, st[6]) 122 wy = sc_sample(base + O_SC, 1, st[6]) 123 sr_param(base + O_SRR, 7, sc_sample(base + O_SC, 2, st[6])) // scene also drives region softness 124 st[6] = st[6] + 2 125 if st[6] > sc_duration(base + O_SC) { st[6] = 0 } // loop the scene 126 } 127 ik_solve(base + O_SK, 6, 5, 500, 0, 0, wx, wy, 0, 6, 500) // R forearm(6) solves, rootStop=shoulder(5) 128 sk_update(base + O_SK) 129 bb_drive(base + O_BB, base + O_SK, base) 130 // hand capsule = the R-forearm effector position, in both regions 131 let eff: *i64 = sk_vs(base + O_SK) 132 ik_effector(base + O_SK, 6, 500, 0, 0, eff) 133 var r: i64 = 0 134 while r < 2 { 135 var reg: i64 = base + O_SRL 136 if r == 1 { reg = base + O_SRR } 137 let cp: *i64 = sr_cap(reg, 0) 138 cp[0] = eff[0]; cp[1] = eff[1] - 150; cp[2] = eff[2] 139 cp[3] = eff[0]; cp[4] = eff[1] + 150; cp[5] = eff[2] 140 r = r + 1 141 } 142 sr_step(base + O_SRL) 143 sr_step(base + O_SRR) 144 st[0] = st[0] + 1 145 return 0 146} 147 148func px(base: i64, x: i64, y: i64, col: i64) -> i64 { 149 if x < 0 { return 0 } 150 if x >= W { return 0 } 151 if y < 0 { return 0 } 152 if y >= H { return 0 } 153 let fb: *i64 = (base + O_FB) as *i64 154 fb[y * W + x] = col 155 return 0 156} 157func rgbp(r: i64, g: i64, b: i64) -> i64 { return r + g * 256 + b * O_MAGIC_65536 } 158func shade(col: i64, num: i64, den: i64) -> i64 { 159 let r: i64 = (col & 255) * num / den 160 let g: i64 = ((col >> 8) & 255) * num / den 161 let b: i64 = ((col >> 16) & 255) * num / den 162 var rr: i64 = r 163 var gg: i64 = g 164 var bb: i64 = b 165 if rr > 255 { rr = 255 } 166 if gg > 255 { gg = 255 } 167 if bb > 255 { bb = 255 } 168 return rr + gg * 256 + bb * O_MAGIC_65536 169} 170func sxof(wx: i64) -> i64 { return HW + wx / SCALE } 171func syof(wy: i64) -> i64 { return GROUNDY - wy / SCALE } 172func chl_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 } 173func chl_min3(a: i64, b: i64, c: i64) -> i64 { var m: i64 = a; if b < m { m = b } if c < m { m = c } return m } 174func chl_max3(a: i64, b: i64, c: i64) -> i64 { var m: i64 = a; if b > m { m = b } if c > m { m = c } return m } 175 176// filled triangle (screen coords) via bounding box + edge-sign test; VERTICAL shade: top lighter 177func fill_tri(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, x2: i64, y2: i64, col: i64) -> i64 { 178 var minx: i64 = chl_min3(x0, x1, x2) 179 var maxx: i64 = chl_max3(x0, x1, x2) 180 var miny: i64 = chl_min3(y0, y1, y2) 181 var maxy: i64 = chl_max3(y0, y1, y2) 182 if minx < 0 { minx = 0 } 183 if miny < 0 { miny = 0 } 184 if maxx >= W { maxx = W - 1 } 185 if maxy >= H { maxy = H - 1 } 186 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 187 if area == 0 { return 0 } 188 var y: i64 = miny 189 while y <= maxy { 190 var x: i64 = minx 191 while x <= maxx { 192 let w0: i64 = (x1 - x) * (y2 - y) - (x2 - x) * (y1 - y) 193 let w1: i64 = (x2 - x) * (y0 - y) - (x0 - x) * (y2 - y) 194 let w2: i64 = (x0 - x) * (y1 - y) - (x1 - x) * (y0 - y) 195 var inside: i64 = 0 196 if area > 0 { if w0 >= 0 { if w1 >= 0 { if w2 >= 0 { inside = 1 } } } } 197 if area < 0 { if w0 <= 0 { if w1 <= 0 { if w2 <= 0 { inside = 1 } } } } 198 if inside == 1 { px(base, x, y, col) } 199 x = x + 1 200 } 201 y = y + 1 202 } 203 return 0 204} 205func fill_disk(base: i64, cx: i64, cy: i64, r: i64, col: i64) -> i64 { 206 var dy: i64 = 0 - r 207 while dy <= r { 208 var dx: i64 = 0 - r 209 while dx <= r { 210 if dx * dx + dy * dy <= r * r { 211 // spherical shade: brighter up-left 212 let sh: i64 = 100 - (dx + dy) * 30 / (r + 1) 213 px(base, cx + dx, cy + dy, shade(col, sh, 100)) 214 } 215 dx = dx + 1 216 } 217 dy = dy + 1 218 } 219 return 0 220} 221// a FILLED, ROUND-ENDED, TAPERED limb between two screen points (a body segment) 222func limb(base: i64, x0: i64, y0: i64, w0: i64, x1: i64, y1: i64, w1: i64, col: i64) -> i64 { 223 let dx: i64 = x1 - x0 224 let dy: i64 = y1 - y0 225 let len: i64 = chl_isqrt(dx * dx + dy * dy) 226 if len == 0 { fill_disk(base, x0, y0, w0, col); return 0 } 227 let px0: i64 = 0 - dy * 256 / len // unit perpendicular * 256 228 let py0: i64 = dx * 256 / len 229 let ax: i64 = x0 + px0 * w0 / 256 230 let ay: i64 = y0 + py0 * w0 / 256 231 let bx: i64 = x0 - px0 * w0 / 256 232 let by: i64 = y0 - py0 * w0 / 256 233 let cx: i64 = x1 + px0 * w1 / 256 234 let cy: i64 = y1 + py0 * w1 / 256 235 let dxx: i64 = x1 - px0 * w1 / 256 236 let dyy: i64 = y1 - py0 * w1 / 256 237 fill_tri(base, ax, ay, bx, by, cx, cy, col) 238 fill_tri(base, bx, by, cx, cy, dxx, dyy, col) 239 fill_disk(base, x0, y0, w0, col) // round joints 240 fill_disk(base, x1, y1, w1, col) 241 return 0 242} 243// a soft-body region filled as a triangle FAN from its anchor to the ring points -> the DENT shows as a real 244// concavity in the filled flesh (star-convex from the center). Shaded + highlight + nipple. 245func fill_region(base: i64, reg: i64, col: i64) -> i64 { 246 let h: *i64 = sr_hdr(reg) 247 let cx: i64 = sxof(h[2]) 248 let cy: i64 = syof(h[3]) 249 var i: i64 = 0 250 while i < 16 { 251 let ax: i64 = sxof(sr_pos(reg, i, 0)) 252 let ay: i64 = syof(sr_pos(reg, i, 1)) 253 let j: i64 = (i + 1) % 16 254 let bx: i64 = sxof(sr_pos(reg, j, 0)) 255 let by: i64 = syof(sr_pos(reg, j, 1)) 256 // per-wedge vertical shade: upper wedges lighter (volume) 257 var sh: i64 = 108 - (ay - cy) * 3 258 if sh > 125 { sh = 125 } 259 if sh < 78 { sh = 78 } 260 fill_tri(base, cx, cy, ax, ay, bx, by, shade(col, sh, 100)) 261 i = i + 1 262 } 263 // highlight up-left + nipple 264 fill_disk(base, cx - 3, cy - 3, 4, shade(col, 122, 100)) 265 fill_disk(base, cx, cy, 2, shade(col, 70, 100)) 266 return 0 267} 268 269func render_at(base: i64) -> i64 { 270 // background gradient + floor 271 var y: i64 = 0 272 while y < H { 273 var col: i64 = rgbp(30 + y / 5, 30 + y / 6, 44 + y / 7) 274 if y >= GROUNDY { col = rgbp(40, 36, 32) } 275 var x: i64 = 0 276 let fb: *i64 = (base + O_FB) as *i64 277 while x < W { fb[y * W + x] = col; x = x + 1 } 278 y = y + 1 279 } 280 let rec: *i64 = chr_ptr(base) 281 let skin: i64 = rgbp(rec[10], rec[11], rec[12]) 282 let sk: i64 = base + O_SK 283 let b0: *i64 = sk_bone(sk, 0) // pelvis 284 let b1: *i64 = sk_bone(sk, 1) // chest 285 let b2: *i64 = sk_bone(sk, 2) // head 286 let b3: *i64 = sk_bone(sk, 3) // L-upperarm 287 let b4: *i64 = sk_bone(sk, 4) // L-forearm 288 let b5: *i64 = sk_bone(sk, 5) // R-upperarm 289 let b6: *i64 = sk_bone(sk, 6) // R-forearm 290 let b7: *i64 = sk_bone(sk, 7) 291 let b8: *i64 = sk_bone(sk, 8) 292 let b9: *i64 = sk_bone(sk, 9) 293 let b10: *i64 = sk_bone(sk, 10) 294 let eff: *i64 = sk_vs(sk) 295 ik_effector(sk, 6, 500, 0, 0, eff) 296 // ground contact shadow 297 fill_disk(base, HW, GROUNDY - 2, 40, rgbp(20, 18, 16)) 298 let dk: i64 = shade(skin, 82, 100) // limbs behind = a touch darker for depth 299 // legs (behind) 300 limb(base, sxof(b0[15]), syof(b0[16]), 12, sxof(b7[15]), syof(b7[16]), 11, dk) 301 limb(base, sxof(b7[15]), syof(b7[16]), 11, sxof(b8[15]), syof(b8[16]), 7, dk) 302 limb(base, sxof(b0[15]), syof(b0[16]), 12, sxof(b9[15]), syof(b9[16]), 11, dk) 303 limb(base, sxof(b9[15]), syof(b9[16]), 11, sxof(b10[15]), syof(b10[16]), 7, dk) 304 fill_disk(base, sxof(b8[15]), syof(b8[16]) + 2, 6, dk) // feet 305 fill_disk(base, sxof(b10[15]), syof(b10[16]) + 2, 6, dk) 306 // L arm (behind the torso) 307 limb(base, sxof(b3[15]), syof(b3[16]), 9, sxof(b4[15]), syof(b4[16]), 6, dk) 308 fill_disk(base, sxof(b4[15]), syof(b4[16]), 6, dk) 309 // torso (pelvis -> chest, tapered waist) + hips + neck 310 limb(base, sxof(b0[15]), syof(b0[16]), 13, sxof(b1[15]), syof(b1[16]), 19, skin) 311 fill_disk(base, sxof(b0[15]), syof(b0[16]) + 1, 15, skin) // hips 312 limb(base, sxof(b1[15]), syof(b1[16]), 6, sxof(b2[15]), syof(b2[16]) + 8, 5, skin) // neck 313 // R arm (the IK arm, in front) 314 limb(base, sxof(b5[15]), syof(b5[16]), 9, sxof(b6[15]), syof(b6[16]), 6, skin) 315 limb(base, sxof(b6[15]), syof(b6[16]), 6, sxof(eff[0]), syof(eff[1]), 5, skin) 316 // head: shaded sphere + hair cap + eyes (smaller -> natural proportion) 317 let hx: i64 = sxof(b2[15]) 318 let hy: i64 = syof(b2[16]) 319 fill_disk(base, hx, hy, 11, skin) 320 var hairdy: i64 = 0 - 11 321 while hairdy <= 0 { 322 var hairdx: i64 = 0 - 11 323 while hairdx <= 11 { 324 if hairdx * hairdx + hairdy * hairdy <= 121 { if hairdy < 0 - 1 { px(base, hx + hairdx, hy + hairdy, rgbp(58, 42, 38)) } } 325 hairdx = hairdx + 1 326 } 327 hairdy = hairdy + 1 328 } 329 px(base, hx - 4, hy + 1, rgbp(30, 26, 30)); px(base, hx - 3, hy + 1, rgbp(30, 26, 30)) 330 px(base, hx + 3, hy + 1, rgbp(30, 26, 30)); px(base, hx + 4, hy + 1, rgbp(30, 26, 30)) 331 // breasts (soft regions, front) -- deform under the hand 332 fill_region(base, base + O_SRL, skin) 333 fill_region(base, base + O_SRR, skin) 334 // the interacting hand 335 fill_disk(base, sxof(eff[0]), syof(eff[1]), 8, shade(skin, 108, 100)) 336 return 0 337} 338 339// HUD diagnostics 340func region_area(base: i64, which: i64) -> i64 { 341 if which == 0 { return sr_area(base + O_SRL) } 342 return sr_area(base + O_SRR) 343} 344 345// ---- base-0 wasm exports ---- 346func start() -> i64 { return start_at(0) } 347func tick(mx: i64, my: i64, buttons: i64) -> i64 { return tick_at(0, mx, my, buttons) } 348func render() -> i64 { return render_at(0) } 349func area_l() -> i64 { return region_area(0, 0) } 350func area_r() -> i64 { return region_area(0, 1) } 351func fb_off() -> i64 { return O_FB } 352func ww() -> i64 { return W } 353func hh() -> i64 { return H } 354func main() -> i64 { start(); return 0 }