@adatechnology/game-urban 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/index.d.ts +343 -2
- package/dist/index.global.js +536 -14
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +544 -17
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -215,6 +215,90 @@ function drawStreetGrid(b, block, count, width, options) {
|
|
|
215
215
|
|
|
216
216
|
// src/block.ts
|
|
217
217
|
import { createCityBuilding } from "@adatechnology/game-floorplan";
|
|
218
|
+
|
|
219
|
+
// src/tower.ts
|
|
220
|
+
var DEFAULT_TOWER_PRESETS = [
|
|
221
|
+
{ id: "office", weight: 0.6, storeyHeight: 4, mechanicalEvery: 20, glass: true, color: 3955315 },
|
|
222
|
+
{ id: "residential", weight: 0.4, storeyHeight: 3.1, mechanicalEvery: 25, glass: false }
|
|
223
|
+
];
|
|
224
|
+
var towerPresetOf = (id, weight, layout, extra = {}) => ({ id, weight, storeyHeight: layout.pitch, mechanicalEvery: 20, glass: layout.facade === "curtain", ...extra });
|
|
225
|
+
var TOWER_DEFAULTS = { minHeight: 60, maxStoreys: 60, mast: 0.06, lit: 0.12 };
|
|
226
|
+
var PODIUM = 1.5;
|
|
227
|
+
var PLANT = 2895667;
|
|
228
|
+
var PODIUM_COLOR = 2764081;
|
|
229
|
+
var PARAPET = 2763308;
|
|
230
|
+
var MAST = 10133670;
|
|
231
|
+
var MAX_MAST = 25;
|
|
232
|
+
var GLASS_COLUMN = 1.6;
|
|
233
|
+
var MASONRY_COLUMN = 3;
|
|
234
|
+
function pickTowerPreset(b, presets) {
|
|
235
|
+
const total = presets.reduce((sum2, p) => sum2 + p.weight, 0);
|
|
236
|
+
let r = b.random() * total;
|
|
237
|
+
for (const preset of presets) {
|
|
238
|
+
r -= preset.weight;
|
|
239
|
+
if (r < 0) return preset;
|
|
240
|
+
}
|
|
241
|
+
return presets[presets.length - 1];
|
|
242
|
+
}
|
|
243
|
+
var towerStoreys = (height, preset) => Math.max(3, Math.round(height / preset.storeyHeight));
|
|
244
|
+
function drawTower(b, request, options = {}) {
|
|
245
|
+
const { x, z, width: w, depth: d, preset } = request;
|
|
246
|
+
const storeys = towerStoreys(request.height, preset);
|
|
247
|
+
const h = preset.storeyHeight;
|
|
248
|
+
const podium = h * PODIUM;
|
|
249
|
+
const skin = preset.color ?? request.color;
|
|
250
|
+
const mechanical = /* @__PURE__ */ new Set();
|
|
251
|
+
if (preset.mechanicalEvery) {
|
|
252
|
+
for (let k = preset.mechanicalEvery; k < storeys - 1; k += preset.mechanicalEvery) if (storeys - 1 - k >= preset.mechanicalEvery / 2) mechanical.add(k);
|
|
253
|
+
}
|
|
254
|
+
mechanical.add(storeys - 1);
|
|
255
|
+
b.box(x, 0, z, w + 0.6, podium, d + 0.6, PODIUM_COLOR);
|
|
256
|
+
const shaftTop = (storeys - 1) * h;
|
|
257
|
+
b.box(x, podium, z, w, shaftTop - podium + (podium - h), d, skin);
|
|
258
|
+
for (const k of mechanical) {
|
|
259
|
+
if (k === storeys - 1) b.box(x, k * h + (podium - h), z, w * 0.82, h, d * 0.82, PLANT);
|
|
260
|
+
else b.box(x, k * h + (podium - h), z, w + 0.4, h, d + 0.4, PLANT);
|
|
261
|
+
}
|
|
262
|
+
const top = storeys * h + (podium - h);
|
|
263
|
+
b.box(x, top, z, w * 0.82 + 0.3, 0.35, d * 0.82 + 0.3, PARAPET);
|
|
264
|
+
const mast = Math.min(MAX_MAST, top * (options.mast ?? TOWER_DEFAULTS.mast));
|
|
265
|
+
if (mast > 0.5) b.cylinder(x, top + 0.35, z, 0.12, 0.2, mast, MAST, -1, 6);
|
|
266
|
+
const column = preset.glass ? GLASS_COLUMN : MASONRY_COLUMN;
|
|
267
|
+
const winHeight = preset.glass ? h * 0.62 : 1.5;
|
|
268
|
+
const winWidth = preset.glass ? column * 0.86 : 1.3;
|
|
269
|
+
const faces = [[0, d / 2 + 0.03, w, 0], [0, -d / 2 - 0.03, w, Math.PI], [w / 2 + 0.03, 0, d, Math.PI / 2], [-w / 2 - 0.03, 0, d, -Math.PI / 2]];
|
|
270
|
+
for (const [ox, oz, faceWidth, ry] of faces) {
|
|
271
|
+
const columns = Math.max(1, Math.floor(faceWidth / column));
|
|
272
|
+
for (let storey = 1; storey < storeys; storey++) {
|
|
273
|
+
if (mechanical.has(storey)) continue;
|
|
274
|
+
const y = storey * h + (podium - h) + h * (preset.glass ? 0.2 : 0.35);
|
|
275
|
+
for (let c = 0; c < columns; c++) {
|
|
276
|
+
const u = (c - (columns - 1) / 2) * column;
|
|
277
|
+
const lit = b.random() < request.lit;
|
|
278
|
+
(lit ? b.windows.lit : b.windows.dark).push({ x: ox ? x + ox : x + u, y, z: ox ? z + u : z + oz, ry, width: winWidth, height: winHeight, isLit: lit });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return { x, z, width: w, depth: d, height: top + 0.35 + (mast > 0.5 ? mast : 0), color: request.color };
|
|
283
|
+
}
|
|
284
|
+
function createTowerDrawer(towers, plain) {
|
|
285
|
+
const minHeight = towers.minHeight ?? TOWER_DEFAULTS.minHeight;
|
|
286
|
+
const presets = towers.presets ?? DEFAULT_TOWER_PRESETS;
|
|
287
|
+
return (b, x, z, width, depth, height, color, o = {}) => {
|
|
288
|
+
if (height < minHeight || presets.length === 0) return plain(b, x, z, width, depth, height, color, o);
|
|
289
|
+
const preset = (o.preset ? presets.find((p) => p.id === o.preset) : void 0) ?? pickTowerPreset(b, presets);
|
|
290
|
+
const top = (preset.maxStoreys ?? towers.maxStoreys ?? TOWER_DEFAULTS.maxStoreys) * preset.storeyHeight;
|
|
291
|
+
const bottom = (preset.minStoreys ?? 0) * preset.storeyHeight;
|
|
292
|
+
const request = { x, z, width, depth, height: Math.min(Math.max(height, bottom), top), color, preset, lit: o.lit ?? towers.lit ?? TOWER_DEFAULTS.lit, ...o.use ? { use: o.use } : {}, ...o.storeys ? { storeys: o.storeys } : {} };
|
|
293
|
+
if (towers.provider) {
|
|
294
|
+
towers.provider(request, b);
|
|
295
|
+
return { x, z, width, depth, height: request.height, color };
|
|
296
|
+
}
|
|
297
|
+
return drawTower(b, request, towers);
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/block.ts
|
|
218
302
|
var DEFAULT_FACADES = [3882821, 4866104, 3489853, 4602426, 4143924, 3093561, 5327167, 5920334];
|
|
219
303
|
var DEFAULT_PARTITIONS = 2;
|
|
220
304
|
var DEFAULT_GAP = 0.08;
|
|
@@ -222,10 +306,22 @@ var STREET_MARGIN = 2;
|
|
|
222
306
|
var FOOTPRINT_TRIM = 1.2;
|
|
223
307
|
var FOOTPRINT_JITTER = 1.5;
|
|
224
308
|
var HEIGHT_CURVE = 1.6;
|
|
309
|
+
var DEFAULT_STOREY_PRESETS = { commercial: "office", residential: "residential" };
|
|
225
310
|
function drawBuilding(b, x, z, width, depth, height, color, options = {}) {
|
|
226
311
|
b.add(createCityBuilding({ x, z, width, depth, height, color, random: b.random, shopChance: options.shop, litChance: options.lit }));
|
|
227
312
|
return { x, z, width, depth, height, color };
|
|
228
313
|
}
|
|
314
|
+
function pickUse(b, uses) {
|
|
315
|
+
const entries = Object.entries(uses).filter(([, w]) => w > 0);
|
|
316
|
+
const total = entries.reduce((sum2, [, w]) => sum2 + w, 0);
|
|
317
|
+
if (total <= 0) return void 0;
|
|
318
|
+
let r = b.random() * total;
|
|
319
|
+
for (const [use, w] of entries) {
|
|
320
|
+
r -= w;
|
|
321
|
+
if (r < 0) return use;
|
|
322
|
+
}
|
|
323
|
+
return entries[entries.length - 1][0];
|
|
324
|
+
}
|
|
229
325
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
230
326
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
231
327
|
const placed = [];
|
|
@@ -238,11 +334,25 @@ function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
|
238
334
|
if (b.random() < (options.gap || DEFAULT_GAP)) continue;
|
|
239
335
|
const width = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
240
336
|
const depth = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
241
|
-
const
|
|
337
|
+
const t = Math.pow(b.random(), HEIGHT_CURVE);
|
|
338
|
+
let height = lerp(minHeight, maxHeight, t);
|
|
339
|
+
let extra = null;
|
|
340
|
+
if (options.floors !== void 0 || options.uses) {
|
|
341
|
+
const use = options.uses ? pickUse(b, options.uses) : void 0;
|
|
342
|
+
const presetId = use ? (options.storeyPresets ?? DEFAULT_STOREY_PRESETS)[use] : void 0;
|
|
343
|
+
const preset = presetId ? (options.presets ?? DEFAULT_TOWER_PRESETS).find((p) => p.id === presetId) : void 0;
|
|
344
|
+
extra = { use, preset: preset?.id };
|
|
345
|
+
if (options.floors !== void 0) {
|
|
346
|
+
const [lo, hi] = typeof options.floors === "number" ? [options.floors, options.floors] : options.floors;
|
|
347
|
+
const storeys = Math.max(1, Math.round(lerp(lo, hi, t)));
|
|
348
|
+
height = storeys * (preset?.storeyHeight ?? options.storeyHeight ?? DEFAULT_TOWER_PRESETS[DEFAULT_TOWER_PRESETS.length - 1].storeyHeight);
|
|
349
|
+
extra.storeys = storeys;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
242
352
|
const x = cx - half + lot * (i + 0.5);
|
|
243
353
|
const z = cz - half + lot * (j + 0.5);
|
|
244
354
|
const color = b.pick(palette);
|
|
245
|
-
placed.push(drawBuilding(b, x, z, width, depth, height, color, options));
|
|
355
|
+
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color, extra ? { ...options, ...extra } : options));
|
|
246
356
|
}
|
|
247
357
|
}
|
|
248
358
|
return placed;
|
|
@@ -313,7 +423,7 @@ function drawWires(b, points, y, color, c) {
|
|
|
313
423
|
}
|
|
314
424
|
|
|
315
425
|
// src/kit.ts
|
|
316
|
-
import { applyPatches, mergeById, removeIds } from "@adatechnology/game-contracts";
|
|
426
|
+
import { applyPatches as applyPatches2, mergeById as mergeById2, removeIds as removeIds2 } from "@adatechnology/game-contracts";
|
|
317
427
|
import { createSilhouetteRandom } from "@adatechnology/game-floorplan";
|
|
318
428
|
|
|
319
429
|
// src/neighbourhoods/houses.ts
|
|
@@ -385,13 +495,13 @@ function composePattern(kit, pattern, composer, b, origin, options = {}) {
|
|
|
385
495
|
}
|
|
386
496
|
};
|
|
387
497
|
composer(context);
|
|
388
|
-
const
|
|
389
|
-
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z,
|
|
498
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
499
|
+
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z, text2.title, text2.caption);
|
|
390
500
|
return plan;
|
|
391
501
|
}
|
|
392
502
|
function patternMarker(pattern, origin, locale = DEFAULT_LOCALE) {
|
|
393
|
-
const
|
|
394
|
-
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label:
|
|
503
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
504
|
+
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label: text2.label, detail: text2.detail };
|
|
395
505
|
}
|
|
396
506
|
|
|
397
507
|
// src/neighbourhoods/composers.ts
|
|
@@ -792,6 +902,368 @@ var SAMPLE_CELLS = {
|
|
|
792
902
|
military: { x: 160, z: 0 }
|
|
793
903
|
};
|
|
794
904
|
|
|
905
|
+
// src/cities/cities.ts
|
|
906
|
+
import { applyPatches, mergeById, removeIds } from "@adatechnology/game-contracts";
|
|
907
|
+
|
|
908
|
+
// src/cities/kinds.ts
|
|
909
|
+
var DEFAULT_DISTRICT_KINDS = [
|
|
910
|
+
{ id: "downtown", color: "#3b4f7a", zone: "zt", pattern: "financial_district", floors: [20, 60], uses: { commercial: 0.7, residential: 0.3 }, block: 90, text: { "pt-BR": { name: "Centro financeiro", description: "Arranha-c\xE9us de escrit\xF3rio e torres residenciais, de 20 a 60 andares." }, en: { name: "Downtown core", description: "Office and residential towers, 20 to 60 storeys." } } },
|
|
911
|
+
{ id: "commercial_center", color: "#5b6fa3", zone: "zc", pattern: "downtown", floors: [4, 15], uses: { commercial: 0.6, residential: 0.4 }, block: 70, text: { "pt-BR": { name: "Centro comercial", description: "Loja no t\xE9rreo e escrit\xF3rios ou apartamentos em cima, de 4 a 15 andares." }, en: { name: "Commercial centre", description: "Shops on the ground floor, offices or flats above, 4 to 15 storeys." } } },
|
|
912
|
+
{ id: "residential_high", color: "#7a68a6", zone: "zt", pattern: "downtown", floors: [8, 25], uses: { residential: 0.9, commercial: 0.1 }, block: 80, text: { "pt-BR": { name: "Bairro de pr\xE9dios", description: "Condom\xEDnios de pr\xE9dios de 8 a 25 andares com \xE1rea de lazer." }, en: { name: "High-rise residential", description: "Apartment blocks, 8 to 25 storeys." } } },
|
|
913
|
+
{ id: "mixed", color: "#9c7fb8", zone: "zr3_zm", pattern: "downtown", floors: [2, 8], uses: { residential: 0.6, commercial: 0.4 }, block: 60, text: { "pt-BR": { name: "Uso misto", description: "Sobrados, pr\xE9dios baixos e com\xE9rcio de rua, de 2 a 8 andares." }, en: { name: "Mixed use", description: "Townhouses, low blocks and street shops, 2 to 8 storeys." } } },
|
|
914
|
+
{ id: "residential_mid", color: "#d9a35b", zone: "zr2", pattern: "suburb", floors: [1, 4], uses: { residential: 0.95, commercial: 0.05 }, block: 60, text: { "pt-BR": { name: "Residencial m\xE9dio", description: "Casas, sobrados e pr\xE9dios pequenos de at\xE9 4 andares." }, en: { name: "Mid-density residential", description: "Houses and small blocks up to 4 storeys." } } },
|
|
915
|
+
{ id: "residential_low", color: "#e8c477", zone: "zr1", pattern: "suburb", floors: [1, 2], uses: { residential: 1 }, block: 70, text: { "pt-BR": { name: "Residencial de casas", description: "Casas t\xE9rreas e sobrados com quintal e recuo." }, en: { name: "Low-density residential", description: "Houses with yards and setbacks." } } },
|
|
916
|
+
{ id: "residential_gated", color: "#c9d27a", zone: "zr_ap", pattern: "suburb", floors: [1, 3], uses: { residential: 1 }, block: 110, text: { "pt-BR": { name: "Condom\xEDnio fechado", description: "Casas grandes em lotes amplos, muro e portaria." }, en: { name: "Gated community", description: "Large houses on big lots behind a wall and gate." } } },
|
|
917
|
+
{ id: "residential_popular", color: "#d97a5b", zone: "zr2", pattern: "working_class", floors: [1, 3], uses: { residential: 0.9, commercial: 0.1 }, block: 45, text: { "pt-BR": { name: "Bairro popular", description: "Casas geminadas de laje, sem recuo, ruas estreitas." }, en: { name: "Working-class housing", description: "Terraced self-built houses, narrow streets." } } },
|
|
918
|
+
{ id: "historic", color: "#b5835a", zone: "zr3_zm", pattern: "historic_street", floors: [1, 3], uses: { residential: 0.6, commercial: 0.4 }, block: 50, text: { "pt-BR": { name: "Centro hist\xF3rico", description: "Casario colonial colado, rua de pedra, igreja e pra\xE7a." }, en: { name: "Historic quarter", description: "Colonial row houses, cobbled streets, church and square." } } },
|
|
919
|
+
{ id: "institutional", color: "#5aa3a0", zone: "zc", pattern: "downtown", floors: [2, 8], uses: { institutional: 1 }, block: 120, text: { "pt-BR": { name: "Institucional", description: "Hospital, escola, campus, prefeitura, delegacia e quartel de bombeiros." }, en: { name: "Institutional", description: "Hospital, schools, campus, city hall, police and fire stations." } } },
|
|
920
|
+
{ id: "industrial", color: "#7d7d74", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 1 }, block: 140, text: { "pt-BR": { name: "Distrito industrial", description: "Galp\xF5es de 8 a 12 m, p\xE1tios e chamin\xE9s." }, en: { name: "Industrial district", description: "Sheds 8 to 12 m tall, yards and stacks." } } },
|
|
921
|
+
{ id: "logistics", color: "#96968a", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 0.8, commercial: 0.2 }, block: 160, text: { "pt-BR": { name: "Distrito log\xEDstico", description: "Centros de distribui\xE7\xE3o, posto de caminhoneiro, p\xE1tio de carretas." }, en: { name: "Logistics", description: "Distribution centres, truck stop, trailer yards." } } },
|
|
922
|
+
{ id: "port", color: "#5f7f8f", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 1 }, block: 150, text: { "pt-BR": { name: "Porto", description: "Cais, guindastes, cont\xEAineres empilhados e armaz\xE9ns." }, en: { name: "Port", description: "Quays, cranes, stacked containers and warehouses." } } },
|
|
923
|
+
{ id: "rail_yard", color: "#6e6158", zone: "zi", pattern: "railway_village", floors: [1, 1], uses: { industrial: 1 }, block: 150, text: { "pt-BR": { name: "P\xE1tio ferrovi\xE1rio", description: "Linhas, rotunda, oficina e vag\xF5es parados." }, en: { name: "Rail yard", description: "Tracks, roundhouse, workshop and parked wagons." } } },
|
|
924
|
+
{ id: "company_housing", color: "#cfae86", zone: "zr1", pattern: "railway_village", floors: [1, 1], uses: { residential: 1 }, block: 50, text: { "pt-BR": { name: "Vila oper\xE1ria", description: "Casas iguais em fila, da ferrovia ou da f\xE1brica." }, en: { name: "Company housing", description: "Identical row houses of the railway or the mill." } } },
|
|
925
|
+
{ id: "hillside_resort", color: "#a3b58a", zone: "zr1", pattern: "mountain_resort", floors: [1, 2], uses: { residential: 0.7, commercial: 0.3 }, block: 60, text: { "pt-BR": { name: "Serra e pousadas", description: "Chal\xE9s e pousadas na encosta, ruas em curva." }, en: { name: "Hillside resort", description: "Chalets and inns on the slope." } } },
|
|
926
|
+
{ id: "stilt", color: "#7fa3b5", zone: "zr1", pattern: "stilt_village", floors: [1, 1], uses: { residential: 1 }, block: 40, text: { "pt-BR": { name: "Palafitas", description: "Casas sobre estacas na beira da \xE1gua, trapiches." }, en: { name: "Stilt village", description: "Houses on stilts by the water, jetties." } } },
|
|
927
|
+
{ id: "park", color: "#6f9a5e", zone: null, pattern: null, floors: [0, 0], uses: {}, block: 0, text: { "pt-BR": { name: "Parque e pra\xE7a", description: "\xC1rea verde, pra\xE7a, campo de futebol." }, en: { name: "Park", description: "Green space, square, football pitch." } } },
|
|
928
|
+
{ id: "rural", color: "#a7b86d", zone: "za", pattern: "rural", floors: [1, 2], uses: { residential: 0.8, agricultural: 0.2 }, block: 250, text: { "pt-BR": { name: "Ch\xE1caras e s\xEDtios", description: "S\xEDtios com casa sede, horta e pomar." }, en: { name: "Smallholdings", description: "Smallholdings with a farmhouse, garden and orchard." } } },
|
|
929
|
+
{ id: "farmland", color: "#c8c67e", zone: "za", pattern: "rural", floors: [0, 1], uses: { agricultural: 1 }, block: 400, text: { "pt-BR": { name: "Lavoura", description: "Planta\xE7\xE3o, silos e estradas de terra." }, en: { name: "Farmland", description: "Crops, silos and dirt roads." } } },
|
|
930
|
+
{ id: "airfield", color: "#b8b8a8", zone: "za", pattern: "rural", floors: [1, 1], uses: { industrial: 1 }, block: 300, text: { "pt-BR": { name: "Aer\xF3dromo", description: "Pista, hangares e torre." }, en: { name: "Airfield", description: "Runway, hangars and tower." } } },
|
|
931
|
+
{ id: "wild", color: "#4f7a4a", zone: null, pattern: null, floors: [0, 0], uses: {}, block: 0, text: { "pt-BR": { name: "Mata e morro", description: "Mata, morro, \xE1rea sem constru\xE7\xE3o." }, en: { name: "Woods and hills", description: "Woods and hills, unbuilt." } } }
|
|
932
|
+
];
|
|
933
|
+
|
|
934
|
+
// src/cities/templates.ts
|
|
935
|
+
var DEFAULT_CITY_PLANS = [
|
|
936
|
+
{
|
|
937
|
+
id: "metropolis",
|
|
938
|
+
scale: "metropolis",
|
|
939
|
+
size: [6e3, 6e3],
|
|
940
|
+
text: { "pt-BR": { name: "Metr\xF3pole", description: "Centro financeiro de arranha-c\xE9us cercado de com\xE9rcio, bairros de pr\xE9dios, casas, periferia e ind\xFAstria na ferrovia." }, en: { name: "Metropolis", description: "Tower downtown ringed by commerce, high-rise and low-rise neighbourhoods, outskirts and industry on the railway." } },
|
|
941
|
+
districts: [
|
|
942
|
+
{ id: "outskirts", kind: "rural", rect: [-3e3, -3e3, 3e3, 3e3] },
|
|
943
|
+
{ id: "suburbs_w", kind: "residential_low", rect: [-2600, -2600, -600, 2600] },
|
|
944
|
+
{ id: "suburbs_e", kind: "residential_mid", rect: [600, -2600, 2600, 1e3] },
|
|
945
|
+
{ id: "periphery", kind: "residential_popular", rect: [-3e3, 1200, -800, 3e3] },
|
|
946
|
+
{ id: "gated", kind: "residential_gated", rect: [1400, -3e3, 3e3, -1800] },
|
|
947
|
+
{ id: "industry", kind: "industrial", rect: [600, 1600, 3e3, 3e3] },
|
|
948
|
+
{ id: "logistics", kind: "logistics", rect: [2200, 400, 3e3, 1600] },
|
|
949
|
+
{ id: "mixed", kind: "mixed", rect: [-1600, -1600, 1600, 1400] },
|
|
950
|
+
{ id: "high", kind: "residential_high", rect: [-1400, -1500, -300, -300] },
|
|
951
|
+
{ id: "high2", kind: "residential_high", rect: [300, -1500, 1400, -600] },
|
|
952
|
+
{ id: "center", kind: "commercial_center", rect: [-900, -700, 1100, 800] },
|
|
953
|
+
{ id: "downtown", kind: "downtown", rect: [-400, -400, 600, 400] },
|
|
954
|
+
{ id: "hospital", kind: "institutional", rect: [-1600, 500, -1e3, 1100] },
|
|
955
|
+
{ id: "campus", kind: "institutional", rect: [1300, -500, 2e3, 300] },
|
|
956
|
+
{ id: "park", kind: "park", rect: [-300, 500, 500, 1100] },
|
|
957
|
+
{ id: "hills", kind: "wild", rect: [-3e3, -3e3, -1800, -1800] }
|
|
958
|
+
],
|
|
959
|
+
lines: [
|
|
960
|
+
{ id: "ring", kind: "avenue", width: 30, points: [[-1700, -1700], [1700, -1700], [1700, 1500], [-1700, 1500], [-1700, -1700]] },
|
|
961
|
+
{ id: "main_ns", kind: "avenue", width: 30, points: [[100, -3e3], [100, 3e3]] },
|
|
962
|
+
{ id: "main_ew", kind: "avenue", width: 30, points: [[-3e3, 0], [3e3, 0]] },
|
|
963
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-3e3, 1500], [0, 1500], [3e3, 1300]] },
|
|
964
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-3e3, -2200], [3e3, -2400]] }
|
|
965
|
+
],
|
|
966
|
+
landmarks: [
|
|
967
|
+
{ kind: "station", at: [0, 1500] },
|
|
968
|
+
{ kind: "hospital", at: [-1300, 800] },
|
|
969
|
+
{ kind: "stadium", at: [1700, -100] },
|
|
970
|
+
{ kind: "cathedral", at: [100, 100] },
|
|
971
|
+
{ kind: "mall", at: [800, 600] },
|
|
972
|
+
{ kind: "police", at: [300, -300] },
|
|
973
|
+
{ kind: "fire", at: [-500, 600] },
|
|
974
|
+
{ kind: "gas_station", at: [2400, -2300] }
|
|
975
|
+
]
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
id: "regional_city",
|
|
979
|
+
scale: "city",
|
|
980
|
+
size: [4e3, 4e3],
|
|
981
|
+
text: { "pt-BR": { name: "Cidade m\xE9dia", description: "Centro comercial com poucos pr\xE9dios altos, bairros de casas e um distrito industrial." }, en: { name: "Regional city", description: "Commercial centre with a few tall buildings, house neighbourhoods and an industrial district." } },
|
|
982
|
+
districts: [
|
|
983
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -2e3, 2e3, 2e3] },
|
|
984
|
+
{ id: "res_w", kind: "residential_low", rect: [-1800, -1600, -300, 1600] },
|
|
985
|
+
{ id: "res_e", kind: "residential_mid", rect: [300, -1600, 1800, 600] },
|
|
986
|
+
{ id: "popular", kind: "residential_popular", rect: [-1800, 800, -500, 1800] },
|
|
987
|
+
{ id: "industry", kind: "industrial", rect: [500, 900, 2e3, 2e3] },
|
|
988
|
+
{ id: "mixed", kind: "mixed", rect: [-800, -800, 900, 700] },
|
|
989
|
+
{ id: "center", kind: "commercial_center", rect: [-400, -400, 500, 350] },
|
|
990
|
+
{ id: "square", kind: "park", rect: [-100, -100, 150, 100] },
|
|
991
|
+
{ id: "hospital", kind: "institutional", rect: [600, -900, 1e3, -500] }
|
|
992
|
+
],
|
|
993
|
+
lines: [
|
|
994
|
+
{ id: "main_ns", kind: "avenue", width: 24, points: [[0, -2e3], [0, 2e3]] },
|
|
995
|
+
{ id: "main_ew", kind: "avenue", width: 24, points: [[-2e3, 0], [2e3, 0]] },
|
|
996
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-2e3, 1600], [2e3, 1500]] }
|
|
997
|
+
],
|
|
998
|
+
landmarks: [
|
|
999
|
+
{ kind: "church", at: [0, 0] },
|
|
1000
|
+
{ kind: "hospital", at: [800, -700] },
|
|
1001
|
+
{ kind: "school", at: [-600, 300] },
|
|
1002
|
+
{ kind: "police", at: [200, -200] },
|
|
1003
|
+
{ kind: "market", at: [300, 200] },
|
|
1004
|
+
{ kind: "gas_station", at: [1500, 1550] }
|
|
1005
|
+
]
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
id: "port_city",
|
|
1009
|
+
scale: "city",
|
|
1010
|
+
size: [4e3, 3e3],
|
|
1011
|
+
text: { "pt-BR": { name: "Cidade portu\xE1ria", description: "Cais e armaz\xE9ns na margem, centro hist\xF3rico atr\xE1s do porto e bairros subindo o morro." }, en: { name: "Port city", description: "Quays and warehouses on the shore, old town behind the port and neighbourhoods up the hill." } },
|
|
1012
|
+
districts: [
|
|
1013
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -1500, 2e3, 1500] },
|
|
1014
|
+
{ id: "hill", kind: "residential_low", rect: [500, -1500, 2e3, -200] },
|
|
1015
|
+
{ id: "res", kind: "residential_mid", rect: [-300, -500, 1300, 900] },
|
|
1016
|
+
{ id: "industry", kind: "industrial", rect: [800, 500, 2e3, 1500] },
|
|
1017
|
+
{ id: "port", kind: "port", rect: [-2e3, -1300, -900, 1100] },
|
|
1018
|
+
{ id: "old_town", kind: "historic", rect: [-900, -800, 200, 400] },
|
|
1019
|
+
{ id: "center", kind: "commercial_center", rect: [-500, -400, 200, 100] },
|
|
1020
|
+
{ id: "stilts", kind: "stilt", rect: [-2e3, 1e3, -1200, 1500] }
|
|
1021
|
+
],
|
|
1022
|
+
lines: [
|
|
1023
|
+
{ id: "shore", kind: "river", width: 60, points: [[-2e3, -1500], [-1950, 0], [-1900, 1500]] },
|
|
1024
|
+
{ id: "main", kind: "avenue", width: 20, points: [[-900, 0], [2e3, 0]] },
|
|
1025
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1200, -1500], [-1e3, -400]] }
|
|
1026
|
+
],
|
|
1027
|
+
landmarks: [
|
|
1028
|
+
{ kind: "harbour_office", at: [-1200, 0] },
|
|
1029
|
+
{ kind: "church", at: [-300, -200] },
|
|
1030
|
+
{ kind: "market", at: [-600, 200] },
|
|
1031
|
+
{ kind: "lighthouse", at: [1600, -1300] },
|
|
1032
|
+
{ kind: "hospital", at: [700, 200] }
|
|
1033
|
+
]
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
id: "rail_town",
|
|
1037
|
+
scale: "town",
|
|
1038
|
+
size: [3e3, 3e3],
|
|
1039
|
+
text: { "pt-BR": { name: "Cidade ferrovi\xE1ria", description: "Cidade que cresceu em volta da esta\xE7\xE3o: p\xE1tio, centro velho, vila da companhia." }, en: { name: "Rail town", description: "A town grown around its station: rail yard, old centre, company village." } },
|
|
1040
|
+
districts: [
|
|
1041
|
+
{ id: "outskirts", kind: "rural", rect: [-1500, -1500, 1500, 1500] },
|
|
1042
|
+
{ id: "res", kind: "residential_low", rect: [500, 500, 1500, 1500] },
|
|
1043
|
+
{ id: "logistics", kind: "logistics", rect: [500, -700, 1500, 500] },
|
|
1044
|
+
{ id: "popular", kind: "residential_popular", rect: [-1500, 600, -300, 1500] },
|
|
1045
|
+
{ id: "company", kind: "company_housing", rect: [-1500, -800, -700, 500] },
|
|
1046
|
+
{ id: "yard", kind: "rail_yard", rect: [-700, 0, 500, 500] },
|
|
1047
|
+
{ id: "old_center", kind: "historic", rect: [-500, -700, 500, 0] }
|
|
1048
|
+
],
|
|
1049
|
+
lines: [
|
|
1050
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1500, 250], [1500, 250]] },
|
|
1051
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-1500, -100], [1500, -200]] }
|
|
1052
|
+
],
|
|
1053
|
+
landmarks: [
|
|
1054
|
+
{ kind: "station", at: [0, 100] },
|
|
1055
|
+
{ kind: "church", at: [0, -400] },
|
|
1056
|
+
{ kind: "gas_station", at: [1e3, -150] }
|
|
1057
|
+
]
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
id: "country_town",
|
|
1061
|
+
scale: "town",
|
|
1062
|
+
size: [2400, 2400],
|
|
1063
|
+
text: { "pt-BR": { name: "Cidade do interior", description: "Pra\xE7a da matriz no meio, com\xE9rcio t\xE9rreo em volta, casas, cooperativa e s\xEDtios em volta." }, en: { name: "Country town", description: "Main square with the church, ground-floor shops around, houses, a co-op and smallholdings." } },
|
|
1064
|
+
districts: [
|
|
1065
|
+
{ id: "farms", kind: "farmland", rect: [-1200, -1200, 1200, 1200] },
|
|
1066
|
+
{ id: "chacaras", kind: "rural", rect: [-900, -900, 900, 900] },
|
|
1067
|
+
{ id: "houses", kind: "residential_low", rect: [-600, -600, 600, 500] },
|
|
1068
|
+
{ id: "company", kind: "company_housing", rect: [-700, 350, -250, 700] },
|
|
1069
|
+
{ id: "coop", kind: "farmland", rect: [350, 500, 900, 900], floors: [1, 1], uses: { agricultural: 0.5, industrial: 0.5 } },
|
|
1070
|
+
{ id: "center", kind: "historic", rect: [-250, -250, 250, 250] },
|
|
1071
|
+
{ id: "square", kind: "park", rect: [-60, -60, 60, 60] }
|
|
1072
|
+
],
|
|
1073
|
+
lines: [
|
|
1074
|
+
{ id: "main", kind: "street", width: 12, points: [[-1200, 0], [1200, 0]] },
|
|
1075
|
+
{ id: "road", kind: "street", width: 8, points: [[0, -1200], [0, 1200]] }
|
|
1076
|
+
],
|
|
1077
|
+
landmarks: [
|
|
1078
|
+
{ kind: "church", at: [0, -70] },
|
|
1079
|
+
{ kind: "market", at: [120, 80] },
|
|
1080
|
+
{ kind: "school", at: [-300, -200] },
|
|
1081
|
+
{ kind: "gas_station", at: [700, 0] },
|
|
1082
|
+
{ kind: "silo", at: [600, 700] }
|
|
1083
|
+
]
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
id: "mountain_village",
|
|
1087
|
+
scale: "village",
|
|
1088
|
+
size: [1600, 1200],
|
|
1089
|
+
text: { "pt-BR": { name: "Vila serrana", description: "Largo da igreja, rua das pousadas subindo a serra e chal\xE9s espalhados na encosta." }, en: { name: "Mountain village", description: "Church square, a street of inns up the slope and chalets scattered on the hill." } },
|
|
1090
|
+
districts: [
|
|
1091
|
+
{ id: "wild", kind: "wild", rect: [-800, -600, 800, 600] },
|
|
1092
|
+
{ id: "slope", kind: "rural", rect: [-700, -500, 700, 500] },
|
|
1093
|
+
{ id: "resort", kind: "hillside_resort", rect: [-400, -450, 500, 0] },
|
|
1094
|
+
{ id: "square", kind: "historic", rect: [-250, 0, 250, 300] },
|
|
1095
|
+
{ id: "station", kind: "rail_yard", rect: [300, 150, 700, 450] }
|
|
1096
|
+
],
|
|
1097
|
+
lines: [
|
|
1098
|
+
{ id: "road", kind: "street", width: 7, points: [[-800, 200], [0, 150], [800, 300]] }
|
|
1099
|
+
],
|
|
1100
|
+
landmarks: [
|
|
1101
|
+
{ kind: "church", at: [0, 150] },
|
|
1102
|
+
{ kind: "inn", at: [100, -200] },
|
|
1103
|
+
{ kind: "station", at: [500, 300] }
|
|
1104
|
+
]
|
|
1105
|
+
}
|
|
1106
|
+
];
|
|
1107
|
+
|
|
1108
|
+
// src/cities/cities.ts
|
|
1109
|
+
var DEFAULT_MAX_FLOORS = 60;
|
|
1110
|
+
var DEFAULT_STREET = 10;
|
|
1111
|
+
var DEFAULT_LOCALE2 = "pt-BR";
|
|
1112
|
+
var DEFAULT_STOREY_PRESETS2 = { commercial: "office", residential: "residential" };
|
|
1113
|
+
var text = (by, locale) => by[locale] ?? by[DEFAULT_LOCALE2];
|
|
1114
|
+
var named = (name, locale) => typeof name === "string" || name === void 0 ? name : name[locale] ?? name[DEFAULT_LOCALE2];
|
|
1115
|
+
var mergeLists = (parent, own) => {
|
|
1116
|
+
const list = [...parent];
|
|
1117
|
+
for (const item of own) {
|
|
1118
|
+
const at = list.findIndex((p) => p.id === item.id);
|
|
1119
|
+
if (at >= 0) list[at] = item;
|
|
1120
|
+
else list.push(item);
|
|
1121
|
+
}
|
|
1122
|
+
return list;
|
|
1123
|
+
};
|
|
1124
|
+
function createCities(o) {
|
|
1125
|
+
const maxFloors = o.maxFloors ?? DEFAULT_MAX_FLOORS;
|
|
1126
|
+
const kindEntries = mergeById(DEFAULT_DISTRICT_KINDS, o.packs?.kinds);
|
|
1127
|
+
applyPatches(kindEntries, o.patch?.kinds);
|
|
1128
|
+
removeIds(kindEntries, o.remove?.kinds);
|
|
1129
|
+
const planEntries = mergeById(DEFAULT_CITY_PLANS, o.packs?.plans);
|
|
1130
|
+
applyPatches(planEntries, o.patch?.plans);
|
|
1131
|
+
removeIds(planEntries, o.remove?.plans);
|
|
1132
|
+
const registry = (entries, label) => ({
|
|
1133
|
+
ids: () => [...entries.keys()],
|
|
1134
|
+
list: () => [...entries.values()],
|
|
1135
|
+
has: (id) => entries.has(id),
|
|
1136
|
+
get(id) {
|
|
1137
|
+
const entry = entries.get(id);
|
|
1138
|
+
if (!entry) throw new Error(`Unknown ${label} "${id}"`);
|
|
1139
|
+
return entry;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
const kinds = registry(kindEntries, "district kind");
|
|
1143
|
+
const rawPlans = registry(planEntries, "city plan");
|
|
1144
|
+
const resolvePlan = (plan, seen = []) => {
|
|
1145
|
+
if (seen.includes(plan.id)) throw new Error(`City plan "${plan.id}" extends itself (${[...seen, plan.id].join(" > ")})`);
|
|
1146
|
+
const parent = plan.extends ? resolvePlan(rawPlans.get(plan.extends), [...seen, plan.id]) : void 0;
|
|
1147
|
+
const scale = plan.scale ?? parent?.scale;
|
|
1148
|
+
const size = plan.size ?? parent?.size;
|
|
1149
|
+
const planText = plan.text ?? parent?.text;
|
|
1150
|
+
if (!scale || !size || !planText) throw new Error(`City plan "${plan.id}" has no ${!scale ? "scale" : !size ? "size" : "text"}`);
|
|
1151
|
+
return {
|
|
1152
|
+
id: plan.id,
|
|
1153
|
+
scale,
|
|
1154
|
+
size,
|
|
1155
|
+
text: planText,
|
|
1156
|
+
extends: plan.extends,
|
|
1157
|
+
districts: mergeLists(parent?.districts ?? [], plan.districts ?? []),
|
|
1158
|
+
lines: mergeLists(parent?.lines ?? [], plan.lines ?? []),
|
|
1159
|
+
landmarks: [...parent?.landmarks ?? [], ...plan.landmarks ?? []]
|
|
1160
|
+
};
|
|
1161
|
+
};
|
|
1162
|
+
const plans = {
|
|
1163
|
+
...rawPlans,
|
|
1164
|
+
/** The plan with everything inherited from the plans it extends. */
|
|
1165
|
+
resolve: (id) => resolvePlan(rawPlans.get(id))
|
|
1166
|
+
};
|
|
1167
|
+
const layout = (planOrId, options = {}) => {
|
|
1168
|
+
const plan = typeof planOrId === "string" ? plans.resolve(planOrId) : resolvePlan(planOrId);
|
|
1169
|
+
const locale = options.locale ?? DEFAULT_LOCALE2;
|
|
1170
|
+
const cap = Math.max(0, options.maxFloors ?? maxFloors);
|
|
1171
|
+
const clamp2 = (n) => Math.min(cap, Math.max(0, n));
|
|
1172
|
+
const districts = plan.districts.map((d) => {
|
|
1173
|
+
const kind = kinds.get(d.kind);
|
|
1174
|
+
const kindText = text(kind.text, locale);
|
|
1175
|
+
const [lo, hi] = d.floors ?? kind.floors;
|
|
1176
|
+
return {
|
|
1177
|
+
id: d.id,
|
|
1178
|
+
kind: kind.id,
|
|
1179
|
+
rect: d.rect,
|
|
1180
|
+
name: named(d.name, locale) ?? kindText.name,
|
|
1181
|
+
description: kindText.description,
|
|
1182
|
+
color: kind.color,
|
|
1183
|
+
zone: kind.zone,
|
|
1184
|
+
pattern: kind.pattern,
|
|
1185
|
+
floors: [clamp2(lo), clamp2(hi)],
|
|
1186
|
+
uses: d.uses ?? kind.uses,
|
|
1187
|
+
block: kind.block
|
|
1188
|
+
};
|
|
1189
|
+
});
|
|
1190
|
+
const inside = (d, x, z) => x >= d.rect[0] && x <= d.rect[2] && z >= d.rect[1] && z <= d.rect[3];
|
|
1191
|
+
const districtAt = (x, z) => {
|
|
1192
|
+
for (let i = districts.length - 1; i >= 0; i--) if (inside(districts[i], x, z)) return districts[i];
|
|
1193
|
+
return void 0;
|
|
1194
|
+
};
|
|
1195
|
+
const blocks = (districtId, bo = {}) => {
|
|
1196
|
+
const d = districts.find((x) => x.id === districtId);
|
|
1197
|
+
if (!d) throw new Error(`City plan "${plan.id}" has no district "${districtId}"`);
|
|
1198
|
+
const street = bo.street ?? DEFAULT_STREET;
|
|
1199
|
+
const cell = d.block;
|
|
1200
|
+
const side = cell - street;
|
|
1201
|
+
if (cell <= 0 || side <= 0) return [];
|
|
1202
|
+
const [x0, z0, x1, z1] = d.rect;
|
|
1203
|
+
const cols = Math.floor((x1 - x0) / cell);
|
|
1204
|
+
const rows = Math.floor((z1 - z0) / cell);
|
|
1205
|
+
const ox = x0 + (x1 - x0 - cols * cell) / 2;
|
|
1206
|
+
const oz = z0 + (z1 - z0 - rows * cell) / 2;
|
|
1207
|
+
const found = [];
|
|
1208
|
+
for (let i = 0; i < cols; i++) {
|
|
1209
|
+
for (let j = 0; j < rows; j++) {
|
|
1210
|
+
const x = ox + cell * (i + 0.5);
|
|
1211
|
+
const z = oz + cell * (j + 0.5);
|
|
1212
|
+
if (districtAt(x, z)?.id !== d.id) continue;
|
|
1213
|
+
found.push({ x, z, size: [side, side], district: d.id, building: { floors: d.floors, uses: d.uses, zone: d.zone, pattern: d.pattern } });
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return found;
|
|
1217
|
+
};
|
|
1218
|
+
const landmarks = plan.landmarks.map((l) => {
|
|
1219
|
+
const { name, ...rest } = l;
|
|
1220
|
+
const label = named(name, locale);
|
|
1221
|
+
return label === void 0 ? rest : { ...rest, name: label };
|
|
1222
|
+
});
|
|
1223
|
+
const planText = text(plan.text, locale);
|
|
1224
|
+
return { id: plan.id, scale: plan.scale, size: plan.size, name: planText.name, description: planText.description, districts, lines: [...plan.lines], landmarks, districtAt, blocks };
|
|
1225
|
+
};
|
|
1226
|
+
const stats = (l) => {
|
|
1227
|
+
const xs = [...new Set(l.districts.flatMap((d) => [d.rect[0], d.rect[2]]))].sort((a, b) => a - b);
|
|
1228
|
+
const zs = [...new Set(l.districts.flatMap((d) => [d.rect[1], d.rect[3]]))].sort((a, b) => a - b);
|
|
1229
|
+
const area = /* @__PURE__ */ new Map();
|
|
1230
|
+
let total = 0;
|
|
1231
|
+
for (let i = 0; i + 1 < xs.length; i++) {
|
|
1232
|
+
for (let j = 0; j + 1 < zs.length; j++) {
|
|
1233
|
+
const x0 = xs[i];
|
|
1234
|
+
const z0 = zs[j];
|
|
1235
|
+
const w = xs[i + 1] - x0;
|
|
1236
|
+
const d = zs[j + 1] - z0;
|
|
1237
|
+
const owner = l.districtAt(x0 + w / 2, z0 + d / 2);
|
|
1238
|
+
if (!owner) continue;
|
|
1239
|
+
area.set(owner.kind, (area.get(owner.kind) ?? 0) + w * d);
|
|
1240
|
+
total += w * d;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
const count = /* @__PURE__ */ new Map();
|
|
1244
|
+
for (const d of l.districts) count.set(d.kind, (count.get(d.kind) ?? 0) + 1);
|
|
1245
|
+
return [...count.keys()].map((kind) => ({ kind, area: area.get(kind) ?? 0, share: total > 0 ? (area.get(kind) ?? 0) / total : 0, districts: count.get(kind) ?? 0 })).sort((a, b) => b.area - a.area);
|
|
1246
|
+
};
|
|
1247
|
+
const fillBlock2 = (b, block, options = {}) => {
|
|
1248
|
+
const { floors, uses, pattern } = block.building;
|
|
1249
|
+
if (floors[1] <= 0 || pattern === null) return [];
|
|
1250
|
+
const { shop, ...rest } = options;
|
|
1251
|
+
const storey = options.storeyHeight ?? o.presets.find((p) => p.id === DEFAULT_STOREY_PRESETS2.residential)?.storeyHeight ?? o.presets[0]?.storeyHeight;
|
|
1252
|
+
return o.fill(b, {
|
|
1253
|
+
cx: block.x,
|
|
1254
|
+
cz: block.z,
|
|
1255
|
+
size: Math.min(block.size[0], block.size[1]),
|
|
1256
|
+
minHeight: floors[0] * storey,
|
|
1257
|
+
maxHeight: floors[1] * storey,
|
|
1258
|
+
floors: [floors[0], floors[1]],
|
|
1259
|
+
uses,
|
|
1260
|
+
shop: shop ?? uses.commercial ?? 0,
|
|
1261
|
+
...rest
|
|
1262
|
+
});
|
|
1263
|
+
};
|
|
1264
|
+
return { maxFloors, kinds, plans, layout, stats, fillBlock: fillBlock2 };
|
|
1265
|
+
}
|
|
1266
|
+
|
|
795
1267
|
// src/terrain/math.ts
|
|
796
1268
|
var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
797
1269
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
@@ -922,17 +1394,20 @@ function drawTerrain(b, terrain, segments) {
|
|
|
922
1394
|
var DEFAULT_LIGHT_BUDGET = 16;
|
|
923
1395
|
var DEFAULT_URBAN_SEED = 7;
|
|
924
1396
|
function createUrban(options = {}) {
|
|
925
|
-
const entries =
|
|
926
|
-
|
|
927
|
-
|
|
1397
|
+
const entries = mergeById2(DEFAULT_POLES, options.packs?.poles);
|
|
1398
|
+
applyPatches2(entries, options.patch?.poles);
|
|
1399
|
+
removeIds2(entries, options.remove?.poles);
|
|
928
1400
|
const shapes = { ...POLE_SHAPES, ...options.shapes };
|
|
929
|
-
const patterns =
|
|
930
|
-
|
|
931
|
-
|
|
1401
|
+
const patterns = mergeById2(DEFAULT_PATTERNS, options.packs?.neighbourhoods);
|
|
1402
|
+
applyPatches2(patterns, options.patch?.neighbourhoods);
|
|
1403
|
+
removeIds2(patterns, options.remove?.neighbourhoods);
|
|
932
1404
|
const composers = { ...COMPOSERS, ...options.composers };
|
|
933
1405
|
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_BUDGET;
|
|
934
1406
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
935
1407
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
1408
|
+
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
1409
|
+
const building = (b, x, z, width, depth, height, color, o) => drawBuilding(b, x, z, width, depth, height, color, o);
|
|
1410
|
+
const drawBlockBuilding = towers ? createTowerDrawer(towers, building) : building;
|
|
936
1411
|
const poles = {
|
|
937
1412
|
ids: () => [...entries.keys()],
|
|
938
1413
|
list: () => [...entries.values()],
|
|
@@ -954,8 +1429,8 @@ function createUrban(options = {}) {
|
|
|
954
1429
|
},
|
|
955
1430
|
block: {
|
|
956
1431
|
facades,
|
|
957
|
-
building:
|
|
958
|
-
fill: (b, g) => fillBlock(b, g.cx, g.cz, g.size, g.minHeight, g.maxHeight, { palette: facades, ...g })
|
|
1432
|
+
building: drawBlockBuilding,
|
|
1433
|
+
fill: (b, g) => fillBlock(b, g.cx, g.cz, g.size, g.minHeight, g.maxHeight, { palette: facades, draw: drawBlockBuilding, ...towers?.presets ? { presets: towers.presets } : {}, ...g })
|
|
959
1434
|
},
|
|
960
1435
|
props: {
|
|
961
1436
|
fence: drawFence,
|
|
@@ -998,7 +1473,15 @@ function createUrban(options = {}) {
|
|
|
998
1473
|
}
|
|
999
1474
|
};
|
|
1000
1475
|
const terrain = (o) => createTerrain(o);
|
|
1001
|
-
|
|
1476
|
+
const cities = createCities({
|
|
1477
|
+
maxFloors: options.cities?.maxFloors,
|
|
1478
|
+
packs: { kinds: options.packs?.districtKinds, plans: options.packs?.cityPlans },
|
|
1479
|
+
patch: { kinds: options.patch?.districtKinds, plans: options.patch?.cityPlans },
|
|
1480
|
+
remove: { kinds: options.remove?.districtKinds, plans: options.remove?.cityPlans },
|
|
1481
|
+
presets: towers?.presets ?? DEFAULT_TOWER_PRESETS,
|
|
1482
|
+
fill: core.block.fill
|
|
1483
|
+
});
|
|
1484
|
+
return { ...core, poles, cities, builder, neighbourhoods, terrain, drawTerrain, towers: { enabled: towers !== null, options: towers } };
|
|
1002
1485
|
}
|
|
1003
1486
|
|
|
1004
1487
|
// src/neighbourhoods/providers.ts
|
|
@@ -1036,27 +1519,67 @@ function createPlantProvider(options = {}) {
|
|
|
1036
1519
|
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
1037
1520
|
};
|
|
1038
1521
|
}
|
|
1522
|
+
|
|
1523
|
+
// src/neighbourhoods/towers.ts
|
|
1524
|
+
var financialDistrict = {
|
|
1525
|
+
id: "financial_district",
|
|
1526
|
+
streetWidth: 16,
|
|
1527
|
+
walkWidth: 4,
|
|
1528
|
+
typologies: ["office_tower", "residential_tower"],
|
|
1529
|
+
pole: "led_double",
|
|
1530
|
+
lightColor: 14674175,
|
|
1531
|
+
spacing: 25,
|
|
1532
|
+
lit: 1,
|
|
1533
|
+
compose: "downtown",
|
|
1534
|
+
marker: { height: 40 },
|
|
1535
|
+
text: {
|
|
1536
|
+
"pt-BR": { label: "Distrito financeiro", title: "DISTRITO FINANCEIRO", caption: "avenida 2 \xD7 8 m \xB7 torres de 70 a 180 m", detail: "Torres de escrit\xF3rios e residenciais de 70 a 180 m. Poste met\xE1lico duplo, LED branco." },
|
|
1537
|
+
en: { label: "Financial district", title: "FINANCIAL DISTRICT", caption: "avenue 2 \xD7 8 m \xB7 towers of 70 to 180 m", detail: "Office and residential towers of 70 to 180 m. Double steel pole, white LED." }
|
|
1538
|
+
},
|
|
1539
|
+
params: {
|
|
1540
|
+
roadLength: 110,
|
|
1541
|
+
roadColor: 2237480,
|
|
1542
|
+
median: { width: 1.6, height: 0.18, color: 3820076 },
|
|
1543
|
+
poles: { from: -50, to: 50 },
|
|
1544
|
+
buildings: { perRow: 4, first: -39, pitch: 26, rowOffset: 24, size: 20, north: { minHeight: 90, range: 90, lit: 0.12 }, south: { minHeight: 70, range: 80, lit: 0.1 } },
|
|
1545
|
+
walks: { offset: 14, length: 110 },
|
|
1546
|
+
car: { dx: -8, dz: -3, ry: 1.57, color: 7038032 }
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
var TOWER_PATTERNS = [financialDistrict];
|
|
1550
|
+
var TOWER_SAMPLE_CELLS = { financial_district: { x: 160, z: 56 } };
|
|
1039
1551
|
export {
|
|
1040
1552
|
COMPOSERS,
|
|
1553
|
+
DEFAULT_CITY_PLANS,
|
|
1554
|
+
DEFAULT_DISTRICT_KINDS,
|
|
1041
1555
|
DEFAULT_FACADES,
|
|
1042
1556
|
DEFAULT_LIGHT_BUDGET,
|
|
1557
|
+
DEFAULT_MAX_FLOORS,
|
|
1043
1558
|
DEFAULT_PATTERNS,
|
|
1044
1559
|
DEFAULT_PLANT_CONTEXTS,
|
|
1045
1560
|
DEFAULT_POLES,
|
|
1046
1561
|
DEFAULT_PROP_COLORS,
|
|
1047
1562
|
DEFAULT_ROAD_STYLE,
|
|
1563
|
+
DEFAULT_STOREY_PRESETS,
|
|
1564
|
+
DEFAULT_STREET,
|
|
1565
|
+
DEFAULT_TOWER_PRESETS,
|
|
1048
1566
|
DEFAULT_URBAN_SEED,
|
|
1049
1567
|
DEFAULT_WALK_STYLE,
|
|
1050
1568
|
PARKED_VEHICLE_IDS,
|
|
1051
1569
|
POLE_SHAPES,
|
|
1052
1570
|
SAMPLE_CELLS,
|
|
1053
1571
|
SILHOUETTE_TYPOLOGIES,
|
|
1572
|
+
TOWER_DEFAULTS,
|
|
1573
|
+
TOWER_PATTERNS,
|
|
1574
|
+
TOWER_SAMPLE_CELLS,
|
|
1054
1575
|
clamp,
|
|
1055
1576
|
composePattern,
|
|
1577
|
+
createCities,
|
|
1056
1578
|
createLightBudget,
|
|
1057
1579
|
createPlantProvider,
|
|
1058
1580
|
createSilhouetteHouses,
|
|
1059
1581
|
createTerrain,
|
|
1582
|
+
createTowerDrawer,
|
|
1060
1583
|
createUrban,
|
|
1061
1584
|
createUrbanBuilder,
|
|
1062
1585
|
createVehicleProvider,
|
|
@@ -1072,6 +1595,7 @@ export {
|
|
|
1072
1595
|
drawStripLabel,
|
|
1073
1596
|
drawTent,
|
|
1074
1597
|
drawTerrain,
|
|
1598
|
+
drawTower,
|
|
1075
1599
|
drawTree,
|
|
1076
1600
|
drawWalk,
|
|
1077
1601
|
drawWatchtower,
|
|
@@ -1082,6 +1606,9 @@ export {
|
|
|
1082
1606
|
insideRect,
|
|
1083
1607
|
lerp2 as lerp,
|
|
1084
1608
|
patternMarker,
|
|
1085
|
-
|
|
1609
|
+
pickTowerPreset,
|
|
1610
|
+
smoothstep,
|
|
1611
|
+
towerPresetOf,
|
|
1612
|
+
towerStoreys
|
|
1086
1613
|
};
|
|
1087
1614
|
//# sourceMappingURL=index.js.map
|