code wiki / _hdl_build / nx_cube3d_gate.nx
nx_cube3d_gate.nx source
↩ module page · 153 lines · 7857 B
1// nx_cube3d_gate.nx -- a REAL 3D SCENE: a rotated, perspective-projected CUBE rasterized with the Z-buffer.
2// 8 verts -> rotate (Y then X, fixed-point Q10) -> perspective project -> 12 triangles (6 faces x 2), each a
3// face color, depth-sorted by nx_depth_tri. NO backface culling needed -- the DEPTH BUFFER decides visibility,
4// so exactly the near faces show. This composes the depth primitive into "we render 3D objects with occlusion."
5// T1 depth-on frame != painter's frame -> the Z-buffer is actually resolving occlusion in the scene.
6// T2 all 3 front-facing face colors (red +X, green +Y, blue +Z) are present -> 3 visible faces, a real cube.
7// T3 center pixel is a face (non-black) -> the cube is on-screen and solid.
8// Artifact: knowledge/nx_cube3d.png (viewable 3D cube). license_tier: ORIGINAL expect_exit: 0
9import "nx_syscalls.nx"
10import "nx_image.nx"
11import "nx_depth_tri.nx"
12import "nx_png_write.nx"
13
14func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
15func 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 }
16
17func img_black(fb: *Image) -> i64 {
18 let p: *u8 = fb.pixels; let n: i64 = fb.height * fb.stride; var i: i64 = 0
19 while i < n { p[i] = 0 as u8; i = i + 1 }
20 return 0
21}
22func img_diff(a: *Image, b: *Image) -> i64 {
23 let pa: *u8 = a.pixels; let pb: *u8 = b.pixels; let n: i64 = a.height * a.stride
24 var d: i64 = 0; var i: i64 = 0
25 while i < n { if pa[i] != pb[i] { d = d + 1 } i = i + 1 }
26 return d
27}
28
29// rotate (Y by ~34.5deg then X by ~25deg, Q10) + perspective project. out[0]=sx out[1]=sy out[2]=depth(Zc)
30func proj(vx: i64, vy: i64, vz: i64, out: *i64) -> i64 {
31 let SA: i64 = 580; let CA: i64 = 845
32 let SB: i64 = 0 - 430; let CB: i64 = 928
33 let x1: i64 = (vx * CA + vz * SA) / 1024
34 let z1: i64 = (0 - (vx * SA) + vz * CA) / 1024
35 let y2: i64 = (vy * CB - z1 * SB) / 1024
36 let z2: i64 = (vy * SB + z1 * CB) / 1024
37 let zc: i64 = z2 + 560
38 out[0] = 100 + (x1 * 360) / zc
39 out[1] = 100 - (y2 * 360) / zc
40 out[2] = zc
41 return 0
42}
43
44// render one quad face as 2 triangles (a,b,c)+(a,c,d)
45func 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, don: i64) -> i64 {
46 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, don)
47 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, don)
48 return 0
49}
50
51// draw the whole cube (all 6 faces) into fb with depth flag `don`
52func cube(fb: *Image, zb: *i64, px: *i64, py: *i64, pz: *i64, npix: i64, don: i64) -> i64 {
53 img_black(fb)
54 depth_clear(zb, npix, 1000000000)
55 face(fb, zb, px, py, pz, 4, 5, 6, 7, 0, 0, 255, don) // +Z front blue
56 face(fb, zb, px, py, pz, 0, 1, 2, 3, 235, 235, 0, don) // -Z back yellow
57 face(fb, zb, px, py, pz, 1, 5, 6, 2, 255, 0, 0, don) // +X right red
58 face(fb, zb, px, py, pz, 0, 4, 7, 3, 0, 235, 235, don) // -X left cyan
59 face(fb, zb, px, py, pz, 3, 2, 6, 7, 0, 235, 0, don) // +Y top green
60 face(fb, zb, px, py, pz, 0, 1, 5, 4, 235, 0, 235, don) // -Y bottom magenta
61 return 0
62}
63
64func main() -> i64 {
65 hw("=== nx_cube3d_gate -- a rotated, perspective-projected CUBE, Z-buffered (real 3D scene) ===\n" as *u8)
66 var fails: i64 = 0
67 let W: i64 = 200
68 let H: i64 = 200
69 let npix: i64 = W * H
70 let S: i64 = 80
71
72 // 8 cube verts
73 let cx: *i64 = sys_mmap(64) as *i64
74 let cy: *i64 = sys_mmap(64) as *i64
75 let cz: *i64 = sys_mmap(64) as *i64
76 cx[0]=0-S; cy[0]=0-S; cz[0]=0-S
77 cx[1]=S; cy[1]=0-S; cz[1]=0-S
78 cx[2]=S; cy[2]=S; cz[2]=0-S
79 cx[3]=0-S; cy[3]=S; cz[3]=0-S
80 cx[4]=0-S; cy[4]=0-S; cz[4]=S
81 cx[5]=S; cy[5]=0-S; cz[5]=S
82 cx[6]=S; cy[6]=S; cz[6]=S
83 cx[7]=0-S; cy[7]=S; cz[7]=S
84
85 // project all 8
86 let px: *i64 = sys_mmap(64) as *i64
87 let py: *i64 = sys_mmap(64) as *i64
88 let pz: *i64 = sys_mmap(64) as *i64
89 let o: *i64 = sys_mmap(32) as *i64
90 var i: i64 = 0
91 while i < 8 { proj(cx[i], cy[i], cz[i], o); px[i]=o[0]; py[i]=o[1]; pz[i]=o[2]; i = i + 1 }
92
93 let zb: *i64 = sys_mmap(npix * 8) as *i64
94 let fbA: *Image = nx_image_alloc(W, H, 3)
95 let fbP: *Image = nx_image_alloc(W, H, 3)
96 cube(fbA, zb, px, py, pz, npix, 1) // depth ON
97 cube(fbP, zb, px, py, pz, npix, 0) // painter's (depth OFF)
98
99 // T1: depth vs painter's differ -> occlusion is happening in the scene
100 let d1: i64 = img_diff(fbA, fbP)
101 hw(" depth-vs-painter diff bytes=" as *u8); pn(d1); hw("\n" as *u8)
102 var t1: i64 = 0
103 if d1 > 0 { t1 = 1 }
104 if t1 == 1 { hw("T1 PASS the Z-buffer resolves occlusion in the 3D scene (depth frame != painter's frame)\n" as *u8) } else { fails=fails+1; hw("T1 FAIL depth had no effect\n" as *u8) }
105
106 // T2: a convex cube shows EXACTLY 3 faces from any generic angle. 6 distinct bright face-colors -> count how
107 // many appear (>50px). Orientation-independent invariant (not tuned to which 3 faces this rotation happens to show).
108 let p: *u8 = fbA.pixels
109 var cbl: i64 = 0
110 var cye: i64 = 0
111 var cre: i64 = 0
112 var ccy: i64 = 0
113 var cgr: i64 = 0
114 var cma: i64 = 0
115 var k: i64 = 0
116 while k < npix {
117 let r: i64 = p[k*3] as i64
118 let g: i64 = p[k*3+1] as i64
119 let b: i64 = p[k*3+2] as i64
120 if r > 120 { if g > 120 { if b < 90 { cye = cye + 1 } } } // yellow
121 if r > 120 { if b > 120 { if g < 90 { cma = cma + 1 } } } // magenta
122 if g > 120 { if b > 120 { if r < 90 { ccy = ccy + 1 } } } // cyan
123 if r > 120 { if g < 90 { if b < 90 { cre = cre + 1 } } } // red
124 if g > 120 { if r < 90 { if b < 90 { cgr = cgr + 1 } } } // green
125 if b > 120 { if r < 90 { if g < 90 { cbl = cbl + 1 } } } // blue
126 k = k + 1
127 }
128 var nfaces: i64 = 0
129 if cbl > 50 { nfaces = nfaces + 1 }
130 if cye > 50 { nfaces = nfaces + 1 }
131 if cre > 50 { nfaces = nfaces + 1 }
132 if ccy > 50 { nfaces = nfaces + 1 }
133 if cgr > 50 { nfaces = nfaces + 1 }
134 if cma > 50 { nfaces = nfaces + 1 }
135 hw(" visible faces=" as *u8); pn(nfaces); hw(" bins[blu=" as *u8); pn(cbl); hw(" yel=" as *u8); pn(cye); hw(" red=" as *u8); pn(cre); hw(" cyn=" as *u8); pn(ccy); hw(" grn=" as *u8); pn(cgr); hw(" mag=" as *u8); pn(cma); hw("]\n" as *u8)
136 var t2: i64 = 0
137 if nfaces == 3 { t2 = 1 }
138 if t2 == 1 { hw("T2 PASS EXACTLY 3 faces visible -> correct convex-cube occlusion (a cube shows at most 3 faces; a generic rotation shows exactly 3)\n" as *u8) } else { fails=fails+1; hw("T2 FAIL not exactly 3 visible faces\n" as *u8) }
139
140 // T3: center pixel is a face (cube is on-screen, solid)
141 let co: i64 = 100 * fbA.stride + 100 * 3
142 var t3: i64 = 0
143 if p[co] != (0 as u8) { t3 = 1 } else { if p[co+1] != (0 as u8) { t3 = 1 } else { if p[co+2] != (0 as u8) { t3 = 1 } } }
144 if t3 == 1 { hw("T3 PASS cube covers center (on-screen, solid)\n" as *u8) } else { fails=fails+1; hw("T3 FAIL center empty\n" as *u8) }
145
146 // artifact
147 nx_png_write_rgb("knowledge/nx_cube3d.png\x00" as *u8, fbA.pixels, W, H)
148 hw("artifact -> knowledge/nx_cube3d.png (viewable 3D cube -- 3 faces, correct Z-buffer occlusion)\n" as *u8)
149
150 if fails == 0 { hw("NX-CUBE3D GREEN -- rotated perspective cube rasterized with correct Z-buffer occlusion (real 3D scene; next: animate + wire into the live window)\n" as *u8); sys_exit(0); return 0 }
151 hw("NX-CUBE3D RED fails=" as *u8); pn(fails); hw("\n" as *u8)
152 sys_exit(1); return 1
153}