massfront-playtest / src /engine /models-skyline.js
CREATORJD's picture
Publish MASSFRONT web 1.33.33 (part 3)
3597f5a verified
Raw
History Blame Contribute Delete
10.2 kB
;
;
/* ============================================================================
SKYLINE LANDMARKS — large-scale worldbuilding verticals
----------------------------------------------------------------------------
The tallest thing any battlefield owned was an 87-unit derelict block — the
whole "city" sat below the height of one gameplay building's footprint, so
districts read as crates, never as a metropolis a war had hollowed out.
These are true skyscrapers: 260–335 world units, authored at plot scale
(≈50-unit footprint, the planner's kind-5 anchor lot), with LIT_WIN /
COOL_WIN strips the night shader already knows how to sell.
Three silhouettes, deliberately distinct at command zoom:
mdlSkyTower1 — stepped brutalist monolith (human worlds)
mdlSkyTower2 — twin spires joined by a high skybridge
mdlSkySpire — an alien crystalline monolith for the foreign worlds:
grown, not built, lit from inside through CRYST facets.
MeshBuilder convention notes (learned the hard way elsewhere): box() takes
the BASE y, not the centre; the primitive patch resolves materials from the
exact palette-constant array passed, so WALL / LIT_WIN / ENERGY carry their
semantics; tri() is NOT patched, so crystalSpike inherits m.mat(MAT.CRYST).
============================================================================ */
/* Vertical window strips proud of each face, floor-banded. LIT_WIN carries
warm interiors, COOL_WIN cold service floors, and an unlit band every fifth
row keeps the tower from reading as a paper lantern. y0/y1 are world bases. */
function skyStrips(m,cx,cz,w,d,y0,y1,step,warm){
const half=w/2, dh=d/2;
let row=0;
for(let y=y0;y<y1-step*0.45;y+=step,row++){
const litC=(row%5===4)?WALL_D:(warm&&(row%2===0)?LIT_WIN:COOL_WIN);
const hh=step*0.44;
for(const sd of [-1,1]){
m.box(cx+sd*(half+0.55), y, cz, 1.1, hh, d*0.60, litC);
m.box(cx, y, cz+sd*(dh+0.55), w*0.60, hh, 1.1, litC);
}
}
}
function mdlSkyTower1(){
const m=MB();
/* Stepped monolith: plinth, three setback tiers, service crown, mast. */
m.box(0, 0,0, 54,12,54, CONC_D); // plinth 0–12
m.box(0, 12,0, 46,10,46, CONC); // podium 12–22
m.box(0, 22,0, 40,96,40, WALL); // tier 1 22–118
skyStrips(m,0,0,40,40,30,112,11,true);
m.box(0,118,0, 44,5,44, ROOFC); // setback ledge
m.box(0,123,0, 32,92,32, WALL_D); // tier 2 123–215
skyStrips(m,0,0,32,32,130,208,11,true);
m.box(0,215,0, 35,5,35, ROOFC);
m.box(0,220,0, 23,74,23, WALL); // tier 3 220–294
skyStrips(m,0,0,23,23,226,288,11,false);
m.box(0,294,0, 26,5,26, ROOF_T); // crown ring
m.box(0,299,0, 13,11,13, MET_D); // plant room
for(const sd of [-1,1]) m.box(sd*8.4,299,0, 2.4,9,2.4, MET_D);
m.cyl(0,310,0, 1.3,0.7, 22, 6, MET_L); // mast 310–332
m.box(0,332,0, 2.2,2.2,2.2, HOT); // aviation beacon
/* Corner piers give the monolith its brutalist read from directly above. */
for(const sx of [-1,1]) for(const sz of [-1,1]){
m.box(sx*20.4,12,sz*20.4, 4.4,204,4.4, CONC_D);
m.box(sx*20.4,216,sz*20.4, 3.4,24,3.4, CONC);
}
return m.build();
}
function mdlSkyTower2(){
const m=MB();
/* Twin spires + high skybridge — a silhouette no gameplay structure has. */
m.box(0,0,0, 56,10,40, CONC_D); // shared plinth
const twr=(cx,H,warm)=>{
m.box(cx,10,0, 20,H,20, WALL);
skyStrips(m,cx,0,20,20,18,10+H-8,10.5,warm);
m.box(cx,10+H,0, 22,5,22, ROOFC);
m.box(cx,15+H,0, 9,8,9, MET_D);
m.cyl(cx,23+H,0, 1.1,0.6, 15, 6, MET_L);
m.box(cx,38+H,0, 1.8,1.8,1.8, HOT); // aviation beacon
};
twr(-16,230,true); twr(16,200,false); // tops 268 / 238
m.box(0,152,0, 14,7,10, WALL_D); // skybridge deck
m.box(0,159,0, 14,2,10, ROOF_T);
skyStrips(m,0,0,12,8,153,158,7,true);
for(const sd of [-1,1]) m.box(sd*6.2,144,0, 1.6,8,1.6, MET_D); // hangers
return m.build();
}
function mdlSkySpire(){
const m=MB(); m.mat(MAT.CRYST);
/* Alien monolith: one dominant crystal blade with satellite spires, grown
through a fractured plinth. Authored near-white — the render tint owns
the world's hue, so one model serves every foreign biome. crystalSpike
emits through tri(), which the material patch leaves alone, so the CRYST
transmission path lights the whole growth from inside. */
const BASE=C(150,140,185), MID=C(210,205,240), TIP=C(255,255,255);
crystalSpike(m, 0.0, 0.0, 11.5, 320, 4.0, 2.2, 0.35, BASE,MID,TIP);
crystalSpike(m, 13.5, 4.5, 6.8, 195, 7.5, 3.5, 1.75, BASE,MID,TIP);
crystalSpike(m,-11.5, 8.0, 5.9, 152, -6.5, 4.5, 0.95, BASE,MID,TIP);
crystalSpike(m, -7.0,-12.5, 5.2, 128, -4.0, -7.0, 2.45, BASE,MID,TIP);
crystalSpike(m, 10.0,-9.5, 4.4, 98, 5.5, -5.5, 3.05, BASE,MID,TIP);
/* Fractured plinth the growth erupted through. */
m.box(0, 0,0, 34,14,34, CONC_D);
m.box(0,14,0, 26,4,26, WALL_D);
for(let k=0;k<6;k++){
const a=k/6*Math.PI*2, r=15.5;
m.box(Math.cos(a)*r, 0, Math.sin(a)*r, 5.5,9,5.5, CONC, a);
}
m.mat(MAT.CRYST);
for(let k=0;k<5;k++){ // shard ring debris
const a=k/5*Math.PI*2+0.5;
crystalSpike(m, Math.cos(a)*21, Math.sin(a)*21, 2.6, 26+((k*13)%18), Math.cos(a)*3, Math.sin(a)*3, k*1.3, BASE,MID,TIP);
}
/* Luminous seams RIDE THE SURFACE of the blade — buried inside the crystal
they were swallowed whole at night. Four ribs lean with the taper, plus
two halo rings and vent glows at the root: the cyan structure lines that
make the monolith read as ALIVE from command zoom after dark. */
for(let k=0;k<4;k++){
const a=k/4*Math.PI*2+0.4;
const rx=Math.cos(a), rz=Math.sin(a);
for(let seg=0;seg<5;seg++){
const y0=18+seg*30, rr2=10.8*(1-(y0+15)/330); // hug the taper
m.box(rx*(rr2+1.3), y0, rz*(rr2+1.3), 2.6, 26, 2.6, ENERGY);
}
}
m.box(0, 186, 0, 12.5, 4.5, 12.5, ENERGY); // high halo rings
m.box(0, 122, 0, 16.5, 3.5, 16.5, ENERGY);
for(let k=0;k<6;k++){ // root vents
const a=k/6*Math.PI*2+0.25;
m.box(Math.cos(a)*13.5, 17, Math.sin(a)*13.5, 3.4, 5, 3.4, ENERGY);
}
return m.build();
}
/* ---------------------------------------------------------------------------
GROUND SKIRT — the piece that makes a structure BELONG to the ground.
A model box meeting graded terrain at a perfect 90 degree line is exactly
what reads as "pasted on": real buildings sit in a berm of spoil, drifted
soil and broken slab. This is a two-band flare drawn per structure, tinted
with the BIOME's ground colour (not the model's), so a tower on arctic ice
is skirted in ice and the same tower on ashland sits in slag.
Built as a rectangle-to-rectangle loop so the instance's cross-axis lane
can stretch it to a footprint's real aspect: scale = width, wide = depth.
Four quads per band covers corners too, because the loop is convex.
--------------------------------------------------------------------------- */
function skirtBand(m,r0,y0,r1,y1,col){
const A=[[-r0,-r0],[r0,-r0],[r0,r0],[-r0,r0]];
const B=[[-r1,-r1],[r1,-r1],[r1,r1],[-r1,r1]];
for(let k=0;k<4;k++){
const j=(k+1)&3;
m.quad([A[k][0],y0,A[k][1]],[A[j][0],y0,A[j][1]],
[B[j][0],y1,B[j][1]],[B[k][0],y1,B[k][1]],col);
}
}
function mdlBaseSkirt(){
const m=MB();
/* Band 1: the structural berm, proud of the wall and steep.
Band 2: the long soil feather that dies into the terrain. */
skirtBand(m, 0.50, 0.075, 0.70,-0.010, CONC_D);
skirtBand(m, 0.70,-0.010, 0.98,-0.055, C(126,120,110));
/* Spoil heaps break the straight contact line. Sizes and offsets are
deliberately uneven — a regular ring of identical lumps reads as a
decoration, an irregular one reads as debris. */
const L=[[0.62,0.18,0.16,0.055],[-0.55,-0.42,0.13,0.042],[0.10,-0.66,0.18,0.048],
[-0.68,0.30,0.11,0.036],[0.48,0.60,0.14,0.040],[-0.22,0.70,0.10,0.030],
[0.72,-0.34,0.12,0.034],[-0.44,0.06,0.09,0.026]];
for(const [lx,lz,r,h] of L) m.box(lx,-0.012,lz, r,h,r*0.82, C(138,132,122), (lx*7+lz*13));
return m.build();
}
/* Partial collapse for the skyline anchors: the podium and a sheared stump of
the first tier, left standing in its own wreckage. Swapped in at half
health so a skyscraper visibly COMES DOWN in stages instead of blinking
out of existence when its bar empties. */
function mdlSkyStump(){
const m=MB();
m.box(0, 0,0, 54,12,54, CONC_D);
m.box(0,12,0, 46,10,46, CONC);
m.box(0,22,0, 40,44,40, WALL); // sheared at 66
skyStrips(m,0,0,40,40,30,58,11,false);
/* A shear is a jagged line, never a clean lid: broken floor plates at
different heights, with rebar stubs standing proud of them. */
const P=[[-12,66,-11,15,5,14],[9,66,10,17,9,15],[-8,66,13,13,3,12],[14,66,-9,11,7,11]];
for(const [x,y,z,w,h,d] of P) m.box(x,y,z, w,h,d, CONC_D);
for(let k=0;k<7;k++){
const a=k/7*Math.PI*2+0.4, r=13+((k*5)%9);
m.box(Math.cos(a)*r, 70, Math.sin(a)*r, 0.9, 5+((k*3)%7), 0.9, MET_D);
}
for(const sx of [-1,1]) for(const sz of [-1,1])
m.box(sx*20.4,12,sz*20.4, 4.4,52,4.4, CONC_D); // piers survive the shear
return m.build();
}
/* REGISTRATION BY TAKEOVER (AGENTS.md): wrap initModels so both the boot path
and the glrecover.js context-loss rebuild pick the skyline up, with zero
edits inside models.js itself. */
const skyBaseInitModels=initModels;
initModels=function(){
skyBaseInitModels();
FX.sky1=new InstMesh(gl,mdlSkyTower1(),48);
FX.sky2=new InstMesh(gl,mdlSkyTower2(),48);
FX.skyA=new InstMesh(gl,mdlSkySpire(),48);
FX.skyS=new InstMesh(gl,mdlSkyStump(),48);
/* Every relic and every player structure can carry a skirt, so the cap is
sized to the whole visible field, not to the skyline. */
FX.skirt=new InstMesh(gl,mdlBaseSkirt(),1200);
};