code wiki / (root) / nx_vpick_lib.nx

nx_vpick_lib.nx

buildroot/runtime/nx_vpick_lib.nx

45716 B788 linesdepth 3pulls 6 transitivereach 1 importersview sourcekind library
docsdependenciesstructsconstsfunctions

about

nx_vpick_lib.nx -- DC4 of the /compare/dcc board: RAY-PICK and a TRANSFORM GIZMO for the sovereign browser viewport. nx_meshview_wasm already RENDERS and ORBITS a real indexed mesh through our own software rasteriser compiled to WASM (zero WebGL, zero Emscripten, zero third-party code). Nothing in that viewport can be SELECTED or DRAGGED, so it is a viewer and not an editor. This library is the missing half: turn a click into a triangle, turn a drag into an axis-constrained translation, and hand that translation to the operation stack as an OP instead of writing it into the vertex buffer. THE GIZMO NEVER MUTATES GEOMETRY. vp_gizmo_delta takes no mesh pointer at all -- it cannot write a vertex even by accident -- and vp_apply_drag pushes ES_OP_ADD / ES_OP_RADD onto an nx_editstack_lib stack. That is the whole point of the rung: a drag becomes an entry in the log, so it is undoable, redoable, replayable bit-exactly, and re-evaluable at depth, exactly like every other edit. A gizmo that wrote vertices directly would be a gizmo whose work cannot be undone without a snapshot. CHECK-BEFORE-BUILD. nx_capsearch was asked first (corpus_complete=1 over 6892 tools and 5517 libs) and nx_vpick is absent from the tree (nx_shelltool find, corpus_complete=1 over 61581 files). THREE incumbents were then READ, not guessed, and each is named with the measured reason it is not the answer: * mtk_ray_tri (nx_meshthick.nx) IS a Moller-Trumbore ray/triangle test and is the closest thing the estate had. It works in fx256, it REQUIRES a unit-length direction, it returns ONLY a scalar distance, and it collapses parallel, behind, outside-the-triangle and no-intersection into a single 0-1 return -- a negative answer that cannot say which negative it is. A picker needs the barycentric coordinates (to place a manipulator on the hit), needs to tell a DEGENERATE triangle from a parallel ray, and must not be forced to normalise a screen ray: normalisation costs an isqrt and discards the sub-unit precision a pixel-accurate pick depends on. Its own consumer nx_castview.nx already carries a standing warning about converting Q14 rays into its fx256 scale. * r3_ray (nx_recon3d.nx) back-projects a pixel to a ray, but from a 3x3 BASIS plus a focal length, and normalises the result. The viewport camera is not that model: it is yaw + orbit distance + half-fov through rc_perspective_cs. Feeding one convention's rays to the other camera is how a pick lands on the wrong triangle. * nx_raycast_voxel.nx is DDA traversal of a voxel grid, not an indexed triangle mesh. THE CAMERA CONVENTION IS COPIED, NOT INVENTED. Everything below is the exact inverse of what mv_render_loaded_cam and mv_proj1 do in nx_meshview_wasm: model -> eye eye = T(0,0,-dist) * Ry(yaw) * model (roty exactly as the renderer builds it) eye -> clip rc_perspective_cs(cos(fovh), sin(fovh), w*Q/h, Q, far) clip -> screen sx = (ndcx + 1) * w / 2 , sy = (1 - ndcy) * h / 2 so screen -> model is: undo the viewport map, divide out f = cot(fovh) and the aspect, set eye-space z to -1, then apply Ry(yaw) TRANSPOSED (a rotation's transpose is its inverse) and place the eye at Ry(yaw)^T * (0,0,dist). The trig is rc_sin_q14 / rc_cos_q14 -- the renderer's OWN Bhaskara approximation, COMPOSED rather than re-implemented, so a pick can never drift from the picture it was taken on. The scale unit is RC_Q, imported from nx_render_core for the same reason: one Q in the estate.

dependencies 2 imports · 1 importers

nx_render_core.nx nx_editstack_lib.nx nx_vpick_lib.nx nx_vpick_gate.nx

