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}