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}