@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/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 height = lerp(minHeight, maxHeight, Math.pow(b.random(), HEIGHT_CURVE));
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 text = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
389
- kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z, text.title, text.caption);
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 text = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
394
- return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label: text.label, detail: text.detail };
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 = mergeById(DEFAULT_POLES, options.packs?.poles);
926
- applyPatches(entries, options.patch?.poles);
927
- removeIds(entries, options.remove?.poles);
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 = mergeById(DEFAULT_PATTERNS, options.packs?.neighbourhoods);
930
- applyPatches(patterns, options.patch?.neighbourhoods);
931
- removeIds(patterns, options.remove?.neighbourhoods);
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: (b, x, z, width, depth, height, color, o) => drawBuilding(b, x, z, width, depth, height, color, o),
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
- return { ...core, poles, builder, neighbourhoods, terrain, drawTerrain };
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
- smoothstep
1609
+ pickTowerPreset,
1610
+ smoothstep,
1611
+ towerPresetOf,
1612
+ towerStoreys
1086
1613
  };
1087
1614
  //# sourceMappingURL=index.js.map