code wiki / (root) / nx_vnormals_lib.nx

nx_vnormals_lib.nx

buildroot/runtime/nx_vnormals_lib.nx

7558 B121 linesdepth 2pulls 2 transitivereach 165 importersview sourcekind library
docsdependenciesstructsconstsfunctions

about

nx_vnormals_lib.nx -- ONE OWNER for AREA-WEIGHTED smooth per-vertex normals over an AoS vertex buffer and a flat triangle-index buffer. Retires the copy in nx_gltf_export.gl_vnormals and the copy that was INLINE inside nx_obj_export.write_obj. WHY THIS FILE EXISTS AND WHY IT IS NOT THE OBVIOUS MERGE. The estate had THREE per-vertex-normal computations. Two of them are the same computation and are retired here. The THIRD IS NOT THE SAME COMPUTATION AND IS DELIBERATELY LEFT WHERE IT IS -- merging it would have silently changed shipped geometry, which is how this estate once deleted live capability by consolidating things that merely looked alike: sc_vertex_normals (nx_shellclose.nx) NORMALISES EACH FACE NORMAL TO UNIT LENGTH BEFORE ACCUMULATING it -- `nrm[vi] = nrm[vi] + (nx * SC_Q) / nl` -- so every incident face contributes EQUALLY regardless of its area. That is UNIFORM (equal-weight) smoothing. The two copies retired here accumulate the RAW cross product, whose magnitude is twice the triangle's area, so a large face dominates a small one. That is AREA-WEIGHTED smoothing. They agree only on a mesh whose incident triangles all have equal area, and they diverge without bound otherwise. nx_vnormals_gate MEASURES that divergence against the real shipped sc_vertex_normals rather than asserting it: on two triangles meeting at a vertex with a 100:1 area ratio, area weighting yields a normal 99.5 percent of the way toward the large face while uniform weighting sits at exactly 45 degrees between them, and the gate proves the two results are not even PARALLEL. Three further differences are recorded for the same reason -- a future reader will be tempted by this merge again: * output scale 1000 here versus SC_Q = 256 there, * a vertex with no usable incident face becomes +Y up here and stays the ZERO vector there, * sc_isqrt is a fixed 24-iteration Newton with its seed clamped to 65536, which for a non-perfect square can settle on either side of the true root, where vm_isqrt iterates to convergence and is an exact floor. Two rulers, not one. THE OUTPUT SCALE IS NOT A CHOICE MADE HERE. VN_SCALE is 1000 because that is the scale BOTH retired copies already emitted and both of their consumers already expect. Changing it here would be a silent format change to two shipped exporters, so it is named as inherited rather than picked. CORRECTION 2026-08-25 -- THE HALF OF THAT SENTENCE THAT WAS A COMMENT, NOT A MEASUREMENT. This header used to justify the scale with "glTF renderers re-normalise after the node's inverse-transpose". That is FALSE as a statement about the FORMAT: glTF 2.0 requires the NORMAL accessor to carry unit vectors, and KhronosGroup/glTF-Validator ranks a non-unit one an Error, not a warning -- 33,656 of them across the 12 .glb files nx_gltf_export had published. The OBJ half of the sentence stands (importers do rescale), and THAT is why the scale is still right here and the defect was never in this function: it was at the glTF write site, and it is fixed there, in nx_glbnorm_lib.gn_unit3. This file is deliberately unchanged in

dependencies 1 imports · 8 importers

nx_vecmath.nx nx_vnormals_lib.nx nx_glbnorm_gate.nx nx_gltf_export.nx nx_gltf_export_material_candidate_ nx_gltf_export_multipart_candidate nx_morf_draw_core.nx nx_obj_cohort_qualification_t282.n nx_obj_export.nx nx_vnormals_gate.nx

imports: nx_vecmath.nx

imported by: nx_glbnorm_gate.nxnx_gltf_export.nxnx_gltf_export_material_candidate_t340.nxnx_gltf_export_multipart_candidate_t345.nxnx_morf_draw_core.nxnx_obj_cohort_qualification_t282.nxnx_obj_export.nxnx_vnormals_gate.nx

structs

none

consts

47const VN_SCALE: i64 = 1000
48const VN_V3: i64 = 3 // components per vertex in the estate's AoS vertex buffers
49const VN_TRI_I: i64 = 3 // vertex indices per triangle in the estate's flat index buffers
50const VN_X: i64 = 0
51const VN_Y: i64 = 1
52const VN_Z: i64 = 2
58const VN_FALLBACK_X: i64 = 0
59const VN_FALLBACK_Y: i64 = 1000 // == VN_SCALE, and deliberately spelled out beside its siblings
60const VN_FALLBACK_Z: i64 = 0
64const VN_MIN_LEN: i64 = 1
66const VN_OK: i64 = 0
67const VN_E_ARGS: i64 = 0 - 48 // clear of nx_editstack_lib (-1..-10), nx_vpick_lib (-16..-26)

functions

71func vn_code_name(c: i64) -> *u8
86func vn_smooth_area(vbuf: *i64, fbuf: *i64, nv: i64, nf: i64, nrm: *i64) -> i64