; ; /* ============================================================================ WORLD MODEL LOADER ---------------------------------------------------------------------------- Turns the compact WORLD_MODELS table into InstMesh-ready geometry. Kept separate from the data file so the data file can be regenerated without touching runtime logic. ============================================================================ */ function civicPackedColumnUVs(v,iArr,ic){ /* Civic V2 cell is five material columns, not a unique unwrap. Authored uv2 tracks world Y in V but shears U on every setback, so the cyan bars stair-step and floor tris sample the window column (copper mip from the tank cell to the left). One geometric axis per triangle. Gutters sit at 0/64/128/192/256 of the 304px cell — stay in content. */ const H0=0.045,H1=0.165,D0=0.250,D1=0.378,V0=0.055,V1=0.945; for(let t=0;t=ax&&ay>=az; const win=!floor&&(raw===MAT.LAMP||raw===MAT.GLASS||raw===MAT.BUILD_OFFICE_COOL ||raw===MAT.BUILD_OFFICE_LIT||raw===MAT.BUILD_NIGHT_FLICKER||raw===MAT.NEON_FACADE); const dark=!floor&&(raw===MAT.GREEBLE||raw===MAT.TRIM); const u0=dark?D0:H0, u1=dark?D1:H1; const uA=floor?0:(ax>=az?2:0), wA=floor?2:1; const uS=0.052, vS=0.048; /* LAMP tris are the authored slat quads. Sweeping them across the cyan column's vertical bars dashed every louvre. Pin to one lit texel (0.50 is a black gutter; 0.875/0.54 is base 44,153,174 NRE.a 169). Authored height is 1.19 with a 0.80 gap — ~3 px at span 180, which aliases into the stair-step on a receding wall. Grow to 2.1; verts are triangle-split so a hull vertex cannot move. */ if(win){ const y0=Math.min(v[oa+1],v[ob+1],v[oc+1]), y1=Math.max(v[oa+1],v[ob+1],v[oc+1]); const yc=(y0+y1)*0.5, half=Math.max((y1-y0)*0.5, 1.05); for(let k=0;k<3;k++){ const o=oa+k*12; v[o+1]=v[o+1]0.45 left CONC panes on the roof. Chimney CAPS are RUST/LAMP (+Y quads span<12) — rust + the industrial amber fill reads as a glowing disc. Windows only on near-vertical walls. A tessellated dome roof is also many small +Y tris: treating each as a chimney, then painting an 80-unit TRIM cylinder under it, turned the whole bowl into unlit metal (solid black). Cluster first; only an isolated island is a stack. */ if(typeof MAT!=='undefined'&&D.idx&&D.pos&&D.nrm){ const roofId=MAT.ROOF+1, trimId=MAT.TRIM+1, litId=MAT.BUILD_OFFICE_LIT+1; const ymax=D.bounds?D.bounds[1][1]:0; const bx0=D.bounds?D.bounds[0][0]:-50, bx1=D.bounds?D.bounds[1][0]:50; const bz0=D.bounds?D.bounds[0][2]:-50, bz1=D.bounds?D.bounds[1][2]:50; const meshXZ=Math.hypot(bx1-bx0, bz1-bz0); const seeds=[]; for(let t=0;tymax*0.28) seeds.push({x,y,z,span}); } const nS=seeds.length, par=new Int32Array(nS); for(let i=0;i{ while(par[i]!==i) i=par[i]=par[par[i]]; return i; }; for(let i=0;i{ const span=Math.hypot(G.x1-G.x0, G.z1-G.z0); if(span{ for(let k=0;kC.y-50&&yraw===MAT.CONC||raw===MAT.BUILD||raw===MAT.GREEBLE||raw===MAT.LAMP ||raw===MAT.BUILD_OFFICE_LIT||raw===MAT.BUILD_OFFICE_COOL||raw===MAT.BUILD_NIGHT_FLICKER ||raw===MAT.PRECAST_BAY||raw===MAT.NEON_FACADE; const uvS=typeof UVS==='number'?UVS:0.055; /* Before the remap below turns every BUILD facade into OFFICE_LIT — column pick must see the authored LAMP/BUILD split. */ if(authoredUV&&name==='mdlCivicBlock') civicPackedColumnUVs(v,iArr,ic); for(let i=0;i0.85; const mid=v[o+11]; const sgn=mid<0?-1:1; const raw=(Math.floor(Math.abs(mid))-1)|0; const capVert=nearCap(v[o],v[o+1],v[o+2]); /* Civic louvres used to stay MAT.LAMP. That tile was a radial orange blob, so a vertical pane read as a fat orb from the command camera. Cool office glass keeps the slat and the light. */ if(capVert&&!(facade&&raw===MAT.LAMP&&ny<=0.55)){ if(ny>0.45) v[o+11]=roofId*sgn; else if(winRaw(raw)||raw===MAT.RUST||raw===MAT.TRIM) v[o+11]=trimId*sgn; }else if(!facade&&(winRaw(raw)||((raw===MAT.RUST||raw===MAT.LAMP)&&ny>0.45))){ v[o+11]=roofId*sgn; // pitches, ridges, decks, interiors }else if(facade&&raw===MAT.LAMP){ v[o+11]=(MAT.BUILD_OFFICE_COOL+1)*sgn; }else if(facade&&(raw===MAT.CONC||raw===MAT.BUILD)){ v[o+11]=litId*sgn; // readable window band on walls } /* Legacy tiled atlas only. V2 samples a unique unwrap inside one packed cell; rewriting those UVs to world XZ (then clamping 0..1) stretched the civic window column across every deck and let mips bleed the neighbour cell's copper into the podium. Tiny crown islands still NaN the legacy cotangent — that path keeps XZ. */ if(!authoredUV&&ay>0.35){ const fin=(Math.floor(Math.abs(v[o+11]))-1)|0; if(fin===MAT.ROOF||fin===MAT.TRIM){ v[o+9]=v[o]*uvS; v[o+10]=v[o+2]*uvS; } } } } return {v:v, i:iArr, count:ic, bones:0, bounds:D.bounds||null,contactY:0,sourceContactY:contactY, uvMode:authoredUV&&D.uv2?'authored':(D.uvMode||'legacy')}; }