File size: 10,215 Bytes
b860a9f
3597f5a
b860a9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74b2f87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b860a9f
 
 
 
 
 
 
 
 
74b2f87
 
 
 
b860a9f
3597f5a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
;
;
/* ============================================================================
   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);
};