Death242 commited on
Commit
ffe67ed
·
verified ·
1 Parent(s): 4530d29

Static site

Browse files
Files changed (4) hide show
  1. index.html +2 -2
  2. static/main.js +2 -1
  3. static/npc.js +20 -18
  4. static/world.js +2 -2
index.html CHANGED
@@ -8,7 +8,7 @@
8
  <meta name="theme-color" content="#10131b">
9
  <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;800;900&family=Inter:wght@400;500;600;700&family=Noto+Kufi+Arabic:wght@700&display=swap" rel="stylesheet">
11
- <link rel="stylesheet" href="/static/style.css?v=1792928107">
12
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Crect width=%2764%27 height=%2764%27 rx=%2714%27 fill=%27%23050506%27/%3E%3Ccircle cx=%2732%27 cy=%2732%27 r=%2716%27 fill=%27none%27 stroke=%27%23fff%27 stroke-width=%275%27/%3E%3C/svg%3E">
13
  <script type="importmap">
14
  { "imports": {
@@ -202,6 +202,6 @@
202
  <div id="toast" role="status"></div>
203
  <noscript><p style="position:fixed;inset:auto 0 0;padding:16px;background:#111;color:#fff;text-align:center">VOID needs JavaScript for the 3D car.</p></noscript>
204
  <script src="/static/hub.js" data-app="void" defer></script>
205
- <script type="module" src="/static/main.js?v=1792928107"></script>
206
  </body>
207
  </html>
 
8
  <meta name="theme-color" content="#10131b">
9
  <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;800;900&family=Inter:wght@400;500;600;700&family=Noto+Kufi+Arabic:wght@700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="/static/style.css?v=1792935345">
12
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Crect width=%2764%27 height=%2764%27 rx=%2714%27 fill=%27%23050506%27/%3E%3Ccircle cx=%2732%27 cy=%2732%27 r=%2716%27 fill=%27none%27 stroke=%27%23fff%27 stroke-width=%275%27/%3E%3C/svg%3E">
13
  <script type="importmap">
14
  { "imports": {
 
202
  <div id="toast" role="status"></div>
203
  <noscript><p style="position:fixed;inset:auto 0 0;padding:16px;background:#111;color:#fff;text-align:center">VOID needs JavaScript for the 3D car.</p></noscript>
204
  <script src="/static/hub.js" data-app="void" defer></script>
205
+ <script type="module" src="/static/main.js?v=1792935345"></script>
206
  </body>
207
  </html>
static/main.js CHANGED
@@ -345,7 +345,7 @@ function drawGauge(S) {
345
  if (S.nitro > .1) { gx.font = `800 ${W2 * .05}px Inter, sans-serif`; gx.fillStyle = "#3df5ff"; gx.fillText("NITRO", c, c - W2 * .16); }
346
  }
347
  let wasWrecked = false, wreckPose = null; const wrecks = [];
348
- let fpsNow = 0;
349
  const perf = { t: 0, n: 0, pr: renderer.getPixelRatio(), max: renderer.getPixelRatio(), down: 0 };
350
  applySettings();
351
  let lastHud = 0, prevGear = 1, feedFlip = 0;
@@ -509,6 +509,7 @@ function frameLoop(fixed) {
509
  prevGear = S.gear;
510
  sound.car({ rpm: S.rpm, load: S.load, engine: mode === "build" || mode === "home" ? .4 : S.engine, thrust: S.thrust.power, speed: S.speed, inside: S.inside });
511
 
 
512
  composer.render();
513
  }
514
 
 
345
  if (S.nitro > .1) { gx.font = `800 ${W2 * .05}px Inter, sans-serif`; gx.fillStyle = "#3df5ff"; gx.fillText("NITRO", c, c - W2 * .16); }
346
  }
347
  let wasWrecked = false, wreckPose = null; const wrecks = [];
348
+ let fpsNow = 0, shadowN = 0;
349
  const perf = { t: 0, n: 0, pr: renderer.getPixelRatio(), max: renderer.getPixelRatio(), down: 0 };
350
  applySettings();
351
  let lastHud = 0, prevGear = 1, feedFlip = 0;
 
509
  prevGear = S.gear;
510
  sound.car({ rpm: S.rpm, load: S.load, engine: mode === "build" || mode === "home" ? .4 : S.engine, thrust: S.thrust.power, speed: S.speed, inside: S.inside });
511
 
512
+ if (++shadowN % ((W.QUALITY[W.quality] || {}).shEvery || 3) === 0) renderer.shadowMap.needsUpdate = true;
513
  composer.render();
514
  }
515
 
static/npc.js CHANGED
@@ -167,7 +167,8 @@ export function createLife(scene, roads, walkways, COARSE) {
167
  // ---- per frame ----
168
  const legQ = new THREE.Quaternion(), legM = new THREE.Matrix4(), legT = new THREE.Matrix4(), Z = new THREE.Vector3(0, 0, 1), squash = new THREE.Vector3(), m4 = new THREE.Matrix4(), q = new THREE.Quaternion(), e = new THREE.Euler(), Y = new THREE.Vector3(0, 1, 0), pos = new THREE.Vector3(), one = new THREE.Vector3(1, 1, 1), zero = new THREE.Vector3();
169
  const buckets = new Map(), tmp = {}, cell = new Map(), CELL = 30;
170
- let frameN = 0;
 
171
  function decide(c, k) { // at the crossing k: straight on, or turn onto the cross road
172
  const axis = c.axis, ok = segOK(axis, c.road, c.dir > 0 ? k : k - 1), oAxis = 1 - axis, oRoad = k;
173
  const opts = [];
@@ -187,7 +188,7 @@ export function createLife(scene, roads, walkways, COARSE) {
187
  function smash(c, vx, vz, power) {
188
  if (power < 14 && !c.wreck) {
189
  c.dent = Math.min(1, (c.dent || 0) + .45); c.stop = 5 + rnd() * 3; c.v = 0; c.col.multiplyScalar(.82); c.yaw += (rnd() - .5) * .5; c.x += vx * .05; c.z += vz * .05;
190
- for (let k = 0; k < 4; k++) { const di = debI++ % DEB, d = deb[di]; d.life = 1.5; d.p.set(c.x, .8, c.z); d.v.set((rnd() - .5) * 6, 2 + rnd() * 3, (rnd() - .5) * 6); d.w.set(rnd() * 8, rnd() * 8, rnd() * 8); debris.setColorAt(di, col.setHex(c.color)); }
191
  if (debris.instanceColor) debris.instanceColor.needsUpdate = true;
192
  return;
193
  }
@@ -197,13 +198,13 @@ export function createLife(scene, roads, walkways, COARSE) {
197
  : { t: 0, vx: vx * .45, vz: vz * .45, vy: 0, wx: 0, wy: (rnd() - .5) * 3, wz: 0 };
198
  c.y = 0; c.rx = 0; c.rz = 0; c.v = 0; c.turn = null;
199
  c.col.setHex(c.color).multiplyScalar(fly ? .28 : .45);
200
- for (let k = 0; k < (fly ? 14 : 6); k++) {
201
  const di = debI++ % DEB, d = deb[di]; d.life = 3 + rnd() * 2; d.p.set(c.x, 1, c.z); d.v.set(vx * .6 + (rnd() - .5) * 14, 3 + rnd() * 9, vz * .6 + (rnd() - .5) * 14); d.w.set(rnd() * 12, rnd() * 12, rnd() * 12); d.c = k % 3 ? c.color : 0x222222;
202
  debris.setColorAt(di, col.setHex(d.c));
203
  }
204
  if (debris.instanceColor) debris.instanceColor.needsUpdate = true;
205
  }
206
- const near = (x, z) => cell.get(Math.floor(x / CELL) * 7919 + Math.floor(z / CELL)) || [];
207
 
208
  const api = { detail: 1 };
209
  return {
@@ -215,20 +216,21 @@ export function createLife(scene, roads, walkways, COARSE) {
215
  update(dt, time, player, camPos) {
216
  frameN++;
217
  const px = player ? player.x : camPos ? camPos.x : 0, pz = player ? player.z : camPos ? camPos.z : 0;
218
- buckets.clear(); cell.clear();
219
  const cx0 = camPos ? camPos.x : px, cz0 = camPos ? camPos.z : pz, alt = camPos ? Math.max(0, camPos.y) : 0;
220
  const D = api.detail || 1, RC = Math.min(900, ((COARSE ? 260 : 380) + alt * 2) * D), RC2 = RC * RC, RP = Math.min(260, ((COARSE ? 90 : 140) + alt * .4) * D), RP2 = RP * RP;
221
  for (const m of meshes) m.k = 0;
222
- for (const c of cars) {
223
  if (c.wreck || c.turn) continue;
224
  const key = c.axis * 100000 + c.road * 1000 + (c.dir > 0 ? 500 : 0) + Math.round(c.lane * 10);
225
  let b = buckets.get(key); if (!b) buckets.set(key, b = []); b.push(c);
226
  }
227
- for (const b of buckets.values()) { b.sort((a, d) => a.s * a.dir - d.s * d.dir); for (let k = 0; k < b.length; k++) b[k].lead = b[k + 1] || null; }
228
  for (const c of cars) {
229
  const far = (Math.abs(c.x - px) > 450 || Math.abs(c.z - pz) > 450) && (Math.abs(c.x - cx0) > RC || Math.abs(c.z - cz0) > RC);
230
- if (far && (frameN + c.n) % 6) { continue; }
231
- const h = far ? dt * 6 : dt;
 
232
  if (c.wreck) { // a smashed car tumbles, bounces, then lies there smoking until it is towed
233
  const w = c.wreck; w.t += h;
234
  if (c.y > 0 || w.vy > 0) { w.vy -= 9.81 * h; c.y = Math.max(0, c.y + w.vy * h); c.rx += w.wx * h; c.rz += w.wz * h; c.yaw += w.wy * h; if (c.y === 0) { w.vy = -w.vy * .3; w.wx *= .5; w.wz *= .5; w.wy *= .5; if (Math.abs(w.vy) < 1) w.vy = 0; } }
@@ -260,21 +262,21 @@ export function createLife(scene, roads, walkways, COARSE) {
260
  c.v += Math.max(-9 * h, Math.min(3 * h, want - c.v));
261
  if (!c.turn) { c.s += c.v * c.dir * h; posOf(c); }
262
  }
263
- const key = Math.floor(c.x / CELL) * 7919 + Math.floor(c.z / CELL); let b = cell.get(key); if (!b) cell.set(key, b = []); b.push(c);
264
- const lean = c.turn ? .05 : 0;
265
- e.set(c.rx, -c.yaw, c.rz + lean, "YXZ"); q.setFromEuler(e); m4.compose(pos.set(c.x, c.y, c.z), q, squash.set(1, c.crush || 1 - (c.dent || 0) * .07, 1));
266
  const ddx = c.x - cx0, ddz = c.z - cz0;
267
- if (ddx * ddx + ddz * ddz < RC2) { const M = meshes[c.ti], k = M.k++; M.body.setMatrixAt(k, m4); M.body.setColorAt(k, c.col); M.lights.setMatrixAt(k, c.wreck ? m4.compose(pos, q, zero) : m4); }
 
 
268
  }
269
  for (const m of meshes) { m.body.count = m.lights.count = m.k; m.body.instanceMatrix.needsUpdate = m.lights.instanceMatrix.needsUpdate = true; if (m.body.instanceColor) m.body.instanceColor.needsUpdate = true; }
270
- // debris
271
- for (let k = 0; k < DEB; k++) {
272
  const d = deb[k];
273
- if (d.life > 0) { d.life -= dt; d.v.y -= 9.81 * dt; d.p.addScaledVector(d.v, dt); if (d.p.y < .1) { d.p.y = .1; d.v.multiplyScalar(.5); d.v.y = Math.abs(d.v.y) * .4; d.w.multiplyScalar(.6); } d.r.x += d.w.x * dt; d.r.y += d.w.y * dt; d.r.z += d.w.z * dt; q.setFromEuler(d.r); m4.compose(d.p, q, one); }
274
  else m4.compose(pos.set(0, -50, 0), q, zero);
275
  debris.setMatrixAt(k, m4); glassBits.setMatrixAt(k, d.life > 0 ? m4.compose(pos.copy(d.p).add({ x: .3, y: .2, z: -.2 }), q, one) : m4);
276
  }
277
- debris.instanceMatrix.needsUpdate = glassBits.instanceMatrix.needsUpdate = true;
278
  // people
279
  let pk = 0;
280
  for (let n = 0; n < people.length; n++) {
@@ -307,7 +309,7 @@ export function createLife(scene, roads, walkways, COARSE) {
307
  e.set(lie * Math.PI / 2 * .95, yaw + (p.fall ? p.fall.rot : 0), 0, "YXZ"); q.setFromEuler(e); m4.compose(pos.set(x, y, z), q, one);
308
  bodies.setMatrixAt(pk, m4); heads.setMatrixAt(pk, m4); hair.setMatrixAt(pk, m4);
309
  const swing = p.wait > 0 || p.down > 0 ? 0 : Math.sin(walk) * .5;
310
- for (const [leg, sgn, off] of [[legL, 1, .1], [legR, -1, -.1]]) { legQ.setFromAxisAngle(Z, swing * sgn); legM.compose(pos.set(0, .86, off), legQ, one); leg.setMatrixAt(pk, legT.copy(m4).multiply(legM)); }
311
  bodies.setColorAt(pk, p.shirt); heads.setColorAt(pk, p.skin); legL.setColorAt(pk, p.pants); legR.setColorAt(pk, p.pants); hair.setColorAt(pk, p.hairC); pk++;
312
  }
313
  }
 
167
  // ---- per frame ----
168
  const legQ = new THREE.Quaternion(), legM = new THREE.Matrix4(), legT = new THREE.Matrix4(), Z = new THREE.Vector3(0, 0, 1), squash = new THREE.Vector3(), m4 = new THREE.Matrix4(), q = new THREE.Quaternion(), e = new THREE.Euler(), Y = new THREE.Vector3(0, 1, 0), pos = new THREE.Vector3(), one = new THREE.Vector3(1, 1, 1), zero = new THREE.Vector3();
169
  const buckets = new Map(), tmp = {}, cell = new Map(), CELL = 30;
170
+ let frameN = 0, debLive = 1;
171
+ const LEGS = [[null, 1, .1], [null, -1, -.1]];
172
  function decide(c, k) { // at the crossing k: straight on, or turn onto the cross road
173
  const axis = c.axis, ok = segOK(axis, c.road, c.dir > 0 ? k : k - 1), oAxis = 1 - axis, oRoad = k;
174
  const opts = [];
 
188
  function smash(c, vx, vz, power) {
189
  if (power < 14 && !c.wreck) {
190
  c.dent = Math.min(1, (c.dent || 0) + .45); c.stop = 5 + rnd() * 3; c.v = 0; c.col.multiplyScalar(.82); c.yaw += (rnd() - .5) * .5; c.x += vx * .05; c.z += vz * .05;
191
+ debLive = 1; for (let k = 0; k < 4; k++) { const di = debI++ % DEB, d = deb[di]; d.life = 1.5; d.p.set(c.x, .8, c.z); d.v.set((rnd() - .5) * 6, 2 + rnd() * 3, (rnd() - .5) * 6); d.w.set(rnd() * 8, rnd() * 8, rnd() * 8); debris.setColorAt(di, col.setHex(c.color)); }
192
  if (debris.instanceColor) debris.instanceColor.needsUpdate = true;
193
  return;
194
  }
 
198
  : { t: 0, vx: vx * .45, vz: vz * .45, vy: 0, wx: 0, wy: (rnd() - .5) * 3, wz: 0 };
199
  c.y = 0; c.rx = 0; c.rz = 0; c.v = 0; c.turn = null;
200
  c.col.setHex(c.color).multiplyScalar(fly ? .28 : .45);
201
+ debLive = 1; for (let k = 0; k < (fly ? 14 : 6); k++) {
202
  const di = debI++ % DEB, d = deb[di]; d.life = 3 + rnd() * 2; d.p.set(c.x, 1, c.z); d.v.set(vx * .6 + (rnd() - .5) * 14, 3 + rnd() * 9, vz * .6 + (rnd() - .5) * 14); d.w.set(rnd() * 12, rnd() * 12, rnd() * 12); d.c = k % 3 ? c.color : 0x222222;
203
  debris.setColorAt(di, col.setHex(d.c));
204
  }
205
  if (debris.instanceColor) debris.instanceColor.needsUpdate = true;
206
  }
207
+ const NONE = [], near = (x, z) => cell.get(Math.floor(x / CELL) * 7919 + Math.floor(z / CELL)) || NONE;
208
 
209
  const api = { detail: 1 };
210
  return {
 
216
  update(dt, time, player, camPos) {
217
  frameN++;
218
  const px = player ? player.x : camPos ? camPos.x : 0, pz = player ? player.z : camPos ? camPos.z : 0;
219
+ const relead = frameN % 4 === 1; if (relead) for (const b of buckets.values()) b.length = 0; for (const b of cell.values()) b.length = 0;
220
  const cx0 = camPos ? camPos.x : px, cz0 = camPos ? camPos.z : pz, alt = camPos ? Math.max(0, camPos.y) : 0;
221
  const D = api.detail || 1, RC = Math.min(900, ((COARSE ? 260 : 380) + alt * 2) * D), RC2 = RC * RC, RP = Math.min(260, ((COARSE ? 90 : 140) + alt * .4) * D), RP2 = RP * RP;
222
  for (const m of meshes) m.k = 0;
223
+ if (relead) for (const c of cars) {
224
  if (c.wreck || c.turn) continue;
225
  const key = c.axis * 100000 + c.road * 1000 + (c.dir > 0 ? 500 : 0) + Math.round(c.lane * 10);
226
  let b = buckets.get(key); if (!b) buckets.set(key, b = []); b.push(c);
227
  }
228
+ if (relead) for (const b of buckets.values()) if (b.length) { b.sort((a, d) => a.s * a.dir - d.s * d.dir); for (let k = 0; k < b.length; k++) b[k].lead = b[k + 1] || null; }
229
  for (const c of cars) {
230
  const far = (Math.abs(c.x - px) > 450 || Math.abs(c.z - pz) > 450) && (Math.abs(c.x - cx0) > RC || Math.abs(c.z - cz0) > RC);
231
+ const vfar = far && (Math.abs(c.x - px) > 1200 || Math.abs(c.z - pz) > 1200), every = vfar ? 15 : 6; // far away nobody sees them: move them in big, rare steps
232
+ if (far && (frameN + c.n) % every) { continue; }
233
+ const h = far ? dt * every : dt;
234
  if (c.wreck) { // a smashed car tumbles, bounces, then lies there smoking until it is towed
235
  const w = c.wreck; w.t += h;
236
  if (c.y > 0 || w.vy > 0) { w.vy -= 9.81 * h; c.y = Math.max(0, c.y + w.vy * h); c.rx += w.wx * h; c.rz += w.wz * h; c.yaw += w.wy * h; if (c.y === 0) { w.vy = -w.vy * .3; w.wx *= .5; w.wz *= .5; w.wy *= .5; if (Math.abs(w.vy) < 1) w.vy = 0; } }
 
262
  c.v += Math.max(-9 * h, Math.min(3 * h, want - c.v));
263
  if (!c.turn) { c.s += c.v * c.dir * h; posOf(c); }
264
  }
265
+ if (!far) { const key = Math.floor(c.x / CELL) * 7919 + Math.floor(c.z / CELL); let b = cell.get(key); if (!b) cell.set(key, b = []); b.push(c); }
 
 
266
  const ddx = c.x - cx0, ddz = c.z - cz0;
267
+ if (ddx * ddx + ddz * ddz < RC2) {
268
+ e.set(c.rx, -c.yaw, c.rz + (c.turn ? .05 : 0), "YXZ"); q.setFromEuler(e); m4.compose(pos.set(c.x, c.y, c.z), q, squash.set(1, c.crush || 1 - (c.dent || 0) * .07, 1));
269
+ const M = meshes[c.ti], k = M.k++; M.body.setMatrixAt(k, m4); M.body.setColorAt(k, c.col); M.lights.setMatrixAt(k, c.wreck ? m4.compose(pos, q, zero) : m4); }
270
  }
271
  for (const m of meshes) { m.body.count = m.lights.count = m.k; m.body.instanceMatrix.needsUpdate = m.lights.instanceMatrix.needsUpdate = true; if (m.body.instanceColor) m.body.instanceColor.needsUpdate = true; }
272
+ // debris (skipped once every piece has landed and been hidden)
273
+ if (debLive) { debLive = 0; for (let k = 0; k < DEB; k++) {
274
  const d = deb[k];
275
+ if (d.life > 0) { debLive++; d.life -= dt; d.v.y -= 9.81 * dt; d.p.addScaledVector(d.v, dt); if (d.p.y < .1) { d.p.y = .1; d.v.multiplyScalar(.5); d.v.y = Math.abs(d.v.y) * .4; d.w.multiplyScalar(.6); } d.r.x += d.w.x * dt; d.r.y += d.w.y * dt; d.r.z += d.w.z * dt; q.setFromEuler(d.r); m4.compose(d.p, q, one); }
276
  else m4.compose(pos.set(0, -50, 0), q, zero);
277
  debris.setMatrixAt(k, m4); glassBits.setMatrixAt(k, d.life > 0 ? m4.compose(pos.copy(d.p).add({ x: .3, y: .2, z: -.2 }), q, one) : m4);
278
  }
279
+ debris.instanceMatrix.needsUpdate = glassBits.instanceMatrix.needsUpdate = true; if (debLive) debLive++; else debLive = 0; }
280
  // people
281
  let pk = 0;
282
  for (let n = 0; n < people.length; n++) {
 
309
  e.set(lie * Math.PI / 2 * .95, yaw + (p.fall ? p.fall.rot : 0), 0, "YXZ"); q.setFromEuler(e); m4.compose(pos.set(x, y, z), q, one);
310
  bodies.setMatrixAt(pk, m4); heads.setMatrixAt(pk, m4); hair.setMatrixAt(pk, m4);
311
  const swing = p.wait > 0 || p.down > 0 ? 0 : Math.sin(walk) * .5;
312
+ LEGS[0][0] = legL; LEGS[1][0] = legR; for (const [leg, sgn, off] of LEGS) { legQ.setFromAxisAngle(Z, swing * sgn); legM.compose(pos.set(0, .86, off), legQ, one); leg.setMatrixAt(pk, legT.copy(m4).multiply(legM)); }
313
  bodies.setColorAt(pk, p.shirt); heads.setColorAt(pk, p.skin); legL.setColorAt(pk, p.pants); legR.setColorAt(pk, p.pants); hair.setColorAt(pk, p.hairC); pk++;
314
  }
315
  }
static/world.js CHANGED
@@ -26,7 +26,7 @@ export const PAD_R = 28;
26
 
27
  export async function createWorld(canvas, COARSE) {
28
  const renderer = new THREE.WebGLRenderer({ canvas, antialias: false, powerPreference: "high-performance", stencil: false });
29
- renderer.shadowMap.enabled = !COARSE; renderer.shadowMap.type = THREE.PCFShadowMap;
30
  renderer.localClippingEnabled = true;
31
  renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2;
32
  renderer.setPixelRatio(Math.min(devicePixelRatio || 1, COARSE ? 1.5 : 1.75));
@@ -148,7 +148,7 @@ export async function createWorld(canvas, COARSE) {
148
  captureCity();
149
  }
150
  // quality: Low / Medium / High / Ultra (the adaptive system steps it down by itself when frames run slow)
151
- const QUALITY = { low: { pr: .75, shadow: 0, bloom: false, grade: false, msaa: 0, bres: .25 }, medium: { pr: 1, shadow: 1024, bloom: true, grade: true, msaa: 0, bres: .3 }, high: { pr: 1.5, shadow: 2048, bloom: true, grade: true, msaa: 4, bres: .5 }, ultra: { pr: 2, shadow: 4096, bloom: true, grade: true, msaa: 4, bres: .5 } };
152
  let quality = "high"; const W0 = { bres: .5 };
153
  function setQuality(q) {
154
  const Q = QUALITY[q] || QUALITY.high; quality = q;
 
26
 
27
  export async function createWorld(canvas, COARSE) {
28
  const renderer = new THREE.WebGLRenderer({ canvas, antialias: false, powerPreference: "high-performance", stencil: false });
29
+ renderer.shadowMap.enabled = !COARSE; renderer.shadowMap.type = THREE.PCFShadowMap; renderer.shadowMap.autoUpdate = false; // redrawn every few frames (main.js): it was half of every frame
30
  renderer.localClippingEnabled = true;
31
  renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2;
32
  renderer.setPixelRatio(Math.min(devicePixelRatio || 1, COARSE ? 1.5 : 1.75));
 
148
  captureCity();
149
  }
150
  // quality: Low / Medium / High / Ultra (the adaptive system steps it down by itself when frames run slow)
151
+ const QUALITY = { low: { pr: .75, shadow: 0, bloom: false, grade: false, msaa: 0, bres: .25 }, medium: { pr: 1, shadow: 1024, bloom: true, grade: true, msaa: 0, bres: .3, shEvery: 4 }, high: { pr: 1.25, shadow: 2048, bloom: true, grade: true, msaa: 4, bres: .5, shEvery: 3 }, ultra: { pr: 2, shadow: 4096, bloom: true, grade: true, msaa: 4, bres: .5, shEvery: 2 } };
152
  let quality = "high"; const W0 = { bres: .5 };
153
  function setQuality(q) {
154
  const Q = QUALITY[q] || QUALITY.high; quality = q;