@adatechnology/game-floorplan 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/MIGRATION.md +387 -0
- package/README.md +123 -2
- package/dist/index.d.ts +1054 -0
- package/dist/index.global.js +2770 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +2745 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,2745 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var WALL_THICKNESS = 0.15;
|
|
3
|
+
var MARGIN = WALL_THICKNESS / 2;
|
|
4
|
+
var EPS = 1e-6;
|
|
5
|
+
var TOL = 1e-3;
|
|
6
|
+
var DOOR_CLEARANCE = 0.9;
|
|
7
|
+
var PLAYER_RADIUS = 0.24;
|
|
8
|
+
var DOOR_EDGE_CLEARANCE = 0.12;
|
|
9
|
+
var WINDOW_EDGE_CLEARANCE = 0.3;
|
|
10
|
+
var OVERHEAD_TOP = 2.15;
|
|
11
|
+
var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
|
|
12
|
+
"stockroom",
|
|
13
|
+
"kitchen",
|
|
14
|
+
"laundry",
|
|
15
|
+
"bathroom",
|
|
16
|
+
"void",
|
|
17
|
+
"elevator",
|
|
18
|
+
"stairs",
|
|
19
|
+
"pantry",
|
|
20
|
+
"closet",
|
|
21
|
+
"corridor",
|
|
22
|
+
"hall",
|
|
23
|
+
"balcony",
|
|
24
|
+
"terrace",
|
|
25
|
+
"garage",
|
|
26
|
+
"storage",
|
|
27
|
+
"porch",
|
|
28
|
+
"tent",
|
|
29
|
+
"outhouse"
|
|
30
|
+
]);
|
|
31
|
+
var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
|
|
32
|
+
|
|
33
|
+
// src/registry.ts
|
|
34
|
+
var CATALOG = {};
|
|
35
|
+
var LIBRARIES = {};
|
|
36
|
+
var LIBRARY_ALIAS = {};
|
|
37
|
+
var TEMPLATES = {};
|
|
38
|
+
var STYLES = {};
|
|
39
|
+
var STYLE_ALIAS = {};
|
|
40
|
+
var PALETTE_STYLES = {};
|
|
41
|
+
var ROOM_COLORS = [];
|
|
42
|
+
var PASSES = [];
|
|
43
|
+
var DECOR_PROFILES = {};
|
|
44
|
+
var GROUND_OVERLAYS = [];
|
|
45
|
+
var STATE_PLUGINS = { normal: { apply: () => [] } };
|
|
46
|
+
var WALL_TOP_HEIGHTS = { none: 0 };
|
|
47
|
+
var SEED_KEYS = { rooms: {}, items: {} };
|
|
48
|
+
var stateDistribution = { weights: null, seedKey: "state" };
|
|
49
|
+
function registerItem(def) {
|
|
50
|
+
const h = def.h;
|
|
51
|
+
const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
|
|
52
|
+
CATALOG[def.type] = item;
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
function registerItems(list) {
|
|
56
|
+
for (const d of list) registerItem(d);
|
|
57
|
+
}
|
|
58
|
+
function registerLibrary(id, library) {
|
|
59
|
+
LIBRARIES[id] = library;
|
|
60
|
+
}
|
|
61
|
+
function registerLibraryAlias(alias, id) {
|
|
62
|
+
LIBRARY_ALIAS[alias] = id;
|
|
63
|
+
}
|
|
64
|
+
function registerTemplate(t) {
|
|
65
|
+
TEMPLATES[t.id] = t;
|
|
66
|
+
return t;
|
|
67
|
+
}
|
|
68
|
+
function registerStyle(style) {
|
|
69
|
+
STYLES[style.id] = style;
|
|
70
|
+
for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
|
|
71
|
+
}
|
|
72
|
+
function registerPaletteStyle(id, def) {
|
|
73
|
+
PALETTE_STYLES[id] = def;
|
|
74
|
+
}
|
|
75
|
+
function registerRoomColors(list) {
|
|
76
|
+
for (const d of list) {
|
|
77
|
+
const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
78
|
+
if (i >= 0) ROOM_COLORS.splice(i, 1);
|
|
79
|
+
ROOM_COLORS.push(d);
|
|
80
|
+
}
|
|
81
|
+
ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
82
|
+
}
|
|
83
|
+
function registerDecorProfile(styleId, profile) {
|
|
84
|
+
DECOR_PROFILES[styleId] = profile;
|
|
85
|
+
}
|
|
86
|
+
function registerPass(name, run) {
|
|
87
|
+
const i = PASSES.findIndex((p) => p.name === name);
|
|
88
|
+
if (i >= 0) PASSES[i] = { name, run };
|
|
89
|
+
else PASSES.push({ name, run });
|
|
90
|
+
}
|
|
91
|
+
function registerGroundOverlay(rule) {
|
|
92
|
+
GROUND_OVERLAYS.push(rule);
|
|
93
|
+
}
|
|
94
|
+
function registerWallTop(id, height) {
|
|
95
|
+
WALL_TOP_HEIGHTS[id] = height;
|
|
96
|
+
}
|
|
97
|
+
function registerSeedKeys(keys) {
|
|
98
|
+
Object.assign(SEED_KEYS.rooms, keys.rooms);
|
|
99
|
+
Object.assign(SEED_KEYS.items, keys.items);
|
|
100
|
+
}
|
|
101
|
+
function registerState(name, plugin) {
|
|
102
|
+
STATE_PLUGINS[name] = plugin;
|
|
103
|
+
}
|
|
104
|
+
function setStateDistribution(weights, seedKey = "state") {
|
|
105
|
+
stateDistribution.weights = weights;
|
|
106
|
+
stateDistribution.seedKey = seedKey;
|
|
107
|
+
}
|
|
108
|
+
var listStates = () => Object.keys(STATE_PLUGINS);
|
|
109
|
+
function resolveStyleId(id) {
|
|
110
|
+
const s = id ?? "";
|
|
111
|
+
return STYLE_ALIAS[s] ?? s;
|
|
112
|
+
}
|
|
113
|
+
function styleOf(id) {
|
|
114
|
+
return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
|
|
115
|
+
}
|
|
116
|
+
var RESULT_EXTENSIONS = [];
|
|
117
|
+
var FINDS_PROVIDERS = [];
|
|
118
|
+
function registerResultExtension(name, run, options = {}) {
|
|
119
|
+
const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
|
|
120
|
+
const i = RESULT_EXTENSIONS.findIndex((x) => x.name === name);
|
|
121
|
+
if (i >= 0) RESULT_EXTENSIONS[i] = entry;
|
|
122
|
+
else RESULT_EXTENSIONS.push(entry);
|
|
123
|
+
}
|
|
124
|
+
function registerFindsProvider(provider) {
|
|
125
|
+
FINDS_PROVIDERS.push(provider);
|
|
126
|
+
}
|
|
127
|
+
var VIEWER_LAYERS = [];
|
|
128
|
+
function registerViewerLayer(layer) {
|
|
129
|
+
const i = VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
|
|
130
|
+
if (i >= 0) VIEWER_LAYERS[i] = layer;
|
|
131
|
+
else VIEWER_LAYERS.push(layer);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/rng.ts
|
|
135
|
+
function mulberry32(a) {
|
|
136
|
+
return function() {
|
|
137
|
+
a |= 0;
|
|
138
|
+
a = a + 1831565813 | 0;
|
|
139
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
140
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
141
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function hashString(input) {
|
|
145
|
+
const s = String(input);
|
|
146
|
+
let h = 1779033703 ^ s.length;
|
|
147
|
+
for (let i = 0; i < s.length; i++) {
|
|
148
|
+
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
149
|
+
h = h << 13 | h >>> 19;
|
|
150
|
+
}
|
|
151
|
+
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
152
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
153
|
+
return (h ^ h >>> 16) >>> 0;
|
|
154
|
+
}
|
|
155
|
+
var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
|
|
156
|
+
function pickWeighted(rng, weights) {
|
|
157
|
+
const keys = Object.keys(weights);
|
|
158
|
+
let total = 0;
|
|
159
|
+
for (const k of keys) total += weights[k];
|
|
160
|
+
let x = rng() * total;
|
|
161
|
+
for (const k of keys) {
|
|
162
|
+
x -= weights[k];
|
|
163
|
+
if (x < 0) return k;
|
|
164
|
+
}
|
|
165
|
+
return keys[keys.length - 1];
|
|
166
|
+
}
|
|
167
|
+
function weightedOrder(candidates, rng) {
|
|
168
|
+
const arr = candidates.slice();
|
|
169
|
+
const out = [];
|
|
170
|
+
while (arr.length) {
|
|
171
|
+
let total = 0;
|
|
172
|
+
for (const c of arr) total += c.weight;
|
|
173
|
+
let x = rng() * total;
|
|
174
|
+
let i = 0;
|
|
175
|
+
for (; i < arr.length - 1; i++) {
|
|
176
|
+
x -= arr[i].weight;
|
|
177
|
+
if (x < 0) break;
|
|
178
|
+
}
|
|
179
|
+
out.push(arr.splice(i, 1)[0]);
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
function shuffle(arr, rng) {
|
|
184
|
+
const a = arr.slice();
|
|
185
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
186
|
+
const j = Math.floor(rng() * (i + 1));
|
|
187
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
188
|
+
}
|
|
189
|
+
return a;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/extensions.ts
|
|
193
|
+
function orderOf(extensions) {
|
|
194
|
+
const ordered = [];
|
|
195
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
196
|
+
const visit = (extension) => {
|
|
197
|
+
if (ordered.includes(extension)) return;
|
|
198
|
+
if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
|
|
199
|
+
visiting.add(extension.name);
|
|
200
|
+
for (const name of extension.after) {
|
|
201
|
+
const before = extensions.find((x) => x.name === name);
|
|
202
|
+
if (before) visit(before);
|
|
203
|
+
}
|
|
204
|
+
visiting.delete(extension.name);
|
|
205
|
+
ordered.push(extension);
|
|
206
|
+
};
|
|
207
|
+
extensions.forEach(visit);
|
|
208
|
+
return ordered;
|
|
209
|
+
}
|
|
210
|
+
function runResultExtensions(plan, result, seed, options) {
|
|
211
|
+
for (const extension of orderOf(RESULT_EXTENSIONS)) {
|
|
212
|
+
result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function findsFor(query) {
|
|
216
|
+
return answerOf(query) ?? [];
|
|
217
|
+
}
|
|
218
|
+
function findsAnswer(query) {
|
|
219
|
+
return answerOf(query);
|
|
220
|
+
}
|
|
221
|
+
function answerOf(query) {
|
|
222
|
+
for (const provider of FINDS_PROVIDERS) {
|
|
223
|
+
const stacks = provider.findsFor(query);
|
|
224
|
+
if (stacks !== null) return stacks;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/geometry.ts
|
|
230
|
+
var r2 = (x) => Math.round(x * 100) / 100;
|
|
231
|
+
var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
232
|
+
var overlaps = (a, b, e = EPS) => a[0] < b[2] - e && a[2] > b[0] + e && a[1] < b[3] - e && a[3] > b[1] + e;
|
|
233
|
+
var inner = (p) => [p[0] + MARGIN, p[1] + MARGIN, p[2] - MARGIN, p[3] - MARGIN];
|
|
234
|
+
var eq = (a, b) => Math.abs(a - b) < TOL;
|
|
235
|
+
var inflate = (r, e) => [r[0] - e, r[1] - e, r[2] + e, r[3] + e];
|
|
236
|
+
var areaOf = (r) => (r[2] - r[0]) * (r[3] - r[1]);
|
|
237
|
+
var topOf = (m) => (m.y || 0) + m.h;
|
|
238
|
+
var extent = (size, f) => Math.abs(f[1]) === 1 ? [size[0], size[1]] : [size[1], size[0]];
|
|
239
|
+
var cardinal = (dx, dz) => Math.abs(dx) > Math.abs(dz) ? [Math.sign(dx), 0] : [0, Math.sign(dz) || 1];
|
|
240
|
+
function clearRect(r, f, clearance) {
|
|
241
|
+
return f[1] === 1 ? [r[0], r[3], r[2], r[3] + clearance] : f[1] === -1 ? [r[0], r[1] - clearance, r[2], r[1]] : f[0] === 1 ? [r[2], r[1], r[2] + clearance, r[3]] : [r[0] - clearance, r[1], r[0], r[3]];
|
|
242
|
+
}
|
|
243
|
+
function insideUnion(r, inners) {
|
|
244
|
+
const e = 1e-4;
|
|
245
|
+
const points = [
|
|
246
|
+
[r[0] + e, r[1] + e],
|
|
247
|
+
[r[2] - e, r[1] + e],
|
|
248
|
+
[r[0] + e, r[3] - e],
|
|
249
|
+
[r[2] - e, r[3] - e],
|
|
250
|
+
[(r[0] + r[2]) / 2, (r[1] + r[3]) / 2]
|
|
251
|
+
];
|
|
252
|
+
return points.every((p) => inners.some((q) => p[0] >= q[0] - 1e-6 && p[0] <= q[2] + 1e-6 && p[1] >= q[1] - 1e-6 && p[1] <= q[3] + 1e-6));
|
|
253
|
+
}
|
|
254
|
+
function cutIntervals(intervals, a, b) {
|
|
255
|
+
const out = [];
|
|
256
|
+
for (const [x, y] of intervals) {
|
|
257
|
+
if (b <= x || a >= y) {
|
|
258
|
+
out.push([x, y]);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (a > x) out.push([x, a]);
|
|
262
|
+
if (b < y) out.push([b, y]);
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function exteriorEdge(room, side, w, d, c) {
|
|
267
|
+
const cand = [];
|
|
268
|
+
for (const p of room.parts) {
|
|
269
|
+
if (side === "front" && eq(p[1], 0)) cand.push({ axis: "u", pos: 0, a0: p[0], a1: p[2] });
|
|
270
|
+
else if (side === "back" && eq(p[3], d)) cand.push({ axis: "u", pos: d, a0: p[0], a1: p[2] });
|
|
271
|
+
else if (side === "left" && eq(p[0], 0)) cand.push({ axis: "v", pos: 0, a0: p[1], a1: p[3] });
|
|
272
|
+
else if (side === "right" && eq(p[2], w)) cand.push({ axis: "v", pos: w, a0: p[1], a1: p[3] });
|
|
273
|
+
}
|
|
274
|
+
if (!cand.length) return null;
|
|
275
|
+
if (c != null) {
|
|
276
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
277
|
+
if (k) return k;
|
|
278
|
+
}
|
|
279
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
280
|
+
}
|
|
281
|
+
function sharedEdge(A, B, c) {
|
|
282
|
+
const cand = [];
|
|
283
|
+
for (const pa of A.parts)
|
|
284
|
+
for (const pb of B.parts) {
|
|
285
|
+
let o;
|
|
286
|
+
if (eq(pa[2], pb[0]) || eq(pa[0], pb[2])) {
|
|
287
|
+
o = [Math.max(pa[1], pb[1]), Math.min(pa[3], pb[3])];
|
|
288
|
+
if (o[1] - o[0] > TOL) cand.push({ axis: "v", pos: eq(pa[2], pb[0]) ? pa[2] : pa[0], a0: o[0], a1: o[1] });
|
|
289
|
+
}
|
|
290
|
+
if (eq(pa[3], pb[1]) || eq(pa[1], pb[3])) {
|
|
291
|
+
o = [Math.max(pa[0], pb[0]), Math.min(pa[2], pb[2])];
|
|
292
|
+
if (o[1] - o[0] > TOL) cand.push({ axis: "u", pos: eq(pa[3], pb[1]) ? pa[3] : pa[1], a0: o[0], a1: o[1] });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!cand.length) return null;
|
|
296
|
+
if (c != null) {
|
|
297
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
298
|
+
if (k) return k;
|
|
299
|
+
}
|
|
300
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
301
|
+
}
|
|
302
|
+
function openingZones(u, v, axis, width, depth) {
|
|
303
|
+
const h = width / 2;
|
|
304
|
+
return axis === "u" ? [[u - h, v - depth, u + h, v], [u - h, v, u + h, v + depth]] : [[u - depth, v - h, u, v + h], [u, v - h, u + depth, v + h]];
|
|
305
|
+
}
|
|
306
|
+
var openingDepth = (width, leaf) => Math.max(0.9, leaf) + MARGIN;
|
|
307
|
+
function entryPoints(ab) {
|
|
308
|
+
return ab.axis === "u" ? [[ab.u, ab.v - 0.55], [ab.u, ab.v + 0.55]] : [[ab.u - 0.55, ab.v], [ab.u + 0.55, ab.v]];
|
|
309
|
+
}
|
|
310
|
+
function normalizeRoom(c, floorIndex) {
|
|
311
|
+
const parts = [c.rect].concat(c.parts || []).map((p) => p.map(r2));
|
|
312
|
+
return {
|
|
313
|
+
id: c.id,
|
|
314
|
+
type: c.type,
|
|
315
|
+
label: c.label,
|
|
316
|
+
floor: floorIndex,
|
|
317
|
+
parts,
|
|
318
|
+
library: c.library || null,
|
|
319
|
+
without: c.without || [],
|
|
320
|
+
overrides: c.overrides || {},
|
|
321
|
+
extras: c.extras || [],
|
|
322
|
+
slots: c.slots || null,
|
|
323
|
+
link: c.link || null,
|
|
324
|
+
outdoor: !!c.outdoor,
|
|
325
|
+
ceiling: c.ceiling || null,
|
|
326
|
+
seedKey: c.seedKey
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function usableParts(room, extra) {
|
|
330
|
+
const m = MARGIN + (extra || 0);
|
|
331
|
+
const out = [];
|
|
332
|
+
for (const p of room.parts) {
|
|
333
|
+
out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
|
|
334
|
+
for (let k = 0; k < 4; k++) {
|
|
335
|
+
const spans = [];
|
|
336
|
+
for (const q of room.parts) {
|
|
337
|
+
if (q === p) continue;
|
|
338
|
+
if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
339
|
+
else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
340
|
+
else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
341
|
+
else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
342
|
+
}
|
|
343
|
+
spans.sort((x, y) => x[0] - y[0]);
|
|
344
|
+
const merged = [];
|
|
345
|
+
for (const span of spans) {
|
|
346
|
+
const last = merged[merged.length - 1];
|
|
347
|
+
if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
|
|
348
|
+
else merged.push([span[0], span[1]]);
|
|
349
|
+
}
|
|
350
|
+
for (const [a, b] of merged) {
|
|
351
|
+
if (b - a <= 0.3) continue;
|
|
352
|
+
const x0 = a + m;
|
|
353
|
+
const x1 = b - m;
|
|
354
|
+
if (x1 <= x0) continue;
|
|
355
|
+
if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
|
|
356
|
+
else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
|
|
357
|
+
else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
|
|
358
|
+
else out.push([x0, p[3] - m, x1, p[3]]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
var inRect = (room, u, v) => room.parts.some((p) => u > p[0] + EPS && u < p[2] - EPS && v > p[1] + EPS && v < p[3] - EPS);
|
|
365
|
+
function buildWalls(rooms, doors, openPairs, floorIndex) {
|
|
366
|
+
const groups = {};
|
|
367
|
+
const add = (axis, pos, a, b, id, side) => {
|
|
368
|
+
const k = axis + "|" + pos.toFixed(2);
|
|
369
|
+
(groups[k] = groups[k] || { axis, pos, es: [] }).es.push({ a, b, id, side });
|
|
370
|
+
};
|
|
371
|
+
for (const c of rooms)
|
|
372
|
+
for (const p of c.parts) {
|
|
373
|
+
add("u", p[1], p[0], p[2], c.id, 1);
|
|
374
|
+
add("u", p[3], p[0], p[2], c.id, -1);
|
|
375
|
+
add("v", p[0], p[1], p[3], c.id, 1);
|
|
376
|
+
add("v", p[2], p[1], p[3], c.id, -1);
|
|
377
|
+
}
|
|
378
|
+
const par = (x, y) => openPairs.some((o) => o[0] === x && o[1] === y || o[0] === y && o[1] === x);
|
|
379
|
+
const typeOf = {};
|
|
380
|
+
for (const c of rooms) typeOf[c.id] = c.type;
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const g of Object.values(groups)) {
|
|
383
|
+
const breaks = [...new Set(g.es.flatMap((e) => [r2(e.a), r2(e.b)]))].sort((x, y) => x - y);
|
|
384
|
+
const segs = [];
|
|
385
|
+
for (let i = 0; i < breaks.length - 1; i++) {
|
|
386
|
+
const p = breaks[i];
|
|
387
|
+
const q = breaks[i + 1];
|
|
388
|
+
const m = (p + q) / 2;
|
|
389
|
+
const after = g.es.find((e) => e.side === 1 && m > e.a && m < e.b);
|
|
390
|
+
const before = g.es.find((e) => e.side === -1 && m > e.a && m < e.b);
|
|
391
|
+
if (!after && !before) continue;
|
|
392
|
+
const A = before ? before.id : "outside";
|
|
393
|
+
const B = after ? after.id : "outside";
|
|
394
|
+
if (A === B || A !== "outside" && B !== "outside" && par(A, B)) continue;
|
|
395
|
+
const last = segs[segs.length - 1];
|
|
396
|
+
if (last && last.A === A && last.B === B && eq(last.q, p)) last.q = q;
|
|
397
|
+
else segs.push({ A, B, p, q });
|
|
398
|
+
}
|
|
399
|
+
for (const s of segs) {
|
|
400
|
+
let pieces = [[s.p, s.q]];
|
|
401
|
+
for (const pt of doors) {
|
|
402
|
+
if (pt.axis !== g.axis || !eq(pt.pos, g.pos)) continue;
|
|
403
|
+
const c0 = pt.at - pt.width / 2;
|
|
404
|
+
const c1 = pt.at + pt.width / 2;
|
|
405
|
+
const n = [];
|
|
406
|
+
for (const [x, y] of pieces) {
|
|
407
|
+
if (c1 <= x + TOL || c0 >= y - TOL) {
|
|
408
|
+
n.push([x, y]);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (c0 > x + TOL) n.push([x, c0]);
|
|
412
|
+
if (c1 < y - TOL) n.push([c1, y]);
|
|
413
|
+
}
|
|
414
|
+
pieces = n;
|
|
415
|
+
}
|
|
416
|
+
for (const [x, y] of pieces) {
|
|
417
|
+
const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
|
|
418
|
+
const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
|
|
419
|
+
if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
|
|
420
|
+
const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
|
|
421
|
+
out.push({
|
|
422
|
+
floor: floorIndex,
|
|
423
|
+
axis: g.axis,
|
|
424
|
+
u0: g.axis === "u" ? x : g.pos,
|
|
425
|
+
v0: g.axis === "u" ? g.pos : x,
|
|
426
|
+
u1: g.axis === "u" ? y : g.pos,
|
|
427
|
+
v1: g.axis === "u" ? g.pos : y,
|
|
428
|
+
thickness: WALL_THICKNESS,
|
|
429
|
+
sides: [s.A, s.B],
|
|
430
|
+
exterior: s.A === "outside" || s.B === "outside",
|
|
431
|
+
type: railing ? "railing" : "wall",
|
|
432
|
+
h: railing ? 1.1 : 0
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/parking.ts
|
|
441
|
+
var STEP = 0.05;
|
|
442
|
+
var MAX_CANDIDATES = 80;
|
|
443
|
+
function carCandidates(slot, item, car) {
|
|
444
|
+
const front = [slot.frontU, slot.frontV];
|
|
445
|
+
const zone = slot._cell.R;
|
|
446
|
+
const alongV = Math.abs(front[1]) === 1;
|
|
447
|
+
const [extentU, extentV] = extent([item.w, item.d], front);
|
|
448
|
+
const across = alongV ? extentU : extentV;
|
|
449
|
+
const along = alongV ? extentV : extentU;
|
|
450
|
+
const a = alongV ? 0 : 1;
|
|
451
|
+
const b = alongV ? 1 : 0;
|
|
452
|
+
const driverSign = alongV ? front[1] : -front[0];
|
|
453
|
+
const frontSign = alongV ? front[1] : front[0];
|
|
454
|
+
const lowClear = driverSign < 0 ? car.driver : car.side;
|
|
455
|
+
const highClear = driverSign < 0 ? car.side : car.driver;
|
|
456
|
+
const lo = zone[a] + lowClear;
|
|
457
|
+
const hi = zone[a + 2] - highClear - across;
|
|
458
|
+
const alongLo = zone[b] + car.end;
|
|
459
|
+
const alongHi = zone[b + 2] - car.end - along;
|
|
460
|
+
if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
|
|
461
|
+
const preferred = (alongV ? slot.x : slot.z) - across / 2;
|
|
462
|
+
const alongStart = frontSign < 0 ? alongHi : alongLo;
|
|
463
|
+
const positions = [];
|
|
464
|
+
for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
|
|
465
|
+
positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
|
|
466
|
+
return positions.slice(0, MAX_CANDIDATES).map((p) => {
|
|
467
|
+
const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
|
|
468
|
+
const clear = alongV ? [p - lowClear, alongStart - car.end, p + across + highClear, alongStart + along + car.end] : [alongStart - car.end, p - lowClear, alongStart + along + car.end, p + across + highClear];
|
|
469
|
+
return { rect, clear };
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/queries.ts
|
|
474
|
+
function zonesOfFloor(plan, floorIndex) {
|
|
475
|
+
const out = [];
|
|
476
|
+
if (floorIndex >= 0) {
|
|
477
|
+
for (const p of plan.doors) if (p.floor === floorIndex) out.push(...p.zones);
|
|
478
|
+
} else {
|
|
479
|
+
for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
|
|
480
|
+
for (const g of plan.gates) out.push(...g.zones);
|
|
481
|
+
for (const c of plan.paths) out.push(...c.rects);
|
|
482
|
+
out.push(...plan.reservations);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
function openingsOfFloor(plan, floorIndex) {
|
|
487
|
+
if (floorIndex >= 0) return plan.doors.filter((p) => p.floor === floorIndex);
|
|
488
|
+
return plan.doors.filter((p) => p.floor === 0 && p.exterior).concat(plan.gates);
|
|
489
|
+
}
|
|
490
|
+
function windowZone(j, outside) {
|
|
491
|
+
const h = j.width / 2 + 0.1;
|
|
492
|
+
const depth = 0.5;
|
|
493
|
+
let n;
|
|
494
|
+
if (j.side === "front") n = [0, 1];
|
|
495
|
+
else if (j.side === "back") n = [0, -1];
|
|
496
|
+
else if (j.side === "left") n = [1, 0];
|
|
497
|
+
else n = [-1, 0];
|
|
498
|
+
if (outside) n = [-n[0], -n[1]];
|
|
499
|
+
const u = j.u;
|
|
500
|
+
const v = j.v;
|
|
501
|
+
if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
|
|
502
|
+
return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
|
|
503
|
+
}
|
|
504
|
+
function blocksWindow(it, win, overheadOff) {
|
|
505
|
+
const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
|
|
506
|
+
return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
|
|
507
|
+
}
|
|
508
|
+
function windowsOfRoom(plan, room) {
|
|
509
|
+
if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
|
|
510
|
+
return plan.windows.filter(
|
|
511
|
+
(j) => j.floor === 0 && room.parts.some((p) => {
|
|
512
|
+
if (j.side === "front") return eq(p[3], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
513
|
+
if (j.side === "back") return eq(p[1], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
514
|
+
if (j.side === "left") return eq(p[2], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
515
|
+
return eq(p[0], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
516
|
+
})
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
function roomAt(plan, u, v, floorIndex) {
|
|
520
|
+
const list = floorIndex < 0 ? plan.zones : plan.rooms;
|
|
521
|
+
return list.find((c) => c.floor === floorIndex && inRect(c, u, v)) || null;
|
|
522
|
+
}
|
|
523
|
+
function isOpenSide(plan, room, pu, pv) {
|
|
524
|
+
const o = roomAt(plan, pu, pv, room.floor);
|
|
525
|
+
if (!o) return false;
|
|
526
|
+
if (o.id === room.id) return true;
|
|
527
|
+
if (room.outdoor) return true;
|
|
528
|
+
return (plan.openPairs[room.floor] || []).some((x) => x[0] === room.id && x[1] === o.id || x[1] === room.id && x[0] === o.id);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/grid.ts
|
|
532
|
+
function wallBodies(plan, floor) {
|
|
533
|
+
const cache = plan.wallBodies = plan.wallBodies || {};
|
|
534
|
+
if (cache[floor]) return cache[floor];
|
|
535
|
+
const doors = plan.doors.filter((p) => p.floor === floor);
|
|
536
|
+
return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
|
|
537
|
+
const a0 = p.axis === "u" ? p.u0 : p.v0;
|
|
538
|
+
const a1 = p.axis === "u" ? p.u1 : p.v1;
|
|
539
|
+
const pos = p.axis === "u" ? p.v0 : p.u0;
|
|
540
|
+
const atDoor = (x) => doors.some((q) => q.axis === p.axis && eq(q.pos, pos) && (eq(q.at - q.width / 2, x) || eq(q.at + q.width / 2, x)));
|
|
541
|
+
const e0 = atDoor(a0) ? 0 : MARGIN;
|
|
542
|
+
const e1 = atDoor(a1) ? 0 : MARGIN;
|
|
543
|
+
return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function createGrid(plan, room, ignored, radius = plan.playerRadius) {
|
|
547
|
+
const gs = room.outdoor ? 0.3 : 0.1;
|
|
548
|
+
let u0 = 1e9;
|
|
549
|
+
let v0 = 1e9;
|
|
550
|
+
let u1 = -1e9;
|
|
551
|
+
let v1 = -1e9;
|
|
552
|
+
for (const p of room.parts) {
|
|
553
|
+
u0 = Math.min(u0, p[0]);
|
|
554
|
+
v0 = Math.min(v0, p[1]);
|
|
555
|
+
u1 = Math.max(u1, p[2]);
|
|
556
|
+
v1 = Math.max(v1, p[3]);
|
|
557
|
+
}
|
|
558
|
+
const nx = Math.ceil((u1 - u0) / gs);
|
|
559
|
+
const nz = Math.ceil((v1 - v0) / gs);
|
|
560
|
+
const insideCells = new Uint8Array(nx * nz);
|
|
561
|
+
const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
|
|
562
|
+
const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
|
|
563
|
+
const tooClose = (u, v) => bodies.some((r) => {
|
|
564
|
+
const dx = Math.max(r[0] - u, 0, u - r[2]);
|
|
565
|
+
const dz = Math.max(r[1] - v, 0, v - r[3]);
|
|
566
|
+
return dx * dx + dz * dz < radius * radius - 1e-9;
|
|
567
|
+
});
|
|
568
|
+
for (let j = 0; j < nz; j++)
|
|
569
|
+
for (let i = 0; i < nx; i++) {
|
|
570
|
+
const u = u0 + (i + 0.5) * gs;
|
|
571
|
+
const v = v0 + (j + 0.5) * gs;
|
|
572
|
+
if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3]) && (room.outdoor || !tooClose(u, v))) insideCells[j * nx + i] = 1;
|
|
573
|
+
}
|
|
574
|
+
const entries = [];
|
|
575
|
+
for (const ab of openingsOfFloor(plan, room.floor))
|
|
576
|
+
if (!(ignored && ignored.has(ab.id)))
|
|
577
|
+
for (const pt of entryPoints(ab)) {
|
|
578
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
579
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
580
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
581
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
582
|
+
}
|
|
583
|
+
if (!room.outdoor)
|
|
584
|
+
for (const pair of plan.openPairs[room.floor] || []) {
|
|
585
|
+
const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
|
|
586
|
+
const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
|
|
587
|
+
if (!other) continue;
|
|
588
|
+
const edge = sharedEdge(room, other, null);
|
|
589
|
+
if (!edge || edge.a1 - edge.a0 > 2) continue;
|
|
590
|
+
const mid = (edge.a0 + edge.a1) / 2;
|
|
591
|
+
for (const offset of [-0.4, 0.4]) {
|
|
592
|
+
const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
|
|
593
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
594
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
595
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
596
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const R = inner(room.parts[0]);
|
|
600
|
+
return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
|
|
601
|
+
}
|
|
602
|
+
function reachable(g, solids, inflation) {
|
|
603
|
+
const { gs, u0, v0, nx, nz, inside } = g;
|
|
604
|
+
const blocked = new Uint8Array(nx * nz);
|
|
605
|
+
for (const r of solids) {
|
|
606
|
+
const i0 = Math.max(0, Math.floor((r[0] - inflation - u0) / gs));
|
|
607
|
+
const i1 = Math.min(nx - 1, Math.floor((r[2] + inflation - u0) / gs));
|
|
608
|
+
const j0 = Math.max(0, Math.floor((r[1] - inflation - v0) / gs));
|
|
609
|
+
const j1 = Math.min(nz - 1, Math.floor((r[3] + inflation - v0) / gs));
|
|
610
|
+
for (let j = j0; j <= j1; j++)
|
|
611
|
+
for (let i = i0; i <= i1; i++) {
|
|
612
|
+
const u = u0 + (i + 0.5) * gs;
|
|
613
|
+
const v = v0 + (j + 0.5) * gs;
|
|
614
|
+
if (u > r[0] - inflation && u < r[2] + inflation && v > r[1] - inflation && v < r[3] + inflation) blocked[j * nx + i] = 1;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
let best = -1;
|
|
618
|
+
let bestDist = 1e9;
|
|
619
|
+
for (let j = 0; j < nz; j++)
|
|
620
|
+
for (let i = 0; i < nx; i++) {
|
|
621
|
+
const k = j * nx + i;
|
|
622
|
+
if (!inside[k] || blocked[k]) continue;
|
|
623
|
+
const dist = Math.hypot(u0 + (i + 0.5) * gs - g.center[0], v0 + (j + 0.5) * gs - g.center[1]);
|
|
624
|
+
if (dist < bestDist) {
|
|
625
|
+
bestDist = dist;
|
|
626
|
+
best = k;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (best < 0) return false;
|
|
630
|
+
if (!g.entries.length) return true;
|
|
631
|
+
for (const [i, j] of g.entries) if (blocked[j * nx + i]) return false;
|
|
632
|
+
const visited = new Uint8Array(nx * nz);
|
|
633
|
+
const queue = new Int32Array(nx * nz);
|
|
634
|
+
let h = 0;
|
|
635
|
+
let t = 0;
|
|
636
|
+
const s = g.entries[0][1] * nx + g.entries[0][0];
|
|
637
|
+
visited[s] = 1;
|
|
638
|
+
queue[t++] = s;
|
|
639
|
+
while (h < t) {
|
|
640
|
+
const k = queue[h++];
|
|
641
|
+
const i = k % nx;
|
|
642
|
+
const j = (k - i) / nx;
|
|
643
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
644
|
+
const a = i + di;
|
|
645
|
+
const b = j + dj;
|
|
646
|
+
if (a < 0 || b < 0 || a >= nx || b >= nz) continue;
|
|
647
|
+
const q = b * nx + a;
|
|
648
|
+
if (visited[q] || !inside[q] || blocked[q]) continue;
|
|
649
|
+
visited[q] = 1;
|
|
650
|
+
queue[t++] = q;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (!visited[best]) return false;
|
|
654
|
+
for (const [i, j] of g.entries) if (!visited[j * nx + i]) return false;
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/fill.ts
|
|
659
|
+
function candidatesOf(cats) {
|
|
660
|
+
const out = [];
|
|
661
|
+
for (const [cat, weight] of Object.entries(cats)) {
|
|
662
|
+
const exact = cat[0] === "=";
|
|
663
|
+
const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
|
|
664
|
+
for (const it of its) out.push({ it, weight: weight / its.length });
|
|
665
|
+
}
|
|
666
|
+
return out;
|
|
667
|
+
}
|
|
668
|
+
function itemRect(slot, it, shift) {
|
|
669
|
+
const c = slot._cell;
|
|
670
|
+
const f = c.f;
|
|
671
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
672
|
+
const R = c.R;
|
|
673
|
+
let u0 = (c.rect ? (c.rect[0] + c.rect[2]) / 2 : slot.x) - extentU / 2;
|
|
674
|
+
let v0 = (c.rect ? (c.rect[1] + c.rect[3]) / 2 : slot.z) - extentV / 2;
|
|
675
|
+
if (shift) {
|
|
676
|
+
if (slot.side === "left" || slot.side === "right") v0 += shift;
|
|
677
|
+
else u0 += shift;
|
|
678
|
+
}
|
|
679
|
+
for (const s of c.enclosed) {
|
|
680
|
+
if (s === "left") u0 = R[0];
|
|
681
|
+
else if (s === "right") u0 = R[2] - extentU;
|
|
682
|
+
else if (s === "front") v0 = R[1];
|
|
683
|
+
else if (s === "back") v0 = R[3] - extentV;
|
|
684
|
+
}
|
|
685
|
+
return [u0, v0, u0 + extentU, v0 + extentV];
|
|
686
|
+
}
|
|
687
|
+
var RELAXED_RADIUS = 0.12;
|
|
688
|
+
var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
|
|
689
|
+
function rotationFor(f, front) {
|
|
690
|
+
const meshFront = MESH_FRONT[front] || [0, 1];
|
|
691
|
+
let ry = Math.atan2(f[0], f[1]) - Math.atan2(meshFront[0], meshFront[1]);
|
|
692
|
+
if (ry > Math.PI + EPS) ry -= 2 * Math.PI;
|
|
693
|
+
if (ry < -Math.PI - EPS) ry += 2 * Math.PI;
|
|
694
|
+
return r2(ry);
|
|
695
|
+
}
|
|
696
|
+
function fillRoom(plan, room, slots2, rng) {
|
|
697
|
+
const items = [];
|
|
698
|
+
const missing = [];
|
|
699
|
+
const state = {};
|
|
700
|
+
const solids = [];
|
|
701
|
+
const reserves = [];
|
|
702
|
+
const rims = [];
|
|
703
|
+
const inners = usableParts(room, 0);
|
|
704
|
+
const grid = createGrid(plan, room);
|
|
705
|
+
const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
|
|
706
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
707
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
708
|
+
const inflation = room.outdoor ? 0.15 : plan.playerRadius;
|
|
709
|
+
const units = [];
|
|
710
|
+
const seen = {};
|
|
711
|
+
for (const s of slots2) {
|
|
712
|
+
if (s.group) {
|
|
713
|
+
if (seen[s.group]) {
|
|
714
|
+
seen[s.group].members.push(s);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const u = { group: s.group, members: [s] };
|
|
718
|
+
seen[s.group] = u;
|
|
719
|
+
units.push(u);
|
|
720
|
+
} else units.push({ group: null, members: [s] });
|
|
721
|
+
}
|
|
722
|
+
for (const u of units) u.required = u.members.some((m) => m.required);
|
|
723
|
+
const queue = units.filter((u) => u.required).concat(units.filter((u) => !u.required));
|
|
724
|
+
const dependencies = (u) => u.members.flatMap((m) => [m.requires, m.on]).filter(Boolean).filter((id) => slots2.some((s) => s.id === id && !u.members.includes(s)));
|
|
725
|
+
const done = /* @__PURE__ */ new Set();
|
|
726
|
+
const ready = (u) => dependencies(u).every((id) => done.has(id));
|
|
727
|
+
const make = (s, it, r, parent, overheadOff) => {
|
|
728
|
+
const f = [s.frontU, s.frontV];
|
|
729
|
+
const id = room.id + "." + s.id;
|
|
730
|
+
const m = {
|
|
731
|
+
id,
|
|
732
|
+
type: it.type,
|
|
733
|
+
category: it.category,
|
|
734
|
+
x: r2((r[0] + r[2]) / 2),
|
|
735
|
+
z: r2((r[1] + r[3]) / 2),
|
|
736
|
+
ry: rotationFor(f, it.front),
|
|
737
|
+
w: it.w,
|
|
738
|
+
d: it.d,
|
|
739
|
+
h: it.h,
|
|
740
|
+
y: parent ? r2((parent.y || 0) + parent.h) : 0,
|
|
741
|
+
frontU: f[0],
|
|
742
|
+
frontV: f[1],
|
|
743
|
+
room: room.id,
|
|
744
|
+
floor: room.floor,
|
|
745
|
+
outdoor: !!room.outdoor,
|
|
746
|
+
slot: s.id,
|
|
747
|
+
on: parent ? s.on : null,
|
|
748
|
+
mode: parent ? s.anchor === "inside" ? "inside" : "on" : null,
|
|
749
|
+
clearance: s.clearance,
|
|
750
|
+
flat: it.flat,
|
|
751
|
+
low: it.low,
|
|
752
|
+
aabb: r.map(r2),
|
|
753
|
+
_r: r,
|
|
754
|
+
_seedId: SEED_KEYS.items[id] ?? id
|
|
755
|
+
};
|
|
756
|
+
if (it.overhead) m.overhead = !overheadOff;
|
|
757
|
+
if (it.category === "car") {
|
|
758
|
+
m.driver = [f[1], -f[0]];
|
|
759
|
+
m.carClearance = plan.carClearance;
|
|
760
|
+
}
|
|
761
|
+
if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
|
|
762
|
+
if (room.link) m.link = { from: room.link[0], to: room.link[1] };
|
|
763
|
+
state[s.id] = m;
|
|
764
|
+
items.push(m);
|
|
765
|
+
return m;
|
|
766
|
+
};
|
|
767
|
+
const tryFit = (s, relax, overheadOff) => {
|
|
768
|
+
const cands = weightedOrder(candidatesOf(s.cats), rng);
|
|
769
|
+
if (s.anchor === "on" || s.anchor === "inside") {
|
|
770
|
+
const parent = state[s.on];
|
|
771
|
+
if (!parent) return null;
|
|
772
|
+
for (const { it } of cands) {
|
|
773
|
+
if (it.w > parent.w + EPS || it.d > parent.d + EPS) continue;
|
|
774
|
+
const f = [parent.frontU, parent.frontV];
|
|
775
|
+
const r = parent.aabb;
|
|
776
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
777
|
+
const cu = (r[0] + r[2]) / 2 - f[0] * (parent.d / 2 - it.d / 2);
|
|
778
|
+
const cv = (r[1] + r[3]) / 2 - f[1] * (parent.d / 2 - it.d / 2);
|
|
779
|
+
return make(s, it, [cu - extentU / 2, cv - extentV / 2, cu + extentU / 2, cv + extentV / 2], parent);
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
for (const { it } of cands) {
|
|
784
|
+
if (it.w > s.w + EPS || it.d > s.d + EPS) continue;
|
|
785
|
+
const f = [s.frontU, s.frontV];
|
|
786
|
+
const slide = s.anchor === "wall" ? s.slide == null ? 2 : s.slide : 0;
|
|
787
|
+
const shifts = [0];
|
|
788
|
+
for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
|
|
789
|
+
let found = null;
|
|
790
|
+
const car = it.category === "car" && !relax;
|
|
791
|
+
const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
|
|
792
|
+
for (const attempt of tries) {
|
|
793
|
+
const r3 = attempt.rect;
|
|
794
|
+
if (!insideUnion(r3, inners)) continue;
|
|
795
|
+
if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
|
|
796
|
+
if (it.flat) {
|
|
797
|
+
found = { rect: r3 };
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
if (zones.some((z) => overlaps(r3, z))) continue;
|
|
801
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
|
|
802
|
+
if (solids.some((o) => overlaps(r3, o))) continue;
|
|
803
|
+
if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
|
|
804
|
+
let cl2 = car ? attempt.clear : null;
|
|
805
|
+
if (!car && !relax && s.clearance > 0 && !it.low) {
|
|
806
|
+
cl2 = clearRect(r3, f, s.clearance);
|
|
807
|
+
if (!insideUnion(cl2, inners)) continue;
|
|
808
|
+
if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
|
|
809
|
+
}
|
|
810
|
+
if (!it.low && reserves.some((q) => overlaps(r3, q))) continue;
|
|
811
|
+
let rimRect2 = null;
|
|
812
|
+
if (it.rim) {
|
|
813
|
+
rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
|
|
814
|
+
if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
|
|
815
|
+
}
|
|
816
|
+
if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
|
|
817
|
+
found = { rect: r3, cl: cl2, rimRect: rimRect2 };
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
if (!found) continue;
|
|
821
|
+
const { rect: r, cl, rimRect } = found;
|
|
822
|
+
if (it.flat) return make(s, it, r, null);
|
|
823
|
+
const m = make(s, it, r, null, overheadOff);
|
|
824
|
+
if (cl) reserves.push(cl);
|
|
825
|
+
if (rimRect) rims.push(rimRect);
|
|
826
|
+
solids.push(r);
|
|
827
|
+
return m;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
};
|
|
831
|
+
const process = (u) => {
|
|
832
|
+
const pr = u.members[0].chance;
|
|
833
|
+
const x = rng();
|
|
834
|
+
const arr = u.members.slice();
|
|
835
|
+
const members = [];
|
|
836
|
+
while (arr.length) members.push(arr.splice(arr.length > 1 ? Math.floor(rng() * arr.length) : 0, 1)[0]);
|
|
837
|
+
const applicable = members.filter((m) => m.valid && (!m.requires || state[m.requires]) && (!m.on || state[m.on]));
|
|
838
|
+
if (!u.required && x >= pr) return;
|
|
839
|
+
let ok = false;
|
|
840
|
+
for (const m of applicable) {
|
|
841
|
+
if (tryFit(m, false)) {
|
|
842
|
+
ok = true;
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (!ok)
|
|
847
|
+
for (const m of applicable) {
|
|
848
|
+
if (tryFit(m, false, true)) {
|
|
849
|
+
ok = true;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (!ok && u.required)
|
|
854
|
+
for (const m of applicable) {
|
|
855
|
+
if (tryFit(m, true, true)) {
|
|
856
|
+
ok = true;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (!ok && u.required && (applicable.length || !u.members.some((m) => m.requires))) missing.push({ room: room.id, slot: u.group || u.members[0].id });
|
|
861
|
+
};
|
|
862
|
+
const pending = queue.slice();
|
|
863
|
+
while (pending.length) {
|
|
864
|
+
let i = pending.findIndex(ready);
|
|
865
|
+
if (i < 0) i = 0;
|
|
866
|
+
const u = pending.splice(i, 1)[0];
|
|
867
|
+
process(u);
|
|
868
|
+
for (const m of u.members) done.add(m.id);
|
|
869
|
+
}
|
|
870
|
+
return { items, missing };
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/legacy-seeds.ts
|
|
874
|
+
var STREAM = {
|
|
875
|
+
facade: "fachada",
|
|
876
|
+
lot: "lote",
|
|
877
|
+
decor: "decor",
|
|
878
|
+
roomPrefix: "c:",
|
|
879
|
+
palettePrefix: "paleta:"
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/context.ts
|
|
883
|
+
var placementKit = {
|
|
884
|
+
MARGIN,
|
|
885
|
+
EPS,
|
|
886
|
+
r2,
|
|
887
|
+
clamp,
|
|
888
|
+
overlaps,
|
|
889
|
+
inner,
|
|
890
|
+
inflate,
|
|
891
|
+
areaOf,
|
|
892
|
+
topOf,
|
|
893
|
+
extent,
|
|
894
|
+
insideUnion,
|
|
895
|
+
usableParts,
|
|
896
|
+
zonesOfFloor,
|
|
897
|
+
windowsOfRoom,
|
|
898
|
+
windowZone,
|
|
899
|
+
roomAt,
|
|
900
|
+
reachable,
|
|
901
|
+
createGrid,
|
|
902
|
+
clearRect,
|
|
903
|
+
pickWeighted,
|
|
904
|
+
createRng,
|
|
905
|
+
roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
|
|
906
|
+
shuffle,
|
|
907
|
+
facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
|
|
908
|
+
cutIntervals,
|
|
909
|
+
blocksWindow
|
|
910
|
+
};
|
|
911
|
+
function createPlacementContext(plan, items) {
|
|
912
|
+
const added = [];
|
|
913
|
+
const counters = {};
|
|
914
|
+
const make = (room, type, r, f, o) => {
|
|
915
|
+
const it = CATALOG[type];
|
|
916
|
+
const n = counters[room.id + type] = (counters[room.id + type] || 0) + 1;
|
|
917
|
+
return Object.assign(
|
|
918
|
+
{
|
|
919
|
+
id: room.id + ".dec_" + type + n,
|
|
920
|
+
type,
|
|
921
|
+
category: it.category,
|
|
922
|
+
x: r2((r[0] + r[2]) / 2),
|
|
923
|
+
z: r2((r[1] + r[3]) / 2),
|
|
924
|
+
ry: r2(Math.atan2(f[0], f[1])),
|
|
925
|
+
w: it.w,
|
|
926
|
+
d: it.d,
|
|
927
|
+
h: it.h,
|
|
928
|
+
y: 0,
|
|
929
|
+
frontU: f[0],
|
|
930
|
+
frontV: f[1],
|
|
931
|
+
room: room.id,
|
|
932
|
+
floor: room.floor,
|
|
933
|
+
outdoor: !!room.outdoor,
|
|
934
|
+
slot: "dec_" + type + n,
|
|
935
|
+
on: null,
|
|
936
|
+
mode: null,
|
|
937
|
+
clearance: 0,
|
|
938
|
+
flat: !!it.flat,
|
|
939
|
+
low: it.low,
|
|
940
|
+
aabb: r.map(r2),
|
|
941
|
+
_r: r,
|
|
942
|
+
decor: true,
|
|
943
|
+
mount: it.mount
|
|
944
|
+
},
|
|
945
|
+
o || {}
|
|
946
|
+
);
|
|
947
|
+
};
|
|
948
|
+
const add = (m) => {
|
|
949
|
+
added.push(m);
|
|
950
|
+
items.push(m);
|
|
951
|
+
return m;
|
|
952
|
+
};
|
|
953
|
+
const itemsOfRoom = (c) => items.filter((m) => m.room === c.id && m.floor === c.floor);
|
|
954
|
+
const extraSolids = {};
|
|
955
|
+
const solidsOfRoom = (c) => itemsOfRoom(c).filter((m) => !m.flat && !m.on && (!m.decor || m.mount === "floor")).map((m) => m._r).concat(extraSolids[c.id] || []);
|
|
956
|
+
const gridCache = {};
|
|
957
|
+
let ignored = null;
|
|
958
|
+
const gridOf = (c) => gridCache[c.id] || (gridCache[c.id] = createGrid(plan, c, ignored));
|
|
959
|
+
function placeOnFloor(room, type, rng2, optsIn) {
|
|
960
|
+
const opts = Object.assign({}, optsIn || {});
|
|
961
|
+
const it0 = CATALOG[type];
|
|
962
|
+
if (it0.flat) opts.flat = true;
|
|
963
|
+
const it = CATALOG[type];
|
|
964
|
+
const inners = usableParts(room, 0);
|
|
965
|
+
const solidList = solidsOfRoom(room);
|
|
966
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
967
|
+
const occupied = itemsOfRoom(room);
|
|
968
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
969
|
+
const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
|
|
970
|
+
const r = m.aabb;
|
|
971
|
+
return m.frontV === 1 ? [r[0], r[3], r[2], r[3] + 1] : m.frontV === -1 ? [r[0], r[1] - 1, r[2], r[1]] : m.frontU === 1 ? [r[2], r[1], r[2] + 1, r[3]] : [r[0] - 1, r[1], r[0], r[3]];
|
|
972
|
+
});
|
|
973
|
+
const cand = opts.cands ? opts.cands.slice() : [];
|
|
974
|
+
if (!opts.cands)
|
|
975
|
+
for (const p of inners) {
|
|
976
|
+
const extentU = opts.dim ? opts.dim[0] : it.w;
|
|
977
|
+
const extentV = opts.dim ? opts.dim[1] : it.d;
|
|
978
|
+
for (const [x, z] of [[p[0], p[1]], [p[2] - extentU, p[1]], [p[0], p[3] - extentV], [p[2] - extentU, p[3] - extentV]]) cand.push([x, z, x + extentU, z + extentV]);
|
|
979
|
+
for (let k = 0; k < 6; k++) {
|
|
980
|
+
const t = rng2();
|
|
981
|
+
const side = Math.floor(rng2() * 4);
|
|
982
|
+
const x = side < 2 ? p[0] + t * Math.max(0, p[2] - p[0] - extentU) : side === 2 ? p[0] : p[2] - extentU;
|
|
983
|
+
const z = side >= 2 ? p[1] + t * Math.max(0, p[3] - p[1] - extentV) : side === 0 ? p[1] : p[3] - extentV;
|
|
984
|
+
cand.push([x, z, x + extentU, z + extentV]);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
|
|
988
|
+
if (!insideUnion(r, inners)) continue;
|
|
989
|
+
if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
|
|
990
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
|
|
991
|
+
if (solidList.some((o) => overlaps(r, o))) continue;
|
|
992
|
+
if (opts.flat) {
|
|
993
|
+
if (!(opts.without && opts.without(r))) return r;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (occupied.some((m) => !m.low && !m.flat && !m.on && !m.decor && m.clearance > 0 && overlaps(r, clearRect(m._r, [m.frontU, m.frontV], m.clearance)))) continue;
|
|
997
|
+
if (opts.without && opts.without(r)) continue;
|
|
998
|
+
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
|
|
999
|
+
return r;
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
const roomById = (id) => plan.rooms.find((c) => c.id === id) || plan.zones.find((c) => c.id === id);
|
|
1004
|
+
return {
|
|
1005
|
+
make,
|
|
1006
|
+
add,
|
|
1007
|
+
placeOnFloor,
|
|
1008
|
+
itemsOfRoom,
|
|
1009
|
+
solidsOfRoom,
|
|
1010
|
+
gridOf,
|
|
1011
|
+
extraSolids,
|
|
1012
|
+
roomById,
|
|
1013
|
+
setIgnored: (s) => {
|
|
1014
|
+
ignored = s;
|
|
1015
|
+
for (const k of Object.keys(gridCache)) delete gridCache[k];
|
|
1016
|
+
},
|
|
1017
|
+
added
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function groundCover(plan, items, ctx) {
|
|
1021
|
+
if (!plan.lot) return;
|
|
1022
|
+
const polygonOf = (r) => [[r[0], r[1]], [r[2], r[1]], [r[2], r[3]], [r[0], r[3]]].map((p) => [r2(p[0]), r2(p[1])]);
|
|
1023
|
+
const groundPiece = (z, type, r, layer, i) => {
|
|
1024
|
+
const m = ctx.make(z, type, r, [0, 1], {
|
|
1025
|
+
id: z.id + ".ground" + (layer ? "_overlay" : "") + i,
|
|
1026
|
+
slot: "ground" + (layer ? "_overlay" : "") + i,
|
|
1027
|
+
w: r2(r[2] - r[0]),
|
|
1028
|
+
d: r2(r[3] - r[1]),
|
|
1029
|
+
h: 0,
|
|
1030
|
+
polygon: polygonOf(r),
|
|
1031
|
+
layer,
|
|
1032
|
+
zone: z.id,
|
|
1033
|
+
outdoor: true,
|
|
1034
|
+
floor: -1
|
|
1035
|
+
});
|
|
1036
|
+
return ctx.add(m);
|
|
1037
|
+
};
|
|
1038
|
+
for (const z of plan.zones) {
|
|
1039
|
+
z.parts.forEach((p, i) => groundPiece(z, z.ground, p, 0, i));
|
|
1040
|
+
let k = 0;
|
|
1041
|
+
const clip = (r) => z.parts.map((p) => [Math.max(r[0], p[0]), Math.max(r[1], p[1]), Math.min(r[2], p[2]), Math.min(r[3], p[3])]).filter((q) => q[2] - q[0] > 0.3 && q[3] - q[1] > 0.3).sort((a, b) => areaOf(b) - areaOf(a))[0];
|
|
1042
|
+
for (const rule of GROUND_OVERLAYS) {
|
|
1043
|
+
const triggers = items.filter((m) => m.room === z.id && m.category === rule.category);
|
|
1044
|
+
if (rule.merge) {
|
|
1045
|
+
if (triggers.length && z.ground !== rule.unlessGround) {
|
|
1046
|
+
const u = triggers.reduce((a, m) => [Math.min(a[0], m._r[0]), Math.min(a[1], m._r[1]), Math.max(a[2], m._r[2]), Math.max(a[3], m._r[3])], [1e9, 1e9, -1e9, -1e9]);
|
|
1047
|
+
const q = clip(inflate(u, rule.inflate));
|
|
1048
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
for (const m of triggers) {
|
|
1052
|
+
const q = clip(inflate(m._r, rule.inflate));
|
|
1053
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
function runPostProcess(plan, items, seed) {
|
|
1060
|
+
const ctx = createPlacementContext(plan, items);
|
|
1061
|
+
const rng = createRng(seed, STREAM.decor);
|
|
1062
|
+
for (const pass of PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: CATALOG, profiles: DECOR_PROFILES });
|
|
1063
|
+
groundCover(plan, items, ctx);
|
|
1064
|
+
return ctx;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/locale.ts
|
|
1068
|
+
var locales = {};
|
|
1069
|
+
function registerLocale(code, table) {
|
|
1070
|
+
const cur = locales[code] = locales[code] || {};
|
|
1071
|
+
for (const ns of Object.keys(table)) cur[ns] = Object.assign(cur[ns] || {}, table[ns]);
|
|
1072
|
+
}
|
|
1073
|
+
var listLocales = () => Object.keys(locales);
|
|
1074
|
+
function translate(ns, key, locale = "en", vars) {
|
|
1075
|
+
const raw = locales[locale]?.[ns]?.[key] ?? locales.en?.[ns]?.[key] ?? key;
|
|
1076
|
+
return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/paths.ts
|
|
1080
|
+
var CLOSE = 1e-6;
|
|
1081
|
+
var rounded = (r) => r.map(r2);
|
|
1082
|
+
var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
|
|
1083
|
+
function addExteriorDoorPaths(plan, c) {
|
|
1084
|
+
const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
|
|
1085
|
+
const social = gates.find((g) => g.type === "pedestrian");
|
|
1086
|
+
const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
|
|
1087
|
+
const parkingZone = zones.find((z) => z.id === "parking");
|
|
1088
|
+
const parking = parkingZone ? parkingZone.parts[0] : null;
|
|
1089
|
+
const midY = setback > 0 ? -setback / 2 : 0;
|
|
1090
|
+
const joinToSocial = (x, width, rects, points) => {
|
|
1091
|
+
if (!socialPath) return;
|
|
1092
|
+
const h = width / 2;
|
|
1093
|
+
const horizontal = socialPath.rects.find((r) => midY >= r[1] - CLOSE && midY <= r[3] + CLOSE && r[2] - r[0] > r[3] - r[1]) || socialPath.rects[0];
|
|
1094
|
+
const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
|
|
1095
|
+
if (Math.abs(targetX - x) > CLOSE) {
|
|
1096
|
+
rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
|
|
1097
|
+
points.push([targetX, midY]);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
|
|
1101
|
+
if (paths.some((p) => p.to === door.id)) continue;
|
|
1102
|
+
const half = Math.max(1, Math.min(door.width, 2)) / 2;
|
|
1103
|
+
const rects = [];
|
|
1104
|
+
const points = [];
|
|
1105
|
+
const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
|
|
1106
|
+
let fromId = social ? social.id : null;
|
|
1107
|
+
let width = 1;
|
|
1108
|
+
const id = isTarget ? "entrance_path" : "path_" + door.id;
|
|
1109
|
+
if (door.side === "front") {
|
|
1110
|
+
if (setback > 0) {
|
|
1111
|
+
const stub = Math.min(1, setback);
|
|
1112
|
+
rects.push([door.u - half, -stub, door.u + half, 0]);
|
|
1113
|
+
points.push([door.u, 0], [door.u, -stub]);
|
|
1114
|
+
if (-stub > midY) {
|
|
1115
|
+
rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
|
|
1116
|
+
points.push([door.u, midY]);
|
|
1117
|
+
}
|
|
1118
|
+
joinToSocial(door.u, width, rects, points);
|
|
1119
|
+
} else {
|
|
1120
|
+
rects.push([door.u - half, -1.2, door.u + half, 0]);
|
|
1121
|
+
points.push([door.u, 0], [door.u, -1.2]);
|
|
1122
|
+
}
|
|
1123
|
+
} else {
|
|
1124
|
+
const lanes = [];
|
|
1125
|
+
if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
|
|
1126
|
+
if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
|
|
1127
|
+
const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1128
|
+
let best = null;
|
|
1129
|
+
for (const lane of lanes) {
|
|
1130
|
+
if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
|
|
1131
|
+
const laneWidth = Math.min(0.9, lane.width - 0.1);
|
|
1132
|
+
const h = laneWidth / 2;
|
|
1133
|
+
const rearStrip = Math.min(0.8, backyard);
|
|
1134
|
+
const rearY = door.v + rearStrip + laneWidth / 2;
|
|
1135
|
+
const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
|
|
1136
|
+
const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
|
|
1137
|
+
let frontStub = null;
|
|
1138
|
+
if (!overlapsParking && setback > 0) {
|
|
1139
|
+
const dir = social && social.u < lane.x ? -1 : 1;
|
|
1140
|
+
const band = [-0.6 - h, -0.6 + h];
|
|
1141
|
+
let nearestX = social ? social.u : lane.x;
|
|
1142
|
+
let nearestGap = Infinity;
|
|
1143
|
+
for (const r of targets) {
|
|
1144
|
+
if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
|
|
1145
|
+
const edge = dir < 0 ? r[2] : r[0];
|
|
1146
|
+
const gap = Math.abs(edge - lane.x);
|
|
1147
|
+
if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
|
|
1148
|
+
nearestGap = gap;
|
|
1149
|
+
nearestX = edge;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
frontStub = [Math.min(lane.x, nearestX) - (dir < 0 ? 0 : h), -0.6 - h, Math.max(lane.x, nearestX) + (dir < 0 ? h : 0), -0.6 + h];
|
|
1153
|
+
}
|
|
1154
|
+
const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
|
|
1155
|
+
const horizontal = door.side === "back" ? [Math.min(door.u, lane.x) - half, rearY - laneWidth / 2, Math.max(door.u, lane.x) + h, rearY + laneWidth / 2] : null;
|
|
1156
|
+
const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
|
|
1157
|
+
if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
|
|
1158
|
+
}
|
|
1159
|
+
if (best) {
|
|
1160
|
+
const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
|
|
1161
|
+
width = laneWidth;
|
|
1162
|
+
if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
|
|
1163
|
+
if (door.side === "back") {
|
|
1164
|
+
rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
|
|
1165
|
+
points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
|
|
1166
|
+
} else {
|
|
1167
|
+
const x0 = door.side === "left" ? lane.x - h : w;
|
|
1168
|
+
const x1 = door.side === "left" ? 0 : lane.x + h;
|
|
1169
|
+
rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
|
|
1170
|
+
points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
|
|
1171
|
+
}
|
|
1172
|
+
if (frontStub) {
|
|
1173
|
+
rects.push(frontStub);
|
|
1174
|
+
points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
|
|
1175
|
+
}
|
|
1176
|
+
} else {
|
|
1177
|
+
const stub = 1;
|
|
1178
|
+
if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
|
|
1179
|
+
else rects.push(door.side === "left" ? [-Math.min(stub, marginLeft), door.v - half, 0, door.v + half] : [w, door.v - half, w + Math.min(stub, marginRight), door.v + half]);
|
|
1180
|
+
points.push([door.u, door.v]);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const reached = rects.slice();
|
|
1184
|
+
const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1185
|
+
let grows = true;
|
|
1186
|
+
while (grows) {
|
|
1187
|
+
grows = false;
|
|
1188
|
+
for (const r of pool) {
|
|
1189
|
+
if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
|
|
1190
|
+
reached.push(r);
|
|
1191
|
+
grows = true;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const inside = (g) => reached.some((r) => g.u >= r[0] - CLOSE && g.u <= r[2] + CLOSE && g.v >= r[1] - CLOSE && g.v <= r[3] + CLOSE);
|
|
1196
|
+
const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
|
|
1197
|
+
if (gate) fromId = gate.id;
|
|
1198
|
+
paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/zoning.ts
|
|
1203
|
+
var ZONES = {};
|
|
1204
|
+
var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
|
|
1205
|
+
function defineZone(zone) {
|
|
1206
|
+
if (!zone || !zone.id) throw new Error("defineZone: id is required");
|
|
1207
|
+
ZONES[zone.id] = zone;
|
|
1208
|
+
return zone;
|
|
1209
|
+
}
|
|
1210
|
+
var getZone = (id) => ZONES[id];
|
|
1211
|
+
var listZones = () => Object.values(ZONES);
|
|
1212
|
+
function registerPermeableGround(...types) {
|
|
1213
|
+
for (const t of types) PERMEABLE_GROUNDS.add(t);
|
|
1214
|
+
}
|
|
1215
|
+
function resolveZone(z) {
|
|
1216
|
+
if (!z) return null;
|
|
1217
|
+
if (typeof z === "string") {
|
|
1218
|
+
const found = ZONES[z];
|
|
1219
|
+
if (!found) throw new Error("Unknown zone: " + z);
|
|
1220
|
+
return found;
|
|
1221
|
+
}
|
|
1222
|
+
return z;
|
|
1223
|
+
}
|
|
1224
|
+
var floorsOfTemplate = (tpl, op) => (tpl.floorsFn ? tpl.floorsFn(op) : tpl.floors || []).length;
|
|
1225
|
+
var useOf = (tpl) => tpl.use || "residential_single";
|
|
1226
|
+
function measure(plan) {
|
|
1227
|
+
const floors = plan.floors;
|
|
1228
|
+
const top = floors.reduce((m, f) => Math.max(m, f.y0 + f.ceiling), 0);
|
|
1229
|
+
const number = plan.floorNumber != null ? plan.floorNumber : floors.length;
|
|
1230
|
+
const rooms = (plan.rooms || []).filter((r) => r.floor >= 0);
|
|
1231
|
+
const area = (r) => r.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
1232
|
+
const footprint = plan.w * plan.d;
|
|
1233
|
+
const built = rooms.reduce((s, r) => s + area(r), 0);
|
|
1234
|
+
return { storeys: number, height: r2(top), footprint, built };
|
|
1235
|
+
}
|
|
1236
|
+
function checkLot(zoneIn, lot, plan) {
|
|
1237
|
+
const zone = resolveZone(zoneIn);
|
|
1238
|
+
const out = [];
|
|
1239
|
+
const m = measure(plan);
|
|
1240
|
+
const use = plan.use || "residential_single";
|
|
1241
|
+
if (!zone.allowedUses.includes(use)) out.push({ rule: "use", actual: use, limit: zone.allowedUses.join(",") });
|
|
1242
|
+
if (m.storeys > zone.maxFloors) out.push({ rule: "floors", actual: m.storeys, limit: zone.maxFloors });
|
|
1243
|
+
if (m.height > zone.maxHeight + 1e-9) out.push({ rule: "height", actual: m.height, limit: zone.maxHeight });
|
|
1244
|
+
if (!lot) return out;
|
|
1245
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
1246
|
+
const width = r2(u1 - u0);
|
|
1247
|
+
const depth = r2(v1 - v0);
|
|
1248
|
+
const area = width * depth;
|
|
1249
|
+
const party = plan.partyWalls || [];
|
|
1250
|
+
const sides = [party.includes("left") ? Infinity : lot.sideYard.left, party.includes("right") ? Infinity : lot.sideYard.right];
|
|
1251
|
+
const left = sides[0];
|
|
1252
|
+
const right = sides[1];
|
|
1253
|
+
if (lot.setback < zone.setbacks.front - 1e-9) out.push({ rule: "setback_front", actual: lot.setback, limit: zone.setbacks.front });
|
|
1254
|
+
if (Math.min(left, right) < zone.setbacks.side - 1e-9) out.push({ rule: "setback_side", actual: Math.min(left, right), limit: zone.setbacks.side });
|
|
1255
|
+
if (lot.backyard < zone.setbacks.back - 1e-9) out.push({ rule: "setback_back", actual: lot.backyard, limit: zone.setbacks.back });
|
|
1256
|
+
if (area < zone.minLot - 1e-9) out.push({ rule: "min_lot", actual: r2(area), limit: zone.minLot });
|
|
1257
|
+
if (width < zone.minFrontage - 1e-9) out.push({ rule: "min_frontage", actual: width, limit: zone.minFrontage });
|
|
1258
|
+
const ground = m.footprint;
|
|
1259
|
+
const coverage = ground / area;
|
|
1260
|
+
if (coverage > zone.maxCoverage + 1e-9) out.push({ rule: "coverage", actual: r2(coverage), limit: zone.maxCoverage });
|
|
1261
|
+
const far = m.built / area;
|
|
1262
|
+
if (far > zone.maxFar + 1e-9) out.push({ rule: "far", actual: r2(far), limit: zone.maxFar });
|
|
1263
|
+
let permeable = 0;
|
|
1264
|
+
for (const z of lot.zones) if (PERMEABLE_GROUNDS.has(z.ground)) for (const p of z.parts) permeable += (p[2] - p[0]) * (p[3] - p[1]);
|
|
1265
|
+
const permeability = permeable / area;
|
|
1266
|
+
if (permeability < zone.permeableMin - 1e-9) out.push({ rule: "permeable", actual: r2(permeability), limit: zone.permeableMin });
|
|
1267
|
+
return out;
|
|
1268
|
+
}
|
|
1269
|
+
function clampOptionsToZone(tpl, op, zone, adjustments) {
|
|
1270
|
+
if (tpl.singleFloor) {
|
|
1271
|
+
const ceiling2 = (tpl.floors || tpl.floorsFn?.(op) || [])[0]?.ceiling ?? 2.8;
|
|
1272
|
+
const current2 = op.floor || 3;
|
|
1273
|
+
const limit2 = Math.max(1, Math.min(zone.maxFloors, Math.floor(zone.maxHeight / ceiling2 + 1e-9)));
|
|
1274
|
+
if (current2 <= limit2) return op;
|
|
1275
|
+
adjustments.push({ field: "floor", from: current2, to: limit2, reason: current2 > zone.maxFloors ? "max_floors" : "max_height" });
|
|
1276
|
+
return Object.assign({}, op, { floor: limit2 });
|
|
1277
|
+
}
|
|
1278
|
+
if (!tpl.floorRange || !tpl.floorsFn) return op;
|
|
1279
|
+
const current = floorsOfTemplate(tpl, op);
|
|
1280
|
+
const floors = tpl.floorsFn(op);
|
|
1281
|
+
const ceiling = floors.reduce((m, f) => Math.max(m, f.ceiling), 0) || 2.8;
|
|
1282
|
+
const byHeight = Math.max(1, Math.floor(zone.maxHeight / ceiling + 1e-9));
|
|
1283
|
+
const limit = Math.max(tpl.floorRange[0], Math.min(zone.maxFloors, byHeight, tpl.floorRange[1]));
|
|
1284
|
+
if (current <= limit) return op;
|
|
1285
|
+
adjustments.push({ field: "floors", from: current, to: limit, reason: current > zone.maxFloors ? "max_floors" : "max_height" });
|
|
1286
|
+
return Object.assign({}, op, { floors: limit });
|
|
1287
|
+
}
|
|
1288
|
+
function clampLotToZone(cfg, zone, adjustments, partyWalls = []) {
|
|
1289
|
+
const raise = (field, key, min, reason) => {
|
|
1290
|
+
if (cfg[key] < min) {
|
|
1291
|
+
adjustments.push({ field, from: cfg[key], to: min, reason });
|
|
1292
|
+
cfg[key] = min;
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
raise("frontSetback", "frontSetback", zone.setbacks.front, "setback_front");
|
|
1296
|
+
if (!partyWalls.includes("left")) raise("marginLeft", "marginLeft", zone.setbacks.side, "setback_side");
|
|
1297
|
+
if (!partyWalls.includes("right")) raise("marginRight", "marginRight", zone.setbacks.side, "setback_side");
|
|
1298
|
+
raise("backyardDepth", "backyardDepth", zone.setbacks.back, "setback_back");
|
|
1299
|
+
}
|
|
1300
|
+
var ADJUSTABLE_ZONES = /* @__PURE__ */ new Set(["garden", "backyard", "side_yard"]);
|
|
1301
|
+
function enforcePermeability(lot, zones, zone, adjustments) {
|
|
1302
|
+
const target = [...PERMEABLE_GROUNDS][0];
|
|
1303
|
+
if (!target) return;
|
|
1304
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
1305
|
+
const area = (u1 - u0) * (v1 - v0);
|
|
1306
|
+
const areaOfZone = (z) => z.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
1307
|
+
let permeable = zones.filter((z) => PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
|
|
1308
|
+
if (permeable / area >= zone.permeableMin) return;
|
|
1309
|
+
const candidates = zones.filter((z) => ADJUSTABLE_ZONES.has(z.type) && !PERMEABLE_GROUNDS.has(z.ground)).sort((a, b) => areaOfZone(b) - areaOfZone(a));
|
|
1310
|
+
for (const z of candidates) {
|
|
1311
|
+
if (permeable / area >= zone.permeableMin) break;
|
|
1312
|
+
adjustments.push({ field: "ground:" + z.id, from: z.ground, to: target, reason: "permeable_min" });
|
|
1313
|
+
z.ground = target;
|
|
1314
|
+
permeable += areaOfZone(z);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/plan.ts
|
|
1319
|
+
var floorsOf = (tpl, op) => tpl.floorsFn ? tpl.floorsFn(op || {}) : tpl.floors;
|
|
1320
|
+
function clampSize(tpl, w, d, op) {
|
|
1321
|
+
const L = tpl.limitsFn ? tpl.limitsFn(op || {}) : tpl.limits;
|
|
1322
|
+
const P0 = tpl.defaultsFn ? tpl.defaultsFn(op || {}) : tpl.defaults;
|
|
1323
|
+
const ww = w == null ? P0.w : clamp(w, L.w[0], L.w[1]);
|
|
1324
|
+
const dd = d == null ? P0.d : clamp(d, L.d[0], L.d[1]);
|
|
1325
|
+
return { w: r2(ww), d: r2(dd) };
|
|
1326
|
+
}
|
|
1327
|
+
function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
|
|
1328
|
+
const op = opIn || {};
|
|
1329
|
+
const { w, d } = clampSize(tpl, wIn, dIn, op);
|
|
1330
|
+
const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
|
|
1331
|
+
const FLOOR_DEFS = floorsOf(tpl, op);
|
|
1332
|
+
const style = styleOf(styleId);
|
|
1333
|
+
const plan = {
|
|
1334
|
+
w,
|
|
1335
|
+
d,
|
|
1336
|
+
tpl,
|
|
1337
|
+
floorDefs: FLOOR_DEFS,
|
|
1338
|
+
style: styleId,
|
|
1339
|
+
floors: [],
|
|
1340
|
+
rooms: [],
|
|
1341
|
+
zones: [],
|
|
1342
|
+
doors: [],
|
|
1343
|
+
windows: [],
|
|
1344
|
+
walls: [],
|
|
1345
|
+
gates: [],
|
|
1346
|
+
paths: [],
|
|
1347
|
+
lot: null,
|
|
1348
|
+
openPairs: {},
|
|
1349
|
+
reservations: [],
|
|
1350
|
+
options: op,
|
|
1351
|
+
playerRadius: op.playerRadius ?? PLAYER_RADIUS,
|
|
1352
|
+
carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
|
|
1353
|
+
};
|
|
1354
|
+
FLOOR_DEFS.forEach((A, floorIndex) => {
|
|
1355
|
+
const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
|
|
1356
|
+
plan.floors.push({
|
|
1357
|
+
index: floorIndex,
|
|
1358
|
+
name: tpl.singleFloor ? "floor_n" : A.label,
|
|
1359
|
+
number: tpl.singleFloor ? floorNumber : void 0,
|
|
1360
|
+
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
|
|
1361
|
+
ceiling: A.ceiling
|
|
1362
|
+
});
|
|
1363
|
+
plan.openPairs[floorIndex] = A.openPairs || [];
|
|
1364
|
+
const list = A.rooms(w, d, op).map((c) => normalizeRoom(c, floorIndex));
|
|
1365
|
+
for (const c of list) {
|
|
1366
|
+
if (plan.rooms.some((x) => x.id === c.id)) throw new Error("Duplicate room: " + c.id);
|
|
1367
|
+
plan.rooms.push(c);
|
|
1368
|
+
}
|
|
1369
|
+
const byId = {};
|
|
1370
|
+
for (const c of list) byId[c.id] = c;
|
|
1371
|
+
const floorDoors = [];
|
|
1372
|
+
(A.doors(w, d, op) || []).forEach((sp, i) => {
|
|
1373
|
+
const a = byId[sp.a];
|
|
1374
|
+
if (!a) throw new Error("Door references a missing room: " + sp.a);
|
|
1375
|
+
let edge;
|
|
1376
|
+
if (sp.edge) edge = sp.edge;
|
|
1377
|
+
else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
|
|
1378
|
+
else {
|
|
1379
|
+
const b = byId[sp.b];
|
|
1380
|
+
if (!b) throw new Error("Door references a missing room: " + sp.b);
|
|
1381
|
+
edge = sharedEdge(a, b, sp.at);
|
|
1382
|
+
}
|
|
1383
|
+
if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
|
|
1384
|
+
const width = sp.width;
|
|
1385
|
+
const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
|
|
1386
|
+
if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
|
|
1387
|
+
const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
|
|
1388
|
+
const exterior = sp.b === "outside";
|
|
1389
|
+
const leaf = exterior && sp.steel ? 0 : width > 1.1 ? width / 2 : width;
|
|
1390
|
+
const u = edge.axis === "u" ? c : edge.pos;
|
|
1391
|
+
const v = edge.axis === "u" ? edge.pos : c;
|
|
1392
|
+
floorDoors.push({
|
|
1393
|
+
id: "d" + floorIndex + "_" + i,
|
|
1394
|
+
floor: floorIndex,
|
|
1395
|
+
u: r2(u),
|
|
1396
|
+
v: r2(v),
|
|
1397
|
+
axis: edge.axis,
|
|
1398
|
+
pos: edge.pos,
|
|
1399
|
+
at: r2(c),
|
|
1400
|
+
width,
|
|
1401
|
+
a: sp.a,
|
|
1402
|
+
b: sp.b,
|
|
1403
|
+
opensTo: sp.opensTo || sp.a,
|
|
1404
|
+
exterior,
|
|
1405
|
+
side: sp.side || null,
|
|
1406
|
+
leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
|
|
1407
|
+
model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
|
|
1408
|
+
type: sp.type || "door",
|
|
1409
|
+
steps: sp.steps || 0,
|
|
1410
|
+
hinge: "start",
|
|
1411
|
+
zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
plan.doors.push(...floorDoors);
|
|
1415
|
+
const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
|
|
1416
|
+
const windowsOut = placeWindows({ list, byId, floorWalls, windowDefs: A.windows(w, d, op) || [], floorIndex, ceiling: A.ceiling, w, d, clearance: tpl.clearance?.window ?? WINDOW_EDGE_CLEARANCE });
|
|
1417
|
+
plan.windows.push(...windowsOut);
|
|
1418
|
+
plan.walls.push(...floorWalls);
|
|
1419
|
+
});
|
|
1420
|
+
if (tpl.config) plan.config = tpl.config(op, w, d);
|
|
1421
|
+
if (tpl.reservations) tpl.reservations(plan);
|
|
1422
|
+
buildLot(plan, tpl, w, d, op, style, styleId, bc);
|
|
1423
|
+
return plan;
|
|
1424
|
+
}
|
|
1425
|
+
function placeWindows(params) {
|
|
1426
|
+
const { list, byId, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
|
|
1427
|
+
const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
|
|
1428
|
+
const found = [];
|
|
1429
|
+
const groups = [];
|
|
1430
|
+
let order = 0;
|
|
1431
|
+
for (const def of windowDefs) {
|
|
1432
|
+
const room = byId[def.room];
|
|
1433
|
+
if (!room) throw new Error("Window references a missing room: " + def.room);
|
|
1434
|
+
const edge = exteriorEdge(room, def.side, w, d, def.at);
|
|
1435
|
+
if (!edge) continue;
|
|
1436
|
+
const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
|
|
1437
|
+
for (const centre of centres) {
|
|
1438
|
+
const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
|
|
1439
|
+
const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
|
|
1440
|
+
const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
|
|
1441
|
+
const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
|
|
1442
|
+
const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
|
|
1443
|
+
if (!target) continue;
|
|
1444
|
+
let group = groups.find((x) => x.wall === target && x.def.room === def.room);
|
|
1445
|
+
if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
|
|
1446
|
+
group.entries.push({ def, centre, order: order++ });
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
for (const g of groups) {
|
|
1450
|
+
const room = byId[g.def.room];
|
|
1451
|
+
const bath = room.type === "bathroom";
|
|
1452
|
+
const length = g.end - g.start;
|
|
1453
|
+
const its = g.entries.slice().sort((x, y) => x.centre - y.centre).map((x) => ({ def: x.def, order: x.order, width: bath ? Math.min(x.def.width, 0.6) : x.def.width }));
|
|
1454
|
+
const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
|
|
1455
|
+
while (its.length && !fits(its)) {
|
|
1456
|
+
const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
|
|
1457
|
+
if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
|
|
1458
|
+
else its.pop();
|
|
1459
|
+
}
|
|
1460
|
+
if (!its.length) continue;
|
|
1461
|
+
const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
|
|
1462
|
+
let pos = g.start + space;
|
|
1463
|
+
for (const x of its) {
|
|
1464
|
+
const c = pos + x.width / 2;
|
|
1465
|
+
pos += x.width + space;
|
|
1466
|
+
const u = g.edge.axis === "u" ? c : g.edge.pos;
|
|
1467
|
+
const v = g.edge.axis === "u" ? g.edge.pos : c;
|
|
1468
|
+
let y0 = x.def.y0 || 1;
|
|
1469
|
+
let y1 = x.def.y1 || 2.15;
|
|
1470
|
+
if (bath) {
|
|
1471
|
+
y0 = Math.max(y0, 2.1);
|
|
1472
|
+
y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
|
|
1473
|
+
}
|
|
1474
|
+
found.push({ id: "", floor: floorIndex, u: r2(u), v: r2(v), axis: g.edge.axis, pos: g.edge.pos, at: r2(c), width: x.width, y0: r2(y0), y1: r2(y1), room: x.def.room, side: x.def.side, _order: x.order });
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
for (const room of list) {
|
|
1478
|
+
if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
|
|
1479
|
+
let best = null;
|
|
1480
|
+
for (const t of stretches) {
|
|
1481
|
+
if (!t.sides.includes(room.id)) continue;
|
|
1482
|
+
const i0 = t.axis === "u" ? t.u0 : t.v0;
|
|
1483
|
+
const i1 = t.axis === "u" ? t.u1 : t.v1;
|
|
1484
|
+
const length = i1 - i0;
|
|
1485
|
+
const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
|
|
1486
|
+
if (width < 0.6 - TOL || best && best.length >= length) continue;
|
|
1487
|
+
const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
|
|
1488
|
+
const c = (i0 + i1) / 2;
|
|
1489
|
+
const pos = t.axis === "u" ? t.v0 : t.u0;
|
|
1490
|
+
const bath = room.type === "bathroom";
|
|
1491
|
+
best = { length, window: { id: "", floor: floorIndex, u: r2(t.axis === "u" ? c : pos), v: r2(t.axis === "u" ? pos : c), axis: t.axis, pos, at: r2(c), width: r2(width), y0: bath ? 2.1 : 1, y1: bath ? Math.min(2.5, ceiling - 0.25) : 2.15, room: room.id, side, _order: 1e6 + found.length } };
|
|
1492
|
+
}
|
|
1493
|
+
if (best) found.push(best.window);
|
|
1494
|
+
}
|
|
1495
|
+
found.sort((x, y) => x._order - y._order);
|
|
1496
|
+
found.forEach((j, i) => {
|
|
1497
|
+
delete j._order;
|
|
1498
|
+
j.id = "w" + floorIndex + "_" + i;
|
|
1499
|
+
});
|
|
1500
|
+
return found;
|
|
1501
|
+
}
|
|
1502
|
+
function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
|
|
1503
|
+
const seed = op.seed == null ? 1 : op.seed;
|
|
1504
|
+
const rFacade = createRng(seed, STREAM.facade);
|
|
1505
|
+
const facadeStyle = style.facade;
|
|
1506
|
+
const palette2 = style.swatches;
|
|
1507
|
+
const color = palette2[Math.floor(rFacade() * palette2.length)];
|
|
1508
|
+
const extra = tpl.facade || {};
|
|
1509
|
+
plan.facade = {
|
|
1510
|
+
style: styleId,
|
|
1511
|
+
color: { name: color[0], hex: color[1] },
|
|
1512
|
+
finish: pickWeighted(rFacade, facadeStyle.finish),
|
|
1513
|
+
grilles: rFacade() < facadeStyle.grilles,
|
|
1514
|
+
roof: pickWeighted(rFacade, facadeStyle.roof),
|
|
1515
|
+
balcony: !!extra.balcony,
|
|
1516
|
+
veranda: !!extra.veranda,
|
|
1517
|
+
steelDoor: !!extra.steelDoor,
|
|
1518
|
+
floors: plan.floorDefs.length
|
|
1519
|
+
};
|
|
1520
|
+
if (tpl.facadeExtra) tpl.facadeExtra(plan.facade, rFacade, w, d, op);
|
|
1521
|
+
if (plan.floorDefs.length > 1 && plan.facade.roof === "fiber_cement") plan.facade.roof = "colonial";
|
|
1522
|
+
if (!tpl.lot) return;
|
|
1523
|
+
const cfg = tpl.lot(w, d, style, op);
|
|
1524
|
+
if (bc.zone) clampLotToZone(cfg, bc.zone, bc.adjustments || [], tpl.partyWalls);
|
|
1525
|
+
const rLot = createRng(seed, STREAM.lot);
|
|
1526
|
+
const setback = cfg.frontSetback;
|
|
1527
|
+
const marginL = cfg.marginLeft;
|
|
1528
|
+
const marginR = cfg.marginRight;
|
|
1529
|
+
const backDepth = cfg.backyardDepth;
|
|
1530
|
+
const lot = {
|
|
1531
|
+
rect: [-marginL, -setback, w + marginR, d + backDepth],
|
|
1532
|
+
setback,
|
|
1533
|
+
sideYard: { left: marginL, right: marginR },
|
|
1534
|
+
backyard: backDepth,
|
|
1535
|
+
style: styleId,
|
|
1536
|
+
zones: [],
|
|
1537
|
+
perimeter: [],
|
|
1538
|
+
gates: [],
|
|
1539
|
+
paths: [],
|
|
1540
|
+
pool: !!cfg.pool
|
|
1541
|
+
};
|
|
1542
|
+
const ZONES2 = [];
|
|
1543
|
+
const libs = Object.assign({ garden: "garden", backyard: "backyard", parking: "parking", sideYard: "side_yard" }, cfg.libraries || {});
|
|
1544
|
+
const addZone = (id, type, label, parts, library) => ZONES2.push(Object.assign(normalizeRoom({ id, type, label, rect: parts[0], parts: parts.slice(1), library, outdoor: true }, -1), { floor: -1, outdoor: true }));
|
|
1545
|
+
if (setback > 0) {
|
|
1546
|
+
let parts = [[-marginL, -setback, w + marginR, 0]];
|
|
1547
|
+
if (cfg.parking) {
|
|
1548
|
+
const garageU = cfg.garage.u;
|
|
1549
|
+
const vw = cfg.parking.width;
|
|
1550
|
+
const x0 = Math.max(-marginL, garageU - vw / 2);
|
|
1551
|
+
const x1 = Math.min(w + marginR, garageU + vw / 2);
|
|
1552
|
+
parts = [];
|
|
1553
|
+
if (x0 + marginL > 0.8) parts.push([-marginL, -setback, x0, 0]);
|
|
1554
|
+
if (w + marginR - x1 > 0.8) parts.push([x1, -setback, w + marginR, 0]);
|
|
1555
|
+
addZone("parking", "parking", "parking", [[x0, -setback, x1, 0]], libs.parking);
|
|
1556
|
+
if (!parts.length) parts = [[x1, -setback, w + marginR, 0]];
|
|
1557
|
+
}
|
|
1558
|
+
parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
|
|
1559
|
+
addZone("garden", "garden", "garden", parts, libs.garden);
|
|
1560
|
+
}
|
|
1561
|
+
const yard = cfg.yardZones;
|
|
1562
|
+
if (backDepth > 0 && yard) {
|
|
1563
|
+
for (const q of yard) addZone(q.id, q.type, q.label || q.id, [[-marginL, d + q.v0, w + marginR, d + Math.min(backDepth, q.v1)]], q.library);
|
|
1564
|
+
} else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
|
|
1565
|
+
for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
|
|
1566
|
+
if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
|
|
1567
|
+
if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
|
|
1568
|
+
const gates = [];
|
|
1569
|
+
const addGate = (type, model, u, v, width, side, toTarget) => {
|
|
1570
|
+
const g = {
|
|
1571
|
+
id: "g" + gates.length,
|
|
1572
|
+
type,
|
|
1573
|
+
model,
|
|
1574
|
+
u: r2(u),
|
|
1575
|
+
v: r2(v),
|
|
1576
|
+
axis: "u",
|
|
1577
|
+
width,
|
|
1578
|
+
height: type === "pedestrian" ? style.height : Math.min(style.height, 2.2),
|
|
1579
|
+
side,
|
|
1580
|
+
to: toTarget || null,
|
|
1581
|
+
material: "iron"
|
|
1582
|
+
};
|
|
1583
|
+
g.zones = openingZones(u, v, "u", width, openingDepth(width, type === "pedestrian" ? width : 0));
|
|
1584
|
+
gates.push(g);
|
|
1585
|
+
return g;
|
|
1586
|
+
};
|
|
1587
|
+
if (cfg.pedestrian && setback > 0) addGate("pedestrian", "pedestrian", cfg.pedestrian.u, -setback, 1, "front", "door");
|
|
1588
|
+
if (cfg.garage && setback > 0) addGate("garage", pickWeighted(rLot, style.gate.garage), cfg.garage.u, -setback, cfg.garage.width, "front", cfg.garage.to);
|
|
1589
|
+
if (cfg.cargo) addGate("cargo", "sliding", cfg.cargo.u, d + backDepth, cfg.cargo.width, "back", "stockroom");
|
|
1590
|
+
plan.gates = gates;
|
|
1591
|
+
lot.gates = gates;
|
|
1592
|
+
const paths = [];
|
|
1593
|
+
const target = (a, side) => plan.doors.find((p) => p.exterior && p.a === a && p.side === side);
|
|
1594
|
+
for (const g of gates) {
|
|
1595
|
+
if (g.type === "pedestrian" && cfg.doorTarget) {
|
|
1596
|
+
const pt = target(cfg.doorTarget.a, cfg.doorTarget.side);
|
|
1597
|
+
if (!pt) continue;
|
|
1598
|
+
const gateU = g.u;
|
|
1599
|
+
const dx = pt.u;
|
|
1600
|
+
const ym = -setback / 2;
|
|
1601
|
+
const L = 1;
|
|
1602
|
+
const h = L / 2;
|
|
1603
|
+
const rs = [];
|
|
1604
|
+
if (Math.abs(gateU - dx) < 0.05) rs.push([dx - h, -setback, dx + h, 0]);
|
|
1605
|
+
else {
|
|
1606
|
+
rs.push([gateU - h, -setback, gateU + h, ym + h]);
|
|
1607
|
+
rs.push([Math.min(gateU, dx) - h, ym - h, Math.max(gateU, dx) + h, ym + h]);
|
|
1608
|
+
rs.push([dx - h, ym - h, dx + h, 0]);
|
|
1609
|
+
}
|
|
1610
|
+
paths.push({ id: "pedestrian_path", type: "pedestrian", from: g.id, to: pt.id, width: L, rects: rs.map((r) => r.map(r2)), points: [[gateU, -setback], [gateU, ym], [dx, ym], [dx, 0]] });
|
|
1611
|
+
} else if (g.type === "garage" && g.to === "inner_garage") {
|
|
1612
|
+
const pt = plan.doors.find((p) => p.exterior && p.side === "front" && Math.abs(p.u - g.u) < 0.6 && p.width >= 2.5);
|
|
1613
|
+
if (!pt) continue;
|
|
1614
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
1615
|
+
paths.push({ id: "garage_path", type: "vehicle", from: g.id, to: pt.id, width: hw * 2, rects: [[g.u - hw, -setback, g.u + hw, 0].map(r2)], points: [[g.u, -setback], [g.u, 0]] });
|
|
1616
|
+
} else if (g.type === "cargo") {
|
|
1617
|
+
const pt = target("stockroom", "back");
|
|
1618
|
+
if (!pt) continue;
|
|
1619
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
1620
|
+
paths.push({ id: "cargo_path", type: "vehicle", from: g.id, to: pt.id, width: hw * 2, rects: [[g.u - hw, d, g.u + hw, d + backDepth].map(r2)], points: [[g.u, d + backDepth], [g.u, d]] });
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
|
|
1624
|
+
else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES2, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
|
|
1625
|
+
plan.paths = paths;
|
|
1626
|
+
lot.paths = paths;
|
|
1627
|
+
const boundary = (rule) => {
|
|
1628
|
+
const type = pickWeighted(rLot, rule.type);
|
|
1629
|
+
const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
|
|
1630
|
+
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
|
|
1631
|
+
return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
|
|
1632
|
+
};
|
|
1633
|
+
const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
|
|
1634
|
+
const seg = (side, u0, v0, u1, v1) => {
|
|
1635
|
+
if (Math.abs(u1 - u0) + Math.abs(v1 - v0) < 0.05) return;
|
|
1636
|
+
lot.perimeter.push(Object.assign({ id: "m" + lot.perimeter.length, side, u0: r2(u0), v0: r2(v0), u1: r2(u1), v1: r2(v1) }, A[side]));
|
|
1637
|
+
};
|
|
1638
|
+
const cutRun = (side, de, to, vfix, gs) => {
|
|
1639
|
+
let pieces = [[de, to]];
|
|
1640
|
+
for (const g of gs) {
|
|
1641
|
+
const g0 = g.u - g.width / 2;
|
|
1642
|
+
const g1 = g.u + g.width / 2;
|
|
1643
|
+
const n = [];
|
|
1644
|
+
for (const [x, y] of pieces) {
|
|
1645
|
+
if (g1 <= x || g0 >= y) {
|
|
1646
|
+
n.push([x, y]);
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (g0 > x) n.push([x, g0]);
|
|
1650
|
+
if (g1 < y) n.push([g1, y]);
|
|
1651
|
+
}
|
|
1652
|
+
pieces = n;
|
|
1653
|
+
}
|
|
1654
|
+
for (const [x, y] of pieces) seg(side, x, vfix, y, vfix);
|
|
1655
|
+
};
|
|
1656
|
+
if (setback > 0)
|
|
1657
|
+
cutRun("front", -marginL, w + marginR, -setback, gates.filter((g) => g.side === "front"));
|
|
1658
|
+
cutRun("back", -marginL, w + marginR, d + backDepth, gates.filter((g) => g.side === "back"));
|
|
1659
|
+
const vi = setback > 0 ? -setback : 0;
|
|
1660
|
+
if (marginL > 0.5) seg("left", -marginL, vi, -marginL, d + backDepth);
|
|
1661
|
+
else if (backDepth > 0) seg("left", 0, d, 0, d + backDepth);
|
|
1662
|
+
if (marginR > 0.5) seg("right", w + marginR, vi, w + marginR, d + backDepth);
|
|
1663
|
+
else if (backDepth > 0) seg("right", w, d, w, d + backDepth);
|
|
1664
|
+
for (const z of ZONES2) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
|
|
1665
|
+
if (bc.zone) enforcePermeability(lot, ZONES2, bc.zone, bc.adjustments || []);
|
|
1666
|
+
plan.zones = ZONES2;
|
|
1667
|
+
lot.zones = ZONES2;
|
|
1668
|
+
plan.lot = lot;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// src/palette.ts
|
|
1672
|
+
var HIGH_SATURATION = 0.4;
|
|
1673
|
+
var MIN_WALL_FLOOR_CONTRAST = 20;
|
|
1674
|
+
var MIN_FABRIC_FLOOR_CONTRAST = 15;
|
|
1675
|
+
var SCHEMES = ["analogous", "complementary", "split_complementary", "triad"];
|
|
1676
|
+
var HARMONY_OFFSETS = { analogous: [-30, 30], complementary: [180, 165], split_complementary: [150, 210], triad: [120, 240] };
|
|
1677
|
+
var ROLE_FABRIC_MAIN = "fabric_main";
|
|
1678
|
+
var ROLE_FABRIC_ACCENT = "fabric_accent";
|
|
1679
|
+
var ROLE_FLOOR_REFERENCE = "floor_living";
|
|
1680
|
+
var hslToInt = (h, s, l) => {
|
|
1681
|
+
h = (h % 360 + 360) % 360;
|
|
1682
|
+
s = clamp(s, 0, 1);
|
|
1683
|
+
l = clamp(l, 0, 1);
|
|
1684
|
+
const a = s * Math.min(l, 1 - l);
|
|
1685
|
+
const f = (n) => {
|
|
1686
|
+
const k = (n + h / 30) % 12;
|
|
1687
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
1688
|
+
};
|
|
1689
|
+
return Math.round(f(0) * 255) << 16 | Math.round(f(8) * 255) << 8 | Math.round(f(4) * 255);
|
|
1690
|
+
};
|
|
1691
|
+
var intToHsl = (n) => {
|
|
1692
|
+
const r = (n >> 16 & 255) / 255;
|
|
1693
|
+
const g = (n >> 8 & 255) / 255;
|
|
1694
|
+
const b = (n & 255) / 255;
|
|
1695
|
+
const mx = Math.max(r, g, b);
|
|
1696
|
+
const mn = Math.min(r, g, b);
|
|
1697
|
+
const l = (mx + mn) / 2;
|
|
1698
|
+
const d = mx - mn;
|
|
1699
|
+
let h = 0;
|
|
1700
|
+
let s = 0;
|
|
1701
|
+
if (d > 1e-9) {
|
|
1702
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
1703
|
+
h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
1704
|
+
h *= 60;
|
|
1705
|
+
}
|
|
1706
|
+
return { h, s, l };
|
|
1707
|
+
};
|
|
1708
|
+
var lstar = (n) => {
|
|
1709
|
+
const linear = (c) => (c /= 255) <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
1710
|
+
const Y = 0.2126 * linear(n >> 16 & 255) + 0.7152 * linear(n >> 8 & 255) + 0.0722 * linear(n & 255);
|
|
1711
|
+
return Y > 216 / 24389 ? 116 * Math.cbrt(Y) - 16 : Y * 24389 / 27;
|
|
1712
|
+
};
|
|
1713
|
+
var hexString = (n) => "#" + (n >>> 0).toString(16).padStart(6, "0");
|
|
1714
|
+
function palette(o) {
|
|
1715
|
+
const opt = o || {};
|
|
1716
|
+
let style = resolveStyleId(opt.style || "budget");
|
|
1717
|
+
if (!PALETTE_STYLES[style]) style = PALETTE_STYLES.mid ? "mid" : Object.keys(PALETTE_STYLES)[0];
|
|
1718
|
+
const def = PALETTE_STYLES[style];
|
|
1719
|
+
const state = opt.state || "normal";
|
|
1720
|
+
const seed = opt.seed == null ? 1 : opt.seed;
|
|
1721
|
+
const rng = createRng(seed, STREAM.palettePrefix + (def.seedKey ?? style));
|
|
1722
|
+
const table = def.facadeColors;
|
|
1723
|
+
const base = table[Math.floor(rng() * table.length)];
|
|
1724
|
+
const scheme = SCHEMES[Math.floor(rng() * 4)];
|
|
1725
|
+
const offsets = HARMONY_OFFSETS[scheme];
|
|
1726
|
+
const jit = (v, a) => v + (rng() - 0.5) * a;
|
|
1727
|
+
const hsl = (h, s, l) => ({ h: (h % 360 + 360) % 360, s: clamp(s, 0, 1), l: clamp(l, 0, 1) });
|
|
1728
|
+
const H0 = base[1];
|
|
1729
|
+
const H1 = H0 + offsets[0];
|
|
1730
|
+
const H2 = H0 + offsets[1];
|
|
1731
|
+
const draw = { rng, base, H0, H1, H2, jit, hsl };
|
|
1732
|
+
const early = def.early(draw);
|
|
1733
|
+
const { wallMode, sMax } = early;
|
|
1734
|
+
const R = Object.assign({}, early.roles);
|
|
1735
|
+
const ctx = Object.assign({ style, sMax, wallMode, generalFloor: early.generalFloor }, draw);
|
|
1736
|
+
for (const c of ROOM_COLORS) {
|
|
1737
|
+
const [wh, wl] = c.wall(ctx);
|
|
1738
|
+
R["wall_" + c.type] = hsl(wh, c.neutral ? Math.min(0.16, sMax) : clamp(jit(sMax * 0.75, sMax * 0.5), 0.03, sMax), jit(wl, 0.03));
|
|
1739
|
+
const pi = c.floor || early.generalFloor;
|
|
1740
|
+
R["floor_" + c.type] = hsl(pi[0], Math.min(pi[1], wallMode ? 0.18 : 0.3), jit(pi[2], 0.04));
|
|
1741
|
+
}
|
|
1742
|
+
Object.assign(R, def.late(draw, early));
|
|
1743
|
+
STATE_PLUGINS[state]?.adjustPalette?.(R);
|
|
1744
|
+
const adjust = (color, ref, min, preferredDir) => {
|
|
1745
|
+
for (let i = 0; i < 60; i++) {
|
|
1746
|
+
if (Math.abs(lstar(hslToInt(color.h, color.s, color.l)) - lstar(hslToInt(ref.h, ref.s, ref.l))) >= min) return;
|
|
1747
|
+
const refL = lstar(hslToInt(ref.h, ref.s, ref.l));
|
|
1748
|
+
let dir = preferredDir || (refL > 50 ? -1 : 1);
|
|
1749
|
+
if (dir < 0 && color.l <= 0.06 || dir > 0 && color.l >= 0.94) dir = -dir;
|
|
1750
|
+
color.l = clamp(color.l + dir * 0.015, 0.04, 0.96);
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
const types = ROOM_COLORS.map((c) => c.type);
|
|
1754
|
+
for (const c of types) {
|
|
1755
|
+
const w = R["wall_" + c];
|
|
1756
|
+
const p = R["floor_" + c];
|
|
1757
|
+
if (w.s > 0.3) p.s = Math.min(p.s, 0.2);
|
|
1758
|
+
if (p.s > HIGH_SATURATION) p.s = HIGH_SATURATION - 0.05;
|
|
1759
|
+
adjust(p, w, MIN_WALL_FLOOR_CONTRAST + 2, lstar(hslToInt(w.h, w.s, w.l)) > 50 ? -1 : 1);
|
|
1760
|
+
}
|
|
1761
|
+
const floorRef = R[ROLE_FLOOR_REFERENCE] || R["floor_" + types[0]];
|
|
1762
|
+
const fabric = R[ROLE_FABRIC_MAIN];
|
|
1763
|
+
adjust(fabric, floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
1764
|
+
adjust(R[ROLE_FABRIC_ACCENT], floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
1765
|
+
for (const c of types) {
|
|
1766
|
+
const p = R["floor_" + c];
|
|
1767
|
+
if (Math.abs(lstar(hslToInt(fabric.h, fabric.s, fabric.l)) - lstar(hslToInt(p.h, p.s, p.l))) < MIN_FABRIC_FLOOR_CONTRAST)
|
|
1768
|
+
adjust(p, fabric, MIN_FABRIC_FLOOR_CONTRAST + 2, lstar(hslToInt(fabric.h, fabric.s, fabric.l)) > 50 ? -1 : 1);
|
|
1769
|
+
}
|
|
1770
|
+
const out = { meta: { seed, style, state, scheme, base: base[0], accentMode: wallMode ? "wall" : "object" } };
|
|
1771
|
+
for (const k of Object.keys(R)) out[k] = hslToInt(R[k].h, R[k].s, R[k].l);
|
|
1772
|
+
const num = (k) => out[k];
|
|
1773
|
+
const pairOk = (w, p) => Math.abs(lstar(w) - lstar(p)) >= MIN_WALL_FLOOR_CONTRAST + 1 && Math.abs(lstar(num(ROLE_FABRIC_MAIN)) - lstar(p)) >= MIN_FABRIC_FLOOR_CONTRAST + 1;
|
|
1774
|
+
const search = (color, ok) => {
|
|
1775
|
+
const h = intToHsl(color);
|
|
1776
|
+
let bestValue = null;
|
|
1777
|
+
let md = 9;
|
|
1778
|
+
for (let l = 0.02; l <= 0.98; l += 5e-3) {
|
|
1779
|
+
const n = hslToInt(h.h, h.s, l);
|
|
1780
|
+
if (ok(n) && Math.abs(l - h.l) < md) {
|
|
1781
|
+
md = Math.abs(l - h.l);
|
|
1782
|
+
bestValue = n;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return bestValue;
|
|
1786
|
+
};
|
|
1787
|
+
for (const c of types) {
|
|
1788
|
+
if (pairOk(num("wall_" + c), num("floor_" + c))) continue;
|
|
1789
|
+
const n = search(num("floor_" + c), (p) => pairOk(num("wall_" + c), p));
|
|
1790
|
+
if (n != null) {
|
|
1791
|
+
out["floor_" + c] = n;
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
const w = search(num("wall_" + c), (w2) => pairOk(w2, num("floor_" + c)));
|
|
1795
|
+
if (w != null) out["wall_" + c] = w;
|
|
1796
|
+
}
|
|
1797
|
+
return out;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// src/slots.ts
|
|
1801
|
+
var SIDE_NORMAL = { left: [1, 0], right: [-1, 0], front: [0, 1], back: [0, -1] };
|
|
1802
|
+
var FIXED_FACING = { "u+": [1, 0], "u-": [-1, 0], "v+": [0, 1], "v-": [0, -1] };
|
|
1803
|
+
var SIDE_OPPOSITE = { front: "back", back: "front", left: "right", right: "left" };
|
|
1804
|
+
var WINDOW_SIDE = { front: "front", back: "back", left: "left", right: "right" };
|
|
1805
|
+
function slotDefsOf(plan, room) {
|
|
1806
|
+
const name = room.library || room.type;
|
|
1807
|
+
const key = LIBRARY_ALIAS[name] || name;
|
|
1808
|
+
const library = room.slots ? room.slots : LIBRARIES[key];
|
|
1809
|
+
if (!library) return [];
|
|
1810
|
+
const R = inner(room.parts[0]);
|
|
1811
|
+
let defs = typeof library === "function" ? library({ w: R[2] - R[0], d: R[3] - R[1] }) : library.slice();
|
|
1812
|
+
defs = defs.map((x) => Object.assign({}, x));
|
|
1813
|
+
for (const id of room.without || []) defs = defs.filter((x) => x.id !== id);
|
|
1814
|
+
for (const [id, aj] of Object.entries(room.overrides || {})) {
|
|
1815
|
+
const i = defs.findIndex((x) => x.id === id);
|
|
1816
|
+
if (i < 0) continue;
|
|
1817
|
+
if (aj === null) defs.splice(i, 1);
|
|
1818
|
+
else defs[i] = Object.assign(defs[i], aj);
|
|
1819
|
+
}
|
|
1820
|
+
defs.push(...(room.extras || []).map((x) => Object.assign({}, x)));
|
|
1821
|
+
const out = [];
|
|
1822
|
+
for (const def of defs) {
|
|
1823
|
+
def.parent = def.on;
|
|
1824
|
+
if (def.anchor === "wall" && def.side === "any") for (const l of ["left", "right", "front", "back"]) out.push(Object.assign({}, def, { id: def.id + "@" + l, side: l, group: def.group || def.id }));
|
|
1825
|
+
else if (def.anchor === "corner" && def.corner === "any") for (const l of ["front-left", "front-right", "back-left", "back-right"]) out.push(Object.assign({}, def, { id: def.id + "@" + l, corner: l, group: def.group || def.id }));
|
|
1826
|
+
else if (def.anchor === "window" && def.n == null) {
|
|
1827
|
+
const windowList = windowsOfRoom(plan, room);
|
|
1828
|
+
windowList.forEach((_j, i) => out.push(Object.assign({}, def, { id: def.id + "@w" + i, n: i, group: def.group || def.id })));
|
|
1829
|
+
} else out.push(def);
|
|
1830
|
+
}
|
|
1831
|
+
return out;
|
|
1832
|
+
}
|
|
1833
|
+
function resolveSlots(plan) {
|
|
1834
|
+
const all = [];
|
|
1835
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
1836
|
+
const defs = slotDefsOf(plan, room);
|
|
1837
|
+
const byId = {};
|
|
1838
|
+
const zones = room.type === "stairs" ? plan.floors.flatMap((_, floorIndex) => zonesOfFloor(plan, floorIndex)) : zonesOfFloor(plan, room.floor);
|
|
1839
|
+
for (const def of defs) {
|
|
1840
|
+
const s = resolveSlot(plan, room, def, byId, zones);
|
|
1841
|
+
byId[def.id] = s;
|
|
1842
|
+
all.push(s);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return all;
|
|
1846
|
+
}
|
|
1847
|
+
function resolveSlot(plan, room, def, byId, zones) {
|
|
1848
|
+
const base = {
|
|
1849
|
+
id: def.id,
|
|
1850
|
+
room: room.id,
|
|
1851
|
+
floor: room.floor,
|
|
1852
|
+
outdoor: room.outdoor,
|
|
1853
|
+
anchor: def.anchor,
|
|
1854
|
+
side: def.side || def.corner || null,
|
|
1855
|
+
cats: def.cats,
|
|
1856
|
+
slide: def.slide,
|
|
1857
|
+
required: !!def.required,
|
|
1858
|
+
chance: def.required ? 1 : def.chance == null ? 1 : def.chance,
|
|
1859
|
+
clearance: def.clearance == null ? 0.5 : def.clearance,
|
|
1860
|
+
front: def.facing || "center",
|
|
1861
|
+
group: def.group || null,
|
|
1862
|
+
requires: def.requires || null,
|
|
1863
|
+
on: def.parent || null,
|
|
1864
|
+
target: def.target || null,
|
|
1865
|
+
valid: true,
|
|
1866
|
+
reason: null
|
|
1867
|
+
};
|
|
1868
|
+
const invalid = (m) => Object.assign(base, { valid: false, reason: m, x: 0, z: 0, ry: 0, w: 0, d: 0, frontU: 0, frontV: 1, aabb: [0, 0, 0, 0], _cell: null });
|
|
1869
|
+
if (def.anchor === "on" || def.anchor === "inside") {
|
|
1870
|
+
const parent = byId[def.parent];
|
|
1871
|
+
if (!parent || !parent.valid) return invalid("invalid_parent");
|
|
1872
|
+
return Object.assign(base, { x: parent.x, z: parent.z, ry: parent.ry, w: parent.w, d: parent.d, frontU: parent.frontU, frontV: parent.frontV, aabb: parent.aabb, _cell: parent._cell });
|
|
1873
|
+
}
|
|
1874
|
+
const size = def.size;
|
|
1875
|
+
const roomCenter = (() => {
|
|
1876
|
+
const R0 = inner(room.parts[0]);
|
|
1877
|
+
return [(R0[0] + R0[2]) / 2, (R0[1] + R0[3]) / 2];
|
|
1878
|
+
})();
|
|
1879
|
+
const openings = openingsOfFloor(plan, room.floor);
|
|
1880
|
+
let point = [0, 0];
|
|
1881
|
+
const targetPoint = (rule2) => {
|
|
1882
|
+
if (rule2 === "door") {
|
|
1883
|
+
let best = null;
|
|
1884
|
+
let bestDist = 1e9;
|
|
1885
|
+
for (const ab of openings) {
|
|
1886
|
+
const dist = Math.hypot(ab.u - point[0], ab.v - point[1]);
|
|
1887
|
+
if (dist < bestDist) {
|
|
1888
|
+
bestDist = dist;
|
|
1889
|
+
best = ab;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return best ? [best.u, best.v] : roomCenter;
|
|
1893
|
+
}
|
|
1894
|
+
if (rule2 === "target") {
|
|
1895
|
+
const t = byId[def.target];
|
|
1896
|
+
return t && t.valid ? [t.x, t.z] : roomCenter;
|
|
1897
|
+
}
|
|
1898
|
+
return roomCenter;
|
|
1899
|
+
};
|
|
1900
|
+
const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
1901
|
+
const rule = def.facing || "center";
|
|
1902
|
+
let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
|
|
1903
|
+
if (def.anchor === "wall" || def.anchor === "window") {
|
|
1904
|
+
let side = def.side;
|
|
1905
|
+
let a = null;
|
|
1906
|
+
let win = null;
|
|
1907
|
+
if (def.anchor === "window") {
|
|
1908
|
+
const windowList = windowsOfRoom(plan, room);
|
|
1909
|
+
win = windowList[def.n];
|
|
1910
|
+
if (!win) return invalid("no_window");
|
|
1911
|
+
side = room.outdoor ? SIDE_OPPOSITE[win.side] : WINDOW_SIDE[win.side];
|
|
1912
|
+
const cand = room.parts.map(inner).find(
|
|
1913
|
+
(r) => (win.axis === "u" ? win.u >= r[0] - TOL && win.u <= r[2] + TOL : win.v >= r[1] - TOL && win.v <= r[3] + TOL) && (side === "left" && eq(r[0], win.pos + MARGIN) || side === "right" && eq(r[2], win.pos - MARGIN) || side === "front" && eq(r[1], win.pos + MARGIN) || side === "back" && eq(r[3], win.pos - MARGIN))
|
|
1914
|
+
);
|
|
1915
|
+
if (!cand) return invalid("window_outside_room");
|
|
1916
|
+
R = cand;
|
|
1917
|
+
a = win.axis === "u" ? win.u : win.v;
|
|
1918
|
+
}
|
|
1919
|
+
const n = SIDE_NORMAL[side];
|
|
1920
|
+
if (!n) return invalid("invalid_side");
|
|
1921
|
+
const horizontal = side === "front" || side === "back";
|
|
1922
|
+
const a0 = horizontal ? R[0] : R[1];
|
|
1923
|
+
const a1 = horizontal ? R[2] : R[3];
|
|
1924
|
+
const len = a1 - a0;
|
|
1925
|
+
const depth = size[1];
|
|
1926
|
+
if (depth > (horizontal ? R[3] - R[1] : R[2] - R[0]) + EPS) return invalid("larger_than_room");
|
|
1927
|
+
const hw = Math.min(size[0], len) / 2;
|
|
1928
|
+
const cell = (ac) => side === "left" ? [R[0], ac - hw, R[0] + depth, ac + hw] : side === "right" ? [R[2] - depth, ac - hw, R[2], ac + hw] : side === "front" ? [ac - hw, R[1], ac + hw, R[1] + depth] : [ac - hw, R[3] - depth, ac + hw, R[3]];
|
|
1929
|
+
const nominal = a != null ? a : a0 + hw + (def.pos == null ? 0.5 : def.pos) * Math.max(0, len - 2 * hw) + (def.shift || 0);
|
|
1930
|
+
const wp = side === "left" ? R[0] - MARGIN : side === "right" ? R[2] + MARGIN : side === "front" ? R[1] - MARGIN : R[3] + MARGIN;
|
|
1931
|
+
const pa = clamp(nominal, a0 + hw, a1 - hw);
|
|
1932
|
+
let found = null;
|
|
1933
|
+
const steps = [0];
|
|
1934
|
+
if (def.anchor !== "window") for (let k = 1; k <= 60; k++) steps.push(k, -k);
|
|
1935
|
+
for (const k of steps) {
|
|
1936
|
+
const ac = pa + k * 0.05;
|
|
1937
|
+
if (ac - hw < a0 - EPS || ac + hw > a1 + EPS) continue;
|
|
1938
|
+
if (free(cell(ac))) {
|
|
1939
|
+
found = ac;
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (found == null) return invalid("blocked_by_door");
|
|
1944
|
+
const sp = horizontal ? [found, wp - n[1] * 0.1] : [wp - n[0] * 0.1, found];
|
|
1945
|
+
if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
1946
|
+
const rect = cell(found);
|
|
1947
|
+
return Object.assign(base, { side, x: r2((rect[0] + rect[2]) / 2), z: r2((rect[1] + rect[3]) / 2), frontU: n[0], frontV: n[1], ry: r2(Math.atan2(n[0], n[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f: n, enclosed: [side], R, size } });
|
|
1948
|
+
}
|
|
1949
|
+
if (def.anchor === "corner") {
|
|
1950
|
+
const [cornerV, cornerU] = def.corner.split("-");
|
|
1951
|
+
const normalV = SIDE_NORMAL[cornerV];
|
|
1952
|
+
const normalU = SIDE_NORMAL[cornerU];
|
|
1953
|
+
if (!normalV || !normalU) return invalid("invalid_corner");
|
|
1954
|
+
const kx = cornerU === "left" ? R[0] : R[2];
|
|
1955
|
+
const kz = cornerV === "front" ? R[1] : R[3];
|
|
1956
|
+
point = [kx, kz];
|
|
1957
|
+
let f;
|
|
1958
|
+
if (FIXED_FACING[rule]) {
|
|
1959
|
+
f = FIXED_FACING[rule];
|
|
1960
|
+
if (!(f[0] === normalU[0] && f[1] === normalU[1] || f[0] === normalV[0] && f[1] === normalV[1])) f = normalU;
|
|
1961
|
+
} else {
|
|
1962
|
+
const t = targetPoint(rule);
|
|
1963
|
+
f = Math.abs(t[0] - kx) >= Math.abs(t[1] - kz) ? normalU : normalV;
|
|
1964
|
+
}
|
|
1965
|
+
const [extentU, extentV] = extent(size, f);
|
|
1966
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
1967
|
+
const rect = [cornerU === "left" ? R[0] : R[2] - extentU, cornerV === "front" ? R[1] : R[3] - extentV, cornerU === "left" ? R[0] + extentU : R[2], cornerV === "front" ? R[1] + extentV : R[3]];
|
|
1968
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
1969
|
+
for (const sp of [[kx - normalU[0] * (MARGIN + 0.1), (rect[1] + rect[3]) / 2], [(rect[0] + rect[2]) / 2, kz - normalV[1] * (MARGIN + 0.1)]]) if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
1970
|
+
return Object.assign(base, { x: r2((rect[0] + rect[2]) / 2), z: r2((rect[1] + rect[3]) / 2), frontU: f[0], frontV: f[1], ry: r2(Math.atan2(f[0], f[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f, enclosed: [cornerU, cornerV], R, size } });
|
|
1971
|
+
}
|
|
1972
|
+
if (def.anchor === "center") {
|
|
1973
|
+
let cx = R[0] + (def.fx == null ? 0.5 : def.fx) * (R[2] - R[0]) + (def.dx || 0);
|
|
1974
|
+
let cz = R[1] + (def.fz == null ? 0.5 : def.fz) * (R[3] - R[1]) + (def.dz || 0);
|
|
1975
|
+
point = [cx, cz];
|
|
1976
|
+
let f;
|
|
1977
|
+
if (FIXED_FACING[rule]) f = FIXED_FACING[rule];
|
|
1978
|
+
else {
|
|
1979
|
+
const t = targetPoint(rule);
|
|
1980
|
+
const dx = t[0] - cx;
|
|
1981
|
+
const dz = t[1] - cz;
|
|
1982
|
+
f = Math.hypot(dx, dz) < 0.05 ? [0, 1] : cardinal(dx, dz);
|
|
1983
|
+
}
|
|
1984
|
+
const [extentU, extentV] = extent(size, f);
|
|
1985
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
1986
|
+
cx = clamp(cx, R[0] + extentU / 2, R[2] - extentU / 2);
|
|
1987
|
+
cz = clamp(cz, R[1] + extentV / 2, R[3] - extentV / 2);
|
|
1988
|
+
const rect = [cx - extentU / 2, cz - extentV / 2, cx + extentU / 2, cz + extentV / 2];
|
|
1989
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
1990
|
+
return Object.assign(base, { x: r2(cx), z: r2(cz), frontU: f[0], frontV: f[1], ry: r2(Math.atan2(f[0], f[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f, enclosed: [], R, size } });
|
|
1991
|
+
}
|
|
1992
|
+
return invalid("unknown_anchor");
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// src/generate.ts
|
|
1996
|
+
function templateOf(t) {
|
|
1997
|
+
if (typeof t === "string") {
|
|
1998
|
+
if (!TEMPLATES[t]) throw new Error("Unknown template: " + t);
|
|
1999
|
+
return TEMPLATES[t];
|
|
2000
|
+
}
|
|
2001
|
+
return t;
|
|
2002
|
+
}
|
|
2003
|
+
var cleanSlot = (s) => {
|
|
2004
|
+
const o = {};
|
|
2005
|
+
for (const k of Object.keys(s)) if (k[0] !== "_") o[k] = s[k];
|
|
2006
|
+
o.cats = Object.keys(s.cats || {});
|
|
2007
|
+
return o;
|
|
2008
|
+
};
|
|
2009
|
+
function slots(template, options) {
|
|
2010
|
+
const tpl = templateOf(template);
|
|
2011
|
+
const o = options || {};
|
|
2012
|
+
const plan = buildPlan(tpl, o.w, o.d, o);
|
|
2013
|
+
const locale = o.locale || "en";
|
|
2014
|
+
return resolveSlots(plan).map(cleanSlot).map((s) => s.reason ? Object.assign(s, { reason: translate("reasons", s.reason, locale) }) : s);
|
|
2015
|
+
}
|
|
2016
|
+
function pickState(requested, seed) {
|
|
2017
|
+
if (requested && STATE_PLUGINS[requested]) return requested;
|
|
2018
|
+
const dist = stateDistribution.weights;
|
|
2019
|
+
if (!dist) return "normal";
|
|
2020
|
+
const x = createRng(seed, stateDistribution.seedKey)();
|
|
2021
|
+
let acc = 0;
|
|
2022
|
+
const keys = Object.keys(dist);
|
|
2023
|
+
for (const k of keys) {
|
|
2024
|
+
acc += dist[k];
|
|
2025
|
+
if (x < acc) return k;
|
|
2026
|
+
}
|
|
2027
|
+
return keys[keys.length - 1];
|
|
2028
|
+
}
|
|
2029
|
+
function colorOf(m, seed) {
|
|
2030
|
+
const spec = m.colorSpec || CATALOG[m.type]?.color || { role: "wood" };
|
|
2031
|
+
if ("role" in spec) return spec.role;
|
|
2032
|
+
if ("hex" in spec) return spec.hex;
|
|
2033
|
+
return spec.pick[hashString(seed + "|" + (m._seedId ?? m.id)) % spec.pick.length];
|
|
2034
|
+
}
|
|
2035
|
+
function applyState(plan, items, ctx, seed, state) {
|
|
2036
|
+
const plugin = STATE_PLUGINS[state];
|
|
2037
|
+
if (!plugin || state === "normal") return [];
|
|
2038
|
+
const c = {
|
|
2039
|
+
plan,
|
|
2040
|
+
items,
|
|
2041
|
+
seed,
|
|
2042
|
+
state,
|
|
2043
|
+
rng: createRng(seed, plugin.seedKey ?? "state:" + state),
|
|
2044
|
+
ctx,
|
|
2045
|
+
kit: placementKit,
|
|
2046
|
+
catalog: CATALOG,
|
|
2047
|
+
wallTopHeights: WALL_TOP_HEIGHTS
|
|
2048
|
+
};
|
|
2049
|
+
return plugin.apply(c);
|
|
2050
|
+
}
|
|
2051
|
+
var loc = (key, locale, ns = "labels", vars) => key ? translate(ns, key, locale, vars) : key;
|
|
2052
|
+
function generate(template, options) {
|
|
2053
|
+
const tpl = templateOf(template);
|
|
2054
|
+
const o = options || {};
|
|
2055
|
+
const seed = o.seed == null ? 1 : o.seed;
|
|
2056
|
+
const locale = o.locale || "en";
|
|
2057
|
+
let op = Object.assign({}, o, { seed });
|
|
2058
|
+
const zone = resolveZone(o.zone);
|
|
2059
|
+
const adjustments = [];
|
|
2060
|
+
if (zone) op = clampOptionsToZone(tpl, op, zone, adjustments);
|
|
2061
|
+
const plan = buildPlan(tpl, op.w, op.d, op, { zone, adjustments });
|
|
2062
|
+
const all = resolveSlots(plan);
|
|
2063
|
+
const items = [];
|
|
2064
|
+
const missing = [];
|
|
2065
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
2066
|
+
const sl = all.filter((s) => s.room === room.id && s.floor === room.floor);
|
|
2067
|
+
const r = fillRoom(plan, room, sl, createRng(seed, STREAM.roomPrefix + (room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id)));
|
|
2068
|
+
items.push(...r.items);
|
|
2069
|
+
missing.push(...r.missing);
|
|
2070
|
+
}
|
|
2071
|
+
const ctx = runPostProcess(plan, items, seed);
|
|
2072
|
+
const state = pickState(o.state, seed);
|
|
2073
|
+
const stateItemsAll = applyState(plan, items, ctx, seed, state);
|
|
2074
|
+
const colors = palette({ seed, style: plan.style, state });
|
|
2075
|
+
const strip = (m) => {
|
|
2076
|
+
const x = {};
|
|
2077
|
+
for (const k of Object.keys(m)) if (k[0] !== "_" && k !== "colorSpec") x[k] = m[k];
|
|
2078
|
+
return x;
|
|
2079
|
+
};
|
|
2080
|
+
const L = plan.lot;
|
|
2081
|
+
const lot = L ? {
|
|
2082
|
+
rect: L.rect.map(r2),
|
|
2083
|
+
setback: r2(L.setback),
|
|
2084
|
+
backyard: r2(L.backyard),
|
|
2085
|
+
sideYard: L.sideYard,
|
|
2086
|
+
style: L.style,
|
|
2087
|
+
pool: L.pool,
|
|
2088
|
+
zones: plan.zones.map((z) => ({ id: z.id, type: z.type, label: z.label, name: loc(z.label, locale), parts: z.parts, ground: z.ground })),
|
|
2089
|
+
perimeter: L.perimeter.map((s) => Object.assign({ color: "wall" }, s)),
|
|
2090
|
+
gates: plan.gates.map((g) => ({ id: g.id, type: g.type, model: g.model, u: g.u, v: g.v, axis: g.axis, width: g.width, height: g.height, side: g.side, to: g.to, material: g.material, open: !!g.open, color: "gate" })),
|
|
2091
|
+
paths: plan.paths.map((c) => ({ id: c.id, type: c.type, from: c.from, to: c.to, width: c.width, rects: c.rects, points: c.points }))
|
|
2092
|
+
} : null;
|
|
2093
|
+
const facade = Object.assign({}, plan.facade, {
|
|
2094
|
+
color: { name: loc(colors.meta.base, locale, "facadeColors"), key: colors.meta.base, hex: hexString(colors.facade) },
|
|
2095
|
+
roles: { facade: "facade", detail: "facade_detail", roof: "roof", wall: "wall", gate: "gate" }
|
|
2096
|
+
});
|
|
2097
|
+
const itemsOut = items.map((m) => {
|
|
2098
|
+
const out = strip(m);
|
|
2099
|
+
out.color = colorOf(m, seed);
|
|
2100
|
+
return out;
|
|
2101
|
+
});
|
|
2102
|
+
const stateItemsOut = stateItemsAll.map((m) => {
|
|
2103
|
+
const out = strip(m);
|
|
2104
|
+
if (m.category) out.color = colorOf(m, seed);
|
|
2105
|
+
return out;
|
|
2106
|
+
});
|
|
2107
|
+
const result = {
|
|
2108
|
+
template: tpl.id,
|
|
2109
|
+
name: translate("templates", tpl.id, locale),
|
|
2110
|
+
use: useOf(tpl),
|
|
2111
|
+
seed,
|
|
2112
|
+
style: plan.style,
|
|
2113
|
+
state,
|
|
2114
|
+
w: plan.w,
|
|
2115
|
+
d: plan.d,
|
|
2116
|
+
floors: plan.floors.map((f) => ({ index: f.index, label: f.name, name: f.number != null ? translate("labels", "floor_n", locale, { n: f.number }) : loc(f.name, locale), number: f.number, y0: f.y0, ceiling: f.ceiling })),
|
|
2117
|
+
rooms: plan.rooms.map((c) => ({
|
|
2118
|
+
id: c.id,
|
|
2119
|
+
type: c.type,
|
|
2120
|
+
label: c.label,
|
|
2121
|
+
name: loc(c.label, locale),
|
|
2122
|
+
floor: c.floor,
|
|
2123
|
+
u0: c.parts[0][0],
|
|
2124
|
+
v0: c.parts[0][1],
|
|
2125
|
+
u1: c.parts[0][2],
|
|
2126
|
+
v1: c.parts[0][3],
|
|
2127
|
+
parts: c.parts,
|
|
2128
|
+
link: c.link,
|
|
2129
|
+
ceiling: c.ceiling || plan.floors[c.floor].ceiling,
|
|
2130
|
+
color: { wall: "wall_" + c.type, floor: "floor_" + c.type }
|
|
2131
|
+
})),
|
|
2132
|
+
walls: plan.walls,
|
|
2133
|
+
doors: plan.doors,
|
|
2134
|
+
windows: plan.windows,
|
|
2135
|
+
openPairs: [].concat(...Object.values(plan.openPairs)),
|
|
2136
|
+
facade,
|
|
2137
|
+
lot,
|
|
2138
|
+
items: itemsOut,
|
|
2139
|
+
stateItems: stateItemsOut,
|
|
2140
|
+
colors,
|
|
2141
|
+
building: tpl.building ? tpl.building(plan.w, plan.d, op) : null,
|
|
2142
|
+
missing
|
|
2143
|
+
};
|
|
2144
|
+
if (tpl.singleFloor) result.floorNumber = op.floor || 3;
|
|
2145
|
+
result.partyWalls = tpl.partyWalls || [];
|
|
2146
|
+
if (plan.config) result.config = plan.config;
|
|
2147
|
+
runResultExtensions(plan, result, seed, op);
|
|
2148
|
+
if (zone) {
|
|
2149
|
+
const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
|
|
2150
|
+
result.zoning = report;
|
|
2151
|
+
}
|
|
2152
|
+
return result;
|
|
2153
|
+
}
|
|
2154
|
+
var isSolid = (m) => !m.flat && !m.on && (!m.decor || m.mount === "floor");
|
|
2155
|
+
function validate(result, options) {
|
|
2156
|
+
const locale = options?.locale || result.locale || "en";
|
|
2157
|
+
const t = (key, vars) => translate("messages", key, locale, vars);
|
|
2158
|
+
const errors = [];
|
|
2159
|
+
const solids = result.items.filter(isSolid);
|
|
2160
|
+
for (let i = 0; i < solids.length; i++)
|
|
2161
|
+
for (let j = i + 1; j < solids.length; j++)
|
|
2162
|
+
if (solids[i].floor === solids[j].floor && !solids[i].barricade && !solids[j].barricade && overlaps(solids[i].aabb, solids[j].aabb, 0.011)) errors.push(t("overlap", { a: solids[i].id, b: solids[j].id }));
|
|
2163
|
+
for (const m of solids) {
|
|
2164
|
+
if (m.barricade) continue;
|
|
2165
|
+
for (const p of result.doors) if (p.floor === (m.outdoor ? 0 : m.floor) && p.zones.some((z) => overlaps(m.aabb, z, 0.011))) errors.push(t("door_blocked", { id: m.id }));
|
|
2166
|
+
if (result.lot) {
|
|
2167
|
+
for (const g of result.lot.gates) if (m.outdoor && openingZones(g.u, g.v, "u", g.width, DOOR_CLEARANCE).some((z) => overlaps(m.aabb, z, 0.011))) errors.push(t("gate_blocked", { id: m.id }));
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
for (const f of result.missing) errors.push(t("missing_required", { room: f.room, slot: f.slot }));
|
|
2171
|
+
if (result.zoning) for (const v of result.zoning.violations) errors.push(t("zone_violation", { zone: result.zoning.zone, rule: v.rule, actual: v.actual, limit: v.limit }));
|
|
2172
|
+
return errors;
|
|
2173
|
+
}
|
|
2174
|
+
function limitsOf(template, options) {
|
|
2175
|
+
const tpl = templateOf(template);
|
|
2176
|
+
const op = options || {};
|
|
2177
|
+
return { limits: tpl.limitsFn ? tpl.limitsFn(op) : tpl.limits, defaults: tpl.defaultsFn ? tpl.defaultsFn(op) : tpl.defaults };
|
|
2178
|
+
}
|
|
2179
|
+
function listTemplates(locale = "en") {
|
|
2180
|
+
return Object.values(TEMPLATES).map((t) => {
|
|
2181
|
+
const a = floorsOf(t, {});
|
|
2182
|
+
return {
|
|
2183
|
+
id: t.id,
|
|
2184
|
+
name: translate("templates", t.id, locale),
|
|
2185
|
+
use: useOf(t),
|
|
2186
|
+
style: typeof t.style === "function" ? t.style({}) : t.style,
|
|
2187
|
+
limits: t.limits,
|
|
2188
|
+
defaults: t.defaults,
|
|
2189
|
+
floors: a.length,
|
|
2190
|
+
floorRange: t.floorRange || null,
|
|
2191
|
+
lot: !!t.lot,
|
|
2192
|
+
options: t.options || {}
|
|
2193
|
+
};
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/ground-items.ts
|
|
2198
|
+
function registerGroundItems() {
|
|
2199
|
+
const flat = { category: "ground_cover", w: 1, d: 1, h: 0, decor: true, mount: "ground", flat: true };
|
|
2200
|
+
registerItems([
|
|
2201
|
+
{ type: "lawn", ...flat, color: { role: "lawn" } },
|
|
2202
|
+
{ type: "tile_floor", ...flat, color: { role: "exterior_floor" } },
|
|
2203
|
+
{ type: "concrete", ...flat, color: { role: "exterior_floor" } },
|
|
2204
|
+
{ type: "deck_floor", ...flat, color: { role: "deck_floor" } },
|
|
2205
|
+
{ type: "dirt", ...flat, color: { role: "dirt" } }
|
|
2206
|
+
]);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// src/slot-kit.ts
|
|
2210
|
+
var slotKit = {
|
|
2211
|
+
wall: (id, side, pos, size, cats, o) => Object.assign({ id, anchor: "wall", side, pos, size, cats }, o),
|
|
2212
|
+
corner: (id, corner, size, cats, o) => Object.assign({ id, anchor: "corner", corner, size, cats }, o),
|
|
2213
|
+
center: (id, fx, fz, size, cats, o) => Object.assign({ id, anchor: "center", fx, fz, size, cats }, o),
|
|
2214
|
+
windowSlot: (id, size, cats, o) => Object.assign({ id, anchor: "window", size, cats }, o),
|
|
2215
|
+
on: (id, parent, cats, o) => Object.assign({ id, anchor: "on", on: parent, cats }, o),
|
|
2216
|
+
inside: (id, parent, cats, o) => Object.assign({ id, anchor: "inside", on: parent, cats }, o)
|
|
2217
|
+
};
|
|
2218
|
+
|
|
2219
|
+
// src/locales/en.json
|
|
2220
|
+
var en_default = {
|
|
2221
|
+
labels: {
|
|
2222
|
+
floor_n: "Floor {n}",
|
|
2223
|
+
parking: "Parking",
|
|
2224
|
+
garden: "Garden",
|
|
2225
|
+
backyard: "Backyard",
|
|
2226
|
+
side_left: "Left side",
|
|
2227
|
+
side_right: "Right side"
|
|
2228
|
+
},
|
|
2229
|
+
reasons: {
|
|
2230
|
+
invalid_parent: "invalid parent",
|
|
2231
|
+
invalid_side: "invalid side",
|
|
2232
|
+
blocked_by_door: "blocked by a door",
|
|
2233
|
+
open_wall: "open wall",
|
|
2234
|
+
invalid_corner: "invalid corner",
|
|
2235
|
+
no_window: "no window",
|
|
2236
|
+
window_outside_room: "window outside the room",
|
|
2237
|
+
larger_than_room: "slot larger than the room",
|
|
2238
|
+
unknown_anchor: "unknown anchor"
|
|
2239
|
+
},
|
|
2240
|
+
messages: {
|
|
2241
|
+
overlap: "Overlap: {a} and {b}",
|
|
2242
|
+
door_blocked: "Door blocked by {id}",
|
|
2243
|
+
gate_blocked: "Gate blocked by {id}",
|
|
2244
|
+
missing_required: "Required item missing: {room}/{slot}",
|
|
2245
|
+
zone_violation: "Zone {zone}: {rule} is {actual}, limit {limit}"
|
|
2246
|
+
},
|
|
2247
|
+
ui: {
|
|
2248
|
+
title: "Floor plan viewer",
|
|
2249
|
+
template: "Template",
|
|
2250
|
+
seed: "Seed",
|
|
2251
|
+
random_seed: "Random",
|
|
2252
|
+
state: "State",
|
|
2253
|
+
style: "Style",
|
|
2254
|
+
zone: "Zone",
|
|
2255
|
+
no_zone: "No zone (unrestricted)",
|
|
2256
|
+
floor: "Floor",
|
|
2257
|
+
outside: "Whole lot",
|
|
2258
|
+
language: "Language",
|
|
2259
|
+
width: "Width (m)",
|
|
2260
|
+
depth: "Depth (m)",
|
|
2261
|
+
auto_size: "Template default size",
|
|
2262
|
+
options: "Options",
|
|
2263
|
+
opt_floors: "Floors",
|
|
2264
|
+
opt_elevator: "Elevator",
|
|
2265
|
+
opt_tier: "Tier",
|
|
2266
|
+
yes: "Yes",
|
|
2267
|
+
no: "No",
|
|
2268
|
+
default: "Default",
|
|
2269
|
+
show_labels: "Labels",
|
|
2270
|
+
show_items: "Items",
|
|
2271
|
+
show_state: "State items",
|
|
2272
|
+
show_lot: "Lot",
|
|
2273
|
+
show_grid: "Grid",
|
|
2274
|
+
legend: "Legend",
|
|
2275
|
+
summary: "Summary",
|
|
2276
|
+
rooms: "Rooms",
|
|
2277
|
+
items: "Items",
|
|
2278
|
+
state_items: "State items",
|
|
2279
|
+
missing: "Missing required items",
|
|
2280
|
+
none: "None",
|
|
2281
|
+
validation: "Validation",
|
|
2282
|
+
valid: "Valid plan",
|
|
2283
|
+
zoning: "Zoning",
|
|
2284
|
+
adjustments: "Adjustments",
|
|
2285
|
+
violations: "Violations",
|
|
2286
|
+
compliant: "Compliant with the zone",
|
|
2287
|
+
hover: "Hover an item for details",
|
|
2288
|
+
meters: "m",
|
|
2289
|
+
use_label: "Use",
|
|
2290
|
+
unit_floor: "Unit floor",
|
|
2291
|
+
legend_wall: "Wall",
|
|
2292
|
+
legend_door: "Door",
|
|
2293
|
+
legend_window: "Window",
|
|
2294
|
+
legend_gate: "Gate",
|
|
2295
|
+
legend_path: "Path",
|
|
2296
|
+
legend_item: "Furniture",
|
|
2297
|
+
legend_state: "State item (outlined)",
|
|
2298
|
+
rule_use: "Use not allowed",
|
|
2299
|
+
rule_floors: "Too many floors",
|
|
2300
|
+
rule_height: "Too tall",
|
|
2301
|
+
rule_setback_front: "Front setback too small",
|
|
2302
|
+
rule_setback_side: "Side setback too small",
|
|
2303
|
+
rule_setback_back: "Back setback too small",
|
|
2304
|
+
rule_min_lot: "Lot too small",
|
|
2305
|
+
rule_min_frontage: "Frontage too narrow",
|
|
2306
|
+
rule_coverage: "Coverage too high",
|
|
2307
|
+
rule_far: "Floor area ratio too high",
|
|
2308
|
+
rule_permeable: "Permeable area too small",
|
|
2309
|
+
reason_max_floors: "Zone floor limit",
|
|
2310
|
+
reason_max_height: "Zone height limit",
|
|
2311
|
+
reason_setback_front: "Zone front setback",
|
|
2312
|
+
reason_setback_side: "Zone side setback",
|
|
2313
|
+
reason_setback_back: "Zone back setback",
|
|
2314
|
+
reason_permeable_min: "Zone permeable area"
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
// src/locales/pt-BR.json
|
|
2319
|
+
var pt_BR_default = {
|
|
2320
|
+
labels: {
|
|
2321
|
+
floor_n: "Andar {n}",
|
|
2322
|
+
parking: "Vaga",
|
|
2323
|
+
garden: "Jardim",
|
|
2324
|
+
backyard: "Quintal",
|
|
2325
|
+
side_left: "Lateral esquerda",
|
|
2326
|
+
side_right: "Lateral direita"
|
|
2327
|
+
},
|
|
2328
|
+
reasons: {
|
|
2329
|
+
invalid_parent: "pai inv\xE1lido",
|
|
2330
|
+
invalid_side: "lado inv\xE1lido",
|
|
2331
|
+
blocked_by_door: "bloqueado por porta",
|
|
2332
|
+
open_wall: "parede aberta",
|
|
2333
|
+
invalid_corner: "canto inv\xE1lido",
|
|
2334
|
+
no_window: "sem janela",
|
|
2335
|
+
window_outside_room: "janela fora do c\xF4modo",
|
|
2336
|
+
larger_than_room: "slot maior que o c\xF4modo",
|
|
2337
|
+
unknown_anchor: "\xE2ncora desconhecida"
|
|
2338
|
+
},
|
|
2339
|
+
messages: {
|
|
2340
|
+
overlap: "Sobreposi\xE7\xE3o: {a} e {b}",
|
|
2341
|
+
door_blocked: "Porta bloqueada por {id}",
|
|
2342
|
+
gate_blocked: "Port\xE3o bloqueado por {id}",
|
|
2343
|
+
missing_required: "Item obrigat\xF3rio ausente: {room}/{slot}",
|
|
2344
|
+
zone_violation: "Zona {zone}: {rule} \xE9 {actual}, limite {limit}"
|
|
2345
|
+
},
|
|
2346
|
+
ui: {
|
|
2347
|
+
title: "Visualizador de plantas",
|
|
2348
|
+
template: "Modelo",
|
|
2349
|
+
seed: "Semente",
|
|
2350
|
+
random_seed: "Aleat\xF3ria",
|
|
2351
|
+
state: "Estado",
|
|
2352
|
+
style: "Estilo",
|
|
2353
|
+
zone: "Zona",
|
|
2354
|
+
no_zone: "Sem zona (livre)",
|
|
2355
|
+
floor: "Andar",
|
|
2356
|
+
outside: "Lote inteiro",
|
|
2357
|
+
language: "Idioma",
|
|
2358
|
+
width: "Largura (m)",
|
|
2359
|
+
depth: "Profundidade (m)",
|
|
2360
|
+
auto_size: "Tamanho padr\xE3o do modelo",
|
|
2361
|
+
options: "Op\xE7\xF5es",
|
|
2362
|
+
opt_floors: "Andares",
|
|
2363
|
+
opt_elevator: "Elevador",
|
|
2364
|
+
opt_tier: "Padr\xE3o",
|
|
2365
|
+
yes: "Sim",
|
|
2366
|
+
no: "N\xE3o",
|
|
2367
|
+
default: "Padr\xE3o",
|
|
2368
|
+
show_labels: "R\xF3tulos",
|
|
2369
|
+
show_items: "M\xF3veis",
|
|
2370
|
+
show_state: "Itens de estado",
|
|
2371
|
+
show_lot: "Lote",
|
|
2372
|
+
show_grid: "Grade",
|
|
2373
|
+
legend: "Legenda",
|
|
2374
|
+
summary: "Resumo",
|
|
2375
|
+
rooms: "C\xF4modos",
|
|
2376
|
+
items: "M\xF3veis",
|
|
2377
|
+
state_items: "Itens de estado",
|
|
2378
|
+
missing: "Itens obrigat\xF3rios ausentes",
|
|
2379
|
+
none: "Nenhum",
|
|
2380
|
+
validation: "Valida\xE7\xE3o",
|
|
2381
|
+
valid: "Planta v\xE1lida",
|
|
2382
|
+
zoning: "Zoneamento",
|
|
2383
|
+
adjustments: "Ajustes",
|
|
2384
|
+
violations: "Viola\xE7\xF5es",
|
|
2385
|
+
compliant: "Em conformidade com a zona",
|
|
2386
|
+
hover: "Passe o mouse sobre um item para ver detalhes",
|
|
2387
|
+
meters: "m",
|
|
2388
|
+
use_label: "Uso",
|
|
2389
|
+
unit_floor: "Andar da unidade",
|
|
2390
|
+
legend_wall: "Parede",
|
|
2391
|
+
legend_door: "Porta",
|
|
2392
|
+
legend_window: "Janela",
|
|
2393
|
+
legend_gate: "Port\xE3o",
|
|
2394
|
+
legend_path: "Caminho",
|
|
2395
|
+
legend_item: "M\xF3vel",
|
|
2396
|
+
legend_state: "Item de estado (contorno)",
|
|
2397
|
+
rule_use: "Uso n\xE3o permitido",
|
|
2398
|
+
rule_floors: "Andares demais",
|
|
2399
|
+
rule_height: "Altura excedida",
|
|
2400
|
+
rule_setback_front: "Recuo frontal insuficiente",
|
|
2401
|
+
rule_setback_side: "Recuo lateral insuficiente",
|
|
2402
|
+
rule_setback_back: "Recuo de fundos insuficiente",
|
|
2403
|
+
rule_min_lot: "Lote pequeno demais",
|
|
2404
|
+
rule_min_frontage: "Testada estreita demais",
|
|
2405
|
+
rule_coverage: "Taxa de ocupa\xE7\xE3o excedida",
|
|
2406
|
+
rule_far: "Coeficiente de aproveitamento excedido",
|
|
2407
|
+
rule_permeable: "\xC1rea perme\xE1vel insuficiente",
|
|
2408
|
+
reason_max_floors: "Limite de andares da zona",
|
|
2409
|
+
reason_max_height: "Limite de altura da zona",
|
|
2410
|
+
reason_setback_front: "Recuo frontal da zona",
|
|
2411
|
+
reason_setback_side: "Recuo lateral da zona",
|
|
2412
|
+
reason_setback_back: "Recuo de fundos da zona",
|
|
2413
|
+
reason_permeable_min: "\xC1rea perme\xE1vel da zona"
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
|
|
2417
|
+
// src/silhouettes/random.ts
|
|
2418
|
+
function createSilhouetteRandom(seed) {
|
|
2419
|
+
let state = seed;
|
|
2420
|
+
return () => {
|
|
2421
|
+
state |= 0;
|
|
2422
|
+
state = state + 1831565813 | 0;
|
|
2423
|
+
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
2424
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2425
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
// src/silhouettes/builder.ts
|
|
2430
|
+
var ROOT = -1;
|
|
2431
|
+
var MAX_BOX_RADIUS = 0.15;
|
|
2432
|
+
var BOX_RADIUS_RATIO = 0.22;
|
|
2433
|
+
var PLAIN_BOX_RADIUS = 0.012;
|
|
2434
|
+
function boxRadius(width, height, depth) {
|
|
2435
|
+
const radius = Math.min(Math.min(width, height, depth) * BOX_RADIUS_RATIO, MAX_BOX_RADIUS);
|
|
2436
|
+
return radius < PLAIN_BOX_RADIUS ? 0 : radius;
|
|
2437
|
+
}
|
|
2438
|
+
function createSilhouetteBuilder(random) {
|
|
2439
|
+
const parts = [];
|
|
2440
|
+
const windows = [];
|
|
2441
|
+
const pick = (list) => list[Math.floor(random() * list.length)];
|
|
2442
|
+
const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
2443
|
+
const box = (x, y, z, width, height, depth, color, o = {}) => {
|
|
2444
|
+
parts.push({ 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) });
|
|
2445
|
+
};
|
|
2446
|
+
const prism = (x, y, z, width, height, depth, color, parent = ROOT) => {
|
|
2447
|
+
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
|
|
2448
|
+
};
|
|
2449
|
+
const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = 14) => {
|
|
2450
|
+
parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
|
|
2451
|
+
};
|
|
2452
|
+
const line = (points, color, parent = ROOT) => {
|
|
2453
|
+
parts.push({ shape: "line", parent, points, color });
|
|
2454
|
+
};
|
|
2455
|
+
const sign = (text, x, y, z, width, height, ry, colors, parent = ROOT) => {
|
|
2456
|
+
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors });
|
|
2457
|
+
};
|
|
2458
|
+
const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
|
|
2459
|
+
const length = Math.hypot(x1 - x0, z1 - z0);
|
|
2460
|
+
const count = Math.ceil(length / 3);
|
|
2461
|
+
for (let index = 0; index <= count; index++) {
|
|
2462
|
+
const t = index / count;
|
|
2463
|
+
cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, parent, 5);
|
|
2464
|
+
}
|
|
2465
|
+
for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
|
|
2466
|
+
};
|
|
2467
|
+
const finish = (footprint) => ({ parts, windows, footprint });
|
|
2468
|
+
return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// src/silhouettes/building.ts
|
|
2472
|
+
var STOREY_HEIGHT = 3;
|
|
2473
|
+
var SHOP_AWNING_HEIGHT = 4.5;
|
|
2474
|
+
var WINDOW_COLUMN_WIDTH = 2.6;
|
|
2475
|
+
var SHOP_SIGNS = ["FARM\xC1CIA", "MAGAZINE", "LOT\xC9RICA", "PADARIA", "\xD3TICA", "CAL\xC7ADOS", "BAR"];
|
|
2476
|
+
var SIGN_COLORS = ["#ffd27a", "#e8dfcf", "#c4483b"];
|
|
2477
|
+
var DEFAULT_LIT_CHANCE = 0.07;
|
|
2478
|
+
function createCityBuilding(p) {
|
|
2479
|
+
const random = p.random ?? createSilhouetteRandom(p.seed ?? 7);
|
|
2480
|
+
const b = createSilhouetteBuilder(random);
|
|
2481
|
+
const { x, z, width: w, depth: d, height: h } = p;
|
|
2482
|
+
b.box(x, 0, z, w, h, d, p.color);
|
|
2483
|
+
b.box(x, h, z, w + 0.3, 0.35, d + 0.3, 2763308);
|
|
2484
|
+
if (random() < 0.4) b.cylinder(x + (random() - 0.5) * (w - 3), h + 0.35, z + (random() - 0.5) * (d - 3), 0.8, 0.8, 1.4, 4932928);
|
|
2485
|
+
const hasShop = p.shopChance !== void 0 && p.shopChance > 0 && random() < p.shopChance;
|
|
2486
|
+
if (hasShop) {
|
|
2487
|
+
for (const face of [1, -1]) {
|
|
2488
|
+
const zFace = z + face * (d / 2);
|
|
2489
|
+
b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
|
|
2490
|
+
b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
|
|
2491
|
+
const text = b.pick(SHOP_SIGNS);
|
|
2492
|
+
b.sign(text, x, 5.3, zFace + face * 0.12, Math.min(w * 0.7, 8), 1, face > 0 ? 0 : Math.PI, { background: "#1a1a1c", foreground: b.pick(SIGN_COLORS) });
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
|
|
2496
|
+
return b.finish({ x, z, width: w, depth: d, height: h + 0.35 });
|
|
2497
|
+
}
|
|
2498
|
+
function addWindows(b, p, base) {
|
|
2499
|
+
const { x, z, width: w, depth: d, height: h } = p;
|
|
2500
|
+
const floors = Math.max(1, Math.floor((h - 1) / STOREY_HEIGHT));
|
|
2501
|
+
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]];
|
|
2502
|
+
for (const [ox, oz, faceWidth, ry] of faces) {
|
|
2503
|
+
const columns = Math.max(1, Math.floor(faceWidth / WINDOW_COLUMN_WIDTH));
|
|
2504
|
+
for (let floor = 0; floor < floors; floor++) {
|
|
2505
|
+
const y = base + 1.8 + floor * STOREY_HEIGHT;
|
|
2506
|
+
for (let column = 0; column < columns; column++) {
|
|
2507
|
+
const u = (column - (columns - 1) / 2) * WINDOW_COLUMN_WIDTH;
|
|
2508
|
+
b.windows.push({ x: ox ? x + ox : x + u, y, z: ox ? z + u : z + oz, ry, width: 1.2, height: 1.4, isLit: b.random() < (p.litChance || DEFAULT_LIT_CHANCE) });
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/silhouettes/houses.ts
|
|
2515
|
+
var LIT_WINDOW = 16757854;
|
|
2516
|
+
var DARK_WINDOW = 1053979;
|
|
2517
|
+
var DEFAULT_ROOF = 5913130;
|
|
2518
|
+
var randomOf = (o) => o.random ?? createSilhouetteRandom(o.seed ?? 7);
|
|
2519
|
+
var pane = (isLit) => ({ color: isLit ? LIT_WINDOW : DARK_WINDOW, emissive: isLit });
|
|
2520
|
+
function createTwoStoreyHouse(p) {
|
|
2521
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2522
|
+
const { width: w, depth: d } = p;
|
|
2523
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2524
|
+
b.box(0, 0, 0, w, 6, d, p.color, { parent: g });
|
|
2525
|
+
b.prism(0, 6, 0, w + 0.4, 1.4, d + 0.4, p.roofColor ?? DEFAULT_ROOF, g);
|
|
2526
|
+
b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
|
|
2527
|
+
for (const floor of [0, 1]) {
|
|
2528
|
+
for (const side of [-1, 1]) {
|
|
2529
|
+
const { color, emissive } = pane(b.random() < 0.12);
|
|
2530
|
+
b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color, { parent: g, emissive });
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
2534
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: 7.4 });
|
|
2535
|
+
}
|
|
2536
|
+
function createSlabHouse(p) {
|
|
2537
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2538
|
+
const { width: w, depth: d, floors } = p;
|
|
2539
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2540
|
+
for (let floor = 0; floor < floors; floor++) {
|
|
2541
|
+
const isTop = floor === floors - 1;
|
|
2542
|
+
b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
|
|
2543
|
+
const { color, emissive } = pane(b.random() < 0.1);
|
|
2544
|
+
b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color, { parent: g, emissive });
|
|
2545
|
+
}
|
|
2546
|
+
b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
|
|
2547
|
+
for (let level = 0; level < floors; level++) b.box(w / 2 + 0.4, level * 2.9, 0, 0.8, 0.18, 1.2, 8026740, { parent: g });
|
|
2548
|
+
if (b.random() < 0.6) for (let index = 0; index < 4; index++) b.box(-w * 0.3 + index * 0.4, floors * 2.9, 0, 0.02, 1, 0.02, 9079434, { parent: g });
|
|
2549
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: floors * 2.9 + 1 });
|
|
2550
|
+
}
|
|
2551
|
+
function createColonialHouse(p) {
|
|
2552
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2553
|
+
const { width: w } = p;
|
|
2554
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2555
|
+
b.box(0, 0, 0, w, 4.2, 10, p.color, { parent: g });
|
|
2556
|
+
b.box(0, 4.2, 4.6, w, 0.8, 0.25, p.color, { parent: g });
|
|
2557
|
+
b.prism(0, 4.2, -0.4, w, 1.4, 9.6, 8014386, g);
|
|
2558
|
+
const count = Math.max(1, Math.floor(w / 2.2));
|
|
2559
|
+
for (let index = 0; index < count; index++) {
|
|
2560
|
+
const u = (index - (count - 1) / 2) * 2.2;
|
|
2561
|
+
const isDoor = index === 0 && p.hasDoor;
|
|
2562
|
+
const lit = isDoor ? false : b.random() < 0.15;
|
|
2563
|
+
const color = isDoor ? 3810326 : lit ? LIT_WINDOW : 2767434;
|
|
2564
|
+
b.box(u, 1, 5.02, 0.9, 2, 0.05, color, { parent: g, emissive: lit });
|
|
2565
|
+
}
|
|
2566
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
|
|
2567
|
+
}
|
|
2568
|
+
function createChalet(p) {
|
|
2569
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2570
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2571
|
+
b.box(0, 0, 0, 7, 0.8, 9, 5920336, { parent: g });
|
|
2572
|
+
b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
|
|
2573
|
+
b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
|
|
2574
|
+
b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
|
|
2575
|
+
const { color, emissive } = pane(b.random() < 0.3);
|
|
2576
|
+
b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color, { parent: g, emissive });
|
|
2577
|
+
b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW, { parent: g });
|
|
2578
|
+
return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
|
|
2579
|
+
}
|
|
2580
|
+
function createCompanyHouse(p) {
|
|
2581
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2582
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2583
|
+
b.box(0, 0, 0, 6, 3, 9, p.color ?? 14209216, { parent: g });
|
|
2584
|
+
b.prism(0, 3, 0, 6.4, 1.6, 9.4, 9062962, g);
|
|
2585
|
+
b.box(0, 0, 4.52, 0.9, 2.1, 0.05, 3811868, { parent: g });
|
|
2586
|
+
b.box(1.8, 1.1, 4.52, 1, 1, 0.04, DARK_WINDOW, { parent: g });
|
|
2587
|
+
b.fence(-3.2, 6.5, 3.2, 6.5, 0.9, 14735560, g);
|
|
2588
|
+
return b.finish({ x: p.x, z: p.z, width: 6.4, depth: 13, height: 4.6 });
|
|
2589
|
+
}
|
|
2590
|
+
function createStiltHouse(p) {
|
|
2591
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2592
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2593
|
+
for (const a of [-2, 2]) for (const c of [-3, 3]) b.cylinder(a, -1.5, c, 0.12, 0.12, 3.1, 3811868, g, 6);
|
|
2594
|
+
b.box(0, 1.5, 0, 5, 0.2, 7, 5916208, { parent: g });
|
|
2595
|
+
b.box(0, 1.7, 0, 4.6, 2.6, 6.4, p.color ?? 6969926, { parent: g });
|
|
2596
|
+
b.prism(0, 4.3, 0, 5.2, 1.1, 7, 8026744, g);
|
|
2597
|
+
b.box(0, 1.5, 5, 1.2, 0.15, 4, 5916208, { parent: g });
|
|
2598
|
+
return b.finish({ x: p.x, z: p.z, width: 5.2, depth: 9, height: 5.4 });
|
|
2599
|
+
}
|
|
2600
|
+
function createFarmhouse(p) {
|
|
2601
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2602
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2603
|
+
b.box(0, 0, 0, 15, 3.2, 11, 14208952, { parent: g });
|
|
2604
|
+
b.prism(0, 3.2, 0, 17, 2.2, 13, 9062962, g);
|
|
2605
|
+
b.box(0, 0, 6.8, 15, 0.25, 2.6, 6969926, { parent: g });
|
|
2606
|
+
for (let index = 0; index < 6; index++) b.cylinder(-7 + index * 2.8, 0, 7.8, 0.1, 0.1, 3.2, 5916208, g, 6);
|
|
2607
|
+
b.box(-3, 1, 5.52, 1.2, 1.2, 0.04, LIT_WINDOW, { parent: g, emissive: true });
|
|
2608
|
+
return b.finish({ x: p.x, z: p.z, width: 17, depth: 13, height: 5.4 });
|
|
2609
|
+
}
|
|
2610
|
+
function createDetachedHouse(p) {
|
|
2611
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2612
|
+
const { width: w, depth: d } = p;
|
|
2613
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2614
|
+
b.box(0, 0, 0, w, 3, d, p.color, { parent: g });
|
|
2615
|
+
b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
|
|
2616
|
+
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
2617
|
+
for (const side of [-1, 1]) {
|
|
2618
|
+
const { color, emissive } = pane(b.random() < 0.15);
|
|
2619
|
+
b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color, { parent: g, emissive });
|
|
2620
|
+
if (p.isBoarded || b.random() < 0.3) for (const k of [-1, 1]) b.box(side * w * 0.28, 1.5 + k * 0.25, d / 2 + 0.08, 1.3, 0.14, 0.04, 7031338, { parent: g, rz: k * 0.15 });
|
|
2621
|
+
}
|
|
2622
|
+
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// src/index.ts
|
|
2626
|
+
registerGroundItems();
|
|
2627
|
+
registerPermeableGround("lawn", "dirt");
|
|
2628
|
+
registerLocale("en", en_default);
|
|
2629
|
+
registerLocale("pt-BR", pt_BR_default);
|
|
2630
|
+
var version = "0.1.0";
|
|
2631
|
+
var engine = {
|
|
2632
|
+
version,
|
|
2633
|
+
generate,
|
|
2634
|
+
slots,
|
|
2635
|
+
validate,
|
|
2636
|
+
palette,
|
|
2637
|
+
limitsOf,
|
|
2638
|
+
listTemplates,
|
|
2639
|
+
listStates,
|
|
2640
|
+
// content registration
|
|
2641
|
+
registerItem,
|
|
2642
|
+
registerItems,
|
|
2643
|
+
registerLibrary,
|
|
2644
|
+
registerLibraryAlias,
|
|
2645
|
+
registerTemplate,
|
|
2646
|
+
registerStyle,
|
|
2647
|
+
registerPaletteStyle,
|
|
2648
|
+
registerRoomColors,
|
|
2649
|
+
registerDecorProfile,
|
|
2650
|
+
registerPass,
|
|
2651
|
+
registerGroundOverlay,
|
|
2652
|
+
registerWallTop,
|
|
2653
|
+
registerSeedKeys,
|
|
2654
|
+
registerState,
|
|
2655
|
+
setStateDistribution,
|
|
2656
|
+
registerLocale,
|
|
2657
|
+
registerResultExtension,
|
|
2658
|
+
registerFindsProvider,
|
|
2659
|
+
findsFor,
|
|
2660
|
+
findsAnswer,
|
|
2661
|
+
registerViewerLayer,
|
|
2662
|
+
viewerLayers: VIEWER_LAYERS,
|
|
2663
|
+
blocksWindow,
|
|
2664
|
+
// zoning
|
|
2665
|
+
defineZone,
|
|
2666
|
+
getZone,
|
|
2667
|
+
listZones,
|
|
2668
|
+
checkLot,
|
|
2669
|
+
registerPermeableGround,
|
|
2670
|
+
// kits and data
|
|
2671
|
+
slotKit,
|
|
2672
|
+
translate,
|
|
2673
|
+
listLocales,
|
|
2674
|
+
templates: TEMPLATES,
|
|
2675
|
+
catalog: CATALOG,
|
|
2676
|
+
libraries: LIBRARIES,
|
|
2677
|
+
styles: STYLES,
|
|
2678
|
+
paletteStyles: PALETTE_STYLES,
|
|
2679
|
+
createRng,
|
|
2680
|
+
mulberry32,
|
|
2681
|
+
hashString,
|
|
2682
|
+
overlaps,
|
|
2683
|
+
windowZone,
|
|
2684
|
+
openingZones,
|
|
2685
|
+
color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
|
|
2686
|
+
constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
|
|
2687
|
+
};
|
|
2688
|
+
export {
|
|
2689
|
+
blocksWindow,
|
|
2690
|
+
checkLot,
|
|
2691
|
+
createChalet,
|
|
2692
|
+
createCityBuilding,
|
|
2693
|
+
createColonialHouse,
|
|
2694
|
+
createCompanyHouse,
|
|
2695
|
+
createDetachedHouse,
|
|
2696
|
+
createFarmhouse,
|
|
2697
|
+
createRng,
|
|
2698
|
+
createSilhouetteRandom,
|
|
2699
|
+
createSlabHouse,
|
|
2700
|
+
createStiltHouse,
|
|
2701
|
+
createTwoStoreyHouse,
|
|
2702
|
+
defineZone,
|
|
2703
|
+
engine,
|
|
2704
|
+
findsAnswer,
|
|
2705
|
+
findsFor,
|
|
2706
|
+
generate,
|
|
2707
|
+
getZone,
|
|
2708
|
+
hashString,
|
|
2709
|
+
limitsOf,
|
|
2710
|
+
listLocales,
|
|
2711
|
+
listStates,
|
|
2712
|
+
listTemplates,
|
|
2713
|
+
listZones,
|
|
2714
|
+
mulberry32,
|
|
2715
|
+
openingZones,
|
|
2716
|
+
overlaps,
|
|
2717
|
+
palette,
|
|
2718
|
+
registerDecorProfile,
|
|
2719
|
+
registerFindsProvider,
|
|
2720
|
+
registerGroundOverlay,
|
|
2721
|
+
registerItem,
|
|
2722
|
+
registerItems,
|
|
2723
|
+
registerLibrary,
|
|
2724
|
+
registerLibraryAlias,
|
|
2725
|
+
registerLocale,
|
|
2726
|
+
registerPaletteStyle,
|
|
2727
|
+
registerPass,
|
|
2728
|
+
registerPermeableGround,
|
|
2729
|
+
registerResultExtension,
|
|
2730
|
+
registerRoomColors,
|
|
2731
|
+
registerSeedKeys,
|
|
2732
|
+
registerState,
|
|
2733
|
+
registerStyle,
|
|
2734
|
+
registerTemplate,
|
|
2735
|
+
registerViewerLayer,
|
|
2736
|
+
registerWallTop,
|
|
2737
|
+
setStateDistribution,
|
|
2738
|
+
slotKit,
|
|
2739
|
+
slots,
|
|
2740
|
+
translate,
|
|
2741
|
+
validate,
|
|
2742
|
+
version,
|
|
2743
|
+
windowZone
|
|
2744
|
+
};
|
|
2745
|
+
//# sourceMappingURL=index.js.map
|