@adatechnology/game-urban 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +170 -2
- package/dist/index.d.ts +1769 -0
- package/dist/index.global.js +31835 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1614 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1614 @@
|
|
|
1
|
+
// src/budget.ts
|
|
2
|
+
function createLightBudget(count) {
|
|
3
|
+
const budget = {
|
|
4
|
+
left: count,
|
|
5
|
+
take() {
|
|
6
|
+
return budget.left-- > 0;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
return budget;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/scene.ts
|
|
13
|
+
import { boxRadius } from "@adatechnology/game-floorplan";
|
|
14
|
+
var ROOT = -1;
|
|
15
|
+
var DEFAULT_CYLINDER_SEGMENTS = 14;
|
|
16
|
+
var DEFAULT_CONE_SEGMENTS = 6;
|
|
17
|
+
var DEFAULT_FENCE_WIRE = 9079950;
|
|
18
|
+
var DEFAULT_FENCE_POST = 6974574;
|
|
19
|
+
var DEFAULT_LABEL_FONT = '700 56px "Arial Narrow", Arial, sans-serif';
|
|
20
|
+
function createUrbanBuilder(random, options = {}) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
const items = [];
|
|
23
|
+
const windows = { lit: [], dark: [] };
|
|
24
|
+
const lights = options.budget ?? createLightBudget(Number.POSITIVE_INFINITY);
|
|
25
|
+
const pick = (list) => list[Math.floor(random() * list.length)];
|
|
26
|
+
const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
27
|
+
const box = (x, y, z, width, height, depth, color, o = {}) => {
|
|
28
|
+
const part = { shape: "box", parent: o.parent ?? ROOT, x, y: y + height / 2, z, width, height, depth, color, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) };
|
|
29
|
+
if (o.style) part.style = o.style;
|
|
30
|
+
parts.push(part);
|
|
31
|
+
};
|
|
32
|
+
const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = DEFAULT_CYLINDER_SEGMENTS, style) => {
|
|
33
|
+
const part = { shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments };
|
|
34
|
+
if (style) part.style = style;
|
|
35
|
+
parts.push(part);
|
|
36
|
+
};
|
|
37
|
+
const cone = (x, y, z, radius, height, color, parent = ROOT, segments = DEFAULT_CONE_SEGMENTS) => {
|
|
38
|
+
parts.push({ shape: "cone", parent, x, y: y + height / 2, z, radius, height, color, segments });
|
|
39
|
+
};
|
|
40
|
+
const prism = (x, y, z, width, height, depth, color, parent = ROOT, ry = 0) => {
|
|
41
|
+
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry });
|
|
42
|
+
};
|
|
43
|
+
const plane = (x, y, z, width, depth, color, style, parent = ROOT) => {
|
|
44
|
+
const part = { shape: "plane", parent, x, y, z, width, depth, color };
|
|
45
|
+
if (style) part.style = style;
|
|
46
|
+
parts.push(part);
|
|
47
|
+
};
|
|
48
|
+
const vault = (part) => {
|
|
49
|
+
parts.push({ shape: "vault", ...part, parent: part.parent ?? ROOT });
|
|
50
|
+
};
|
|
51
|
+
const terrain = (grid, parent = ROOT) => {
|
|
52
|
+
parts.push({ shape: "terrain", parent, grid });
|
|
53
|
+
};
|
|
54
|
+
const crown = (x, y, z, radius, color, parent = ROOT) => {
|
|
55
|
+
parts.push({ shape: "crown", parent, x, y, z, radius, color });
|
|
56
|
+
};
|
|
57
|
+
const line = (points, color, parent = ROOT) => {
|
|
58
|
+
parts.push({ shape: "line", parent, points, color });
|
|
59
|
+
};
|
|
60
|
+
const label = (lines, x, y, z, width, height, ry, o, parent = ROOT) => {
|
|
61
|
+
parts.push({ shape: "label", parent, x, y, z, width, height, ry, lines, background: o.background, foreground: o.foreground, font: o.font ?? DEFAULT_LABEL_FONT, canvas: o.canvas ?? { width: 512, height: 128 } });
|
|
62
|
+
};
|
|
63
|
+
const light = (part) => {
|
|
64
|
+
parts.push({ shape: "light", kind: "point", parent: ROOT, decay: 2, flicker: false, ...part });
|
|
65
|
+
};
|
|
66
|
+
const spot = (part) => {
|
|
67
|
+
parts.push({ shape: "light", kind: "spot", parent: ROOT, ...part });
|
|
68
|
+
};
|
|
69
|
+
const place = (kind, id, x, z, o = {}) => {
|
|
70
|
+
const part = { shape: "placement", parent: o.parent ?? ROOT, kind, id, x, y: o.y ?? 0, z, ry: o.ry ?? 0, scale: o.scale ?? 1, seed: o.seed ?? `${id}:${x}:${z}` };
|
|
71
|
+
if (o.color !== void 0) part.color = o.color;
|
|
72
|
+
parts.push(part);
|
|
73
|
+
};
|
|
74
|
+
const item = (x, y, z, width, height, depth, color, ry = 0, rx = 0, rz = 0) => {
|
|
75
|
+
items.push({ x, y, z, width, height, depth, color, rx, ry, rz });
|
|
76
|
+
};
|
|
77
|
+
const fence = (x0, z0, x1, z1, height, color = DEFAULT_FENCE_POST) => {
|
|
78
|
+
const count = Math.ceil(Math.hypot(x1 - x0, z1 - z0) / 3);
|
|
79
|
+
for (let index = 0; index <= count; index++) {
|
|
80
|
+
const t = index / count;
|
|
81
|
+
cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, ROOT, 5);
|
|
82
|
+
}
|
|
83
|
+
for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], DEFAULT_FENCE_WIRE);
|
|
84
|
+
};
|
|
85
|
+
const add = (silhouette) => {
|
|
86
|
+
const offset = parts.length;
|
|
87
|
+
for (const part of silhouette.parts) parts.push(part.parent < 0 ? part : { ...part, parent: part.parent + offset });
|
|
88
|
+
for (const window of silhouette.windows) (window.isLit ? windows.lit : windows.dark).push(window);
|
|
89
|
+
};
|
|
90
|
+
const scene = () => ({ parts, items, windows });
|
|
91
|
+
return { random, pick, lights, parts, items, windows, group, box, cylinder, cone, prism, plane, vault, terrain, crown, line, label, light, spot, place, item, fence, add, scene };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/poles.ts
|
|
95
|
+
var on = (o) => o.on ?? true;
|
|
96
|
+
function drawMast(b, mast, x, z, parent = -1) {
|
|
97
|
+
b.cylinder(x, 0, z, mast.radiusTop, mast.radiusBottom, mast.height, mast.color, parent, mast.segments, mast.style);
|
|
98
|
+
}
|
|
99
|
+
function spendLight(b, light, x, z, o) {
|
|
100
|
+
if (on(o) && b.lights.take()) b.light({ x: x + (light.reach ?? 0), y: light.y, z: z + (light.z ?? 0), color: light.color, intensity: light.intensity, distance: light.distance, flicker: o.flicker ?? false });
|
|
101
|
+
}
|
|
102
|
+
var doubleArm = (b, spec, o) => {
|
|
103
|
+
const group = b.group(o.x, 0, o.z, o.ry ?? 0);
|
|
104
|
+
drawMast(b, spec.mast, 0, 0, group);
|
|
105
|
+
for (const side of [-1, 1]) {
|
|
106
|
+
b.box(side * spec.arm.reach, spec.arm.y, 0, spec.arm.width, spec.arm.height, spec.arm.depth, spec.arm.color, { parent: group });
|
|
107
|
+
b.box(side * spec.lamp.reach, spec.lamp.y, 0, spec.lamp.width, spec.lamp.height, spec.lamp.depth, on(o) ? spec.lens.lit : spec.lens.unlit, { parent: group, emissive: on(o) });
|
|
108
|
+
}
|
|
109
|
+
spendLight(b, spec.light, o.x, o.z, o);
|
|
110
|
+
};
|
|
111
|
+
var singleArm = (b, spec, o) => {
|
|
112
|
+
const dir = o.dir ?? 1;
|
|
113
|
+
drawMast(b, spec.mast, o.x, o.z);
|
|
114
|
+
b.box(o.x + dir * spec.arm.reach, spec.arm.y, o.z, spec.arm.width, spec.arm.height, spec.arm.depth, spec.arm.color);
|
|
115
|
+
b.box(o.x + dir * spec.lamp.reach, spec.lamp.y, o.z, spec.lamp.width, spec.lamp.height, spec.lamp.depth, on(o) ? spec.lens.lit : spec.lens.unlit, { emissive: on(o) });
|
|
116
|
+
if (o.transformer) b.cylinder(o.x - dir * spec.transformer.offset, spec.transformer.y, o.z, spec.transformer.radius, spec.transformer.radius, spec.transformer.height, spec.transformer.color, -1, spec.transformer.segments);
|
|
117
|
+
spendLight(b, { ...spec.light, reach: dir * (spec.light.reach ?? spec.lamp.reach) }, o.x, o.z, o);
|
|
118
|
+
};
|
|
119
|
+
var lantern = (b, spec, o) => {
|
|
120
|
+
drawMast(b, spec.mast, o.x, o.z);
|
|
121
|
+
b.box(o.x + spec.lamp.reach, spec.lamp.y, o.z, spec.lamp.width, spec.lamp.height, spec.lamp.depth, on(o) ? spec.lens.lit : spec.lens.unlit, { emissive: on(o) });
|
|
122
|
+
b.cone(o.x, spec.cap.y, o.z, spec.cap.radius, spec.cap.height, spec.cap.color, -1, spec.cap.segments);
|
|
123
|
+
spendLight(b, spec.light, o.x, o.z, o);
|
|
124
|
+
};
|
|
125
|
+
var crossarm = (b, spec, o) => {
|
|
126
|
+
drawMast(b, spec.mast, o.x, o.z);
|
|
127
|
+
b.box(o.x + spec.arm.reach, spec.arm.y, o.z, spec.arm.width, spec.arm.height, spec.arm.depth, spec.arm.color);
|
|
128
|
+
b.box(o.x + spec.lamp.reach, spec.lamp.y, o.z, spec.lamp.width, spec.lamp.height, spec.lamp.depth, on(o) ? spec.lens.lit : spec.lens.unlit, { emissive: on(o) });
|
|
129
|
+
spendLight(b, { ...spec.light, reach: spec.light.reach ?? spec.lamp.reach }, o.x, o.z, o);
|
|
130
|
+
};
|
|
131
|
+
var floodTower = (b, spec, o) => {
|
|
132
|
+
for (const a of [-1, 1]) for (const c of [-1, 1]) b.box(o.x + a * spec.legs.offset, 0, o.z + c * spec.legs.offset, spec.legs.size, spec.legs.height, spec.legs.size, spec.legs.color);
|
|
133
|
+
b.box(o.x + spec.deck.reach, spec.deck.y, o.z, spec.deck.width, spec.deck.height, spec.deck.depth, spec.deck.color);
|
|
134
|
+
for (let index = 0; index < spec.heads.count; index++) {
|
|
135
|
+
b.box(o.x + spec.heads.start + index * spec.heads.spacing, spec.heads.y, o.z + spec.heads.z, spec.heads.width, spec.heads.height, spec.heads.depth, on(o) ? spec.lens.lit : spec.lens.unlit, { emissive: on(o) });
|
|
136
|
+
}
|
|
137
|
+
spendLight(b, spec.light, o.x, o.z, o);
|
|
138
|
+
};
|
|
139
|
+
var POLE_SHAPES = { double_arm: doubleArm, single_arm: singleArm, lantern, crossarm, flood_tower: floodTower };
|
|
140
|
+
var LED = {
|
|
141
|
+
mast: { radiusTop: 0.1, radiusBottom: 0.14, height: 9, color: 5593438, style: { metalness: 0.6, roughness: 0.5 } },
|
|
142
|
+
arm: { reach: 0.8, y: 8.8, width: 1.6, height: 0.1, depth: 0.12, color: 5593438 },
|
|
143
|
+
lamp: { reach: 1.5, y: 8.6, width: 0.6, height: 0.12, depth: 0.3 },
|
|
144
|
+
lens: { lit: 15660287, unlit: 3815994 },
|
|
145
|
+
light: { color: 14674175, intensity: 1.3, distance: 24, y: 8 }
|
|
146
|
+
};
|
|
147
|
+
var CONCRETE = {
|
|
148
|
+
mast: { radiusTop: 0.12, radiusBottom: 0.2, height: 8, color: 9079428, segments: 4 },
|
|
149
|
+
arm: { reach: 0.7, y: 7.6, width: 1.4, height: 0.1, depth: 0.1, color: 6974054 },
|
|
150
|
+
lamp: { reach: 1.35, y: 7.45, width: 0.5, height: 0.16, depth: 0.3 },
|
|
151
|
+
lens: { lit: 16752704, unlit: 3815994 },
|
|
152
|
+
light: { color: 16751168, intensity: 1.3, distance: 20, y: 7.2 },
|
|
153
|
+
transformer: { offset: 0.35, y: 6.2, radius: 0.28, height: 0.7, color: 5922402, segments: 8 }
|
|
154
|
+
};
|
|
155
|
+
var IRON = {
|
|
156
|
+
mast: { radiusTop: 0.06, radiusBottom: 0.12, height: 4, color: 1974306, segments: 8 },
|
|
157
|
+
lamp: { reach: 0, y: 4, width: 0.42, height: 0.5, depth: 0.42 },
|
|
158
|
+
cap: { y: 4.5, radius: 0.36, height: 0.3, color: 1974306, segments: 4 },
|
|
159
|
+
lens: { lit: 16765072, unlit: 2763306 },
|
|
160
|
+
light: { color: 16763008, intensity: 1, distance: 14, y: 3.9 }
|
|
161
|
+
};
|
|
162
|
+
var WOOD = {
|
|
163
|
+
mast: { radiusTop: 0.11, radiusBottom: 0.14, height: 7, color: 5916208, segments: 6 },
|
|
164
|
+
arm: { reach: 0, y: 6.6, width: 1.2, height: 0.08, depth: 0.08, color: 4864552 },
|
|
165
|
+
lamp: { reach: 0.5, y: 6.35, width: 0.16, height: 0.22, depth: 0.16 },
|
|
166
|
+
lens: { lit: 16769162, unlit: 3815994 },
|
|
167
|
+
light: { color: 16765562, intensity: 0.9, distance: 16, y: 6.2 }
|
|
168
|
+
};
|
|
169
|
+
var FLOOD = {
|
|
170
|
+
legs: { offset: 0.4, height: 12, size: 0.12, color: 6974574 },
|
|
171
|
+
deck: { reach: 0, y: 12, width: 2, height: 0.15, depth: 1.2, color: 6974574 },
|
|
172
|
+
heads: { count: 3, start: -0.6, spacing: 0.6, y: 12.2, z: 0.4, width: 0.45, height: 0.35, depth: 0.2 },
|
|
173
|
+
lens: { lit: 16773848, unlit: 3815994 },
|
|
174
|
+
light: { color: 16773328, intensity: 1.6, distance: 34, y: 11.5, z: 2 }
|
|
175
|
+
};
|
|
176
|
+
var DEFAULT_POLES = [
|
|
177
|
+
{ id: "led_double", shape: "double_arm", spec: LED },
|
|
178
|
+
{ id: "concrete_arm", shape: "single_arm", spec: CONCRETE },
|
|
179
|
+
{ id: "cast_iron", shape: "lantern", spec: IRON },
|
|
180
|
+
{ id: "wood_crossarm", shape: "crossarm", spec: WOOD },
|
|
181
|
+
{ id: "flood_tower", shape: "flood_tower", spec: FLOOD }
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// src/streets.ts
|
|
185
|
+
var DEFAULT_ROAD_STYLE = { color: 2237480, dash: { color: 9404239, every: 5, first: 2, length: 1.8, width: 0.16, height: 0.01, y: 0.025 } };
|
|
186
|
+
var DEFAULT_WALK_STYLE = { color: 3816252, height: 0.15, castShadow: false };
|
|
187
|
+
var ROAD_LEVEL = 0.02;
|
|
188
|
+
var DEFAULT_GROUND_COLOR = 1382426;
|
|
189
|
+
var DEFAULT_GROUND_SIZE = 600;
|
|
190
|
+
var roadStyle = (o) => ({ ...DEFAULT_ROAD_STYLE, ...o?.road, dash: { ...DEFAULT_ROAD_STYLE.dash, ...o?.road?.dash } });
|
|
191
|
+
var walkStyle = (o) => ({ ...DEFAULT_WALK_STYLE, ...o?.walk });
|
|
192
|
+
function drawRoad(b, x, z, w, l, along, options) {
|
|
193
|
+
const style = roadStyle(options);
|
|
194
|
+
const alongX = along === "x";
|
|
195
|
+
b.plane(x, ROAD_LEVEL, z, alongX ? l : w, alongX ? w : l, style.color);
|
|
196
|
+
const { dash } = style;
|
|
197
|
+
for (let u = -l / 2 + dash.first; u < l / 2; u += dash.every) {
|
|
198
|
+
b.item(alongX ? x + u : x, dash.y, alongX ? z : z + u, alongX ? dash.length : dash.width, dash.height, alongX ? dash.width : dash.length, dash.color);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function drawWalk(b, x, z, w, d, options) {
|
|
202
|
+
const style = walkStyle(options);
|
|
203
|
+
b.box(x, 0, z, w, style.height, d, style.color, { style: { castShadow: style.castShadow } });
|
|
204
|
+
}
|
|
205
|
+
function drawGround(b, color = DEFAULT_GROUND_COLOR, size = DEFAULT_GROUND_SIZE) {
|
|
206
|
+
b.plane(0, 0, 0, size, size, color);
|
|
207
|
+
}
|
|
208
|
+
function drawStreetGrid(b, block, count, width, options) {
|
|
209
|
+
for (let k = -count; k <= count; k++) {
|
|
210
|
+
drawRoad(b, k * block, 0, width, block * 2 * count + width, "z", options);
|
|
211
|
+
drawRoad(b, 0, k * block, width, block * 2 * count + width, "x", options);
|
|
212
|
+
}
|
|
213
|
+
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);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/block.ts
|
|
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
|
|
302
|
+
var DEFAULT_FACADES = [3882821, 4866104, 3489853, 4602426, 4143924, 3093561, 5327167, 5920334];
|
|
303
|
+
var DEFAULT_PARTITIONS = 2;
|
|
304
|
+
var DEFAULT_GAP = 0.08;
|
|
305
|
+
var STREET_MARGIN = 2;
|
|
306
|
+
var FOOTPRINT_TRIM = 1.2;
|
|
307
|
+
var FOOTPRINT_JITTER = 1.5;
|
|
308
|
+
var HEIGHT_CURVE = 1.6;
|
|
309
|
+
var DEFAULT_STOREY_PRESETS = { commercial: "office", residential: "residential" };
|
|
310
|
+
function drawBuilding(b, x, z, width, depth, height, color, options = {}) {
|
|
311
|
+
b.add(createCityBuilding({ x, z, width, depth, height, color, random: b.random, shopChance: options.shop, litChance: options.lit }));
|
|
312
|
+
return { x, z, width, depth, height, color };
|
|
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
|
+
}
|
|
325
|
+
var lerp = (a, b, t) => a + (b - a) * t;
|
|
326
|
+
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
327
|
+
const placed = [];
|
|
328
|
+
const palette = options.palette ?? DEFAULT_FACADES;
|
|
329
|
+
const half = (size - STREET_MARGIN) / 2;
|
|
330
|
+
const k = options.k || DEFAULT_PARTITIONS;
|
|
331
|
+
const lot = (size - STREET_MARGIN) / k;
|
|
332
|
+
for (let i = 0; i < k; i++) {
|
|
333
|
+
for (let j = 0; j < k; j++) {
|
|
334
|
+
if (b.random() < (options.gap || DEFAULT_GAP)) continue;
|
|
335
|
+
const width = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
336
|
+
const depth = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
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
|
+
}
|
|
352
|
+
const x = cx - half + lot * (i + 0.5);
|
|
353
|
+
const z = cz - half + lot * (j + 0.5);
|
|
354
|
+
const color = b.pick(palette);
|
|
355
|
+
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color, extra ? { ...options, ...extra } : options));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return placed;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/props.ts
|
|
362
|
+
var DEFAULT_PROP_COLORS = {
|
|
363
|
+
tent: 5199416,
|
|
364
|
+
trunk: 3811868,
|
|
365
|
+
pines: [2044708, 2373674, 1781024],
|
|
366
|
+
crowns: [3425578, 4016684, 3095846],
|
|
367
|
+
sandbags: 9075290,
|
|
368
|
+
fence: 6974574,
|
|
369
|
+
wire: 2763306,
|
|
370
|
+
label: { background: "rgba(16,18,22,.9)", foreground: "#e8dfcf" },
|
|
371
|
+
watchtower: { leg: 4864554, cabin: 5199416, deck: 3815978 }
|
|
372
|
+
};
|
|
373
|
+
var LABEL_FONT = '700 44px "Arial Narrow", Arial, sans-serif';
|
|
374
|
+
var LABEL_CANVAS = { width: 1024, height: 200 };
|
|
375
|
+
var SANDBAG_LENGTH = 0.56;
|
|
376
|
+
var colors = (c) => ({ ...DEFAULT_PROP_COLORS, ...c });
|
|
377
|
+
function drawFence(b, x0, z0, x1, z1, height, color) {
|
|
378
|
+
b.fence(x0, z0, x1, z1, height, color);
|
|
379
|
+
}
|
|
380
|
+
function drawTent(b, x, z, ry, w, d, color, c) {
|
|
381
|
+
b.prism(x, 0, z, w, w * 0.7, d, color ?? colors(c).tent, -1, ry);
|
|
382
|
+
}
|
|
383
|
+
function drawPine(b, x, z, scale = 1, y = 0, c) {
|
|
384
|
+
const palette = colors(c);
|
|
385
|
+
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale, palette.trunk);
|
|
386
|
+
const green = b.pick(palette.pines);
|
|
387
|
+
b.cone(x, y + 0.8 * scale, z, 1.1 * scale, 2.2 * scale, green);
|
|
388
|
+
b.cone(x, y + 2.1 * scale, z, 0.8 * scale, 1.8 * scale, green);
|
|
389
|
+
}
|
|
390
|
+
function drawTree(b, x, z, scale = 1, c) {
|
|
391
|
+
const palette = colors(c);
|
|
392
|
+
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale, palette.trunk);
|
|
393
|
+
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(palette.crowns));
|
|
394
|
+
}
|
|
395
|
+
function drawContainer(b, x, y, z, ry, color) {
|
|
396
|
+
const group = b.group(x, y, z, ry);
|
|
397
|
+
b.box(0, 0, 0, 2.44, 2.6, 6.06, color, { parent: group, style: { roughness: 0.7, metalness: 0.3 } });
|
|
398
|
+
for (let k = -2; k <= 2; k++) b.box(k * 0.5, 0, 3.04, 0.06, 2.6, 0.04, 1710618, { parent: group });
|
|
399
|
+
}
|
|
400
|
+
function drawWatchtower(b, x, z, searchlight = false, c) {
|
|
401
|
+
const palette = colors(c).watchtower;
|
|
402
|
+
for (const a of [-1, 1]) for (const d of [-1, 1]) b.box(x + a * 1.2, 0, z + d * 1.2, 0.25, 7, 0.25, palette.leg);
|
|
403
|
+
b.box(x, 7, z, 3.2, 1.2, 3.2, palette.cabin);
|
|
404
|
+
b.box(x, 8.2, z, 3.6, 0.15, 3.6, palette.deck);
|
|
405
|
+
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15, palette.deck);
|
|
406
|
+
if (searchlight) b.spot({ x, y: 9, z, color: 15134463, intensity: 2.4, distance: 70, decay: 1.2, angle: 0.35, penumbra: 0.5, target: { x: x + 20, y: 0, z: z + 20 }, sweep: { speed: 0.5, radius: 25 } });
|
|
407
|
+
}
|
|
408
|
+
function drawSandbags(b, x, z, length, along, rows = 3, c) {
|
|
409
|
+
const color = colors(c).sandbags;
|
|
410
|
+
for (let r = 0; r < rows; r++) {
|
|
411
|
+
for (let i = 0; i < length / SANDBAG_LENGTH; i++) {
|
|
412
|
+
const u = -length / 2 + 0.28 + i * SANDBAG_LENGTH + r % 2 * 0.28;
|
|
413
|
+
b.item(along === "x" ? x + u : x, r * 0.21, along === "x" ? z : z + u, along === "x" ? 0.55 : 0.32, 0.22, along === "x" ? 0.32 : 0.55, color);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function drawStripLabel(b, x, z, title, subtitle, c) {
|
|
418
|
+
const { background, foreground } = colors(c).label;
|
|
419
|
+
b.label([title, subtitle], x, 2.4, z, 22, 4.4, 0, { background, foreground, font: LABEL_FONT, canvas: LABEL_CANVAS });
|
|
420
|
+
}
|
|
421
|
+
function drawWires(b, points, y, color, c) {
|
|
422
|
+
b.line(points.map(([px, pz]) => [px, y, pz]), color ?? colors(c).wire);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/kit.ts
|
|
426
|
+
import { applyPatches as applyPatches2, mergeById as mergeById2, removeIds as removeIds2 } from "@adatechnology/game-contracts";
|
|
427
|
+
import { createSilhouetteRandom } from "@adatechnology/game-floorplan";
|
|
428
|
+
|
|
429
|
+
// src/neighbourhoods/houses.ts
|
|
430
|
+
import { createChalet, createColonialHouse, createCompanyHouse, createDetachedHouse, createFarmhouse, createSlabHouse, createStiltHouse, createTwoStoreyHouse } from "@adatechnology/game-floorplan";
|
|
431
|
+
var DEFAULT_WIDTH = 8;
|
|
432
|
+
var DEFAULT_DEPTH = 10;
|
|
433
|
+
var DEFAULT_COLOR = 13154456;
|
|
434
|
+
function turned(silhouette, ry) {
|
|
435
|
+
const first = silhouette.parts[0];
|
|
436
|
+
if (ry === void 0 || !first || first.shape !== "group") return silhouette;
|
|
437
|
+
return { ...silhouette, parts: [{ ...first, ry }, ...silhouette.parts.slice(1)] };
|
|
438
|
+
}
|
|
439
|
+
var SILHOUETTE_TYPOLOGIES = {
|
|
440
|
+
two_storey: (r, random) => createTwoStoreyHouse({ x: r.x, z: r.z, width: r.width ?? DEFAULT_WIDTH, depth: r.depth ?? DEFAULT_DEPTH, color: r.color ?? DEFAULT_COLOR, roofColor: r.roofColor, ry: r.ry, random }),
|
|
441
|
+
detached: (r, random) => createDetachedHouse({ x: r.x, z: r.z, width: r.width ?? DEFAULT_WIDTH, depth: r.depth ?? DEFAULT_DEPTH, color: r.color ?? DEFAULT_COLOR, roofColor: r.roofColor, ry: r.ry, isBoarded: r.isBoarded, random }),
|
|
442
|
+
slab: (r, random) => createSlabHouse({ x: r.x, z: r.z, width: r.width ?? 7, depth: r.depth ?? 8, floors: r.floors ?? 1, color: r.color ?? 11052188, random }),
|
|
443
|
+
colonial: (r, random) => turned(createColonialHouse({ x: r.x, z: r.z, width: r.width ?? 7, color: r.color ?? 13681832, hasDoor: r.hasDoor ?? false, random }), r.ry),
|
|
444
|
+
chalet: (r, random) => createChalet({ x: r.x, z: r.z, color: r.color, ry: r.ry, random }),
|
|
445
|
+
company_house: (r, random) => turned(createCompanyHouse({ x: r.x, z: r.z, color: r.color, random }), r.ry),
|
|
446
|
+
stilt: (r, random) => createStiltHouse({ x: r.x, z: r.z, color: r.color, random }),
|
|
447
|
+
farmhouse: (r, random) => createFarmhouse({ x: r.x, z: r.z, random })
|
|
448
|
+
};
|
|
449
|
+
function createSilhouetteHouses(typologies = SILHOUETTE_TYPOLOGIES) {
|
|
450
|
+
return (typology, request, b) => {
|
|
451
|
+
const factory = typologies[typology];
|
|
452
|
+
if (!factory) throw new Error(`The house provider has no typology "${typology}"`);
|
|
453
|
+
b.add(factory(request, b.random));
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/neighbourhoods/compose.ts
|
|
458
|
+
var LABEL_OFFSET_Z = 28;
|
|
459
|
+
var DEFAULT_LOCALE = "pt-BR";
|
|
460
|
+
var SAMPLE_GROUND = { color: 1974564, size: 500 };
|
|
461
|
+
function composePattern(kit, pattern, composer, b, origin, options = {}) {
|
|
462
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
463
|
+
const providers = options.providers ?? {};
|
|
464
|
+
const house = providers.house ?? createSilhouetteHouses();
|
|
465
|
+
const plan = { id: pattern.id, origin: { ...origin }, houses: [], buildings: [], poles: [], vehicles: [], plants: [] };
|
|
466
|
+
const context = {
|
|
467
|
+
kit,
|
|
468
|
+
b,
|
|
469
|
+
pattern,
|
|
470
|
+
params: pattern.params,
|
|
471
|
+
x0: origin.x,
|
|
472
|
+
z0: origin.z,
|
|
473
|
+
plan,
|
|
474
|
+
isLit: (share) => share >= 1 || b.random() < share,
|
|
475
|
+
pole: ({ id, ...rest }) => {
|
|
476
|
+
kit.poles.get(id ?? pattern.pole).build(b, rest);
|
|
477
|
+
plan.poles.push({ pole: id ?? pattern.pole, x: rest.x, z: rest.z, on: rest.on ?? true });
|
|
478
|
+
},
|
|
479
|
+
house: (typology, request) => {
|
|
480
|
+
house(typology, request, b);
|
|
481
|
+
plan.houses.push({ typology, x: request.x, z: request.z, ry: request.ry ?? 0 });
|
|
482
|
+
},
|
|
483
|
+
vehicle: (request) => {
|
|
484
|
+
providers.vehicle?.(request, b);
|
|
485
|
+
plan.vehicles.push(request);
|
|
486
|
+
},
|
|
487
|
+
plant: (request) => {
|
|
488
|
+
if (providers.plant) providers.plant(request, b);
|
|
489
|
+
else if (request.kind === "pine") kit.props.pine(b, request.x, request.z, request.scale);
|
|
490
|
+
else kit.props.tree(b, request.x, request.z, request.scale);
|
|
491
|
+
plan.plants.push(request);
|
|
492
|
+
},
|
|
493
|
+
building: (x, z, width, depth, height, color, o) => {
|
|
494
|
+
plan.buildings.push(kit.block.building(b, x, z, width, depth, height, color, o));
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
composer(context);
|
|
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);
|
|
500
|
+
return plan;
|
|
501
|
+
}
|
|
502
|
+
function patternMarker(pattern, origin, locale = DEFAULT_LOCALE) {
|
|
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 };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/neighbourhoods/composers.ts
|
|
508
|
+
var composeDowntown = ({ b, kit, pattern, params, x0, z0, pole, building, vehicle }) => {
|
|
509
|
+
b.plane(x0, 0.02, z0, params.roadLength, pattern.streetWidth, params.roadColor);
|
|
510
|
+
b.box(x0, 0, z0, params.roadLength, params.median.height, params.median.width, params.median.color);
|
|
511
|
+
for (let u = params.poles.from; u <= params.poles.to; u += pattern.spacing) pole({ x: x0 + u, z: z0, on: true });
|
|
512
|
+
const { buildings } = params;
|
|
513
|
+
for (let i = 0; i < buildings.perRow; i++) {
|
|
514
|
+
const x = x0 + buildings.first + i * buildings.pitch;
|
|
515
|
+
const northHeight = buildings.north.minHeight + b.random() * buildings.north.range;
|
|
516
|
+
building(x, z0 - buildings.rowOffset, buildings.size, buildings.size, northHeight, b.pick(kit.block.facades), { lit: buildings.north.lit });
|
|
517
|
+
building(x, z0 + buildings.rowOffset, buildings.size, buildings.size, buildings.south.minHeight + b.random() * buildings.south.range, b.pick(kit.block.facades), { lit: buildings.south.lit });
|
|
518
|
+
}
|
|
519
|
+
kit.streets.walk(b, x0, z0 - params.walks.offset, params.walks.length, pattern.walkWidth);
|
|
520
|
+
kit.streets.walk(b, x0, z0 + params.walks.offset, params.walks.length, pattern.walkWidth);
|
|
521
|
+
vehicle({ x: x0 + params.car.dx, z: z0 + params.car.dz, ry: params.car.ry, color: params.car.color });
|
|
522
|
+
};
|
|
523
|
+
var composeSuburb = ({ b, kit, pattern, params, x0, z0, pole, house, isLit }) => {
|
|
524
|
+
kit.streets.road(b, x0, z0, pattern.streetWidth, params.roadLength, "x");
|
|
525
|
+
kit.streets.walk(b, x0, z0 - params.walkOffset, params.roadLength, pattern.walkWidth);
|
|
526
|
+
kit.streets.walk(b, x0, z0 + params.walkOffset, params.roadLength, pattern.walkWidth);
|
|
527
|
+
const { houses, walls } = params;
|
|
528
|
+
for (let i = 0; i < houses.count; i++) {
|
|
529
|
+
const x = x0 + houses.first + i * houses.pitch;
|
|
530
|
+
house(houses.north.typologies[i % 2 ? 0 : 1], { x, z: z0 + houses.north.z, width: houses.width, depth: houses.depth, color: b.pick(houses.north.colors), roofColor: houses.roofColor, ry: 0 });
|
|
531
|
+
b.box(x, 0, z0 - walls.offset, walls.length, walls.height, walls.thickness, walls.color);
|
|
532
|
+
b.box(x + walls.gateOffset, 0, z0 - walls.gateInset, walls.gateWidth, walls.gateHeight, 0.06, walls.gateColor);
|
|
533
|
+
b.box(x, 0, z0 + walls.offset, walls.length, walls.height, walls.thickness, walls.color);
|
|
534
|
+
b.box(x - walls.gateOffset, 0, z0 + walls.gateInset, walls.gateWidth, walls.gateHeight, 0.06, walls.gateColor);
|
|
535
|
+
}
|
|
536
|
+
for (const u of params.poles.at) pole({ x: x0 + u, z: z0 + params.poles.z, dir: 1, on: isLit(pattern.lit), transformer: u > params.poles.transformerFrom });
|
|
537
|
+
kit.props.wires(b, [[x0 - params.wires.half, z0 + params.poles.z], [x0 + params.wires.half, z0 + params.poles.z]], params.wires.height);
|
|
538
|
+
for (let i = 0; i < houses.count; i++) {
|
|
539
|
+
house("detached", { x: x0 + houses.first + i * houses.pitch, z: z0 + houses.south.z, width: houses.width, depth: houses.depth, color: b.pick(houses.south.colors), roofColor: houses.roofColor, ry: Math.PI });
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
var composeWorkingClass = ({ b, kit, pattern, params, x0, z0, pole, house, isLit }) => {
|
|
543
|
+
kit.streets.road(b, x0, z0, pattern.streetWidth, 60, "x");
|
|
544
|
+
const { houses } = params;
|
|
545
|
+
for (let i = 0; i < houses.count; i++) {
|
|
546
|
+
const x = x0 + houses.first + i * houses.pitch;
|
|
547
|
+
house("slab", { x, z: z0 + houses.north.z, width: houses.width, depth: houses.depth, floors: 1 + Math.floor(b.random() * houses.maxFloors), color: b.pick(houses.north.colors) });
|
|
548
|
+
house("slab", { x, z: z0 + houses.south.z, width: houses.width, depth: houses.depth, floors: 1 + Math.floor(b.random() * houses.maxFloors), color: b.pick(houses.south.colors) });
|
|
549
|
+
}
|
|
550
|
+
for (let u = params.poles.from; u <= params.poles.to; u += pattern.spacing + b.random() * params.poles.jitter) pole({ x: x0 + u, z: z0 + params.poles.z, dir: 1, on: isLit(pattern.lit), transformer: true });
|
|
551
|
+
const { wires } = params;
|
|
552
|
+
for (let k = 0; k < wires.count; k++) {
|
|
553
|
+
kit.props.wires(b, [[x0 + wires.from, z0 + wires.z + k * wires.step], [x0 + wires.to, z0 + wires.z + (b.random() - 0.5) * wires.sag]], wires.height + k * wires.heightStep, wires.color);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
var composeHistoricStreet = ({ b, pattern, params, x0, z0, pole, house, isLit }) => {
|
|
557
|
+
const { cobbles, rows } = params;
|
|
558
|
+
b.plane(x0, cobbles.y, z0, cobbles.length, pattern.streetWidth, cobbles.color);
|
|
559
|
+
for (let u = cobbles.from; u < cobbles.to; u += cobbles.step) b.item(x0 + u, cobbles.y, z0 + (b.random() - 0.5) * cobbles.spread, cobbles.sizes[0], cobbles.sizes[1], cobbles.sizes[2], b.pick(cobbles.colors));
|
|
560
|
+
for (const side of [rows.north, rows.south]) {
|
|
561
|
+
let x = x0 + rows.from;
|
|
562
|
+
while (x < x0 + rows.to) {
|
|
563
|
+
const width = rows.minWidth + Math.floor(b.random() * rows.widthSteps);
|
|
564
|
+
house("colonial", { x: x + width / 2, z: z0 + side.z, width, color: b.pick(side.colors), hasDoor: true, ry: side === rows.south ? Math.PI : void 0 });
|
|
565
|
+
x += width;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
for (let u = params.poles.from; u <= params.poles.to; u += params.poles.step) {
|
|
569
|
+
for (const s of [-1, 1]) pole({ x: x0 + u + (s > 0 ? params.poles.sideOffset : 0), z: z0 + s * params.poles.z, on: isLit(pattern.lit) });
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var composeIndustrial = ({ b, kit, pattern, params, x0, z0, pole }) => {
|
|
573
|
+
b.plane(x0, 0.02, z0, params.yard.length, pattern.streetWidth, params.yard.color);
|
|
574
|
+
const { hangar } = params;
|
|
575
|
+
for (const s of [-1, 1]) {
|
|
576
|
+
const g = b.group(x0 + s * hangar.x, 0, z0 + hangar.z);
|
|
577
|
+
b.box(0, 0, 0, hangar.width, hangar.height, hangar.depth, hangar.color, { parent: g });
|
|
578
|
+
b.vault({ x: 0, y: hangar.roofY, z: 0, radius: hangar.roofRadius, length: hangar.depth, arc: Math.PI, segments: 16, color: hangar.roofColor, rotation: { x: Math.PI / 2, y: 0, z: Math.PI / 2 }, scale: { x: 1, y: 1, z: 0.25 }, parent: g });
|
|
579
|
+
b.box(0, 0, hangar.depth / 2 + 0.05, hangar.doorWidth, hangar.doorHeight, 0.1, hangar.doorColor, { parent: g });
|
|
580
|
+
b.box(0, 6, hangar.depth / 2 + 0.06, 10, 1.2, 0.05, hangar.stripeColor, { parent: g });
|
|
581
|
+
}
|
|
582
|
+
for (const tower of params.towers) pole({ x: x0 + tower.dx, z: z0 + tower.dz, on: tower.on });
|
|
583
|
+
kit.props.container(b, x0 + params.container.dx, 0, z0 + params.container.dz, params.container.ry, params.container.color);
|
|
584
|
+
};
|
|
585
|
+
var composeRailwayVillage = ({ b, kit, pattern, params, x0, z0, pole, house, isLit }) => {
|
|
586
|
+
kit.streets.road(b, x0, z0, pattern.streetWidth, 60, "x");
|
|
587
|
+
const { houses } = params;
|
|
588
|
+
for (let i = 0; i < houses.count; i++) {
|
|
589
|
+
const x = x0 + houses.first + i * houses.pitch;
|
|
590
|
+
house("company_house", { x, z: z0 + houses.north.z, color: b.pick(houses.north.colors) });
|
|
591
|
+
house("company_house", { x, z: z0 + houses.south.z, color: houses.south.color, ry: Math.PI });
|
|
592
|
+
}
|
|
593
|
+
for (const u of params.poles.at) pole({ x: x0 + u, z: z0 + params.poles.z, dir: 1, on: isLit(pattern.lit), transformer: u > params.poles.transformerFrom });
|
|
594
|
+
kit.props.wires(b, [[x0 - params.wires.half, z0 + params.poles.z], [x0 + params.wires.half, z0 + params.poles.z]], params.wires.height);
|
|
595
|
+
};
|
|
596
|
+
var composeMountainResort = ({ b, pattern, params, x0, z0, pole, house, plant, isLit }) => {
|
|
597
|
+
b.plane(x0, params.road.y, z0, params.road.length, pattern.streetWidth, params.road.color);
|
|
598
|
+
const { chalets } = params;
|
|
599
|
+
for (let i = 0; i < chalets.count; i++) {
|
|
600
|
+
house("chalet", { x: x0 + chalets.first + i * chalets.pitch, z: z0 + chalets.northZ, color: b.pick(chalets.northColors) });
|
|
601
|
+
house("chalet", { x: x0 + chalets.first + i * chalets.pitch + chalets.southShift, z: z0 + chalets.southZ, color: b.pick(chalets.southColors), ry: Math.PI });
|
|
602
|
+
}
|
|
603
|
+
for (let u = params.poles.from; u <= params.poles.to; u += params.poles.step) {
|
|
604
|
+
for (const s of [-1, 1]) pole({ x: x0 + u + (s > 0 ? params.poles.sideOffset : 0), z: z0 + s * params.poles.z, on: isLit(pattern.lit) });
|
|
605
|
+
}
|
|
606
|
+
const { pines } = params;
|
|
607
|
+
for (let i = 0; i < pines.count; i++) plant({ kind: "pine", x: x0 + pines.first + b.random() * pines.span, z: z0 + (b.random() < 0.5 ? -pines.rowZ : pines.rowZ), scale: pines.minScale + b.random() * pines.scaleRange });
|
|
608
|
+
};
|
|
609
|
+
var composeStiltVillage = ({ b, params, x0, z0, house }) => {
|
|
610
|
+
const { water, bank, boardwalk, houses } = params;
|
|
611
|
+
b.plane(x0, water.y, z0 + water.z, water.length, water.depth, water.color, { roughness: water.roughness, metalness: water.metalness });
|
|
612
|
+
b.box(x0, 0, z0 + bank.z, bank.length, 0.2, bank.depth, bank.color);
|
|
613
|
+
b.box(x0, boardwalk.y, z0, boardwalk.length, 0.2, boardwalk.width, boardwalk.color);
|
|
614
|
+
for (let u = boardwalk.pilesFrom; u <= boardwalk.pilesTo; u += boardwalk.pilesStep) b.cylinder(x0 + u, -1.5, z0 + 0.9, 0.1, 0.1, 2.9, 3811868, -1, 5);
|
|
615
|
+
for (let i = 0; i < houses.count; i++) {
|
|
616
|
+
const x = x0 + houses.first + i * houses.pitch;
|
|
617
|
+
house("stilt", { x, z: z0 + houses.z, color: b.pick(houses.colors) });
|
|
618
|
+
const lit = i % 2 === 1;
|
|
619
|
+
b.box(x + houses.lantern.dx, houses.lantern.y, z0 + houses.lantern.dz, 0.2, 0.28, 0.2, lit ? houses.lantern.lit : houses.lantern.unlit, { emissive: lit });
|
|
620
|
+
}
|
|
621
|
+
b.light({ x: x0 + params.lamp.dx, y: params.lamp.y, z: z0 + params.lamp.dz, color: params.lamp.color, intensity: params.lamp.intensity, distance: params.lamp.distance });
|
|
622
|
+
b.box(x0 + params.boat.dx, -0.2, z0 + params.boat.dz, 1.6, 0.5, 4.6, params.boat.color);
|
|
623
|
+
};
|
|
624
|
+
var composeMilitary = ({ b, kit, pattern, params, x0, z0 }) => {
|
|
625
|
+
b.plane(x0, 0.02, z0, params.yard.length, pattern.streetWidth, params.yard.color);
|
|
626
|
+
const { fence } = params;
|
|
627
|
+
for (let u = fence.from; u <= fence.to; u += fence.step) b.cylinder(x0 + u, 0, z0 + fence.z, 0.06, 0.06, 3, fence.color, -1, 4);
|
|
628
|
+
b.line([[x0 + fence.from, 2.9, z0 + fence.z], [x0 + fence.to, 2.9, z0 + fence.z]], fence.wireColor);
|
|
629
|
+
for (const u of params.towers.dx) kit.props.watchtower(b, x0 + u, z0 + params.towers.dz, true);
|
|
630
|
+
const { alarm } = params;
|
|
631
|
+
b.light({ x: x0, y: alarm.y, z: z0 + alarm.dz, color: alarm.color, intensity: alarm.intensity, distance: alarm.distance });
|
|
632
|
+
b.box(x0, alarm.lampY, z0 + alarm.lampDz, 0.3, 0.3, 0.3, alarm.color, { emissive: true });
|
|
633
|
+
kit.props.sandbags(b, x0, z0 + params.sandbags.dz, params.sandbags.length, "x", params.sandbags.rows);
|
|
634
|
+
for (let i = 0; i < params.tents.count; i++) kit.props.tent(b, x0 + params.tents.first + i * params.tents.pitch, z0 + params.tents.dz, 0, params.tents.width, params.tents.depth);
|
|
635
|
+
};
|
|
636
|
+
var composeRural = ({ b, pattern, params, x0, z0, pole, house, plant }) => {
|
|
637
|
+
b.plane(x0, 0.02, z0, params.road.length, pattern.streetWidth, params.road.color);
|
|
638
|
+
b.plane(x0, params.field.y, z0 + params.field.dz, params.field.length, params.field.depth, params.field.color);
|
|
639
|
+
house("farmhouse", { x: x0 + params.farmhouse.dx, z: z0 + params.farmhouse.dz });
|
|
640
|
+
pole({ x: x0 + params.pole.dx, z: z0 + params.pole.dz, on: true });
|
|
641
|
+
b.fence(x0 - params.fence.half, z0 + params.fence.dz, x0 + params.fence.half, z0 + params.fence.dz, params.fence.height, params.fence.color);
|
|
642
|
+
const { trees, crops } = params;
|
|
643
|
+
for (let i = 0; i < trees.count; i++) plant({ kind: "tree", x: x0 + trees.first + b.random() * trees.span, z: z0 + trees.dz + b.random() * trees.spread, scale: trees.minScale + b.random() * trees.scaleRange });
|
|
644
|
+
for (let r = 0; r < crops.rows; r++) {
|
|
645
|
+
for (let i = 0; i < crops.perRow; i++) b.item(x0 + crops.first + i * crops.pitch, 0, z0 + crops.dz + r * crops.rowPitch, crops.width, crops.minHeight + b.random() * crops.heightRange, crops.width, crops.color);
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var COMPOSERS = {
|
|
649
|
+
downtown: composeDowntown,
|
|
650
|
+
suburb: composeSuburb,
|
|
651
|
+
working_class: composeWorkingClass,
|
|
652
|
+
historic_street: composeHistoricStreet,
|
|
653
|
+
industrial_port: composeIndustrial,
|
|
654
|
+
railway_village: composeRailwayVillage,
|
|
655
|
+
mountain_resort: composeMountainResort,
|
|
656
|
+
stilt_village: composeStiltVillage,
|
|
657
|
+
military: composeMilitary,
|
|
658
|
+
rural: composeRural
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// src/neighbourhoods/patterns.ts
|
|
662
|
+
var suburb = {
|
|
663
|
+
id: "suburb",
|
|
664
|
+
streetWidth: 7,
|
|
665
|
+
walkWidth: 2,
|
|
666
|
+
typologies: ["detached", "two_storey"],
|
|
667
|
+
pole: "concrete_arm",
|
|
668
|
+
lightColor: 16751168,
|
|
669
|
+
spacing: 35,
|
|
670
|
+
lit: 0.75,
|
|
671
|
+
compose: "suburb",
|
|
672
|
+
marker: { height: 10 },
|
|
673
|
+
text: {
|
|
674
|
+
"pt-BR": { label: "Sub\xFArbio", title: "SUB\xDARBIO", caption: "casa t\xE9rrea e sobrado \xB7 muro 1,8 m \xB7 s\xF3dio a cada 35 m", detail: "Casa t\xE9rrea e sobrado, muro de 1,8 m. Poste de concreto com s\xF3dio laranja." },
|
|
675
|
+
en: { label: "Suburb", title: "SUBURB", caption: "one-storey and two-storey houses \xB7 1.8 m wall \xB7 sodium every 35 m", detail: "One-storey and two-storey houses behind a 1.8 m wall. Concrete pole with an orange sodium lamp." }
|
|
676
|
+
},
|
|
677
|
+
params: {
|
|
678
|
+
roadLength: 60,
|
|
679
|
+
walkOffset: 4.5,
|
|
680
|
+
houses: { count: 5, first: -24, pitch: 12, north: { z: -15, typologies: ["two_storey", "detached"], colors: [13154458, 11055264, 13680800, 12101776] }, south: { z: 15, colors: [13154458, 11055264, 13680800] }, roofColor: 6961706, width: 8, depth: 10 },
|
|
681
|
+
walls: { color: 12235424, height: 1.8, thickness: 0.2, offset: 5.8, gateColor: 3815994, gateWidth: 2.4, gateHeight: 1.6, gateOffset: 3.5, gateInset: 5.7, length: 10 },
|
|
682
|
+
poles: { at: [-26, 9], z: -4.8, transformerFrom: 0 },
|
|
683
|
+
wires: { half: 28, height: 6.9 }
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
var downtown = {
|
|
687
|
+
id: "downtown",
|
|
688
|
+
streetWidth: 14,
|
|
689
|
+
walkWidth: 3,
|
|
690
|
+
typologies: ["city_building"],
|
|
691
|
+
pole: "led_double",
|
|
692
|
+
lightColor: 14674175,
|
|
693
|
+
spacing: 25,
|
|
694
|
+
lit: 1,
|
|
695
|
+
compose: "downtown",
|
|
696
|
+
marker: { height: 14 },
|
|
697
|
+
text: {
|
|
698
|
+
"pt-BR": { label: "Centro", title: "CENTRO", caption: "avenida 2 \xD7 7 m \xB7 LED branco a cada 25 m", detail: "Pr\xE9dio residencial e comercial de 4 a 12 andares. Poste met\xE1lico duplo, LED branco." },
|
|
699
|
+
en: { label: "Downtown", title: "DOWNTOWN", caption: "avenue 2 \xD7 7 m \xB7 white LED every 25 m", detail: "Residential and commercial buildings of 4 to 12 floors. Double steel pole, white LED." }
|
|
700
|
+
},
|
|
701
|
+
params: {
|
|
702
|
+
roadLength: 60,
|
|
703
|
+
roadColor: 2237480,
|
|
704
|
+
median: { width: 1.6, height: 0.18, color: 3820076 },
|
|
705
|
+
poles: { from: -26, to: 26 },
|
|
706
|
+
buildings: { perRow: 4, first: -21, pitch: 14, rowOffset: 18, size: 12, north: { minHeight: 12, range: 24, lit: 0.1 }, south: { minHeight: 12, range: 18, lit: 0.08 } },
|
|
707
|
+
walks: { offset: 10, length: 60 },
|
|
708
|
+
car: { dx: -8, dz: -3, ry: 1.57, color: 7038032 }
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
var workingClass = {
|
|
712
|
+
id: "working_class",
|
|
713
|
+
streetWidth: 6,
|
|
714
|
+
walkWidth: 0,
|
|
715
|
+
typologies: ["slab"],
|
|
716
|
+
pole: "concrete_arm",
|
|
717
|
+
lightColor: 16751168,
|
|
718
|
+
spacing: 36,
|
|
719
|
+
lit: 0.5,
|
|
720
|
+
compose: "working_class",
|
|
721
|
+
marker: { height: 10 },
|
|
722
|
+
text: {
|
|
723
|
+
"pt-BR": { label: "Bairro popular", title: "BAIRRO POPULAR", caption: "lajes de 1 a 3 \xB7 caixa d'\xE1gua azul \xB7 metade das luzes queimadas", detail: "Casas geminadas de laje, fios emaranhados, metade das luzes queimadas." },
|
|
724
|
+
en: { label: "Working-class quarter", title: "WORKING-CLASS QUARTER", caption: "slab houses of 1 to 3 floors \xB7 blue water tank \xB7 half the lamps burnt out", detail: "Row slab houses, tangled wires, half the lamps burnt out." }
|
|
725
|
+
},
|
|
726
|
+
params: {
|
|
727
|
+
houses: { count: 10, first: -27, pitch: 6, width: 5.6, depth: 10, north: { z: -9, colors: [10510922, 10119764, 9067076] }, south: { z: 9, colors: [10510922, 10119764, 13156528] }, maxFloors: 3 },
|
|
728
|
+
poles: { from: -24, to: 26, z: -3.5, jitter: 8 },
|
|
729
|
+
wires: { count: 4, from: -26, to: 26, z: -3.5, step: 0.15, height: 6.3, heightStep: 0.25, sag: 2, color: 1710618 }
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
var historicStreet = {
|
|
733
|
+
id: "historic_street",
|
|
734
|
+
streetWidth: 5,
|
|
735
|
+
walkWidth: 0,
|
|
736
|
+
typologies: ["colonial"],
|
|
737
|
+
pole: "cast_iron",
|
|
738
|
+
lightColor: 16763008,
|
|
739
|
+
spacing: 20,
|
|
740
|
+
lit: 0.8,
|
|
741
|
+
compose: "historic_street",
|
|
742
|
+
marker: { height: 8 },
|
|
743
|
+
text: {
|
|
744
|
+
"pt-BR": { label: "Rua hist\xF3rica", title: "RUA HIST\xD3RICA", caption: "casas coloniais coladas \xB7 pedra 5 m \xB7 ferro fundido a cada 20 m", detail: "Casas coloniais coladas, rua de pedra, lumin\xE1ria de ferro fundido." },
|
|
745
|
+
en: { label: "Historic street", title: "HISTORIC STREET", caption: "colonial houses wall to wall \xB7 5 m cobbles \xB7 cast iron every 20 m", detail: "Colonial houses wall to wall, cobbled street, cast-iron lanterns." }
|
|
746
|
+
},
|
|
747
|
+
params: {
|
|
748
|
+
cobbles: { y: 0.03, length: 60, color: 4867650, from: -28, to: 28, step: 0.9, spread: 4, sizes: [0.8, 0.04, 0.6], colors: [5920336, 5130822, 6446680] },
|
|
749
|
+
rows: { from: -28, to: 26, minWidth: 5, widthSteps: 3, north: { z: -8, colors: [14207136, 11059392, 14727312, 12626104, 13684928] }, south: { z: 8, colors: [14207136, 11059392, 14727312, 12626104] } },
|
|
750
|
+
poles: { from: -25, to: 25, step: 20, z: 2.9, sideOffset: 10 }
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
var industrialPort = {
|
|
754
|
+
id: "industrial_port",
|
|
755
|
+
streetWidth: 40,
|
|
756
|
+
walkWidth: 0,
|
|
757
|
+
typologies: ["hangar"],
|
|
758
|
+
pole: "flood_tower",
|
|
759
|
+
lightColor: 16773328,
|
|
760
|
+
spacing: 60,
|
|
761
|
+
lit: 1,
|
|
762
|
+
compose: "industrial_port",
|
|
763
|
+
marker: { height: 16 },
|
|
764
|
+
text: {
|
|
765
|
+
"pt-BR": { label: "Industrial e porto", title: "INDUSTRIAL E PORTO", caption: "galp\xE3o (amostra encurtada) \xB7 torre de refletor a cada 60 m", detail: "Galp\xF5es e torres de refletor de 12 m." },
|
|
766
|
+
en: { label: "Industrial and port", title: "INDUSTRIAL AND PORT", caption: "warehouse (shortened sample) \xB7 floodlight tower every 60 m", detail: "Warehouses and 12 m floodlight towers." }
|
|
767
|
+
},
|
|
768
|
+
params: {
|
|
769
|
+
yard: { length: 60, color: 2763564 },
|
|
770
|
+
hangar: { x: 15, z: -6, width: 26, height: 10, depth: 30, color: 6974568, roofColor: 5921880, roofRadius: 13, roofY: 10, doorColor: 3815994, doorWidth: 6, doorHeight: 5, stripeColor: 14200874 },
|
|
771
|
+
towers: [{ dx: -30, dz: 14, on: true }, { dx: 30, dz: 14, on: false }],
|
|
772
|
+
container: { dx: 20, dz: 14, ry: 0.2, color: 9058858 }
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
var railwayVillage = {
|
|
776
|
+
id: "railway_village",
|
|
777
|
+
streetWidth: 6,
|
|
778
|
+
walkWidth: 0,
|
|
779
|
+
typologies: ["company_house"],
|
|
780
|
+
pole: "concrete_arm",
|
|
781
|
+
lightColor: 16751168,
|
|
782
|
+
spacing: 35,
|
|
783
|
+
lit: 0.7,
|
|
784
|
+
compose: "railway_village",
|
|
785
|
+
marker: { height: 8 },
|
|
786
|
+
text: {
|
|
787
|
+
"pt-BR": { label: "Vila ferrovi\xE1ria", title: "VILA FERROVI\xC1RIA", caption: "casas da companhia, todas iguais \xB7 poste residencial a cada 35 m", detail: "Casas da companhia em fila, todas iguais." },
|
|
788
|
+
en: { label: "Railway village", title: "RAILWAY VILLAGE", caption: "company houses, all alike \xB7 residential pole every 35 m", detail: "Company houses in a row, all alike." }
|
|
789
|
+
},
|
|
790
|
+
params: {
|
|
791
|
+
houses: { count: 6, first: -25, pitch: 10, north: { z: -12, colors: [14209216, 13682868] }, south: { z: 12, color: 14209216 } },
|
|
792
|
+
poles: { at: [-26, 9], z: -4, transformerFrom: 0 },
|
|
793
|
+
wires: { half: 26, height: 6.9 }
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
var mountainResort = {
|
|
797
|
+
id: "mountain_resort",
|
|
798
|
+
streetWidth: 6,
|
|
799
|
+
walkWidth: 0,
|
|
800
|
+
typologies: ["chalet"],
|
|
801
|
+
pole: "cast_iron",
|
|
802
|
+
lightColor: 16763008,
|
|
803
|
+
spacing: 20,
|
|
804
|
+
lit: 0.7,
|
|
805
|
+
compose: "mountain_resort",
|
|
806
|
+
marker: { height: 10 },
|
|
807
|
+
text: {
|
|
808
|
+
"pt-BR": { label: "Serra", title: "SERRA \xB7 RUA DAS POUSADAS", caption: "chal\xE9 de madeira e pedra \xB7 telhado 45\xB0 \xB7 ferro fundido a cada 20 m", detail: "Rua das Pousadas: chal\xE9s de madeira e pedra com telhado \xEDngreme e lumin\xE1ria de ferro fundido. Na Encosta, sem ilumina\xE7\xE3o." },
|
|
809
|
+
en: { label: "Mountain", title: "MOUNTAIN \xB7 INN STREET", caption: "wood and stone chalets \xB7 45\xB0 roof \xB7 cast iron every 20 m", detail: "Inn Street: wood and stone chalets with a steep roof and cast-iron lanterns. Up the slope there is no lighting." }
|
|
810
|
+
},
|
|
811
|
+
params: {
|
|
812
|
+
road: { y: 0.02, length: 60, color: 2763304 },
|
|
813
|
+
chalets: { count: 5, first: -24, pitch: 12, northZ: -12, southZ: 12, southShift: 6, northColors: [7031344, 5914152, 8018490], southColors: [7031344, 5914152] },
|
|
814
|
+
poles: { from: -25, to: 25, step: 20, z: 2.6, sideOffset: 10 },
|
|
815
|
+
pines: { count: 10, first: -28, span: 56, rowZ: 22, minScale: 1.4, scaleRange: 1 }
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
var stiltVillage = {
|
|
819
|
+
id: "stilt_village",
|
|
820
|
+
streetWidth: 2,
|
|
821
|
+
walkWidth: 0,
|
|
822
|
+
typologies: ["stilt"],
|
|
823
|
+
pole: "",
|
|
824
|
+
lightColor: 16756832,
|
|
825
|
+
spacing: 0,
|
|
826
|
+
lit: 1,
|
|
827
|
+
compose: "stilt_village",
|
|
828
|
+
marker: { height: 7 },
|
|
829
|
+
text: {
|
|
830
|
+
"pt-BR": { label: "Palafitas", title: "PALAFITAS", caption: "madeira sobre estacas \xB7 sem luz p\xFAblica \xB7 lampi\xE3o", detail: "Vila dos Pescadores: sem ilumina\xE7\xE3o p\xFAblica, s\xF3 lampi\xE3o." },
|
|
831
|
+
en: { label: "Stilt village", title: "STILT VILLAGE", caption: "timber on piles \xB7 no public lighting \xB7 lanterns", detail: "Fishermen's village: no public lighting, only lanterns." }
|
|
832
|
+
},
|
|
833
|
+
params: {
|
|
834
|
+
water: { y: 0.04, z: -2, length: 60, depth: 30, color: 2241598, roughness: 0.25, metalness: 0.3 },
|
|
835
|
+
bank: { z: 14, length: 60, depth: 8, color: 4867126 },
|
|
836
|
+
boardwalk: { y: 1.3, length: 56, width: 2, color: 5916208, pilesFrom: -26, pilesTo: 26, pilesStep: 4 },
|
|
837
|
+
houses: { count: 5, first: -22, pitch: 11, z: -7, colors: [6969926, 5925482, 8018502], lantern: { dx: 1.5, y: 3.2, dz: -3.4, lit: 16760944, unlit: 3815994 } },
|
|
838
|
+
lamp: { dx: -11, y: 3, dz: -3, color: 16756832, intensity: 0.9, distance: 10 },
|
|
839
|
+
boat: { dx: 14, dz: 6, color: 5913130 }
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var military = {
|
|
843
|
+
id: "military",
|
|
844
|
+
streetWidth: 40,
|
|
845
|
+
walkWidth: 0,
|
|
846
|
+
typologies: ["tent"],
|
|
847
|
+
pole: "",
|
|
848
|
+
lightColor: 15134463,
|
|
849
|
+
spacing: 40,
|
|
850
|
+
lit: 1,
|
|
851
|
+
compose: "military",
|
|
852
|
+
marker: { height: 12, dz: -14 },
|
|
853
|
+
text: {
|
|
854
|
+
"pt-BR": { label: "Militar", title: "MILITAR", caption: "torre de vigia e trip\xE9 \xB7 holofote branco \xB7 luz vermelha \xB7 a cada 40 m", detail: "Base, bloqueio e penitenci\xE1ria: torres com holofote branco e luz vermelha de emerg\xEAncia no per\xEDmetro." },
|
|
855
|
+
en: { label: "Military", title: "MILITARY", caption: "watch tower and tripod \xB7 white searchlight \xB7 red lamp \xB7 every 40 m", detail: "Base, roadblock and penitentiary: towers with a white searchlight and a red emergency lamp on the perimeter." }
|
|
856
|
+
},
|
|
857
|
+
params: {
|
|
858
|
+
yard: { length: 60, color: 3815984 },
|
|
859
|
+
fence: { from: -28, to: 28, step: 4, z: -12, height: 3, color: 6974574, wireColor: 10132122 },
|
|
860
|
+
towers: { dx: [-20, 20], dz: -14 },
|
|
861
|
+
alarm: { dz: -10, color: 16722458, intensity: 1.2, distance: 16, y: 3, lampDz: -11.7, lampY: 2.6 },
|
|
862
|
+
sandbags: { dz: -6, length: 10, rows: 3 },
|
|
863
|
+
tents: { count: 3, first: -14, pitch: 14, dz: 8, width: 6, depth: 8 }
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
var rural = {
|
|
867
|
+
id: "rural",
|
|
868
|
+
streetWidth: 6,
|
|
869
|
+
walkWidth: 0,
|
|
870
|
+
typologies: ["farmhouse"],
|
|
871
|
+
pole: "wood_crossarm",
|
|
872
|
+
lightColor: 16765562,
|
|
873
|
+
spacing: 0,
|
|
874
|
+
lit: 1,
|
|
875
|
+
compose: "rural",
|
|
876
|
+
marker: { height: 7 },
|
|
877
|
+
text: {
|
|
878
|
+
"pt-BR": { label: "Rural", title: "RURAL", caption: "casa sede com varanda \xB7 poste de madeira s\xF3 no cruzamento", detail: "Casa sede com varanda. Luz s\xF3 em cruzamentos e sedes." },
|
|
879
|
+
en: { label: "Rural", title: "RURAL", caption: "farmhouse with a veranda \xB7 wooden pole only at the junction", detail: "Farmhouse with a veranda. Light only at junctions and farmhouses." }
|
|
880
|
+
},
|
|
881
|
+
params: {
|
|
882
|
+
road: { length: 60, color: 4864554 },
|
|
883
|
+
field: { y: 0.01, dz: 15, length: 60, depth: 22, color: 3820074 },
|
|
884
|
+
farmhouse: { dx: -10, dz: -14 },
|
|
885
|
+
pole: { dx: 6, dz: -4 },
|
|
886
|
+
fence: { dz: -4, half: 28, height: 1.2, color: 5916208 },
|
|
887
|
+
trees: { count: 8, first: 8, span: 20, dz: -16, spread: 10, minScale: 1, scaleRange: 0.5 },
|
|
888
|
+
crops: { rows: 6, perRow: 20, first: -26, pitch: 2.7, dz: 8, rowPitch: 2.2, width: 0.4, minHeight: 0.6, heightRange: 0.3, color: 4872746 }
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
var DEFAULT_PATTERNS = [downtown, suburb, workingClass, historicStreet, industrialPort, railwayVillage, mountainResort, stiltVillage, military, rural];
|
|
892
|
+
var SAMPLE_CELLS = {
|
|
893
|
+
downtown: { x: -80, z: -56 },
|
|
894
|
+
suburb: { x: 0, z: -56 },
|
|
895
|
+
working_class: { x: 80, z: -56 },
|
|
896
|
+
historic_street: { x: -80, z: 0 },
|
|
897
|
+
industrial_port: { x: 0, z: 0 },
|
|
898
|
+
railway_village: { x: 80, z: 0 },
|
|
899
|
+
mountain_resort: { x: -80, z: 56 },
|
|
900
|
+
stilt_village: { x: 0, z: 56 },
|
|
901
|
+
rural: { x: 80, z: 56 },
|
|
902
|
+
military: { x: 160, z: 0 }
|
|
903
|
+
};
|
|
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
|
+
|
|
1267
|
+
// src/terrain/math.ts
|
|
1268
|
+
var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
1269
|
+
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
1270
|
+
var smoothstep = (a, b, x) => {
|
|
1271
|
+
const t = clamp((x - a) / (b - a), 0, 1);
|
|
1272
|
+
return t * t * (3 - 2 * t);
|
|
1273
|
+
};
|
|
1274
|
+
var hash2 = (a, b) => {
|
|
1275
|
+
const n = Math.sin(a * 127.1 + b * 311.7) * 43758.5453;
|
|
1276
|
+
return n - Math.floor(n);
|
|
1277
|
+
};
|
|
1278
|
+
function distanceToPolyline(px, py, points) {
|
|
1279
|
+
let best = 1e9;
|
|
1280
|
+
for (let index = 0; index < points.length - 1; index++) {
|
|
1281
|
+
const [ax, ay] = points[index];
|
|
1282
|
+
const [bx, by] = points[index + 1];
|
|
1283
|
+
const dx = bx - ax;
|
|
1284
|
+
const dy = by - ay;
|
|
1285
|
+
const t = clamp(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0, 1);
|
|
1286
|
+
const d = Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
1287
|
+
if (d < best) best = d;
|
|
1288
|
+
}
|
|
1289
|
+
return best;
|
|
1290
|
+
}
|
|
1291
|
+
var insideRect = (px, py, r, margin) => px > r[0] - margin && px < r[2] + margin && py > r[1] - margin && py < r[3] + margin;
|
|
1292
|
+
var insideEllipse = (px, py, center, radius) => Math.hypot((px - center[0]) / radius[0], (py - center[1]) / radius[1]) < 1;
|
|
1293
|
+
|
|
1294
|
+
// src/terrain/terrain.ts
|
|
1295
|
+
var TRIG = { sin: Math.sin, cos: Math.cos };
|
|
1296
|
+
function wave(w, px, py) {
|
|
1297
|
+
return w.kind === "product" ? TRIG[w.x[0]](px * w.x[1]) * TRIG[w.y[0]](py * w.y[1]) * w.amplitude : Math.sin(px * w.kx + py * w.ky) * w.amplitude;
|
|
1298
|
+
}
|
|
1299
|
+
function ramp(r, px, py) {
|
|
1300
|
+
const v = r.axis === "x" ? px : py;
|
|
1301
|
+
return r.fullUntil !== void 0 && v < r.fullUntil ? 1 : smoothstep(r.from, r.to, v);
|
|
1302
|
+
}
|
|
1303
|
+
function mask(ramps, px, py) {
|
|
1304
|
+
let m = 1;
|
|
1305
|
+
for (const r of ramps) m *= ramp(r, px, py);
|
|
1306
|
+
return m;
|
|
1307
|
+
}
|
|
1308
|
+
var sum = (start, waves, px, py) => {
|
|
1309
|
+
let total = start;
|
|
1310
|
+
for (const w of waves) total += wave(w, px, py);
|
|
1311
|
+
return total;
|
|
1312
|
+
};
|
|
1313
|
+
function createTerrain(options) {
|
|
1314
|
+
const { world } = options;
|
|
1315
|
+
const toWorld = (px, py) => [px * world.scale + world.origin, py * world.scale + world.origin];
|
|
1316
|
+
const fromWorld = (x, z) => [(x - world.origin) / world.scale, (z - world.origin) / world.scale];
|
|
1317
|
+
function heightAt(px, py) {
|
|
1318
|
+
let h = sum(options.base.level, options.base.waves, px, py);
|
|
1319
|
+
for (const layer of options.layers) h += mask(layer.mask, px, py) * sum(layer.level, layer.waves, px, py);
|
|
1320
|
+
for (const f of options.flatten) {
|
|
1321
|
+
if (!insideRect(px, py, f.rect, f.margin)) continue;
|
|
1322
|
+
const inside = Math.min(smoothstep(f.edge[0], f.edge[1], Math.min(px - f.rect[0], f.rect[2] - px, py - f.rect[1], f.rect[3] - py)), 1);
|
|
1323
|
+
h = lerp2(h, f.level, inside);
|
|
1324
|
+
}
|
|
1325
|
+
const land = h;
|
|
1326
|
+
for (const river of options.rivers) {
|
|
1327
|
+
const bed = "fixed" in river.bed ? river.bed.fixed : Math.max(river.bed.floor, land - river.bed.below);
|
|
1328
|
+
h = lerp2(bed, h, smoothstep(river.width[0], river.width[1], distanceToPolyline(px, py, river.points)));
|
|
1329
|
+
}
|
|
1330
|
+
for (const lake of options.lakes) {
|
|
1331
|
+
const d = Math.hypot((px - lake.center[0]) / lake.radius[0], (py - lake.center[1]) / lake.radius[1]);
|
|
1332
|
+
if (d < lake.reach) h = lerp2(lake.level, h, smoothstep(lake.edge[0], lake.edge[1], d));
|
|
1333
|
+
}
|
|
1334
|
+
return options.custom ? options.custom(px, py, h) : h;
|
|
1335
|
+
}
|
|
1336
|
+
function forestAt(px, py) {
|
|
1337
|
+
let density = 0;
|
|
1338
|
+
for (const zone of options.forest.zones) density = Math.max(density, mask(zone.mask, px, py));
|
|
1339
|
+
const patch = options.forest.patch;
|
|
1340
|
+
if (patch) density = Math.max(density, smoothstep(patch.edge[0], patch.edge[1], sum(0, patch.waves, px, py) * 0.5 + 0.5) * patch.weight);
|
|
1341
|
+
return density;
|
|
1342
|
+
}
|
|
1343
|
+
const nearRiver = (px, py, margin) => options.rivers.some((river) => distanceToPolyline(px, py, river.points) < margin);
|
|
1344
|
+
function matches(rule, px, py, h, forest) {
|
|
1345
|
+
switch (rule.kind) {
|
|
1346
|
+
case "below":
|
|
1347
|
+
return h < rule.height;
|
|
1348
|
+
case "above":
|
|
1349
|
+
return h > rule.height;
|
|
1350
|
+
case "ellipse":
|
|
1351
|
+
return insideEllipse(px, py, rule.center, rule.radius);
|
|
1352
|
+
case "rects":
|
|
1353
|
+
return rule.rects.some((r) => insideRect(px, py, r, rule.margin));
|
|
1354
|
+
case "forest":
|
|
1355
|
+
return forest > rule.above;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function colorAt(px, py, h = heightAt(px, py)) {
|
|
1359
|
+
const forest = forestAt(px, py);
|
|
1360
|
+
for (const rule of options.colors) if (matches(rule, px, py, h, forest)) return rule.color;
|
|
1361
|
+
const { colors: colors2, cell } = options.fields;
|
|
1362
|
+
return colors2[Math.floor(hash2(Math.floor(px / cell[0]), Math.floor(py / cell[1])) * colors2.length)];
|
|
1363
|
+
}
|
|
1364
|
+
function grid(segments = world.segments) {
|
|
1365
|
+
const count = (segments + 1) * (segments + 1);
|
|
1366
|
+
const x = new Float32Array(count);
|
|
1367
|
+
const z = new Float32Array(count);
|
|
1368
|
+
const heights = new Float32Array(count);
|
|
1369
|
+
const colors2 = new Uint32Array(count);
|
|
1370
|
+
const half = world.size / 2;
|
|
1371
|
+
const step = world.size / segments;
|
|
1372
|
+
for (let iy = 0; iy <= segments; iy++) {
|
|
1373
|
+
for (let ix = 0; ix <= segments; ix++) {
|
|
1374
|
+
const i = iy * (segments + 1) + ix;
|
|
1375
|
+
x[i] = ix * step - half;
|
|
1376
|
+
z[i] = iy * step - half;
|
|
1377
|
+
const [px, py] = fromWorld(x[i], z[i]);
|
|
1378
|
+
const h = heightAt(px, py);
|
|
1379
|
+
heights[i] = h;
|
|
1380
|
+
colors2[i] = colorAt(px, py, h);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return { segments, size: world.size, x, z, heights, colors: colors2 };
|
|
1384
|
+
}
|
|
1385
|
+
return { options, toWorld, fromWorld, heightAt, forestAt, nearRiver, colorAt, grid };
|
|
1386
|
+
}
|
|
1387
|
+
function drawTerrain(b, terrain, segments) {
|
|
1388
|
+
b.terrain(terrain.grid(segments));
|
|
1389
|
+
const { water, world } = terrain.options;
|
|
1390
|
+
b.plane(0, water.level, 0, world.size, world.size, water.color, { roughness: water.roughness, metalness: water.metalness });
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/kit.ts
|
|
1394
|
+
var DEFAULT_LIGHT_BUDGET = 16;
|
|
1395
|
+
var DEFAULT_URBAN_SEED = 7;
|
|
1396
|
+
function createUrban(options = {}) {
|
|
1397
|
+
const entries = mergeById2(DEFAULT_POLES, options.packs?.poles);
|
|
1398
|
+
applyPatches2(entries, options.patch?.poles);
|
|
1399
|
+
removeIds2(entries, options.remove?.poles);
|
|
1400
|
+
const shapes = { ...POLE_SHAPES, ...options.shapes };
|
|
1401
|
+
const patterns = mergeById2(DEFAULT_PATTERNS, options.packs?.neighbourhoods);
|
|
1402
|
+
applyPatches2(patterns, options.patch?.neighbourhoods);
|
|
1403
|
+
removeIds2(patterns, options.remove?.neighbourhoods);
|
|
1404
|
+
const composers = { ...COMPOSERS, ...options.composers };
|
|
1405
|
+
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_BUDGET;
|
|
1406
|
+
const facades = options.facades ?? DEFAULT_FACADES;
|
|
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;
|
|
1411
|
+
const poles = {
|
|
1412
|
+
ids: () => [...entries.keys()],
|
|
1413
|
+
list: () => [...entries.values()],
|
|
1414
|
+
get(id) {
|
|
1415
|
+
const def = entries.get(id);
|
|
1416
|
+
if (!def) throw new Error(`Unknown pole "${id}"`);
|
|
1417
|
+
const shape = shapes[def.shape];
|
|
1418
|
+
if (!shape) throw new Error(`Pole "${id}" needs the shape "${def.shape}", which is not registered`);
|
|
1419
|
+
return { def, build: (b, o) => shape(b, def.spec, o) };
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
const core = {
|
|
1423
|
+
poles,
|
|
1424
|
+
streets: {
|
|
1425
|
+
road: (b, x, z, w, l, along) => drawRoad(b, x, z, w, l, along, options.streets),
|
|
1426
|
+
walk: (b, x, z, w, d) => drawWalk(b, x, z, w, d, options.streets),
|
|
1427
|
+
ground: drawGround,
|
|
1428
|
+
grid: (b, g) => drawStreetGrid(b, g.block, g.count, g.width, options.streets)
|
|
1429
|
+
},
|
|
1430
|
+
block: {
|
|
1431
|
+
facades,
|
|
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 })
|
|
1434
|
+
},
|
|
1435
|
+
props: {
|
|
1436
|
+
fence: drawFence,
|
|
1437
|
+
tent: (b, x, z, ry, w, d, color) => drawTent(b, x, z, ry, w, d, color, propColors),
|
|
1438
|
+
pine: (b, x, z, scale, y) => drawPine(b, x, z, scale, y, propColors),
|
|
1439
|
+
tree: (b, x, z, scale) => drawTree(b, x, z, scale, propColors),
|
|
1440
|
+
container: drawContainer,
|
|
1441
|
+
watchtower: (b, x, z, searchlight) => drawWatchtower(b, x, z, searchlight, propColors),
|
|
1442
|
+
sandbags: (b, x, z, length, along, rows) => drawSandbags(b, x, z, length, along, rows, propColors),
|
|
1443
|
+
label: (b, x, z, title, subtitle) => drawStripLabel(b, x, z, title, subtitle, propColors),
|
|
1444
|
+
wires: (b, points, y, color) => drawWires(b, points, y, color, propColors)
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget: createLightBudget(o.budget ?? lightBudget) });
|
|
1448
|
+
const neighbourhoods = {
|
|
1449
|
+
ids: () => [...patterns.keys()],
|
|
1450
|
+
get(id) {
|
|
1451
|
+
const pattern = patterns.get(id);
|
|
1452
|
+
if (!pattern) throw new Error(`Unknown neighbourhood pattern "${id}"`);
|
|
1453
|
+
const composer = composers[pattern.compose];
|
|
1454
|
+
if (!composer) throw new Error(`Pattern "${id}" needs the composer "${pattern.compose}", which is not registered`);
|
|
1455
|
+
return {
|
|
1456
|
+
pattern,
|
|
1457
|
+
/** Draws the pattern at `origin` and gives back what it put there. */
|
|
1458
|
+
compose: (b, origin, o) => composePattern(core, pattern, composer, b, origin, o),
|
|
1459
|
+
marker: (origin, locale) => patternMarker(pattern, origin, locale)
|
|
1460
|
+
};
|
|
1461
|
+
},
|
|
1462
|
+
/** The Galeria's sample sheet: the ground, then each pattern in its cell, in the order the page builds them. */
|
|
1463
|
+
composeSampleSheet(b, o = {}) {
|
|
1464
|
+
if (o.ground !== false) drawGround(b, (o.ground ?? SAMPLE_GROUND).color, (o.ground ?? SAMPLE_GROUND).size);
|
|
1465
|
+
const cells = { ...SAMPLE_CELLS, ...o.cells };
|
|
1466
|
+
const plans = [];
|
|
1467
|
+
for (const id of o.ids ?? [...patterns.keys()]) {
|
|
1468
|
+
const cell = cells[id];
|
|
1469
|
+
if (!cell) throw new Error(`The sample sheet has no cell for "${id}"`);
|
|
1470
|
+
plans.push(neighbourhoods.get(id).compose(b, cell, o));
|
|
1471
|
+
}
|
|
1472
|
+
return { plans, markers: plans.map((plan) => neighbourhoods.get(plan.id).marker(plan.origin, o.locale)) };
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
const terrain = (o) => createTerrain(o);
|
|
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 } };
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// src/neighbourhoods/providers.ts
|
|
1488
|
+
import { cars, pickups, suvs, vans } from "@adatechnology/game-vehicles-fleet";
|
|
1489
|
+
import { pickForContext, regions } from "@adatechnology/game-vegetation-species";
|
|
1490
|
+
var PARKED_VEHICLE_IDS = [...cars, ...suvs, ...pickups.filter((id) => id !== "monster_truck"), ...vans];
|
|
1491
|
+
var hashSpot = (x, z, ry) => {
|
|
1492
|
+
let h = 2166136261;
|
|
1493
|
+
for (const value of [Math.round(x * 100), Math.round(z * 100), Math.round(ry * 100)]) {
|
|
1494
|
+
h ^= value & 65535;
|
|
1495
|
+
h = Math.imul(h, 16777619);
|
|
1496
|
+
h ^= value >> 16 & 65535;
|
|
1497
|
+
h = Math.imul(h, 16777619);
|
|
1498
|
+
}
|
|
1499
|
+
return h >>> 0;
|
|
1500
|
+
};
|
|
1501
|
+
function createVehicleProvider(options = {}) {
|
|
1502
|
+
const ids = options.ids ?? PARKED_VEHICLE_IDS;
|
|
1503
|
+
if (ids.length === 0) throw new Error("The vehicle provider needs at least one vehicle id");
|
|
1504
|
+
return (request, b) => {
|
|
1505
|
+
const id = options.pick ? options.pick(request, ids) : ids[hashSpot(request.x, request.z, request.ry) % ids.length];
|
|
1506
|
+
const color = options.paint ? options.paint(request) : request.color;
|
|
1507
|
+
b.place("vehicle", id, request.x, request.z, { ry: request.ry, ...color === void 0 ? {} : { color }, seed: `${id}:${request.x}:${request.z}` });
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
var DEFAULT_PLANT_CONTEXTS = { tree: "sidewalk_tree", pine: "square" };
|
|
1511
|
+
function createPlantProvider(options = {}) {
|
|
1512
|
+
const region = options.region ?? regions.br;
|
|
1513
|
+
const contexts = { ...DEFAULT_PLANT_CONTEXTS, ...options.contexts };
|
|
1514
|
+
const seed = options.seed ?? 1;
|
|
1515
|
+
return (request, b) => {
|
|
1516
|
+
const context = contexts[request.kind];
|
|
1517
|
+
const key = `${seed}:${request.kind}:${request.x}:${request.z}`;
|
|
1518
|
+
const id = options.pick ? options.pick(request, context, key) : pickForContext(region, context, key);
|
|
1519
|
+
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
1520
|
+
};
|
|
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 } };
|
|
1551
|
+
export {
|
|
1552
|
+
COMPOSERS,
|
|
1553
|
+
DEFAULT_CITY_PLANS,
|
|
1554
|
+
DEFAULT_DISTRICT_KINDS,
|
|
1555
|
+
DEFAULT_FACADES,
|
|
1556
|
+
DEFAULT_LIGHT_BUDGET,
|
|
1557
|
+
DEFAULT_MAX_FLOORS,
|
|
1558
|
+
DEFAULT_PATTERNS,
|
|
1559
|
+
DEFAULT_PLANT_CONTEXTS,
|
|
1560
|
+
DEFAULT_POLES,
|
|
1561
|
+
DEFAULT_PROP_COLORS,
|
|
1562
|
+
DEFAULT_ROAD_STYLE,
|
|
1563
|
+
DEFAULT_STOREY_PRESETS,
|
|
1564
|
+
DEFAULT_STREET,
|
|
1565
|
+
DEFAULT_TOWER_PRESETS,
|
|
1566
|
+
DEFAULT_URBAN_SEED,
|
|
1567
|
+
DEFAULT_WALK_STYLE,
|
|
1568
|
+
PARKED_VEHICLE_IDS,
|
|
1569
|
+
POLE_SHAPES,
|
|
1570
|
+
SAMPLE_CELLS,
|
|
1571
|
+
SILHOUETTE_TYPOLOGIES,
|
|
1572
|
+
TOWER_DEFAULTS,
|
|
1573
|
+
TOWER_PATTERNS,
|
|
1574
|
+
TOWER_SAMPLE_CELLS,
|
|
1575
|
+
clamp,
|
|
1576
|
+
composePattern,
|
|
1577
|
+
createCities,
|
|
1578
|
+
createLightBudget,
|
|
1579
|
+
createPlantProvider,
|
|
1580
|
+
createSilhouetteHouses,
|
|
1581
|
+
createTerrain,
|
|
1582
|
+
createTowerDrawer,
|
|
1583
|
+
createUrban,
|
|
1584
|
+
createUrbanBuilder,
|
|
1585
|
+
createVehicleProvider,
|
|
1586
|
+
distanceToPolyline,
|
|
1587
|
+
drawBuilding,
|
|
1588
|
+
drawContainer,
|
|
1589
|
+
drawFence,
|
|
1590
|
+
drawGround,
|
|
1591
|
+
drawPine,
|
|
1592
|
+
drawRoad,
|
|
1593
|
+
drawSandbags,
|
|
1594
|
+
drawStreetGrid,
|
|
1595
|
+
drawStripLabel,
|
|
1596
|
+
drawTent,
|
|
1597
|
+
drawTerrain,
|
|
1598
|
+
drawTower,
|
|
1599
|
+
drawTree,
|
|
1600
|
+
drawWalk,
|
|
1601
|
+
drawWatchtower,
|
|
1602
|
+
drawWires,
|
|
1603
|
+
fillBlock,
|
|
1604
|
+
hash2,
|
|
1605
|
+
insideEllipse,
|
|
1606
|
+
insideRect,
|
|
1607
|
+
lerp2 as lerp,
|
|
1608
|
+
patternMarker,
|
|
1609
|
+
pickTowerPreset,
|
|
1610
|
+
smoothstep,
|
|
1611
|
+
towerPresetOf,
|
|
1612
|
+
towerStoreys
|
|
1613
|
+
};
|
|
1614
|
+
//# sourceMappingURL=index.js.map
|