@adatechnology/game-urban 0.1.0 → 0.2.1
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 +7 -4
package/dist/index.global.js
CHANGED
|
@@ -22,25 +22,36 @@ var GameUrban = (() => {
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
COMPOSERS: () => COMPOSERS,
|
|
25
|
+
DEFAULT_CITY_PLANS: () => DEFAULT_CITY_PLANS,
|
|
26
|
+
DEFAULT_DISTRICT_KINDS: () => DEFAULT_DISTRICT_KINDS,
|
|
25
27
|
DEFAULT_FACADES: () => DEFAULT_FACADES,
|
|
26
28
|
DEFAULT_LIGHT_BUDGET: () => DEFAULT_LIGHT_BUDGET,
|
|
29
|
+
DEFAULT_MAX_FLOORS: () => DEFAULT_MAX_FLOORS,
|
|
27
30
|
DEFAULT_PATTERNS: () => DEFAULT_PATTERNS,
|
|
28
31
|
DEFAULT_PLANT_CONTEXTS: () => DEFAULT_PLANT_CONTEXTS,
|
|
29
32
|
DEFAULT_POLES: () => DEFAULT_POLES,
|
|
30
33
|
DEFAULT_PROP_COLORS: () => DEFAULT_PROP_COLORS,
|
|
31
34
|
DEFAULT_ROAD_STYLE: () => DEFAULT_ROAD_STYLE,
|
|
35
|
+
DEFAULT_STOREY_PRESETS: () => DEFAULT_STOREY_PRESETS,
|
|
36
|
+
DEFAULT_STREET: () => DEFAULT_STREET,
|
|
37
|
+
DEFAULT_TOWER_PRESETS: () => DEFAULT_TOWER_PRESETS,
|
|
32
38
|
DEFAULT_URBAN_SEED: () => DEFAULT_URBAN_SEED,
|
|
33
39
|
DEFAULT_WALK_STYLE: () => DEFAULT_WALK_STYLE,
|
|
34
40
|
PARKED_VEHICLE_IDS: () => PARKED_VEHICLE_IDS,
|
|
35
41
|
POLE_SHAPES: () => POLE_SHAPES,
|
|
36
42
|
SAMPLE_CELLS: () => SAMPLE_CELLS,
|
|
37
43
|
SILHOUETTE_TYPOLOGIES: () => SILHOUETTE_TYPOLOGIES,
|
|
44
|
+
TOWER_DEFAULTS: () => TOWER_DEFAULTS,
|
|
45
|
+
TOWER_PATTERNS: () => TOWER_PATTERNS,
|
|
46
|
+
TOWER_SAMPLE_CELLS: () => TOWER_SAMPLE_CELLS,
|
|
38
47
|
clamp: () => clamp,
|
|
39
48
|
composePattern: () => composePattern,
|
|
49
|
+
createCities: () => createCities,
|
|
40
50
|
createLightBudget: () => createLightBudget,
|
|
41
51
|
createPlantProvider: () => createPlantProvider,
|
|
42
52
|
createSilhouetteHouses: () => createSilhouetteHouses,
|
|
43
53
|
createTerrain: () => createTerrain,
|
|
54
|
+
createTowerDrawer: () => createTowerDrawer,
|
|
44
55
|
createUrban: () => createUrban,
|
|
45
56
|
createUrbanBuilder: () => createUrbanBuilder,
|
|
46
57
|
createVehicleProvider: () => createVehicleProvider,
|
|
@@ -56,6 +67,7 @@ var GameUrban = (() => {
|
|
|
56
67
|
drawStripLabel: () => drawStripLabel,
|
|
57
68
|
drawTent: () => drawTent,
|
|
58
69
|
drawTerrain: () => drawTerrain,
|
|
70
|
+
drawTower: () => drawTower,
|
|
59
71
|
drawTree: () => drawTree,
|
|
60
72
|
drawWalk: () => drawWalk,
|
|
61
73
|
drawWatchtower: () => drawWatchtower,
|
|
@@ -66,7 +78,10 @@ var GameUrban = (() => {
|
|
|
66
78
|
insideRect: () => insideRect,
|
|
67
79
|
lerp: () => lerp2,
|
|
68
80
|
patternMarker: () => patternMarker,
|
|
69
|
-
|
|
81
|
+
pickTowerPreset: () => pickTowerPreset,
|
|
82
|
+
smoothstep: () => smoothstep,
|
|
83
|
+
towerPresetOf: () => towerPresetOf,
|
|
84
|
+
towerStoreys: () => towerStoreys
|
|
70
85
|
});
|
|
71
86
|
|
|
72
87
|
// src/budget.ts
|
|
@@ -341,8 +356,8 @@ var GameUrban = (() => {
|
|
|
341
356
|
const line = (points, color, parent = ROOT) => {
|
|
342
357
|
parts.push({ shape: "line", parent, points, color });
|
|
343
358
|
};
|
|
344
|
-
const sign = (
|
|
345
|
-
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors2 });
|
|
359
|
+
const sign = (text2, x, y, z, width, height, ry, colors2, parent = ROOT) => {
|
|
360
|
+
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text: text2, ...colors2 });
|
|
346
361
|
};
|
|
347
362
|
const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
|
|
348
363
|
const length = Math.hypot(x1 - x0, z1 - z0);
|
|
@@ -375,8 +390,8 @@ var GameUrban = (() => {
|
|
|
375
390
|
const zFace = z + face * (d / 2);
|
|
376
391
|
b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
|
|
377
392
|
b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
|
|
378
|
-
const
|
|
379
|
-
b.sign(
|
|
393
|
+
const text2 = b.pick(SHOP_SIGNS);
|
|
394
|
+
b.sign(text2, x, 5.3, zFace + face * 0.12, Math.min(w * 0.7, 8), 1, face > 0 ? 0 : Math.PI, { background: "#1a1a1c", foreground: b.pick(SIGN_COLORS) });
|
|
380
395
|
}
|
|
381
396
|
}
|
|
382
397
|
addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
|
|
@@ -714,6 +729,88 @@ var GameUrban = (() => {
|
|
|
714
729
|
for (let i = -count; i < count; i++) for (let j = -count; j < count; j++) drawWalk(b, i * block + block / 2, j * block + block / 2, block - width, block - width, options);
|
|
715
730
|
}
|
|
716
731
|
|
|
732
|
+
// src/tower.ts
|
|
733
|
+
var DEFAULT_TOWER_PRESETS = [
|
|
734
|
+
{ id: "office", weight: 0.6, storeyHeight: 4, mechanicalEvery: 20, glass: true, color: 3955315 },
|
|
735
|
+
{ id: "residential", weight: 0.4, storeyHeight: 3.1, mechanicalEvery: 25, glass: false }
|
|
736
|
+
];
|
|
737
|
+
var towerPresetOf = (id, weight, layout, extra = {}) => ({ id, weight, storeyHeight: layout.pitch, mechanicalEvery: 20, glass: layout.facade === "curtain", ...extra });
|
|
738
|
+
var TOWER_DEFAULTS = { minHeight: 60, maxStoreys: 60, mast: 0.06, lit: 0.12 };
|
|
739
|
+
var PODIUM = 1.5;
|
|
740
|
+
var PLANT = 2895667;
|
|
741
|
+
var PODIUM_COLOR = 2764081;
|
|
742
|
+
var PARAPET = 2763308;
|
|
743
|
+
var MAST = 10133670;
|
|
744
|
+
var MAX_MAST = 25;
|
|
745
|
+
var GLASS_COLUMN = 1.6;
|
|
746
|
+
var MASONRY_COLUMN = 3;
|
|
747
|
+
function pickTowerPreset(b, presets) {
|
|
748
|
+
const total = presets.reduce((sum2, p) => sum2 + p.weight, 0);
|
|
749
|
+
let r = b.random() * total;
|
|
750
|
+
for (const preset of presets) {
|
|
751
|
+
r -= preset.weight;
|
|
752
|
+
if (r < 0) return preset;
|
|
753
|
+
}
|
|
754
|
+
return presets[presets.length - 1];
|
|
755
|
+
}
|
|
756
|
+
var towerStoreys = (height, preset) => Math.max(3, Math.round(height / preset.storeyHeight));
|
|
757
|
+
function drawTower(b, request, options = {}) {
|
|
758
|
+
const { x, z, width: w, depth: d, preset } = request;
|
|
759
|
+
const storeys = towerStoreys(request.height, preset);
|
|
760
|
+
const h = preset.storeyHeight;
|
|
761
|
+
const podium = h * PODIUM;
|
|
762
|
+
const skin = preset.color ?? request.color;
|
|
763
|
+
const mechanical = /* @__PURE__ */ new Set();
|
|
764
|
+
if (preset.mechanicalEvery) {
|
|
765
|
+
for (let k = preset.mechanicalEvery; k < storeys - 1; k += preset.mechanicalEvery) if (storeys - 1 - k >= preset.mechanicalEvery / 2) mechanical.add(k);
|
|
766
|
+
}
|
|
767
|
+
mechanical.add(storeys - 1);
|
|
768
|
+
b.box(x, 0, z, w + 0.6, podium, d + 0.6, PODIUM_COLOR);
|
|
769
|
+
const shaftTop = (storeys - 1) * h;
|
|
770
|
+
b.box(x, podium, z, w, shaftTop - podium + (podium - h), d, skin);
|
|
771
|
+
for (const k of mechanical) {
|
|
772
|
+
if (k === storeys - 1) b.box(x, k * h + (podium - h), z, w * 0.82, h, d * 0.82, PLANT);
|
|
773
|
+
else b.box(x, k * h + (podium - h), z, w + 0.4, h, d + 0.4, PLANT);
|
|
774
|
+
}
|
|
775
|
+
const top = storeys * h + (podium - h);
|
|
776
|
+
b.box(x, top, z, w * 0.82 + 0.3, 0.35, d * 0.82 + 0.3, PARAPET);
|
|
777
|
+
const mast = Math.min(MAX_MAST, top * (options.mast ?? TOWER_DEFAULTS.mast));
|
|
778
|
+
if (mast > 0.5) b.cylinder(x, top + 0.35, z, 0.12, 0.2, mast, MAST, -1, 6);
|
|
779
|
+
const column = preset.glass ? GLASS_COLUMN : MASONRY_COLUMN;
|
|
780
|
+
const winHeight = preset.glass ? h * 0.62 : 1.5;
|
|
781
|
+
const winWidth = preset.glass ? column * 0.86 : 1.3;
|
|
782
|
+
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]];
|
|
783
|
+
for (const [ox, oz, faceWidth, ry] of faces) {
|
|
784
|
+
const columns = Math.max(1, Math.floor(faceWidth / column));
|
|
785
|
+
for (let storey = 1; storey < storeys; storey++) {
|
|
786
|
+
if (mechanical.has(storey)) continue;
|
|
787
|
+
const y = storey * h + (podium - h) + h * (preset.glass ? 0.2 : 0.35);
|
|
788
|
+
for (let c = 0; c < columns; c++) {
|
|
789
|
+
const u = (c - (columns - 1) / 2) * column;
|
|
790
|
+
const lit = b.random() < request.lit;
|
|
791
|
+
(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 });
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return { x, z, width: w, depth: d, height: top + 0.35 + (mast > 0.5 ? mast : 0), color: request.color };
|
|
796
|
+
}
|
|
797
|
+
function createTowerDrawer(towers, plain) {
|
|
798
|
+
const minHeight = towers.minHeight ?? TOWER_DEFAULTS.minHeight;
|
|
799
|
+
const presets = towers.presets ?? DEFAULT_TOWER_PRESETS;
|
|
800
|
+
return (b, x, z, width, depth, height, color, o = {}) => {
|
|
801
|
+
if (height < minHeight || presets.length === 0) return plain(b, x, z, width, depth, height, color, o);
|
|
802
|
+
const preset = (o.preset ? presets.find((p) => p.id === o.preset) : void 0) ?? pickTowerPreset(b, presets);
|
|
803
|
+
const top = (preset.maxStoreys ?? towers.maxStoreys ?? TOWER_DEFAULTS.maxStoreys) * preset.storeyHeight;
|
|
804
|
+
const bottom = (preset.minStoreys ?? 0) * preset.storeyHeight;
|
|
805
|
+
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 } : {} };
|
|
806
|
+
if (towers.provider) {
|
|
807
|
+
towers.provider(request, b);
|
|
808
|
+
return { x, z, width, depth, height: request.height, color };
|
|
809
|
+
}
|
|
810
|
+
return drawTower(b, request, towers);
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
717
814
|
// src/block.ts
|
|
718
815
|
var DEFAULT_FACADES = [3882821, 4866104, 3489853, 4602426, 4143924, 3093561, 5327167, 5920334];
|
|
719
816
|
var DEFAULT_PARTITIONS = 2;
|
|
@@ -722,10 +819,22 @@ var GameUrban = (() => {
|
|
|
722
819
|
var FOOTPRINT_TRIM = 1.2;
|
|
723
820
|
var FOOTPRINT_JITTER = 1.5;
|
|
724
821
|
var HEIGHT_CURVE = 1.6;
|
|
822
|
+
var DEFAULT_STOREY_PRESETS = { commercial: "office", residential: "residential" };
|
|
725
823
|
function drawBuilding(b, x, z, width, depth, height, color, options = {}) {
|
|
726
824
|
b.add(createCityBuilding({ x, z, width, depth, height, color, random: b.random, shopChance: options.shop, litChance: options.lit }));
|
|
727
825
|
return { x, z, width, depth, height, color };
|
|
728
826
|
}
|
|
827
|
+
function pickUse(b, uses) {
|
|
828
|
+
const entries = Object.entries(uses).filter(([, w]) => w > 0);
|
|
829
|
+
const total = entries.reduce((sum2, [, w]) => sum2 + w, 0);
|
|
830
|
+
if (total <= 0) return void 0;
|
|
831
|
+
let r = b.random() * total;
|
|
832
|
+
for (const [use, w] of entries) {
|
|
833
|
+
r -= w;
|
|
834
|
+
if (r < 0) return use;
|
|
835
|
+
}
|
|
836
|
+
return entries[entries.length - 1][0];
|
|
837
|
+
}
|
|
729
838
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
730
839
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
731
840
|
const placed = [];
|
|
@@ -738,11 +847,25 @@ var GameUrban = (() => {
|
|
|
738
847
|
if (b.random() < (options.gap || DEFAULT_GAP)) continue;
|
|
739
848
|
const width = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
740
849
|
const depth = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
741
|
-
const
|
|
850
|
+
const t = Math.pow(b.random(), HEIGHT_CURVE);
|
|
851
|
+
let height = lerp(minHeight, maxHeight, t);
|
|
852
|
+
let extra = null;
|
|
853
|
+
if (options.floors !== void 0 || options.uses) {
|
|
854
|
+
const use = options.uses ? pickUse(b, options.uses) : void 0;
|
|
855
|
+
const presetId = use ? (options.storeyPresets ?? DEFAULT_STOREY_PRESETS)[use] : void 0;
|
|
856
|
+
const preset = presetId ? (options.presets ?? DEFAULT_TOWER_PRESETS).find((p) => p.id === presetId) : void 0;
|
|
857
|
+
extra = { use, preset: preset?.id };
|
|
858
|
+
if (options.floors !== void 0) {
|
|
859
|
+
const [lo, hi] = typeof options.floors === "number" ? [options.floors, options.floors] : options.floors;
|
|
860
|
+
const storeys = Math.max(1, Math.round(lerp(lo, hi, t)));
|
|
861
|
+
height = storeys * (preset?.storeyHeight ?? options.storeyHeight ?? DEFAULT_TOWER_PRESETS[DEFAULT_TOWER_PRESETS.length - 1].storeyHeight);
|
|
862
|
+
extra.storeys = storeys;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
742
865
|
const x = cx - half + lot * (i + 0.5);
|
|
743
866
|
const z = cz - half + lot * (j + 0.5);
|
|
744
867
|
const color = b.pick(palette);
|
|
745
|
-
placed.push(drawBuilding(b, x, z, width, depth, height, color, options));
|
|
868
|
+
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color, extra ? { ...options, ...extra } : options));
|
|
746
869
|
}
|
|
747
870
|
}
|
|
748
871
|
return placed;
|
|
@@ -908,13 +1031,13 @@ var GameUrban = (() => {
|
|
|
908
1031
|
}
|
|
909
1032
|
};
|
|
910
1033
|
composer(context);
|
|
911
|
-
const
|
|
912
|
-
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z,
|
|
1034
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
1035
|
+
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z, text2.title, text2.caption);
|
|
913
1036
|
return plan;
|
|
914
1037
|
}
|
|
915
1038
|
function patternMarker(pattern, origin, locale = DEFAULT_LOCALE) {
|
|
916
|
-
const
|
|
917
|
-
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label:
|
|
1039
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
1040
|
+
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label: text2.label, detail: text2.detail };
|
|
918
1041
|
}
|
|
919
1042
|
|
|
920
1043
|
// src/neighbourhoods/composers.ts
|
|
@@ -1315,6 +1438,365 @@ var GameUrban = (() => {
|
|
|
1315
1438
|
military: { x: 160, z: 0 }
|
|
1316
1439
|
};
|
|
1317
1440
|
|
|
1441
|
+
// src/cities/kinds.ts
|
|
1442
|
+
var DEFAULT_DISTRICT_KINDS = [
|
|
1443
|
+
{ 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." } } },
|
|
1444
|
+
{ 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." } } },
|
|
1445
|
+
{ 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." } } },
|
|
1446
|
+
{ 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." } } },
|
|
1447
|
+
{ 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." } } },
|
|
1448
|
+
{ 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." } } },
|
|
1449
|
+
{ 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." } } },
|
|
1450
|
+
{ 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." } } },
|
|
1451
|
+
{ 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." } } },
|
|
1452
|
+
{ 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." } } },
|
|
1453
|
+
{ 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." } } },
|
|
1454
|
+
{ 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." } } },
|
|
1455
|
+
{ 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." } } },
|
|
1456
|
+
{ 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." } } },
|
|
1457
|
+
{ 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." } } },
|
|
1458
|
+
{ 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." } } },
|
|
1459
|
+
{ 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." } } },
|
|
1460
|
+
{ 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." } } },
|
|
1461
|
+
{ 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." } } },
|
|
1462
|
+
{ 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." } } },
|
|
1463
|
+
{ 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." } } },
|
|
1464
|
+
{ 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." } } }
|
|
1465
|
+
];
|
|
1466
|
+
|
|
1467
|
+
// src/cities/templates.ts
|
|
1468
|
+
var DEFAULT_CITY_PLANS = [
|
|
1469
|
+
{
|
|
1470
|
+
id: "metropolis",
|
|
1471
|
+
scale: "metropolis",
|
|
1472
|
+
size: [6e3, 6e3],
|
|
1473
|
+
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." } },
|
|
1474
|
+
districts: [
|
|
1475
|
+
{ id: "outskirts", kind: "rural", rect: [-3e3, -3e3, 3e3, 3e3] },
|
|
1476
|
+
{ id: "suburbs_w", kind: "residential_low", rect: [-2600, -2600, -600, 2600] },
|
|
1477
|
+
{ id: "suburbs_e", kind: "residential_mid", rect: [600, -2600, 2600, 1e3] },
|
|
1478
|
+
{ id: "periphery", kind: "residential_popular", rect: [-3e3, 1200, -800, 3e3] },
|
|
1479
|
+
{ id: "gated", kind: "residential_gated", rect: [1400, -3e3, 3e3, -1800] },
|
|
1480
|
+
{ id: "industry", kind: "industrial", rect: [600, 1600, 3e3, 3e3] },
|
|
1481
|
+
{ id: "logistics", kind: "logistics", rect: [2200, 400, 3e3, 1600] },
|
|
1482
|
+
{ id: "mixed", kind: "mixed", rect: [-1600, -1600, 1600, 1400] },
|
|
1483
|
+
{ id: "high", kind: "residential_high", rect: [-1400, -1500, -300, -300] },
|
|
1484
|
+
{ id: "high2", kind: "residential_high", rect: [300, -1500, 1400, -600] },
|
|
1485
|
+
{ id: "center", kind: "commercial_center", rect: [-900, -700, 1100, 800] },
|
|
1486
|
+
{ id: "downtown", kind: "downtown", rect: [-400, -400, 600, 400] },
|
|
1487
|
+
{ id: "hospital", kind: "institutional", rect: [-1600, 500, -1e3, 1100] },
|
|
1488
|
+
{ id: "campus", kind: "institutional", rect: [1300, -500, 2e3, 300] },
|
|
1489
|
+
{ id: "park", kind: "park", rect: [-300, 500, 500, 1100] },
|
|
1490
|
+
{ id: "hills", kind: "wild", rect: [-3e3, -3e3, -1800, -1800] }
|
|
1491
|
+
],
|
|
1492
|
+
lines: [
|
|
1493
|
+
{ id: "ring", kind: "avenue", width: 30, points: [[-1700, -1700], [1700, -1700], [1700, 1500], [-1700, 1500], [-1700, -1700]] },
|
|
1494
|
+
{ id: "main_ns", kind: "avenue", width: 30, points: [[100, -3e3], [100, 3e3]] },
|
|
1495
|
+
{ id: "main_ew", kind: "avenue", width: 30, points: [[-3e3, 0], [3e3, 0]] },
|
|
1496
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-3e3, 1500], [0, 1500], [3e3, 1300]] },
|
|
1497
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-3e3, -2200], [3e3, -2400]] }
|
|
1498
|
+
],
|
|
1499
|
+
landmarks: [
|
|
1500
|
+
{ kind: "station", at: [0, 1500] },
|
|
1501
|
+
{ kind: "hospital", at: [-1300, 800] },
|
|
1502
|
+
{ kind: "stadium", at: [1700, -100] },
|
|
1503
|
+
{ kind: "cathedral", at: [100, 100] },
|
|
1504
|
+
{ kind: "mall", at: [800, 600] },
|
|
1505
|
+
{ kind: "police", at: [300, -300] },
|
|
1506
|
+
{ kind: "fire", at: [-500, 600] },
|
|
1507
|
+
{ kind: "gas_station", at: [2400, -2300] }
|
|
1508
|
+
]
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
id: "regional_city",
|
|
1512
|
+
scale: "city",
|
|
1513
|
+
size: [4e3, 4e3],
|
|
1514
|
+
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." } },
|
|
1515
|
+
districts: [
|
|
1516
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -2e3, 2e3, 2e3] },
|
|
1517
|
+
{ id: "res_w", kind: "residential_low", rect: [-1800, -1600, -300, 1600] },
|
|
1518
|
+
{ id: "res_e", kind: "residential_mid", rect: [300, -1600, 1800, 600] },
|
|
1519
|
+
{ id: "popular", kind: "residential_popular", rect: [-1800, 800, -500, 1800] },
|
|
1520
|
+
{ id: "industry", kind: "industrial", rect: [500, 900, 2e3, 2e3] },
|
|
1521
|
+
{ id: "mixed", kind: "mixed", rect: [-800, -800, 900, 700] },
|
|
1522
|
+
{ id: "center", kind: "commercial_center", rect: [-400, -400, 500, 350] },
|
|
1523
|
+
{ id: "square", kind: "park", rect: [-100, -100, 150, 100] },
|
|
1524
|
+
{ id: "hospital", kind: "institutional", rect: [600, -900, 1e3, -500] }
|
|
1525
|
+
],
|
|
1526
|
+
lines: [
|
|
1527
|
+
{ id: "main_ns", kind: "avenue", width: 24, points: [[0, -2e3], [0, 2e3]] },
|
|
1528
|
+
{ id: "main_ew", kind: "avenue", width: 24, points: [[-2e3, 0], [2e3, 0]] },
|
|
1529
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-2e3, 1600], [2e3, 1500]] }
|
|
1530
|
+
],
|
|
1531
|
+
landmarks: [
|
|
1532
|
+
{ kind: "church", at: [0, 0] },
|
|
1533
|
+
{ kind: "hospital", at: [800, -700] },
|
|
1534
|
+
{ kind: "school", at: [-600, 300] },
|
|
1535
|
+
{ kind: "police", at: [200, -200] },
|
|
1536
|
+
{ kind: "market", at: [300, 200] },
|
|
1537
|
+
{ kind: "gas_station", at: [1500, 1550] }
|
|
1538
|
+
]
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
id: "port_city",
|
|
1542
|
+
scale: "city",
|
|
1543
|
+
size: [4e3, 3e3],
|
|
1544
|
+
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." } },
|
|
1545
|
+
districts: [
|
|
1546
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -1500, 2e3, 1500] },
|
|
1547
|
+
{ id: "hill", kind: "residential_low", rect: [500, -1500, 2e3, -200] },
|
|
1548
|
+
{ id: "res", kind: "residential_mid", rect: [-300, -500, 1300, 900] },
|
|
1549
|
+
{ id: "industry", kind: "industrial", rect: [800, 500, 2e3, 1500] },
|
|
1550
|
+
{ id: "port", kind: "port", rect: [-2e3, -1300, -900, 1100] },
|
|
1551
|
+
{ id: "old_town", kind: "historic", rect: [-900, -800, 200, 400] },
|
|
1552
|
+
{ id: "center", kind: "commercial_center", rect: [-500, -400, 200, 100] },
|
|
1553
|
+
{ id: "stilts", kind: "stilt", rect: [-2e3, 1e3, -1200, 1500] }
|
|
1554
|
+
],
|
|
1555
|
+
lines: [
|
|
1556
|
+
{ id: "shore", kind: "river", width: 60, points: [[-2e3, -1500], [-1950, 0], [-1900, 1500]] },
|
|
1557
|
+
{ id: "main", kind: "avenue", width: 20, points: [[-900, 0], [2e3, 0]] },
|
|
1558
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1200, -1500], [-1e3, -400]] }
|
|
1559
|
+
],
|
|
1560
|
+
landmarks: [
|
|
1561
|
+
{ kind: "harbour_office", at: [-1200, 0] },
|
|
1562
|
+
{ kind: "church", at: [-300, -200] },
|
|
1563
|
+
{ kind: "market", at: [-600, 200] },
|
|
1564
|
+
{ kind: "lighthouse", at: [1600, -1300] },
|
|
1565
|
+
{ kind: "hospital", at: [700, 200] }
|
|
1566
|
+
]
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
id: "rail_town",
|
|
1570
|
+
scale: "town",
|
|
1571
|
+
size: [3e3, 3e3],
|
|
1572
|
+
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." } },
|
|
1573
|
+
districts: [
|
|
1574
|
+
{ id: "outskirts", kind: "rural", rect: [-1500, -1500, 1500, 1500] },
|
|
1575
|
+
{ id: "res", kind: "residential_low", rect: [500, 500, 1500, 1500] },
|
|
1576
|
+
{ id: "logistics", kind: "logistics", rect: [500, -700, 1500, 500] },
|
|
1577
|
+
{ id: "popular", kind: "residential_popular", rect: [-1500, 600, -300, 1500] },
|
|
1578
|
+
{ id: "company", kind: "company_housing", rect: [-1500, -800, -700, 500] },
|
|
1579
|
+
{ id: "yard", kind: "rail_yard", rect: [-700, 0, 500, 500] },
|
|
1580
|
+
{ id: "old_center", kind: "historic", rect: [-500, -700, 500, 0] }
|
|
1581
|
+
],
|
|
1582
|
+
lines: [
|
|
1583
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1500, 250], [1500, 250]] },
|
|
1584
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-1500, -100], [1500, -200]] }
|
|
1585
|
+
],
|
|
1586
|
+
landmarks: [
|
|
1587
|
+
{ kind: "station", at: [0, 100] },
|
|
1588
|
+
{ kind: "church", at: [0, -400] },
|
|
1589
|
+
{ kind: "gas_station", at: [1e3, -150] }
|
|
1590
|
+
]
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
id: "country_town",
|
|
1594
|
+
scale: "town",
|
|
1595
|
+
size: [2400, 2400],
|
|
1596
|
+
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." } },
|
|
1597
|
+
districts: [
|
|
1598
|
+
{ id: "farms", kind: "farmland", rect: [-1200, -1200, 1200, 1200] },
|
|
1599
|
+
{ id: "chacaras", kind: "rural", rect: [-900, -900, 900, 900] },
|
|
1600
|
+
{ id: "houses", kind: "residential_low", rect: [-600, -600, 600, 500] },
|
|
1601
|
+
{ id: "company", kind: "company_housing", rect: [-700, 350, -250, 700] },
|
|
1602
|
+
{ id: "coop", kind: "farmland", rect: [350, 500, 900, 900], floors: [1, 1], uses: { agricultural: 0.5, industrial: 0.5 } },
|
|
1603
|
+
{ id: "center", kind: "historic", rect: [-250, -250, 250, 250] },
|
|
1604
|
+
{ id: "square", kind: "park", rect: [-60, -60, 60, 60] }
|
|
1605
|
+
],
|
|
1606
|
+
lines: [
|
|
1607
|
+
{ id: "main", kind: "street", width: 12, points: [[-1200, 0], [1200, 0]] },
|
|
1608
|
+
{ id: "road", kind: "street", width: 8, points: [[0, -1200], [0, 1200]] }
|
|
1609
|
+
],
|
|
1610
|
+
landmarks: [
|
|
1611
|
+
{ kind: "church", at: [0, -70] },
|
|
1612
|
+
{ kind: "market", at: [120, 80] },
|
|
1613
|
+
{ kind: "school", at: [-300, -200] },
|
|
1614
|
+
{ kind: "gas_station", at: [700, 0] },
|
|
1615
|
+
{ kind: "silo", at: [600, 700] }
|
|
1616
|
+
]
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
id: "mountain_village",
|
|
1620
|
+
scale: "village",
|
|
1621
|
+
size: [1600, 1200],
|
|
1622
|
+
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." } },
|
|
1623
|
+
districts: [
|
|
1624
|
+
{ id: "wild", kind: "wild", rect: [-800, -600, 800, 600] },
|
|
1625
|
+
{ id: "slope", kind: "rural", rect: [-700, -500, 700, 500] },
|
|
1626
|
+
{ id: "resort", kind: "hillside_resort", rect: [-400, -450, 500, 0] },
|
|
1627
|
+
{ id: "square", kind: "historic", rect: [-250, 0, 250, 300] },
|
|
1628
|
+
{ id: "station", kind: "rail_yard", rect: [300, 150, 700, 450] }
|
|
1629
|
+
],
|
|
1630
|
+
lines: [
|
|
1631
|
+
{ id: "road", kind: "street", width: 7, points: [[-800, 200], [0, 150], [800, 300]] }
|
|
1632
|
+
],
|
|
1633
|
+
landmarks: [
|
|
1634
|
+
{ kind: "church", at: [0, 150] },
|
|
1635
|
+
{ kind: "inn", at: [100, -200] },
|
|
1636
|
+
{ kind: "station", at: [500, 300] }
|
|
1637
|
+
]
|
|
1638
|
+
}
|
|
1639
|
+
];
|
|
1640
|
+
|
|
1641
|
+
// src/cities/cities.ts
|
|
1642
|
+
var DEFAULT_MAX_FLOORS = 60;
|
|
1643
|
+
var DEFAULT_STREET = 10;
|
|
1644
|
+
var DEFAULT_LOCALE2 = "pt-BR";
|
|
1645
|
+
var DEFAULT_STOREY_PRESETS2 = { commercial: "office", residential: "residential" };
|
|
1646
|
+
var text = (by, locale) => by[locale] ?? by[DEFAULT_LOCALE2];
|
|
1647
|
+
var named = (name, locale) => typeof name === "string" || name === void 0 ? name : name[locale] ?? name[DEFAULT_LOCALE2];
|
|
1648
|
+
var mergeLists = (parent, own) => {
|
|
1649
|
+
const list = [...parent];
|
|
1650
|
+
for (const item of own) {
|
|
1651
|
+
const at = list.findIndex((p) => p.id === item.id);
|
|
1652
|
+
if (at >= 0) list[at] = item;
|
|
1653
|
+
else list.push(item);
|
|
1654
|
+
}
|
|
1655
|
+
return list;
|
|
1656
|
+
};
|
|
1657
|
+
function createCities(o) {
|
|
1658
|
+
const maxFloors = o.maxFloors ?? DEFAULT_MAX_FLOORS;
|
|
1659
|
+
const kindEntries = mergeById(DEFAULT_DISTRICT_KINDS, o.packs?.kinds);
|
|
1660
|
+
applyPatches(kindEntries, o.patch?.kinds);
|
|
1661
|
+
removeIds(kindEntries, o.remove?.kinds);
|
|
1662
|
+
const planEntries = mergeById(DEFAULT_CITY_PLANS, o.packs?.plans);
|
|
1663
|
+
applyPatches(planEntries, o.patch?.plans);
|
|
1664
|
+
removeIds(planEntries, o.remove?.plans);
|
|
1665
|
+
const registry = (entries, label) => ({
|
|
1666
|
+
ids: () => [...entries.keys()],
|
|
1667
|
+
list: () => [...entries.values()],
|
|
1668
|
+
has: (id) => entries.has(id),
|
|
1669
|
+
get(id) {
|
|
1670
|
+
const entry = entries.get(id);
|
|
1671
|
+
if (!entry) throw new Error(`Unknown ${label} "${id}"`);
|
|
1672
|
+
return entry;
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
const kinds = registry(kindEntries, "district kind");
|
|
1676
|
+
const rawPlans = registry(planEntries, "city plan");
|
|
1677
|
+
const resolvePlan = (plan, seen = []) => {
|
|
1678
|
+
if (seen.includes(plan.id)) throw new Error(`City plan "${plan.id}" extends itself (${[...seen, plan.id].join(" > ")})`);
|
|
1679
|
+
const parent = plan.extends ? resolvePlan(rawPlans.get(plan.extends), [...seen, plan.id]) : void 0;
|
|
1680
|
+
const scale = plan.scale ?? parent?.scale;
|
|
1681
|
+
const size = plan.size ?? parent?.size;
|
|
1682
|
+
const planText = plan.text ?? parent?.text;
|
|
1683
|
+
if (!scale || !size || !planText) throw new Error(`City plan "${plan.id}" has no ${!scale ? "scale" : !size ? "size" : "text"}`);
|
|
1684
|
+
return {
|
|
1685
|
+
id: plan.id,
|
|
1686
|
+
scale,
|
|
1687
|
+
size,
|
|
1688
|
+
text: planText,
|
|
1689
|
+
extends: plan.extends,
|
|
1690
|
+
districts: mergeLists(parent?.districts ?? [], plan.districts ?? []),
|
|
1691
|
+
lines: mergeLists(parent?.lines ?? [], plan.lines ?? []),
|
|
1692
|
+
landmarks: [...parent?.landmarks ?? [], ...plan.landmarks ?? []]
|
|
1693
|
+
};
|
|
1694
|
+
};
|
|
1695
|
+
const plans = {
|
|
1696
|
+
...rawPlans,
|
|
1697
|
+
/** The plan with everything inherited from the plans it extends. */
|
|
1698
|
+
resolve: (id) => resolvePlan(rawPlans.get(id))
|
|
1699
|
+
};
|
|
1700
|
+
const layout = (planOrId, options = {}) => {
|
|
1701
|
+
const plan = typeof planOrId === "string" ? plans.resolve(planOrId) : resolvePlan(planOrId);
|
|
1702
|
+
const locale = options.locale ?? DEFAULT_LOCALE2;
|
|
1703
|
+
const cap = Math.max(0, options.maxFloors ?? maxFloors);
|
|
1704
|
+
const clamp2 = (n) => Math.min(cap, Math.max(0, n));
|
|
1705
|
+
const districts = plan.districts.map((d) => {
|
|
1706
|
+
const kind = kinds.get(d.kind);
|
|
1707
|
+
const kindText = text(kind.text, locale);
|
|
1708
|
+
const [lo, hi] = d.floors ?? kind.floors;
|
|
1709
|
+
return {
|
|
1710
|
+
id: d.id,
|
|
1711
|
+
kind: kind.id,
|
|
1712
|
+
rect: d.rect,
|
|
1713
|
+
name: named(d.name, locale) ?? kindText.name,
|
|
1714
|
+
description: kindText.description,
|
|
1715
|
+
color: kind.color,
|
|
1716
|
+
zone: kind.zone,
|
|
1717
|
+
pattern: kind.pattern,
|
|
1718
|
+
floors: [clamp2(lo), clamp2(hi)],
|
|
1719
|
+
uses: d.uses ?? kind.uses,
|
|
1720
|
+
block: kind.block
|
|
1721
|
+
};
|
|
1722
|
+
});
|
|
1723
|
+
const inside = (d, x, z) => x >= d.rect[0] && x <= d.rect[2] && z >= d.rect[1] && z <= d.rect[3];
|
|
1724
|
+
const districtAt = (x, z) => {
|
|
1725
|
+
for (let i = districts.length - 1; i >= 0; i--) if (inside(districts[i], x, z)) return districts[i];
|
|
1726
|
+
return void 0;
|
|
1727
|
+
};
|
|
1728
|
+
const blocks = (districtId, bo = {}) => {
|
|
1729
|
+
const d = districts.find((x) => x.id === districtId);
|
|
1730
|
+
if (!d) throw new Error(`City plan "${plan.id}" has no district "${districtId}"`);
|
|
1731
|
+
const street = bo.street ?? DEFAULT_STREET;
|
|
1732
|
+
const cell = d.block;
|
|
1733
|
+
const side = cell - street;
|
|
1734
|
+
if (cell <= 0 || side <= 0) return [];
|
|
1735
|
+
const [x0, z0, x1, z1] = d.rect;
|
|
1736
|
+
const cols = Math.floor((x1 - x0) / cell);
|
|
1737
|
+
const rows = Math.floor((z1 - z0) / cell);
|
|
1738
|
+
const ox = x0 + (x1 - x0 - cols * cell) / 2;
|
|
1739
|
+
const oz = z0 + (z1 - z0 - rows * cell) / 2;
|
|
1740
|
+
const found = [];
|
|
1741
|
+
for (let i = 0; i < cols; i++) {
|
|
1742
|
+
for (let j = 0; j < rows; j++) {
|
|
1743
|
+
const x = ox + cell * (i + 0.5);
|
|
1744
|
+
const z = oz + cell * (j + 0.5);
|
|
1745
|
+
if (districtAt(x, z)?.id !== d.id) continue;
|
|
1746
|
+
found.push({ x, z, size: [side, side], district: d.id, building: { floors: d.floors, uses: d.uses, zone: d.zone, pattern: d.pattern } });
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return found;
|
|
1750
|
+
};
|
|
1751
|
+
const landmarks = plan.landmarks.map((l) => {
|
|
1752
|
+
const { name, ...rest } = l;
|
|
1753
|
+
const label = named(name, locale);
|
|
1754
|
+
return label === void 0 ? rest : { ...rest, name: label };
|
|
1755
|
+
});
|
|
1756
|
+
const planText = text(plan.text, locale);
|
|
1757
|
+
return { id: plan.id, scale: plan.scale, size: plan.size, name: planText.name, description: planText.description, districts, lines: [...plan.lines], landmarks, districtAt, blocks };
|
|
1758
|
+
};
|
|
1759
|
+
const stats = (l) => {
|
|
1760
|
+
const xs = [...new Set(l.districts.flatMap((d) => [d.rect[0], d.rect[2]]))].sort((a, b) => a - b);
|
|
1761
|
+
const zs = [...new Set(l.districts.flatMap((d) => [d.rect[1], d.rect[3]]))].sort((a, b) => a - b);
|
|
1762
|
+
const area = /* @__PURE__ */ new Map();
|
|
1763
|
+
let total = 0;
|
|
1764
|
+
for (let i = 0; i + 1 < xs.length; i++) {
|
|
1765
|
+
for (let j = 0; j + 1 < zs.length; j++) {
|
|
1766
|
+
const x0 = xs[i];
|
|
1767
|
+
const z0 = zs[j];
|
|
1768
|
+
const w = xs[i + 1] - x0;
|
|
1769
|
+
const d = zs[j + 1] - z0;
|
|
1770
|
+
const owner = l.districtAt(x0 + w / 2, z0 + d / 2);
|
|
1771
|
+
if (!owner) continue;
|
|
1772
|
+
area.set(owner.kind, (area.get(owner.kind) ?? 0) + w * d);
|
|
1773
|
+
total += w * d;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
const count = /* @__PURE__ */ new Map();
|
|
1777
|
+
for (const d of l.districts) count.set(d.kind, (count.get(d.kind) ?? 0) + 1);
|
|
1778
|
+
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);
|
|
1779
|
+
};
|
|
1780
|
+
const fillBlock2 = (b, block, options = {}) => {
|
|
1781
|
+
const { floors, uses, pattern } = block.building;
|
|
1782
|
+
if (floors[1] <= 0 || pattern === null) return [];
|
|
1783
|
+
const { shop, ...rest } = options;
|
|
1784
|
+
const storey = options.storeyHeight ?? o.presets.find((p) => p.id === DEFAULT_STOREY_PRESETS2.residential)?.storeyHeight ?? o.presets[0]?.storeyHeight;
|
|
1785
|
+
return o.fill(b, {
|
|
1786
|
+
cx: block.x,
|
|
1787
|
+
cz: block.z,
|
|
1788
|
+
size: Math.min(block.size[0], block.size[1]),
|
|
1789
|
+
minHeight: floors[0] * storey,
|
|
1790
|
+
maxHeight: floors[1] * storey,
|
|
1791
|
+
floors: [floors[0], floors[1]],
|
|
1792
|
+
uses,
|
|
1793
|
+
shop: shop ?? uses.commercial ?? 0,
|
|
1794
|
+
...rest
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
return { maxFloors, kinds, plans, layout, stats, fillBlock: fillBlock2 };
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1318
1800
|
// src/terrain/math.ts
|
|
1319
1801
|
var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
1320
1802
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
@@ -1456,6 +1938,9 @@ var GameUrban = (() => {
|
|
|
1456
1938
|
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_BUDGET;
|
|
1457
1939
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
1458
1940
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
1941
|
+
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
1942
|
+
const building = (b, x, z, width, depth, height, color, o) => drawBuilding(b, x, z, width, depth, height, color, o);
|
|
1943
|
+
const drawBlockBuilding = towers ? createTowerDrawer(towers, building) : building;
|
|
1459
1944
|
const poles = {
|
|
1460
1945
|
ids: () => [...entries.keys()],
|
|
1461
1946
|
list: () => [...entries.values()],
|
|
@@ -1477,8 +1962,8 @@ var GameUrban = (() => {
|
|
|
1477
1962
|
},
|
|
1478
1963
|
block: {
|
|
1479
1964
|
facades,
|
|
1480
|
-
building:
|
|
1481
|
-
fill: (b, g) => fillBlock(b, g.cx, g.cz, g.size, g.minHeight, g.maxHeight, { palette: facades, ...g })
|
|
1965
|
+
building: drawBlockBuilding,
|
|
1966
|
+
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 })
|
|
1482
1967
|
},
|
|
1483
1968
|
props: {
|
|
1484
1969
|
fence: drawFence,
|
|
@@ -1521,7 +2006,15 @@ var GameUrban = (() => {
|
|
|
1521
2006
|
}
|
|
1522
2007
|
};
|
|
1523
2008
|
const terrain = (o) => createTerrain(o);
|
|
1524
|
-
|
|
2009
|
+
const cities = createCities({
|
|
2010
|
+
maxFloors: options.cities?.maxFloors,
|
|
2011
|
+
packs: { kinds: options.packs?.districtKinds, plans: options.packs?.cityPlans },
|
|
2012
|
+
patch: { kinds: options.patch?.districtKinds, plans: options.patch?.cityPlans },
|
|
2013
|
+
remove: { kinds: options.remove?.districtKinds, plans: options.remove?.cityPlans },
|
|
2014
|
+
presets: towers?.presets ?? DEFAULT_TOWER_PRESETS,
|
|
2015
|
+
fill: core.block.fill
|
|
2016
|
+
});
|
|
2017
|
+
return { ...core, poles, cities, builder, neighbourhoods, terrain, drawTerrain, towers: { enabled: towers !== null, options: towers } };
|
|
1525
2018
|
}
|
|
1526
2019
|
|
|
1527
2020
|
// ../game-vehicles-fleet/dist/index.js
|
|
@@ -31308,6 +31801,35 @@ var GameUrban = (() => {
|
|
|
31308
31801
|
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
31309
31802
|
};
|
|
31310
31803
|
}
|
|
31804
|
+
|
|
31805
|
+
// src/neighbourhoods/towers.ts
|
|
31806
|
+
var financialDistrict = {
|
|
31807
|
+
id: "financial_district",
|
|
31808
|
+
streetWidth: 16,
|
|
31809
|
+
walkWidth: 4,
|
|
31810
|
+
typologies: ["office_tower", "residential_tower"],
|
|
31811
|
+
pole: "led_double",
|
|
31812
|
+
lightColor: 14674175,
|
|
31813
|
+
spacing: 25,
|
|
31814
|
+
lit: 1,
|
|
31815
|
+
compose: "downtown",
|
|
31816
|
+
marker: { height: 40 },
|
|
31817
|
+
text: {
|
|
31818
|
+
"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." },
|
|
31819
|
+
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." }
|
|
31820
|
+
},
|
|
31821
|
+
params: {
|
|
31822
|
+
roadLength: 110,
|
|
31823
|
+
roadColor: 2237480,
|
|
31824
|
+
median: { width: 1.6, height: 0.18, color: 3820076 },
|
|
31825
|
+
poles: { from: -50, to: 50 },
|
|
31826
|
+
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 } },
|
|
31827
|
+
walks: { offset: 14, length: 110 },
|
|
31828
|
+
car: { dx: -8, dz: -3, ry: 1.57, color: 7038032 }
|
|
31829
|
+
}
|
|
31830
|
+
};
|
|
31831
|
+
var TOWER_PATTERNS = [financialDistrict];
|
|
31832
|
+
var TOWER_SAMPLE_CELLS = { financial_district: { x: 160, z: 56 } };
|
|
31311
31833
|
return __toCommonJS(index_exports);
|
|
31312
31834
|
})();
|
|
31313
31835
|
//# sourceMappingURL=index.global.js.map
|