imports: nx_render_core.nxnx_editstack_lib.nx

imported by: nx_vpick_gate.nx

structs

none

consts

97const VP_RAY_OX: i64 = 0
98const VP_RAY_OY: i64 = 1
99const VP_RAY_OZ: i64 = 2
100const VP_RAY_DX: i64 = 3
101const VP_RAY_DY: i64 = 4
102const VP_RAY_DZ: i64 = 5
103const VP_RAY_WORDS: i64 = 6
107const VP_CAM_W: i64 = 0
108const VP_CAM_H: i64 = 1
109const VP_CAM_YAW: i64 = 2 // degrees, fed straight to rc_sin_q14 like the renderer does
110const VP_CAM_DIST: i64 = 3 // Q14 orbit distance, the renderer's `dist`
111const VP_CAM_FOVH: i64 = 4 // half field-of-view in degrees, the renderer's FOVH
112const VP_CAM_PX: i64 = 5
113const VP_CAM_PY: i64 = 6
114const VP_CAM_PZ: i64 = 7
115const VP_CAM_WORDS: i64 = 8
118const VP_R_T: i64 = 0 // ray parameter, Q14: hit = origin + t*dir/Q
119const VP_R_U: i64 = 1 // barycentric weight of v1, Q14
120const VP_R_V: i64 = 2 // barycentric weight of v2, Q14
121const VP_R_W: i64 = 3 // barycentric weight of v0, Q14 (Q - u - v)
122const VP_R_HX: i64 = 4
123const VP_R_HY: i64 = 5
124const VP_R_HZ: i64 = 6
125const VP_R_WORDS: i64 = 7
128const VP_G_DX: i64 = 0
129const VP_G_DY: i64 = 1
130const VP_G_DZ: i64 = 2
131const VP_G_SCALAR: i64 = 3 // signed magnitude along the chosen axis; equals the one nonzero component
132const VP_G_WORDS: i64 = 4
135const VP_DRAG_AX: i64 = 0
136const VP_DRAG_AY: i64 = 1
137const VP_DRAG_BX: i64 = 2
138const VP_DRAG_BY: i64 = 3
139const VP_DRAG_WORDS: i64 = 4
143const VP_SC_RAY_A: i64 = 0
144const VP_SC_RAY_B: i64 = 6
145const VP_SC_HIT_A: i64 = 12
146const VP_SC_HIT_B: i64 = 15
147const VP_SCRATCH_WORDS: i64 = 18
149const VP_AXIS_X: i64 = 0
150const VP_AXIS_Y: i64 = 1
151const VP_AXIS_Z: i64 = 2
155const VP_HIT: i64 = 1
156const VP_NOHIT_DEGENERATE: i64 = 0 - 1 // zero-area triangle: e1 x e2 is exactly the zero vector
157const VP_NOHIT_PARALLEL: i64 = 0 - 2 // ray parallel to a genuinely non-degenerate triangle
158const VP_NOHIT_OUTSIDE: i64 = 0 - 3 // plane hit, but the barycentric point is outside the triangle
159const VP_NOHIT_BEHIND: i64 = 0 - 4 // intersection is behind the ray origin
165const VP_OK: i64 = 0
166const VP_MISS_NONE: i64 = 0 - 16 // NO triangle was intersected. Deliberately not 0 and not -1.
167const VP_E_TRI_IDX: i64 = 0 - 17
168const VP_E_ZERO_DIR: i64 = 0 - 18
169const VP_E_SCREEN: i64 = 0 - 19
170const VP_E_BOUND: i64 = 0 - 20
171const VP_E_VIEWPORT: i64 = 0 - 21
172const VP_E_AXIS: i64 = 0 - 22
173const VP_E_ARGS: i64 = 0 - 23
174const VP_E_CELL: i64 = 0 - 24
175const VP_E_FOV: i64 = 0 - 25
176const VP_E_PLANE: i64 = 0 - 26
179const VP_VERT_MAX: i64 = 262144 // 16.0 in Q14
180const VP_ORIGIN_MAX: i64 = 524288 // 32.0 in Q14 == DV_DIST_MAX in nx_meshview_wasm
181const VP_DIR_MAX: i64 = 262144 // 16.0 in Q14
184const VP_WORLD_MAX: i64 = 786432 // VP_ORIGIN_MAX + VP_VERT_MAX
188const VP_RAYT_MAX: i64 = 1099511627776
190const VP_THREE: i64 = 3 // components per vertex in the estate's AoS mesh buffers
191const VP_TWO: i64 = 2
196const VP_TRI_I: i64 = 3 // vertex indices per triangle in the estate's index buffers
197const VP_TI_A: i64 = 0
198const VP_TI_B: i64 = 1
199const VP_TI_C: i64 = 2
635const VP_NS_PER_US: i64 = 1000 // nanoseconds in a microsecond: a unit conversion, never a tunable
636const VP_PERMIL: i64 = 1000 // parts per thousand

