code wiki / _hdl_build / nx_cube3d_spin_gate.nx
nx_cube3d_spin_gate.nx source
↩ module page · 162 lines · 7499 B
1// nx_cube3d_spin_gate.nx -- ANIMATED 3D: the cube SPINS. Adds an integer sin/cos primitive (Bhaskara Q10) so
2// the rotation is a function of TIME, then renders 6 successive Y-rotation angles as a FILMSTRIP (6 cubes side
3// by side) -- the "video" axis for 3D scenes, all depth-sorted. Proves motion + reusable trig for every later
4// animation. Composes nx_depth_tri. license_tier: ORIGINAL expect_exit: 0
5import "nx_syscalls.nx"
6import "nx_image.nx"
7import "nx_depth_tri.nx"
8import "nx_png_write.nx"
9
10func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
11func pn(v: i64) -> i64 { let t: *u8=sys_mmap(24); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let b: *u8=sys_mmap(24); var j: i64=0; while j<k{b[j]=t[k-1-j];j=j+1} sys_write(1,b,k); return 0 }
12
13// integer sine, Q10 (scaled by 1024). Bhaskara I approximation: sin(d) ~= 4d(180-d)/(40500 - d(180-d)) on [0,180].
14func isin(deg: i64) -> i64 {
15 var d: i64 = deg % 360
16 if d < 0 { d = d + 360 }
17 var sign: i64 = 1
18 if d >= 180 { sign = 0 - 1; d = d - 180 }
19 let hh: i64 = d * (180 - d)
20 return sign * (4 * hh * 1024 / (40500 - hh))
21}
22func icos(deg: i64) -> i64 { return isin(deg + 90) }
23
24// rotate (Y by ay, X by ax) + perspective project, centered at (cx,cy). out[0]=sx out[1]=sy out[2]=depth
25func projA(vx: i64, vy: i64, vz: i64, ay: i64, ax: i64, cx: i64, cy: i64, out: *i64) -> i64 {
26 let SA: i64 = isin(ay)
27 let CA: i64 = icos(ay)
28 let SB: i64 = isin(ax)
29 let CB: i64 = icos(ax)
30 let x1: i64 = (vx * CA + vz * SA) / 1024
31 let z1: i64 = (0 - (vx * SA) + vz * CA) / 1024
32 let y2: i64 = (vy * CB - z1 * SB) / 1024
33 let z2: i64 = (vy * SB + z1 * CB) / 1024
34 let zc: i64 = z2 + 560
35 out[0] = cx + (x1 * 330) / zc
36 out[1] = cy - (y2 * 330) / zc
37 out[2] = zc
38 return 0
39}
40
41func face(fb: *Image, zb: *i64, px: *i64, py: *i64, pz: *i64, ia: i64, ib: i64, ic: i64, id: i64, r: i64, g: i64, b: i64) -> i64 {
42 depth_tri_render(fb, zb, px[ia], py[ia], pz[ia], px[ib], py[ib], pz[ib], px[ic], py[ic], pz[ic], r, g, b, 1)
43 depth_tri_render(fb, zb, px[ia], py[ia], pz[ia], px[ic], py[ic], pz[ic], px[id], py[id], pz[id], r, g, b, 1)
44 return 0
45}
46
47// render one cube at Y-angle `ay`, centered (cx,cy), into the shared fb/zbuf
48func draw_cube(fb: *Image, zb: *i64, cvx: *i64, cvy: *i64, cvz: *i64, px: *i64, py: *i64, pz: *i64, o: *i64, ay: i64, cx: i64, cy: i64) -> i64 {
49 var i: i64 = 0
50 while i < 8 { projA(cvx[i], cvy[i], cvz[i], ay, 25, cx, cy, o); px[i]=o[0]; py[i]=o[1]; pz[i]=o[2]; i = i + 1 }
51 face(fb, zb, px, py, pz, 4, 5, 6, 7, 0, 0, 255) // +Z blue
52 face(fb, zb, px, py, pz, 0, 1, 2, 3, 235, 235, 0) // -Z yellow
53 face(fb, zb, px, py, pz, 1, 5, 6, 2, 255, 0, 0) // +X red
54 face(fb, zb, px, py, pz, 0, 4, 7, 3, 0, 235, 235) // -X cyan
55 face(fb, zb, px, py, pz, 3, 2, 6, 7, 0, 235, 0) // +Y green
56 face(fb, zb, px, py, pz, 0, 1, 5, 4, 235, 0, 235) // -Y magenta
57 return 0
58}
59
60// count differing bytes between two CELL-wide vertical slices at x=xa and x=xb
61func cell_diff(fb: *Image, xa: i64, xb: i64, cw: i64, h: i64) -> i64 {
62 let p: *u8 = fb.pixels
63 var d: i64 = 0
64 var y: i64 = 0
65 while y < h {
66 var x: i64 = 0
67 while x < cw {
68 let oa: i64 = y * fb.stride + (xa + x) * 3
69 let ob: i64 = y * fb.stride + (xb + x) * 3
70 if p[oa] != p[ob] { d = d + 1 }
71 if p[oa+1] != p[ob+1] { d = d + 1 }
72 if p[oa+2] != p[ob+2] { d = d + 1 }
73 x = x + 1
74 }
75 y = y + 1
76 }
77 return d
78}
79
80func main() -> i64 {
81 hw("=== nx_cube3d_spin_gate -- ANIMATED 3D: a spinning cube filmstrip (integer sin/cos + Z-buffer) ===\n" as *u8)
82 var fails: i64 = 0
83
84 // sanity on the trig primitive first (Bhaskara Q10): sin90=1024, sin30~512, cos0=1024
85 hw(" isin(90)=" as *u8); pn(isin(90)); hw(" isin(30)=" as *u8); pn(isin(30)); hw(" icos(0)=" as *u8); pn(icos(0)); hw(" isin(0)=" as *u8); pn(isin(0)); hw("\n" as *u8)
86 var t0: i64 = 0
87 if isin(90) == 1024 { if isin(0) == 0 { if icos(0) == 1024 { if isin(30) > 500 { if isin(30) < 525 { t0 = 1 } } } } }
88 if t0 == 1 { hw("T0 PASS integer sin/cos primitive correct (sin90=1024, sin0=0, cos0=1024, sin30~512)\n" as *u8) } else { fails=fails+1; hw("T0 FAIL trig\n" as *u8) }
89
90 let CELL: i64 = 160
91 let NC: i64 = 6
92 let W: i64 = CELL * NC
93 let H: i64 = 170
94 let npix: i64 = W * H
95 let S: i64 = 48
96
97 let cvx: *i64 = sys_mmap(64) as *i64
98 let cvy: *i64 = sys_mmap(64) as *i64
99 let cvz: *i64 = sys_mmap(64) as *i64
100 cvx[0]=0-S; cvy[0]=0-S; cvz[0]=0-S
101 cvx[1]=S; cvy[1]=0-S; cvz[1]=0-S
102 cvx[2]=S; cvy[2]=S; cvz[2]=0-S
103 cvx[3]=0-S; cvy[3]=S; cvz[3]=0-S
104 cvx[4]=0-S; cvy[4]=0-S; cvz[4]=S
105 cvx[5]=S; cvy[5]=0-S; cvz[5]=S
106 cvx[6]=S; cvy[6]=S; cvz[6]=S
107 cvx[7]=0-S; cvy[7]=S; cvz[7]=S
108
109 let px: *i64 = sys_mmap(64) as *i64
110 let py: *i64 = sys_mmap(64) as *i64
111 let pz: *i64 = sys_mmap(64) as *i64
112 let o: *i64 = sys_mmap(32) as *i64
113
114 let fb: *Image = nx_image_alloc(W, H, 3)
115 let pbk: *u8 = fb.pixels
116 var bi: i64 = 0
117 while bi < npix * 3 { pbk[bi] = 8 as u8; bi = bi + 1 } // dark background
118 let zb: *i64 = sys_mmap(npix * 8) as *i64
119 depth_clear(zb, npix, 1000000000)
120
121 // 6 successive rotation angles across the strip (each cell = the next animation frame)
122 var c: i64 = 0
123 while c < NC {
124 let cx: i64 = c * CELL + CELL / 2
125 let ay: i64 = c * 18 // 0,18,36,54,72,90 -- a quarter turn
126 draw_cube(fb, zb, cvx, cvy, cvz, px, py, pz, o, ay, cx, 88)
127 c = c + 1
128 }
129
130 // T1: each successive frame differs from the previous -> real MOTION (not a static repeat)
131 var minmove: i64 = 1000000000
132 var pair: i64 = 0
133 while pair < NC - 1 {
134 let d: i64 = cell_diff(fb, pair * CELL, (pair+1) * CELL, CELL, H)
135 if d < minmove { minmove = d }
136 pair = pair + 1
137 }
138 hw(" min adjacent-frame diff bytes=" as *u8); pn(minmove); hw("\n" as *u8)
139 var t1: i64 = 0
140 if minmove > 200 { t1 = 1 }
141 if t1 == 1 { hw("T1 PASS every successive frame moves (all 5 adjacent frame-pairs differ) -> the cube SPINS\n" as *u8) } else { fails=fails+1; hw("T1 FAIL some frames identical (no motion)\n" as *u8) }
142
143 // T2: a cube is present in EVERY cell (center non-black)
144 var allpresent: i64 = 1
145 var cc: i64 = 0
146 while cc < NC {
147 let cx: i64 = cc * CELL + CELL / 2
148 let co: i64 = 88 * fb.stride + cx * 3
149 var nb: i64 = 0
150 if pbk[co] > 40 { nb = 1 } else { if pbk[co+1] > 40 { nb = 1 } else { if pbk[co+2] > 40 { nb = 1 } } }
151 if nb == 0 { allpresent = 0 }
152 cc = cc + 1
153 }
154 if allpresent == 1 { hw("T2 PASS a solid cube rendered in every one of the 6 frames\n" as *u8) } else { fails=fails+1; hw("T2 FAIL a frame is empty\n" as *u8) }
155
156 nx_png_write_rgb("knowledge/nx_cube3d_spin.png\x00" as *u8, fb.pixels, W, H)
157 hw("artifact -> knowledge/nx_cube3d_spin.png (6-frame spin filmstrip, left->right = a quarter turn)\n" as *u8)
158
159 if fails == 0 { hw("NX-CUBE3D-SPIN GREEN -- animated 3D: integer trig + spinning depth-sorted cube (the video axis for 3D scenes)\n" as *u8); sys_exit(0); return 0 }
160 hw("NX-CUBE3D-SPIN RED fails=" as *u8); pn(fails); hw("\n" as *u8)
161 sys_exit(1); return 1
162}