nx_uvunwrap_lib.nx
buildroot/runtime/nx_uvunwrap_lib.nx
about
nx_uvunwrap_lib.nx -- A GENERAL UV UNWRAP FOR AN ARBITRARY MESH, WITH A DISTORTION REFEREE (/compare/dcc DC5).
WHAT WAS ABSENT, MEASURED NOT ASSERTED. nx_capsearch over 6,892 organs (1,375 tools + 5,517 libs,
corpus_complete=1) returned NO general parameterisation: the estate's only unwrap is ntx_apply in
nx_nxa_texc_lib, a bone-anchored CYLINDRICAL map that reads SKEL parents and SKIN weights and refuses
outright without them ("no SKIN section -- refusing to fabricate bone ownership"). That is the right
design for a humanoid NXA and it is unreachable for everything else: a sculpt, a boolean result, a
terrain patch, a retopologised prop has no skeleton to anchor to, so it could not be unwrapped AT ALL,
and texture painting was blocked for every non-humanoid asset in the estate.
OUTPUT IS WIRE-COMPATIBLE WITH THE INCUMBENT BAKE PATH ON PURPOSE. uv_emit_texc writes exactly the TEXC
payload nx_nxa_texbake_lib already consumes -- header [nv][stride 3][atlas grid g][reserved 0] then per
vertex [u Q16][v Q16][chart], charts owning disjoint tiles of a g x g grid with a one-unit gutter, the
same shape and the same fixed-point scale ntx_apply emits. So a mesh unwrapped here bakes through the
existing sampler with nothing else changed.
BUT THE BRIDGE IS LOSSY AND IT SAYS SO. TEXC is PER VERTEX and a seam is exactly the place where one
mesh vertex needs TWO texture coordinates, so the incumbent wire format cannot represent a seam at
all. This library solves per CORNER (a mesh vertex split by seams into several UV vertices, which is
what makes a cut actually cut), and uv_emit_texc COUNTS the vertices whose extra coordinates the
per-vertex form drops, into UV_H_TEXCLOSS. A lossy conversion that reports zero loss would be the
defect; one that names its loss is a bridge.
THE HONEST ALGORITHM. A least-squares conformal map (Levy et al., LSCM) needs a linear solve, and there
is no linear solver in this estate and no float type to build one on. So this is an ITERATIVE relaxation
toward the same stationary condition, and every part of that choice is declared rather than hidden:
* The energy is the LSCM energy in its Dirichlet-minus-area form, E = sum_edges W|dU|^2 - 2 sum_t A_t.
THAT SECOND TERM IS THE WHOLE REASON THIS DOES NOT COLLAPSE. A pure harmonic (Dirichlet-only)
relaxation with a free boundary and two pinned vertices has its minimum at the degenerate map that
puts every vertex on the segment between the pins -- a fold, scoring zero by every naive measure.
The -2*area term rewards area and makes the non-degenerate map the minimiser instead.
* Setting dE/dU_p = 0 gives a closed-form per-vertex update, so this is a damped Jacobi sweep over
the exact stationary condition rather than an invented heuristic. Interior vertices see a pure
cotangent-Laplacian average (their one-ring area terms telescope to zero); the area term acts only
on the boundary, pushing it outward. That is LSCM, iterated.
* TWO PINNED VERTICES PER CHART, the standard LSCM constraint: the conformal energy is invariant to
translation, rotation and scale, so two pins remove exactly the null space and no more.
* The iteration bound is DERIVED FROM THE CHART, the convergence criterion is DECLARED, and a chart
that reaches neither is REFUSED BY NAME (UV_E_NOCONVERGE) rather than emitted folded.
THE REFEREE IS THE POINT. uv_distortion reports angle (quasi-conformal dilatation) and area distortion
dependencies 3 imports · 2 importers
imports: nx_syscalls.nxnx_vecmath.nxnx_meshvalid_lib.nx
imported by: nx_nxa_corner_prepare_candidate_t280.nxnx_uvunwrap_gate.nx
structs
| none |
consts
| 58 | const UV_H_NV: i64 = 0 |
| 59 | const UV_H_NT: i64 = 1 |
| 60 | const UV_H_ECAP: i64 = 2 |
| 61 | const UV_H_SCAP: i64 = 3 |
| 62 | const UV_H_NC: i64 = 4 |
| 63 | const UV_H_NUV: i64 = 5 |
| 64 | const UV_H_ERR: i64 = 6 |
| 65 | const UV_H_STAGE: i64 = 7 |
| 66 | const UV_H_GRID: i64 = 8 |
| 67 | const UV_H_NSEAM: i64 = 9 |
| 68 | const UV_H_ITERS: i64 = 10 |
| 69 | const UV_H_MAXMOVE: i64 = 11 |
| 70 | const UV_H_FLIP: i64 = 12 |
| 71 | const UV_H_SING: i64 = 13 |
| 72 | const UV_H_UVDEGEN: i64 = 14 |
| 73 | const UV_H_NOCONV: i64 = 15 |
| 74 | const UV_H_ANGMEAN: i64 = 16 |
| 75 | const UV_H_ANGMAX: i64 = 17 |
| 76 | const UV_H_AREAMEAN: i64 = 18 |
| 77 | const UV_H_AREAMAX: i64 = 19 |
| 78 | const UV_H_OVERLAP: i64 = 20 |
| 79 | const UV_H_TEXCLOSS: i64 = 21 |
| 80 | const UV_H_MVERR: i64 = 22 |
| 81 | const UV_H_MVCLOSED: i64 = 23 |
| 82 | const UV_H_NCORN: i64 = 24 |
| 84 | const UV_O_VERT: i64 = 25 |
| 85 | const UV_O_TRI: i64 = 26 |
| 86 | const UV_O_ETAB: i64 = 27 |
| 87 | const UV_O_STAB: i64 = 28 |
| 88 | const UV_O_TCHART: i64 = 29 |
| 89 | const UV_O_STACK: i64 = 30 |
| 90 | const UV_O_CSTART: i64 = 31 |
| 91 | const UV_O_CORDER: i64 = 32 |
| 92 | const UV_O_UVF: i64 = 33 |
| 93 | const UV_O_UVSRC: i64 = 34 |
| 94 | const UV_O_UVCH: i64 = 35 |
| 95 | const UV_O_WU: i64 = 36 |
| 96 | const UV_O_WV: i64 = 37 |
| 97 | const UV_O_ACCU: i64 = 38 |
| 98 | const UV_O_ACCV: i64 = 39 |
| 99 | const UV_O_ACCW: i64 = 40 |
| 100 | const UV_O_ACCAU: i64 = 41 |
| 101 | const UV_O_ACCAV: i64 = 42 |
| 102 | const UV_O_CUV: i64 = 43 |
| 103 | const UV_O_TANG: i64 = 44 |
| 104 | const UV_O_TRAT: i64 = 45 |
| 105 | const UV_O_TSAR: i64 = 46 |
| 106 | const UV_O_CBOX: i64 = 47 |
| 107 | const UV_O_CCNT: i64 = 48 |
| 108 | const UV_O_CDIST: i64 = 49 |
| 109 | const UV_O_LIST: i64 = 50 |
| 110 | const UV_O_STAMP: i64 = 51 |
| 111 | const UV_O_TSEEN: i64 = 52 |
| 112 | const UV_O_PLACED: i64 = 53 |
| 113 | const UV_O_ROOT: i64 = 54 |
| 118 | const UV_HDR: i64 = 64 |
| 119 | const UV_I64: i64 = 8 |
| 120 | const UV_TRI: i64 = 3 |
| 121 | const UV_EREC: i64 = 4 // edge record: [lo, hi, tri0+1, tri1+1] |
| 122 | const UV_SREC: i64 = 3 // seam record: [lo, hi, used] |
| 126 | const UV_ED_LO: i64 = 0 |
| 127 | const UV_ED_HI: i64 = 1 |
| 128 | const UV_ED_T0: i64 = 2 // first triangle, stored +1 so that 0 reads as EMPTY |
| 129 | const UV_ED_T1: i64 = 3 // second triangle, same +1 bias |
| 130 | const UV_SM_LO: i64 = 0 |
| 131 | const UV_SM_HI: i64 = 1 |
| 132 | const UV_SM_USED: i64 = 2 |
| 133 | const UV_ACCN: i64 = 5 // accU accV accW accAU accAV |
| 141 | const UV_CUVREC: i64 = 2 // per-corner uv pair: [u, v] |
| 142 | const UV_BOXREC: i64 = 4 // per-chart uv bounds: [u0, v0, u1, v1] |
| 143 | const UV_DISTREC: i64 = 4 // per-chart distortion: [amean, amax, dmean, dmax] |
| 144 | const UV_P_U: i64 = 0 |
| 145 | const UV_P_V: i64 = 1 |
| 146 | const UV_B_U0: i64 = 0 |
| 147 | const UV_B_V0: i64 = 1 |
| 148 | const UV_B_U1: i64 = 2 |
| 149 | const UV_B_V1: i64 = 3 |
| 150 | const UV_D_AMEAN: i64 = 0 |
| 151 | const UV_D_AMAX: i64 = 1 |
| 152 | const UV_D_DMEAN: i64 = 2 |
| 153 | const UV_D_DMAX: i64 = 3 |
| 159 | const UV_X_HDR: i64 = 4 |
| 160 | const UV_X_NV: i64 = 0 |
| 161 | const UV_X_STRIDE: i64 = 1 |
| 162 | const UV_X_GRID: i64 = 2 |
| 163 | const UV_X_RSVD: i64 = 3 |
| 164 | const UV_X_U: i64 = 0 // per-vertex payload slots, relative to that vertex's record base |
| 165 | const UV_X_V: i64 = 1 |
| 166 | const UV_X_CH: i64 = 2 |
| 169 | const UV_ST_NEW: i64 = 0 |
| 170 | const UV_ST_CHARTS: i64 = 1 |
| 171 | const UV_ST_SOLVED: i64 = 2 |
| 172 | const UV_ST_MEASURED: i64 = 3 |
| 176 | const UV_OK: i64 = 0 |
| 177 | const UV_E_ARGS: i64 = 0 - 1 |
| 178 | const UV_E_TOPOLOGY: i64 = 0 - 2 |
| 179 | const UV_E_NOPIN: i64 = 0 - 3 |
| 180 | const UV_E_NOCONVERGE: i64 = 0 - 4 |
| 181 | const UV_E_FOLD: i64 = 0 - 5 |
| 182 | const UV_E_UVDEGEN: i64 = 0 - 6 |
| 183 | const UV_E_OVERLAP: i64 = 0 - 7 |
| 184 | const UV_E_STAGE: i64 = 0 - 8 |
| 185 | const UV_E_SPAN: i64 = 0 - 9 |
| 186 | const UV_E_NOTRUN: i64 = 0 - 10 |
| 191 | const UV_E_UNPLACED: i64 = 0 - 11 |
| 197 | const UV_Q16: i64 = 65536 |
| 198 | const UV_Q16M: i64 = 65535 |
| 202 | const UV_GUTTER: i64 = 1 |
| 206 | const UV_W_Q: i64 = 4096 |
| 212 | const UV_PIN_SPAN: i64 = 1048576 |
| 215 | const UV_GUESS_SPAN: i64 = 65536 |
| 220 | const UV_COT_CLAMP: i64 = 64 |
| 227 | const UV_CONV_EPS: i64 = 32 |
| 233 | const UV_ITER_PER_UVV: i64 = 32 |
| 235 | const UV_ITER_MIN: i64 = 64 |
| 241 | const UV_OMEGA_DEN: i64 = 2 |
| 246 | const UV_ABC_MAX: i64 = 1048576 |
| 251 | const UV_SS_MAX: i64 = 1000000000 |
| 256 | const UV_SDEN_MAX: i64 = 100000000000 |
| 257 | const UV_PERMIL: i64 = 1000 |
| 258 | const UV_MIN_PINS: i64 = 2 |
| 816 | const UV_UNF_Q: i64 = 4096 |
functions
| 260 | func uv_err_name(e: i64) -> *u8 called by 1: main |
| 281 | func uv_layout(h: *i64) -> i64 |
| 327 | func uv_new(nv: i64, nt: i64) -> *i64 |
| 354 | func uv_words_used(m: *i64) -> i64 { return m[UV_O_CDIST] + m[UV_H_NT] * UV_DISTREC } |
| 356 | func uv_set_vert(m: *i64, i: i64, x: i64, y: i64, z: i64) -> i64 |
| 366 | func uv_set_tri(m: *i64, i: i64, a: i64, b: i64, c: i64) -> i64 |
| 376 | func uv_vx(m: *i64, v: i64) -> i64 { return m[m[UV_O_VERT] + v * UV_TRI + 0] } |
| 377 | func uv_vy(m: *i64, v: i64) -> i64 { return m[m[UV_O_VERT] + v * UV_TRI + 1] } |
| 378 | func uv_vz(m: *i64, v: i64) -> i64 { return m[m[UV_O_VERT] + v * UV_TRI + 2] } |
| 379 | func uv_ti(m: *i64, t: i64, k: i64) -> i64 { return m[m[UV_O_TRI] + t * UV_TRI + k] } |
| 388 | func uv_seam_set(m: *i64, a: i64, b: i64) -> i64 called by 12: ncp_restore_topologyncp_seamsua_clone_seamsucp_fixtureuct_closed_pathucp_fixture+6 calls 1: mv_mix |
| 419 | func uv_is_seam(m: *i64, a: i64, b: i64) -> i64 |
| 440 | func uvi_edge_slot(m: *i64, a: i64, b: i64, t: i64) -> i64 |
| 467 | func uvi_edge_find(m: *i64, a: i64, b: i64) -> i64 |
| 486 | func uvi_find(f: *i64, base: i64, x: i64) -> i64 |
| 492 | func uvi_union(f: *i64, base: i64, a: i64, b: i64) -> i64 |
| 501 | func uvi_corner_of(m: *i64, t: i64, v: i64) -> i64 |
| 517 | func uvi_validate(m: *i64) -> i64 |
| 542 | func uv_chart_build(m: *i64) -> i64 called by 22: ncp_restore_topologymainmainmainucp_fixturemain+16 calls 8: uvi_validateuv_tiuvi_edge_slotuv_is_seamuvi_edge_finduvi_corner_of+2 |
| 706 | func uv_chart_count(m: *i64) -> i64 { return m[UV_H_NC] } |
| 707 | func uv_uvvert_count(m: *i64) -> i64 { return m[UV_H_NUV] } called by 1: main |
| 708 | func uv_seam_count(m: *i64) -> i64 { return m[UV_H_NSEAM] } |
| 709 | func uv_chart_of_tri(m: *i64, t: i64) -> i64 |
| 715 | func uv_chart_tris(m: *i64, c: i64) -> i64 |
| 723 | func uvi_chart_classes(m: *i64, c: i64) -> i64 |
| 751 | func uvi_d2(m: *i64, va: i64, vb: i64) -> i64 |
| 763 | func uv_pins_for(m: *i64, c: i64, out: *i64) -> i64 |
| 793 | func uvi_cotq(m: *i64, vi: i64, vj: i64, vk: i64) -> i64 |
| 819 | func uvi_len_q(m: *i64, va: i64, vb: i64) -> i64 |
| 832 | func uvi_place_third(m: *i64, tp: i64, tn: i64, va: i64, vb: i64) -> i64 |
| 875 | func uvi_unfold(m: *i64, c: i64, n: i64) -> i64 |
| 974 | func uvi_initial(m: *i64, c: i64, n: i64, pa: i64, pb: i64) -> i64 |
| 1028 | func uvi_solve_chart(m: *i64, c: i64) -> i64 called by 2: mainuv_conformal calls 6: sys_mmapuv_pins_foruvi_chart_classesuvi_initialuvi_cotqvm_abs |
| 1136 | func uv_grid_for(nc: i64) -> i64 called by 1: uvi_atlas |
| 1145 | func uvi_atlas(m: *i64) -> i64 |
| 1217 | func uv_conformal(m: *i64) -> i64 |
| 1237 | func uv_iters(m: *i64) -> i64 { return m[UV_H_ITERS] } called by 1: main |
| 1238 | func uv_maxmove(m: *i64) -> i64 { return m[UV_H_MAXMOVE] } called by 1: main |
| 1242 | func uv_singular_sweeps(m: *i64) -> i64 { return m[UV_H_SING] } called by 1: main |
| 1243 | func uv_grid(m: *i64) -> i64 { return m[UV_H_GRID] } |
| 1244 | func uv_corner_u(m: *i64, t: i64, k: i64) -> i64 { return m[m[UV_O_CUV] + (t * UV_TRI + k) * UV_CUVREC + UV_P_U] } |
| 1245 | func uv_corner_v(m: *i64, t: i64, k: i64) -> i64 { return m[m[UV_O_CUV] + (t * UV_TRI + k) * UV_CUVREC + UV_P_V] } |
| 1246 | func uv_corner_class(m: *i64, t: i64, k: i64) -> i64 { return m[m[UV_O_UVF] + t * UV_TRI + k] } |
| 1263 | func uvi_tri_metrics(m: *i64, t: i64) -> i64 |
| 1362 | func uv_distortion(m: *i64) -> i64 |
| 1467 | func uv_angle_mean(m: *i64) -> i64 { return m[UV_H_ANGMEAN] } |
| 1468 | func uv_angle_max(m: *i64) -> i64 { return m[UV_H_ANGMAX] } |
| 1469 | func uv_area_mean(m: *i64) -> i64 { return m[UV_H_AREAMEAN] } |
| 1470 | func uv_area_max(m: *i64) -> i64 { return m[UV_H_AREAMAX] } |
| 1471 | func uv_flipped(m: *i64) -> i64 { return m[UV_H_FLIP] } |
| 1472 | func uv_uvdegen(m: *i64) -> i64 { return m[UV_H_UVDEGEN] } |
| 1473 | func uv_chart_angle_mean(m: *i64, c: i64) -> i64 called by 1: ua_worst |
| 1478 | func uv_chart_angle_max(m: *i64, c: i64) -> i64 called by 1: ua_worst |
| 1483 | func uv_chart_area_mean(m: *i64, c: i64) -> i64 called by 1: ua_worst |
| 1488 | func uv_chart_area_max(m: *i64, c: i64) -> i64 called by 1: ua_worst |
| 1497 | func uv_chart_uv_area(m: *i64, c: i64) -> i64 |
| 1522 | func uv_overlap_check(m: *i64) -> i64 |
| 1547 | func uv_overlaps(m: *i64) -> i64 { return m[UV_H_OVERLAP] } |
| 1548 | func uv_chart_box(m: *i64, c: i64, k: i64) -> i64 called by 1: main |
| 1559 | func uv_force_box(m: *i64, c: i64, u0: i64, v0: i64, u1: i64, v1: i64) -> i64 called by 1: main |
| 1574 | func uv_force_corner(m: *i64, t: i64, k: i64, u: i64, v: i64) -> i64 called by 1: main |
| 1585 | func uv_unwrap(m: *i64) -> i64 |
| 1595 | func uv_verdict(m: *i64) -> i64 { return m[UV_H_ERR] } called by 1: main |
| 1596 | func uv_stage(m: *i64) -> i64 { return m[UV_H_STAGE] } |
| 1597 | func uv_mv_verdict(m: *i64) -> i64 { return m[UV_H_MVERR] } |
| 1598 | func uv_mv_closed(m: *i64) -> i64 { return m[UV_H_MVCLOSED] } |
| 1608 | func uv_texc_words(m: *i64) -> i64 { return UV_X_HDR + m[UV_H_NV] * UV_TRI } called by 1: main |
| 1610 | func uv_emit_texc(m: *i64, out: *i64) -> i64 |
| 1642 | func uv_texc_loss(m: *i64) -> i64 { return m[UV_H_TEXCLOSS] } called by 1: main |