functions

206func vp_abs(a: i64) -> i64 { if a < 0 { return 0 - a } return a }
208func vp_within(v: i64, m: i64) -> i64
216func vp_code_name(c: i64) -> *u8
237func vp_axis_ok(axis: i64) -> i64
244func vp_dir_is_zero(ray: *i64, rbase: i64) -> i64
251func vp_ray_within_bounds(ray: *i64, rbase: i64) -> i64
261func vp_vert_within_bounds(mesh: *i64, v: i64) -> i64
274func vp_ray_from_screen(cam: *i64, px: i64, py: i64, ray: *i64, base: i64) -> i64
337func vp_tri_hit(mesh: *i64, nverts: i64, i0: i64, i1: i64, i2: i64, ray: *i64, rbase: i64, out: *i64) -> i64
419func vp_tri_hit_at(mesh: *i64, nverts: i64, idx: *i64, ntris: i64, tri: i64, ray: *i64, rbase: i64, out: *i64) -> i64
437func vp_pick(mesh: *i64, nverts: i64, idx: *i64, ntris: i64, ray: *i64, rbase: i64, out: *i64) -> i64
472func vp_pick_screen(cam: *i64, px: i64, py: i64, mesh: *i64, nverts: i64, idx: *i64, ntris: i64, scratch: *i64, out: *i64) -> i64
488func vp_plane_hit(cam: *i64, px: i64, py: i64, scratch: *i64, rbase: i64, out: *i64, obase: i64) -> i64
526func vp_gizmo_delta(cam: *i64, drag: *i64, axis: i64, scratch: *i64, out: *i64) -> i64
561func vp_cell(axis: i64, vert: i64, nverts: i64) -> i64 { return axis * nverts + vert }
562func vp_cells_needed(nverts: i64) -> i64 { return VP_THREE * nverts }
566func vp_seed_stack(st: *i64, mesh: *i64, nverts: i64) -> i64
581func vp_vert_component(st: *i64, vert: i64, axis: i64, nverts: i64) -> i64
591func vp_apply_drag(st: *i64, cam: *i64, drag: *i64, axis: i64, first_vert: i64, count: i64, nverts: i64, scratch: *i64, out: *i64) -> i64
640func vp_bench_verts(g: i64) -> i64 { return (g + 1) * (g + 1) }
641func vp_bench_tris(g: i64) -> i64 { return VP_TWO * g * g }
653func vp_bench_grid(mesh: *i64, idx: *i64, g: i64) -> i64
702func vp_bench_run(cam: *i64, px: i64, py: i64, mesh: *i64, nverts: i64, idx: *i64, ntris: i64, scratch: *i64, out: *i64, reps: i64) -> i64
718func vp_ns_per_op(elapsed_us: i64, reps: i64) -> i64
728func vp_cost_per_tri(ns_per_op: i64, ntris: i64) -> i64
737func vp_frame_permil(ns_per_op: i64, frame_us: i64) -> i64
746func vp_frame_ppm(ns_per_op: i64, frame_us: i64) -> i64
771func vp_growth_ok(n_lo: i64, k_lo: i64, n_hi: i64, k_hi: i64) -> i64
784func vp_growth_permil(k_lo: i64, k_hi: i64) -> i64