code wiki / (root) / nx_stl_test.nx

nx_stl_test.nx source

↩ module page · 160 lines · 5752 B

1// nx_stl_test.nx -- hand-build a binary STL tetrahedron in-memory, 2// parse it back, verify everything round-trips correctly. 3// 4// Tetrahedron at unit-millimetre scale (becomes Q14 = 16384 after parse): 5// v0 = (0, 0, 0) 6// v1 = (1, 0, 0) 7// v2 = (0, 1, 0) 8// v3 = (0, 0, 1) 9// 10// Triangles (CCW outward winding matching nx_mesh_make_tetrahedron): 11// T0: v1, v2, v3 (far face) 12// T1: v0, v3, v2 (x=0 face) 13// T2: v0, v1, v3 (y=0 face) 14// T3: v0, v2, v1 (z=0 face) 15// 16// Each triangle = 50 bytes: 17// 12 bytes normal (we write zeros; parser ignores them) 18// 36 bytes 9 floats for 3 verts 19// 2 bytes attribute (zero) 20// 21// Total file size = 80-byte header + 4-byte n_tris + 4*50 = 284 bytes. 22// 23// Closed-form invariants: 24// (a) parse returns OK verdict 25// (b) n_tris_header == 4 26// (c) n_verts_unique == 4 (perfect dedup -- tetrahedron really 27// does have exactly 4 unique vertices) 28// (d) mesh.n_tris == 4 29// (e) parsed coordinates match expected Q14 values 30// (f) nx_mesh_is_manifold returns OK on the parsed mesh 31// (g) Truncated buffer detected 32// (h) ASCII STL detected and refused 33// 34// expect_exit: 0 35// license_tier: ORIGINAL 36 37import "nx_syscalls.nx" 38import "nx_mesh.nx" 39import "nx_mesh_print_check.nx" 40import "nx_stl.nx" 41 42// Write a binary32 bit pattern as 4 little-endian bytes at buf[off]. 43func smoke_put_f32_bits(buf: *u8, off: i64, bits32: i64) -> i64 { 44 buf[off + 0] = bits32 & 0xff 45 buf[off + 1] = (bits32 >> 8) & 0xff 46 buf[off + 2] = (bits32 >> 16) & 0xff 47 buf[off + 3] = (bits32 >> 24) & 0xff 48 return 0 49} 50 51// Write the 9 floats of one triangle's vertex block (after the 52// 12-byte normal placeholder). Coords are bit patterns; we pass 53// either 0x00000000 (0.0) or 0x3F800000 (1.0). 54func smoke_put_tri(buf: *u8, off: i64, 55 x0: i64, y0: i64, z0: i64, 56 x1: i64, y1: i64, z1: i64, 57 x2: i64, y2: i64, z2: i64) -> i64 { 58 // Normal: 3 zero floats 59 smoke_put_f32_bits(buf, off + 0, 0) 60 smoke_put_f32_bits(buf, off + 4, 0) 61 smoke_put_f32_bits(buf, off + 8, 0) 62 // 3 verts 63 smoke_put_f32_bits(buf, off + 12, x0) 64 smoke_put_f32_bits(buf, off + 16, y0) 65 smoke_put_f32_bits(buf, off + 20, z0) 66 smoke_put_f32_bits(buf, off + 24, x1) 67 smoke_put_f32_bits(buf, off + 28, y1) 68 smoke_put_f32_bits(buf, off + 32, z1) 69 smoke_put_f32_bits(buf, off + 36, x2) 70 smoke_put_f32_bits(buf, off + 40, y2) 71 smoke_put_f32_bits(buf, off + 44, z2) 72 // Attribute bytes 73 buf[off + 48] = 0 74 buf[off + 49] = 0 75 return 0 76} 77 78const F32_ZERO: i64 = 0x00000000 79const F32_ONE: i64 = 0x3F800000 80const Q14_ONE: i64 = 16384 81 82func main() -> i64 { 83 let stl: *u8 = sys_mmap(512) 84 // Header: 80 zero bytes (already zero from mmap). 85 // n_tris little-endian uint32 = 4 86 stl[80] = 4 87 stl[81] = 0 88 stl[82] = 0 89 stl[83] = 0 90 91 // T0: v1(1,0,0), v2(0,1,0), v3(0,0,1) 92 smoke_put_tri(stl, 84 + 0 * 50, 93 F32_ONE, F32_ZERO, F32_ZERO, 94 F32_ZERO, F32_ONE, F32_ZERO, 95 F32_ZERO, F32_ZERO, F32_ONE) 96 // T1: v0(0,0,0), v3(0,0,1), v2(0,1,0) 97 smoke_put_tri(stl, 84 + 1 * 50, 98 F32_ZERO, F32_ZERO, F32_ZERO, 99 F32_ZERO, F32_ZERO, F32_ONE, 100 F32_ZERO, F32_ONE, F32_ZERO) 101 // T2: v0(0,0,0), v1(1,0,0), v3(0,0,1) 102 smoke_put_tri(stl, 84 + 2 * 50, 103 F32_ZERO, F32_ZERO, F32_ZERO, 104 F32_ONE, F32_ZERO, F32_ZERO, 105 F32_ZERO, F32_ZERO, F32_ONE) 106 // T3: v0(0,0,0), v2(0,1,0), v1(1,0,0) 107 smoke_put_tri(stl, 84 + 3 * 50, 108 F32_ZERO, F32_ZERO, F32_ZERO, 109 F32_ZERO, F32_ONE, F32_ZERO, 110 F32_ONE, F32_ZERO, F32_ZERO) 111 112 let total_len: i64 = 84 + 4 * 50 // 284 113 114 // --- (a)(b)(c)(d) Parse + counts --- 115 let r: *NxStlResult = nx_stl_load_binary(stl, total_len) 116 if r.verdict != NX_STL_OK { return 10 } 117 if r.n_tris_header != 4 { return 11 } 118 if r.n_verts_unique != 4 { return 12 } 119 if r.mesh.n_tris != 4 { return 13 } 120 121 // --- (e) Coordinate round-trip --- 122 // We don't know which vertex got which index (depends on hash 123 // insertion order), so we verify the set of vertex coords matches 124 // {(0,0,0), (Q14,0,0), (0,Q14,0), (0,0,Q14)}. 125 var saw_origin: i64 = 0 126 var saw_x: i64 = 0 127 var saw_y: i64 = 0 128 var saw_z: i64 = 0 129 var vi: i64 = 0 130 while vi < r.mesh.n_verts { 131 let x: i64 = nx_mesh_get_vertex_x(r.mesh, vi) 132 let y: i64 = nx_mesh_get_vertex_y(r.mesh, vi) 133 let z: i64 = nx_mesh_get_vertex_z(r.mesh, vi) 134 if x == 0 { if y == 0 { if z == 0 { saw_origin = saw_origin + 1 } } } 135 if x == Q14_ONE { if y == 0 { if z == 0 { saw_x = saw_x + 1 } } } 136 if x == 0 { if y == Q14_ONE { if z == 0 { saw_y = saw_y + 1 } } } 137 if x == 0 { if y == 0 { if z == Q14_ONE { saw_z = saw_z + 1 } } } 138 vi = vi + 1 139 } 140 if saw_origin != 1 { return 20 } 141 if saw_x != 1 { return 21 } 142 if saw_y != 1 { return 22 } 143 if saw_z != 1 { return 23 } 144 145 // --- (f) Manifold check --- 146 if nx_mesh_is_manifold(r.mesh) != NX_MESH_PRINT_OK { return 30 } 147 148 // --- (g) Truncated buffer --- 149 let r_short: *NxStlResult = nx_stl_load_binary(stl, 100) 150 if r_short.verdict != NX_STL_ERR_TRUNCATED { return 40 } 151 152 // --- (h) ASCII STL refused --- 153 let ascii: *u8 = sys_mmap(128) 154 ascii[0] = 115; ascii[1] = 111; ascii[2] = 108 155 ascii[3] = 105; ascii[4] = 100 // "solid" 156 let r_ascii: *NxStlResult = nx_stl_load_binary(ascii, 128) 157 if r_ascii.verdict != NX_STL_ERR_ASCII_REFUSED { return 50 } 158 159 return 0 160}