@adatechnology/game-floorplan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +32 -0
- package/dist/index.d.ts +446 -62
- package/dist/index.global.js +847 -214
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +847 -214
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.global.js
CHANGED
|
@@ -21,10 +21,23 @@ var GameFloorplan = (() => {
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
blocksWindow: () => blocksWindow,
|
|
24
25
|
checkLot: () => checkLot,
|
|
26
|
+
createChalet: () => createChalet,
|
|
27
|
+
createCityBuilding: () => createCityBuilding,
|
|
28
|
+
createColonialHouse: () => createColonialHouse,
|
|
29
|
+
createCompanyHouse: () => createCompanyHouse,
|
|
30
|
+
createDetachedHouse: () => createDetachedHouse,
|
|
31
|
+
createFarmhouse: () => createFarmhouse,
|
|
25
32
|
createRng: () => createRng,
|
|
33
|
+
createSilhouetteRandom: () => createSilhouetteRandom,
|
|
34
|
+
createSlabHouse: () => createSlabHouse,
|
|
35
|
+
createStiltHouse: () => createStiltHouse,
|
|
36
|
+
createTwoStoreyHouse: () => createTwoStoreyHouse,
|
|
26
37
|
defineZone: () => defineZone,
|
|
27
38
|
engine: () => engine,
|
|
39
|
+
findsAnswer: () => findsAnswer,
|
|
40
|
+
findsFor: () => findsFor,
|
|
28
41
|
generate: () => generate,
|
|
29
42
|
getZone: () => getZone,
|
|
30
43
|
hashString: () => hashString,
|
|
@@ -38,6 +51,7 @@ var GameFloorplan = (() => {
|
|
|
38
51
|
overlaps: () => overlaps,
|
|
39
52
|
palette: () => palette,
|
|
40
53
|
registerDecorProfile: () => registerDecorProfile,
|
|
54
|
+
registerFindsProvider: () => registerFindsProvider,
|
|
41
55
|
registerGroundOverlay: () => registerGroundOverlay,
|
|
42
56
|
registerItem: () => registerItem,
|
|
43
57
|
registerItems: () => registerItems,
|
|
@@ -47,11 +61,13 @@ var GameFloorplan = (() => {
|
|
|
47
61
|
registerPaletteStyle: () => registerPaletteStyle,
|
|
48
62
|
registerPass: () => registerPass,
|
|
49
63
|
registerPermeableGround: () => registerPermeableGround,
|
|
64
|
+
registerResultExtension: () => registerResultExtension,
|
|
50
65
|
registerRoomColors: () => registerRoomColors,
|
|
51
66
|
registerSeedKeys: () => registerSeedKeys,
|
|
52
67
|
registerState: () => registerState,
|
|
53
68
|
registerStyle: () => registerStyle,
|
|
54
69
|
registerTemplate: () => registerTemplate,
|
|
70
|
+
registerViewerLayer: () => registerViewerLayer,
|
|
55
71
|
registerWallTop: () => registerWallTop,
|
|
56
72
|
setStateDistribution: () => setStateDistribution,
|
|
57
73
|
slotKit: () => slotKit,
|
|
@@ -68,6 +84,227 @@ var GameFloorplan = (() => {
|
|
|
68
84
|
var EPS = 1e-6;
|
|
69
85
|
var TOL = 1e-3;
|
|
70
86
|
var DOOR_CLEARANCE = 0.9;
|
|
87
|
+
var PLAYER_RADIUS = 0.24;
|
|
88
|
+
var DOOR_EDGE_CLEARANCE = 0.12;
|
|
89
|
+
var WINDOW_EDGE_CLEARANCE = 0.3;
|
|
90
|
+
var OVERHEAD_TOP = 2.15;
|
|
91
|
+
var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
|
|
92
|
+
"stockroom",
|
|
93
|
+
"kitchen",
|
|
94
|
+
"laundry",
|
|
95
|
+
"bathroom",
|
|
96
|
+
"void",
|
|
97
|
+
"elevator",
|
|
98
|
+
"stairs",
|
|
99
|
+
"pantry",
|
|
100
|
+
"closet",
|
|
101
|
+
"corridor",
|
|
102
|
+
"hall",
|
|
103
|
+
"balcony",
|
|
104
|
+
"terrace",
|
|
105
|
+
"garage",
|
|
106
|
+
"storage",
|
|
107
|
+
"porch",
|
|
108
|
+
"tent",
|
|
109
|
+
"outhouse"
|
|
110
|
+
]);
|
|
111
|
+
var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
|
|
112
|
+
|
|
113
|
+
// src/registry.ts
|
|
114
|
+
var CATALOG = {};
|
|
115
|
+
var LIBRARIES = {};
|
|
116
|
+
var LIBRARY_ALIAS = {};
|
|
117
|
+
var TEMPLATES = {};
|
|
118
|
+
var STYLES = {};
|
|
119
|
+
var STYLE_ALIAS = {};
|
|
120
|
+
var PALETTE_STYLES = {};
|
|
121
|
+
var ROOM_COLORS = [];
|
|
122
|
+
var PASSES = [];
|
|
123
|
+
var DECOR_PROFILES = {};
|
|
124
|
+
var GROUND_OVERLAYS = [];
|
|
125
|
+
var STATE_PLUGINS = { normal: { apply: () => [] } };
|
|
126
|
+
var WALL_TOP_HEIGHTS = { none: 0 };
|
|
127
|
+
var SEED_KEYS = { rooms: {}, items: {} };
|
|
128
|
+
var stateDistribution = { weights: null, seedKey: "state" };
|
|
129
|
+
function registerItem(def) {
|
|
130
|
+
const h = def.h;
|
|
131
|
+
const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
|
|
132
|
+
CATALOG[def.type] = item;
|
|
133
|
+
return item;
|
|
134
|
+
}
|
|
135
|
+
function registerItems(list) {
|
|
136
|
+
for (const d of list) registerItem(d);
|
|
137
|
+
}
|
|
138
|
+
function registerLibrary(id, library) {
|
|
139
|
+
LIBRARIES[id] = library;
|
|
140
|
+
}
|
|
141
|
+
function registerLibraryAlias(alias, id) {
|
|
142
|
+
LIBRARY_ALIAS[alias] = id;
|
|
143
|
+
}
|
|
144
|
+
function registerTemplate(t) {
|
|
145
|
+
TEMPLATES[t.id] = t;
|
|
146
|
+
return t;
|
|
147
|
+
}
|
|
148
|
+
function registerStyle(style) {
|
|
149
|
+
STYLES[style.id] = style;
|
|
150
|
+
for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
|
|
151
|
+
}
|
|
152
|
+
function registerPaletteStyle(id, def) {
|
|
153
|
+
PALETTE_STYLES[id] = def;
|
|
154
|
+
}
|
|
155
|
+
function registerRoomColors(list) {
|
|
156
|
+
for (const d of list) {
|
|
157
|
+
const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
158
|
+
if (i >= 0) ROOM_COLORS.splice(i, 1);
|
|
159
|
+
ROOM_COLORS.push(d);
|
|
160
|
+
}
|
|
161
|
+
ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
162
|
+
}
|
|
163
|
+
function registerDecorProfile(styleId, profile) {
|
|
164
|
+
DECOR_PROFILES[styleId] = profile;
|
|
165
|
+
}
|
|
166
|
+
function registerPass(name, run) {
|
|
167
|
+
const i = PASSES.findIndex((p) => p.name === name);
|
|
168
|
+
if (i >= 0) PASSES[i] = { name, run };
|
|
169
|
+
else PASSES.push({ name, run });
|
|
170
|
+
}
|
|
171
|
+
function registerGroundOverlay(rule) {
|
|
172
|
+
GROUND_OVERLAYS.push(rule);
|
|
173
|
+
}
|
|
174
|
+
function registerWallTop(id, height) {
|
|
175
|
+
WALL_TOP_HEIGHTS[id] = height;
|
|
176
|
+
}
|
|
177
|
+
function registerSeedKeys(keys) {
|
|
178
|
+
Object.assign(SEED_KEYS.rooms, keys.rooms);
|
|
179
|
+
Object.assign(SEED_KEYS.items, keys.items);
|
|
180
|
+
}
|
|
181
|
+
function registerState(name, plugin) {
|
|
182
|
+
STATE_PLUGINS[name] = plugin;
|
|
183
|
+
}
|
|
184
|
+
function setStateDistribution(weights, seedKey = "state") {
|
|
185
|
+
stateDistribution.weights = weights;
|
|
186
|
+
stateDistribution.seedKey = seedKey;
|
|
187
|
+
}
|
|
188
|
+
var listStates = () => Object.keys(STATE_PLUGINS);
|
|
189
|
+
function resolveStyleId(id) {
|
|
190
|
+
const s = id ?? "";
|
|
191
|
+
return STYLE_ALIAS[s] ?? s;
|
|
192
|
+
}
|
|
193
|
+
function styleOf(id) {
|
|
194
|
+
return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
|
|
195
|
+
}
|
|
196
|
+
var RESULT_EXTENSIONS = [];
|
|
197
|
+
var FINDS_PROVIDERS = [];
|
|
198
|
+
function registerResultExtension(name, run, options = {}) {
|
|
199
|
+
const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
|
|
200
|
+
const i = RESULT_EXTENSIONS.findIndex((x) => x.name === name);
|
|
201
|
+
if (i >= 0) RESULT_EXTENSIONS[i] = entry;
|
|
202
|
+
else RESULT_EXTENSIONS.push(entry);
|
|
203
|
+
}
|
|
204
|
+
function registerFindsProvider(provider) {
|
|
205
|
+
FINDS_PROVIDERS.push(provider);
|
|
206
|
+
}
|
|
207
|
+
var VIEWER_LAYERS = [];
|
|
208
|
+
function registerViewerLayer(layer) {
|
|
209
|
+
const i = VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
|
|
210
|
+
if (i >= 0) VIEWER_LAYERS[i] = layer;
|
|
211
|
+
else VIEWER_LAYERS.push(layer);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/rng.ts
|
|
215
|
+
function mulberry32(a) {
|
|
216
|
+
return function() {
|
|
217
|
+
a |= 0;
|
|
218
|
+
a = a + 1831565813 | 0;
|
|
219
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
220
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
221
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function hashString(input) {
|
|
225
|
+
const s = String(input);
|
|
226
|
+
let h = 1779033703 ^ s.length;
|
|
227
|
+
for (let i = 0; i < s.length; i++) {
|
|
228
|
+
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
229
|
+
h = h << 13 | h >>> 19;
|
|
230
|
+
}
|
|
231
|
+
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
232
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
233
|
+
return (h ^ h >>> 16) >>> 0;
|
|
234
|
+
}
|
|
235
|
+
var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
|
|
236
|
+
function pickWeighted(rng, weights) {
|
|
237
|
+
const keys = Object.keys(weights);
|
|
238
|
+
let total = 0;
|
|
239
|
+
for (const k of keys) total += weights[k];
|
|
240
|
+
let x = rng() * total;
|
|
241
|
+
for (const k of keys) {
|
|
242
|
+
x -= weights[k];
|
|
243
|
+
if (x < 0) return k;
|
|
244
|
+
}
|
|
245
|
+
return keys[keys.length - 1];
|
|
246
|
+
}
|
|
247
|
+
function weightedOrder(candidates, rng) {
|
|
248
|
+
const arr = candidates.slice();
|
|
249
|
+
const out = [];
|
|
250
|
+
while (arr.length) {
|
|
251
|
+
let total = 0;
|
|
252
|
+
for (const c of arr) total += c.weight;
|
|
253
|
+
let x = rng() * total;
|
|
254
|
+
let i = 0;
|
|
255
|
+
for (; i < arr.length - 1; i++) {
|
|
256
|
+
x -= arr[i].weight;
|
|
257
|
+
if (x < 0) break;
|
|
258
|
+
}
|
|
259
|
+
out.push(arr.splice(i, 1)[0]);
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
function shuffle(arr, rng) {
|
|
264
|
+
const a = arr.slice();
|
|
265
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
266
|
+
const j = Math.floor(rng() * (i + 1));
|
|
267
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
268
|
+
}
|
|
269
|
+
return a;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/extensions.ts
|
|
273
|
+
function orderOf(extensions) {
|
|
274
|
+
const ordered = [];
|
|
275
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
276
|
+
const visit = (extension) => {
|
|
277
|
+
if (ordered.includes(extension)) return;
|
|
278
|
+
if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
|
|
279
|
+
visiting.add(extension.name);
|
|
280
|
+
for (const name of extension.after) {
|
|
281
|
+
const before = extensions.find((x) => x.name === name);
|
|
282
|
+
if (before) visit(before);
|
|
283
|
+
}
|
|
284
|
+
visiting.delete(extension.name);
|
|
285
|
+
ordered.push(extension);
|
|
286
|
+
};
|
|
287
|
+
extensions.forEach(visit);
|
|
288
|
+
return ordered;
|
|
289
|
+
}
|
|
290
|
+
function runResultExtensions(plan, result, seed, options) {
|
|
291
|
+
for (const extension of orderOf(RESULT_EXTENSIONS)) {
|
|
292
|
+
result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function findsFor(query) {
|
|
296
|
+
return answerOf(query) ?? [];
|
|
297
|
+
}
|
|
298
|
+
function findsAnswer(query) {
|
|
299
|
+
return answerOf(query);
|
|
300
|
+
}
|
|
301
|
+
function answerOf(query) {
|
|
302
|
+
for (const provider of FINDS_PROVIDERS) {
|
|
303
|
+
const stacks = provider.findsFor(query);
|
|
304
|
+
if (stacks !== null) return stacks;
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
71
308
|
|
|
72
309
|
// src/geometry.ts
|
|
73
310
|
var r2 = (x) => Math.round(x * 100) / 100;
|
|
@@ -170,13 +407,39 @@ var GameFloorplan = (() => {
|
|
|
170
407
|
};
|
|
171
408
|
}
|
|
172
409
|
function usableParts(room, extra) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
410
|
+
const m = MARGIN + (extra || 0);
|
|
411
|
+
const out = [];
|
|
412
|
+
for (const p of room.parts) {
|
|
413
|
+
out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
|
|
414
|
+
for (let k = 0; k < 4; k++) {
|
|
415
|
+
const spans = [];
|
|
416
|
+
for (const q of room.parts) {
|
|
417
|
+
if (q === p) continue;
|
|
418
|
+
if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
419
|
+
else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
420
|
+
else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
421
|
+
else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
422
|
+
}
|
|
423
|
+
spans.sort((x, y) => x[0] - y[0]);
|
|
424
|
+
const merged = [];
|
|
425
|
+
for (const span of spans) {
|
|
426
|
+
const last = merged[merged.length - 1];
|
|
427
|
+
if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
|
|
428
|
+
else merged.push([span[0], span[1]]);
|
|
429
|
+
}
|
|
430
|
+
for (const [a, b] of merged) {
|
|
431
|
+
if (b - a <= 0.3) continue;
|
|
432
|
+
const x0 = a + m;
|
|
433
|
+
const x1 = b - m;
|
|
434
|
+
if (x1 <= x0) continue;
|
|
435
|
+
if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
|
|
436
|
+
else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
|
|
437
|
+
else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
|
|
438
|
+
else out.push([x0, p[3] - m, x1, p[3]]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
180
443
|
}
|
|
181
444
|
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);
|
|
182
445
|
function buildWalls(rooms, doors, openPairs, floorIndex) {
|
|
@@ -231,7 +494,7 @@ var GameFloorplan = (() => {
|
|
|
231
494
|
pieces = n;
|
|
232
495
|
}
|
|
233
496
|
for (const [x, y] of pieces) {
|
|
234
|
-
const isOpen = (t) => t === "balcony" || t === "terrace";
|
|
497
|
+
const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
|
|
235
498
|
const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
|
|
236
499
|
if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
|
|
237
500
|
const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
|
|
@@ -254,6 +517,39 @@ var GameFloorplan = (() => {
|
|
|
254
517
|
return out;
|
|
255
518
|
}
|
|
256
519
|
|
|
520
|
+
// src/parking.ts
|
|
521
|
+
var STEP = 0.05;
|
|
522
|
+
var MAX_CANDIDATES = 80;
|
|
523
|
+
function carCandidates(slot, item, car) {
|
|
524
|
+
const front = [slot.frontU, slot.frontV];
|
|
525
|
+
const zone = slot._cell.R;
|
|
526
|
+
const alongV = Math.abs(front[1]) === 1;
|
|
527
|
+
const [extentU, extentV] = extent([item.w, item.d], front);
|
|
528
|
+
const across = alongV ? extentU : extentV;
|
|
529
|
+
const along = alongV ? extentV : extentU;
|
|
530
|
+
const a = alongV ? 0 : 1;
|
|
531
|
+
const b = alongV ? 1 : 0;
|
|
532
|
+
const driverSign = alongV ? front[1] : -front[0];
|
|
533
|
+
const frontSign = alongV ? front[1] : front[0];
|
|
534
|
+
const lowClear = driverSign < 0 ? car.driver : car.side;
|
|
535
|
+
const highClear = driverSign < 0 ? car.side : car.driver;
|
|
536
|
+
const lo = zone[a] + lowClear;
|
|
537
|
+
const hi = zone[a + 2] - highClear - across;
|
|
538
|
+
const alongLo = zone[b] + car.end;
|
|
539
|
+
const alongHi = zone[b + 2] - car.end - along;
|
|
540
|
+
if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
|
|
541
|
+
const preferred = (alongV ? slot.x : slot.z) - across / 2;
|
|
542
|
+
const alongStart = frontSign < 0 ? alongHi : alongLo;
|
|
543
|
+
const positions = [];
|
|
544
|
+
for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
|
|
545
|
+
positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
|
|
546
|
+
return positions.slice(0, MAX_CANDIDATES).map((p) => {
|
|
547
|
+
const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
|
|
548
|
+
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];
|
|
549
|
+
return { rect, clear };
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
257
553
|
// src/queries.ts
|
|
258
554
|
function zonesOfFloor(plan, floorIndex) {
|
|
259
555
|
const out = [];
|
|
@@ -263,6 +559,7 @@ var GameFloorplan = (() => {
|
|
|
263
559
|
for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
|
|
264
560
|
for (const g of plan.gates) out.push(...g.zones);
|
|
265
561
|
for (const c of plan.paths) out.push(...c.rects);
|
|
562
|
+
out.push(...plan.reservations);
|
|
266
563
|
}
|
|
267
564
|
return out;
|
|
268
565
|
}
|
|
@@ -284,7 +581,10 @@ var GameFloorplan = (() => {
|
|
|
284
581
|
if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
|
|
285
582
|
return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
|
|
286
583
|
}
|
|
287
|
-
|
|
584
|
+
function blocksWindow(it, win, overheadOff) {
|
|
585
|
+
const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
|
|
586
|
+
return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
|
|
587
|
+
}
|
|
288
588
|
function windowsOfRoom(plan, room) {
|
|
289
589
|
if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
|
|
290
590
|
return plan.windows.filter(
|
|
@@ -309,8 +609,22 @@ var GameFloorplan = (() => {
|
|
|
309
609
|
}
|
|
310
610
|
|
|
311
611
|
// src/grid.ts
|
|
312
|
-
function
|
|
313
|
-
const
|
|
612
|
+
function wallBodies(plan, floor) {
|
|
613
|
+
const cache = plan.wallBodies = plan.wallBodies || {};
|
|
614
|
+
if (cache[floor]) return cache[floor];
|
|
615
|
+
const doors = plan.doors.filter((p) => p.floor === floor);
|
|
616
|
+
return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
|
|
617
|
+
const a0 = p.axis === "u" ? p.u0 : p.v0;
|
|
618
|
+
const a1 = p.axis === "u" ? p.u1 : p.v1;
|
|
619
|
+
const pos = p.axis === "u" ? p.v0 : p.u0;
|
|
620
|
+
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)));
|
|
621
|
+
const e0 = atDoor(a0) ? 0 : MARGIN;
|
|
622
|
+
const e1 = atDoor(a1) ? 0 : MARGIN;
|
|
623
|
+
return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function createGrid(plan, room, ignored, radius = plan.playerRadius) {
|
|
627
|
+
const gs = room.outdoor ? 0.3 : 0.1;
|
|
314
628
|
let u0 = 1e9;
|
|
315
629
|
let v0 = 1e9;
|
|
316
630
|
let u1 = -1e9;
|
|
@@ -324,12 +638,18 @@ var GameFloorplan = (() => {
|
|
|
324
638
|
const nx = Math.ceil((u1 - u0) / gs);
|
|
325
639
|
const nz = Math.ceil((v1 - v0) / gs);
|
|
326
640
|
const insideCells = new Uint8Array(nx * nz);
|
|
327
|
-
const insets = usableParts(room, 0.08);
|
|
641
|
+
const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
|
|
642
|
+
const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
|
|
643
|
+
const tooClose = (u, v) => bodies.some((r) => {
|
|
644
|
+
const dx = Math.max(r[0] - u, 0, u - r[2]);
|
|
645
|
+
const dz = Math.max(r[1] - v, 0, v - r[3]);
|
|
646
|
+
return dx * dx + dz * dz < radius * radius - 1e-9;
|
|
647
|
+
});
|
|
328
648
|
for (let j = 0; j < nz; j++)
|
|
329
649
|
for (let i = 0; i < nx; i++) {
|
|
330
650
|
const u = u0 + (i + 0.5) * gs;
|
|
331
651
|
const v = v0 + (j + 0.5) * gs;
|
|
332
|
-
if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3])) insideCells[j * nx + i] = 1;
|
|
652
|
+
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;
|
|
333
653
|
}
|
|
334
654
|
const entries = [];
|
|
335
655
|
for (const ab of openingsOfFloor(plan, room.floor))
|
|
@@ -340,6 +660,22 @@ var GameFloorplan = (() => {
|
|
|
340
660
|
const j = Math.floor((pt[1] - v0) / gs);
|
|
341
661
|
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
342
662
|
}
|
|
663
|
+
if (!room.outdoor)
|
|
664
|
+
for (const pair of plan.openPairs[room.floor] || []) {
|
|
665
|
+
const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
|
|
666
|
+
const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
|
|
667
|
+
if (!other) continue;
|
|
668
|
+
const edge = sharedEdge(room, other, null);
|
|
669
|
+
if (!edge || edge.a1 - edge.a0 > 2) continue;
|
|
670
|
+
const mid = (edge.a0 + edge.a1) / 2;
|
|
671
|
+
for (const offset of [-0.4, 0.4]) {
|
|
672
|
+
const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
|
|
673
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
674
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
675
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
676
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
343
679
|
const R = inner(room.parts[0]);
|
|
344
680
|
return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
|
|
345
681
|
}
|
|
@@ -399,154 +735,12 @@ var GameFloorplan = (() => {
|
|
|
399
735
|
return true;
|
|
400
736
|
}
|
|
401
737
|
|
|
402
|
-
// src/registry.ts
|
|
403
|
-
var CATALOG = {};
|
|
404
|
-
var LIBRARIES = {};
|
|
405
|
-
var LIBRARY_ALIAS = {};
|
|
406
|
-
var TEMPLATES = {};
|
|
407
|
-
var STYLES = {};
|
|
408
|
-
var STYLE_ALIAS = {};
|
|
409
|
-
var PALETTE_STYLES = {};
|
|
410
|
-
var ROOM_COLORS = [];
|
|
411
|
-
var PASSES = [];
|
|
412
|
-
var DECOR_PROFILES = {};
|
|
413
|
-
var GROUND_OVERLAYS = [];
|
|
414
|
-
var STATE_PLUGINS = { normal: { apply: () => [] } };
|
|
415
|
-
var WALL_TOP_HEIGHTS = { none: 0 };
|
|
416
|
-
var SEED_KEYS = { rooms: {}, items: {} };
|
|
417
|
-
var stateDistribution = { weights: null, seedKey: "state" };
|
|
418
|
-
function registerItem(def) {
|
|
419
|
-
const h = def.h;
|
|
420
|
-
const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
|
|
421
|
-
CATALOG[def.type] = item;
|
|
422
|
-
return item;
|
|
423
|
-
}
|
|
424
|
-
function registerItems(list) {
|
|
425
|
-
for (const d of list) registerItem(d);
|
|
426
|
-
}
|
|
427
|
-
function registerLibrary(id, library) {
|
|
428
|
-
LIBRARIES[id] = library;
|
|
429
|
-
}
|
|
430
|
-
function registerLibraryAlias(alias, id) {
|
|
431
|
-
LIBRARY_ALIAS[alias] = id;
|
|
432
|
-
}
|
|
433
|
-
function registerTemplate(t) {
|
|
434
|
-
TEMPLATES[t.id] = t;
|
|
435
|
-
return t;
|
|
436
|
-
}
|
|
437
|
-
function registerStyle(style) {
|
|
438
|
-
STYLES[style.id] = style;
|
|
439
|
-
for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
|
|
440
|
-
}
|
|
441
|
-
function registerPaletteStyle(id, def) {
|
|
442
|
-
PALETTE_STYLES[id] = def;
|
|
443
|
-
}
|
|
444
|
-
function registerRoomColors(list) {
|
|
445
|
-
for (const d of list) {
|
|
446
|
-
const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
447
|
-
if (i >= 0) ROOM_COLORS.splice(i, 1);
|
|
448
|
-
ROOM_COLORS.push(d);
|
|
449
|
-
}
|
|
450
|
-
ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
451
|
-
}
|
|
452
|
-
function registerDecorProfile(styleId, profile) {
|
|
453
|
-
DECOR_PROFILES[styleId] = profile;
|
|
454
|
-
}
|
|
455
|
-
function registerPass(name, run) {
|
|
456
|
-
const i = PASSES.findIndex((p) => p.name === name);
|
|
457
|
-
if (i >= 0) PASSES[i] = { name, run };
|
|
458
|
-
else PASSES.push({ name, run });
|
|
459
|
-
}
|
|
460
|
-
function registerGroundOverlay(rule) {
|
|
461
|
-
GROUND_OVERLAYS.push(rule);
|
|
462
|
-
}
|
|
463
|
-
function registerWallTop(id, height) {
|
|
464
|
-
WALL_TOP_HEIGHTS[id] = height;
|
|
465
|
-
}
|
|
466
|
-
function registerSeedKeys(keys) {
|
|
467
|
-
Object.assign(SEED_KEYS.rooms, keys.rooms);
|
|
468
|
-
Object.assign(SEED_KEYS.items, keys.items);
|
|
469
|
-
}
|
|
470
|
-
function registerState(name, plugin) {
|
|
471
|
-
STATE_PLUGINS[name] = plugin;
|
|
472
|
-
}
|
|
473
|
-
function setStateDistribution(weights, seedKey = "state") {
|
|
474
|
-
stateDistribution.weights = weights;
|
|
475
|
-
stateDistribution.seedKey = seedKey;
|
|
476
|
-
}
|
|
477
|
-
var listStates = () => Object.keys(STATE_PLUGINS);
|
|
478
|
-
function resolveStyleId(id) {
|
|
479
|
-
const s = id ?? "";
|
|
480
|
-
return STYLE_ALIAS[s] ?? s;
|
|
481
|
-
}
|
|
482
|
-
function styleOf(id) {
|
|
483
|
-
return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// src/rng.ts
|
|
487
|
-
function mulberry32(a) {
|
|
488
|
-
return function() {
|
|
489
|
-
a |= 0;
|
|
490
|
-
a = a + 1831565813 | 0;
|
|
491
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
492
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
493
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
function hashString(input) {
|
|
497
|
-
const s = String(input);
|
|
498
|
-
let h = 1779033703 ^ s.length;
|
|
499
|
-
for (let i = 0; i < s.length; i++) {
|
|
500
|
-
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
501
|
-
h = h << 13 | h >>> 19;
|
|
502
|
-
}
|
|
503
|
-
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
504
|
-
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
505
|
-
return (h ^ h >>> 16) >>> 0;
|
|
506
|
-
}
|
|
507
|
-
var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
|
|
508
|
-
function pickWeighted(rng, weights) {
|
|
509
|
-
const keys = Object.keys(weights);
|
|
510
|
-
let total = 0;
|
|
511
|
-
for (const k of keys) total += weights[k];
|
|
512
|
-
let x = rng() * total;
|
|
513
|
-
for (const k of keys) {
|
|
514
|
-
x -= weights[k];
|
|
515
|
-
if (x < 0) return k;
|
|
516
|
-
}
|
|
517
|
-
return keys[keys.length - 1];
|
|
518
|
-
}
|
|
519
|
-
function weightedOrder(candidates, rng) {
|
|
520
|
-
const arr = candidates.slice();
|
|
521
|
-
const out = [];
|
|
522
|
-
while (arr.length) {
|
|
523
|
-
let total = 0;
|
|
524
|
-
for (const c of arr) total += c.weight;
|
|
525
|
-
let x = rng() * total;
|
|
526
|
-
let i = 0;
|
|
527
|
-
for (; i < arr.length - 1; i++) {
|
|
528
|
-
x -= arr[i].weight;
|
|
529
|
-
if (x < 0) break;
|
|
530
|
-
}
|
|
531
|
-
out.push(arr.splice(i, 1)[0]);
|
|
532
|
-
}
|
|
533
|
-
return out;
|
|
534
|
-
}
|
|
535
|
-
function shuffle(arr, rng) {
|
|
536
|
-
const a = arr.slice();
|
|
537
|
-
for (let i = a.length - 1; i > 0; i--) {
|
|
538
|
-
const j = Math.floor(rng() * (i + 1));
|
|
539
|
-
[a[i], a[j]] = [a[j], a[i]];
|
|
540
|
-
}
|
|
541
|
-
return a;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
738
|
// src/fill.ts
|
|
545
739
|
function candidatesOf(cats) {
|
|
546
740
|
const out = [];
|
|
547
741
|
for (const [cat, weight] of Object.entries(cats)) {
|
|
548
742
|
const exact = cat[0] === "=";
|
|
549
|
-
const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : i.category === cat || i.type === cat));
|
|
743
|
+
const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
|
|
550
744
|
for (const it of its) out.push({ it, weight: weight / its.length });
|
|
551
745
|
}
|
|
552
746
|
return out;
|
|
@@ -570,6 +764,7 @@ var GameFloorplan = (() => {
|
|
|
570
764
|
}
|
|
571
765
|
return [u0, v0, u0 + extentU, v0 + extentV];
|
|
572
766
|
}
|
|
767
|
+
var RELAXED_RADIUS = 0.12;
|
|
573
768
|
var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
|
|
574
769
|
function rotationFor(f, front) {
|
|
575
770
|
const meshFront = MESH_FRONT[front] || [0, 1];
|
|
@@ -587,9 +782,10 @@ var GameFloorplan = (() => {
|
|
|
587
782
|
const rims = [];
|
|
588
783
|
const inners = usableParts(room, 0);
|
|
589
784
|
const grid = createGrid(plan, room);
|
|
785
|
+
const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
|
|
590
786
|
const zones = zonesOfFloor(plan, room.floor);
|
|
591
787
|
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
592
|
-
const inflation = room.outdoor ? 0.15 :
|
|
788
|
+
const inflation = room.outdoor ? 0.15 : plan.playerRadius;
|
|
593
789
|
const units = [];
|
|
594
790
|
const seen = {};
|
|
595
791
|
for (const s of slots2) {
|
|
@@ -608,7 +804,7 @@ var GameFloorplan = (() => {
|
|
|
608
804
|
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)));
|
|
609
805
|
const done = /* @__PURE__ */ new Set();
|
|
610
806
|
const ready = (u) => dependencies(u).every((id) => done.has(id));
|
|
611
|
-
const make = (s, it, r, parent) => {
|
|
807
|
+
const make = (s, it, r, parent, overheadOff) => {
|
|
612
808
|
const f = [s.frontU, s.frontV];
|
|
613
809
|
const id = room.id + "." + s.id;
|
|
614
810
|
const m = {
|
|
@@ -637,13 +833,18 @@ var GameFloorplan = (() => {
|
|
|
637
833
|
_r: r,
|
|
638
834
|
_seedId: SEED_KEYS.items[id] ?? id
|
|
639
835
|
};
|
|
836
|
+
if (it.overhead) m.overhead = !overheadOff;
|
|
837
|
+
if (it.category === "car") {
|
|
838
|
+
m.driver = [f[1], -f[0]];
|
|
839
|
+
m.carClearance = plan.carClearance;
|
|
840
|
+
}
|
|
640
841
|
if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
|
|
641
842
|
if (room.link) m.link = { from: room.link[0], to: room.link[1] };
|
|
642
843
|
state[s.id] = m;
|
|
643
844
|
items.push(m);
|
|
644
845
|
return m;
|
|
645
846
|
};
|
|
646
|
-
const tryFit = (s, relax) => {
|
|
847
|
+
const tryFit = (s, relax, overheadOff) => {
|
|
647
848
|
const cands = weightedOrder(candidatesOf(s.cats), rng);
|
|
648
849
|
if (s.anchor === "on" || s.anchor === "inside") {
|
|
649
850
|
const parent = state[s.on];
|
|
@@ -666,19 +867,22 @@ var GameFloorplan = (() => {
|
|
|
666
867
|
const shifts = [0];
|
|
667
868
|
for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
|
|
668
869
|
let found = null;
|
|
669
|
-
|
|
670
|
-
|
|
870
|
+
const car = it.category === "car" && !relax;
|
|
871
|
+
const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
|
|
872
|
+
for (const attempt of tries) {
|
|
873
|
+
const r3 = attempt.rect;
|
|
671
874
|
if (!insideUnion(r3, inners)) continue;
|
|
875
|
+
if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
|
|
672
876
|
if (it.flat) {
|
|
673
877
|
found = { rect: r3 };
|
|
674
878
|
break;
|
|
675
879
|
}
|
|
676
880
|
if (zones.some((z) => overlaps(r3, z))) continue;
|
|
677
|
-
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r3, z))) continue;
|
|
881
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
|
|
678
882
|
if (solids.some((o) => overlaps(r3, o))) continue;
|
|
679
883
|
if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
|
|
680
|
-
let cl2 = null;
|
|
681
|
-
if (!relax && s.clearance > 0 && !it.low) {
|
|
884
|
+
let cl2 = car ? attempt.clear : null;
|
|
885
|
+
if (!car && !relax && s.clearance > 0 && !it.low) {
|
|
682
886
|
cl2 = clearRect(r3, f, s.clearance);
|
|
683
887
|
if (!insideUnion(cl2, inners)) continue;
|
|
684
888
|
if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
|
|
@@ -689,14 +893,14 @@ var GameFloorplan = (() => {
|
|
|
689
893
|
rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
|
|
690
894
|
if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
|
|
691
895
|
}
|
|
692
|
-
if (!reachable(grid, solids.concat([r3]), inflation)) continue;
|
|
896
|
+
if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
|
|
693
897
|
found = { rect: r3, cl: cl2, rimRect: rimRect2 };
|
|
694
898
|
break;
|
|
695
899
|
}
|
|
696
900
|
if (!found) continue;
|
|
697
901
|
const { rect: r, cl, rimRect } = found;
|
|
698
902
|
if (it.flat) return make(s, it, r, null);
|
|
699
|
-
const m = make(s, it, r, null);
|
|
903
|
+
const m = make(s, it, r, null, overheadOff);
|
|
700
904
|
if (cl) reserves.push(cl);
|
|
701
905
|
if (rimRect) rims.push(rimRect);
|
|
702
906
|
solids.push(r);
|
|
@@ -719,9 +923,16 @@ var GameFloorplan = (() => {
|
|
|
719
923
|
break;
|
|
720
924
|
}
|
|
721
925
|
}
|
|
926
|
+
if (!ok)
|
|
927
|
+
for (const m of applicable) {
|
|
928
|
+
if (tryFit(m, false, true)) {
|
|
929
|
+
ok = true;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
722
933
|
if (!ok && u.required)
|
|
723
934
|
for (const m of applicable) {
|
|
724
|
-
if (tryFit(m, true)) {
|
|
935
|
+
if (tryFit(m, true, true)) {
|
|
725
936
|
ok = true;
|
|
726
937
|
break;
|
|
727
938
|
}
|
|
@@ -770,6 +981,8 @@ var GameFloorplan = (() => {
|
|
|
770
981
|
createGrid,
|
|
771
982
|
clearRect,
|
|
772
983
|
pickWeighted,
|
|
984
|
+
createRng,
|
|
985
|
+
roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
|
|
773
986
|
shuffle,
|
|
774
987
|
facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
|
|
775
988
|
cutIntervals,
|
|
@@ -833,6 +1046,10 @@ var GameFloorplan = (() => {
|
|
|
833
1046
|
const zones = zonesOfFloor(plan, room.floor);
|
|
834
1047
|
const occupied = itemsOfRoom(room);
|
|
835
1048
|
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
1049
|
+
const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
|
|
1050
|
+
const r = m.aabb;
|
|
1051
|
+
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]];
|
|
1052
|
+
});
|
|
836
1053
|
const cand = opts.cands ? opts.cands.slice() : [];
|
|
837
1054
|
if (!opts.cands)
|
|
838
1055
|
for (const p of inners) {
|
|
@@ -847,9 +1064,9 @@ var GameFloorplan = (() => {
|
|
|
847
1064
|
cand.push([x, z, x + extentU, z + extentV]);
|
|
848
1065
|
}
|
|
849
1066
|
}
|
|
850
|
-
for (const r of shuffle(cand, rng2)) {
|
|
1067
|
+
for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
|
|
851
1068
|
if (!insideUnion(r, inners)) continue;
|
|
852
|
-
if (zones.some((z) => overlaps(r, z))) continue;
|
|
1069
|
+
if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
|
|
853
1070
|
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
|
|
854
1071
|
if (solidList.some((o) => overlaps(r, o))) continue;
|
|
855
1072
|
if (opts.flat) {
|
|
@@ -858,7 +1075,7 @@ var GameFloorplan = (() => {
|
|
|
858
1075
|
}
|
|
859
1076
|
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;
|
|
860
1077
|
if (opts.without && opts.without(r)) continue;
|
|
861
|
-
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), 0.16)) continue;
|
|
1078
|
+
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
|
|
862
1079
|
return r;
|
|
863
1080
|
}
|
|
864
1081
|
return null;
|
|
@@ -939,6 +1156,129 @@ var GameFloorplan = (() => {
|
|
|
939
1156
|
return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
|
|
940
1157
|
}
|
|
941
1158
|
|
|
1159
|
+
// src/paths.ts
|
|
1160
|
+
var CLOSE = 1e-6;
|
|
1161
|
+
var rounded = (r) => r.map(r2);
|
|
1162
|
+
var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
|
|
1163
|
+
function addExteriorDoorPaths(plan, c) {
|
|
1164
|
+
const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
|
|
1165
|
+
const social = gates.find((g) => g.type === "pedestrian");
|
|
1166
|
+
const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
|
|
1167
|
+
const parkingZone = zones.find((z) => z.id === "parking");
|
|
1168
|
+
const parking = parkingZone ? parkingZone.parts[0] : null;
|
|
1169
|
+
const midY = setback > 0 ? -setback / 2 : 0;
|
|
1170
|
+
const joinToSocial = (x, width, rects, points) => {
|
|
1171
|
+
if (!socialPath) return;
|
|
1172
|
+
const h = width / 2;
|
|
1173
|
+
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];
|
|
1174
|
+
const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
|
|
1175
|
+
if (Math.abs(targetX - x) > CLOSE) {
|
|
1176
|
+
rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
|
|
1177
|
+
points.push([targetX, midY]);
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
|
|
1181
|
+
if (paths.some((p) => p.to === door.id)) continue;
|
|
1182
|
+
const half = Math.max(1, Math.min(door.width, 2)) / 2;
|
|
1183
|
+
const rects = [];
|
|
1184
|
+
const points = [];
|
|
1185
|
+
const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
|
|
1186
|
+
let fromId = social ? social.id : null;
|
|
1187
|
+
let width = 1;
|
|
1188
|
+
const id = isTarget ? "entrance_path" : "path_" + door.id;
|
|
1189
|
+
if (door.side === "front") {
|
|
1190
|
+
if (setback > 0) {
|
|
1191
|
+
const stub = Math.min(1, setback);
|
|
1192
|
+
rects.push([door.u - half, -stub, door.u + half, 0]);
|
|
1193
|
+
points.push([door.u, 0], [door.u, -stub]);
|
|
1194
|
+
if (-stub > midY) {
|
|
1195
|
+
rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
|
|
1196
|
+
points.push([door.u, midY]);
|
|
1197
|
+
}
|
|
1198
|
+
joinToSocial(door.u, width, rects, points);
|
|
1199
|
+
} else {
|
|
1200
|
+
rects.push([door.u - half, -1.2, door.u + half, 0]);
|
|
1201
|
+
points.push([door.u, 0], [door.u, -1.2]);
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
const lanes = [];
|
|
1205
|
+
if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
|
|
1206
|
+
if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
|
|
1207
|
+
const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1208
|
+
let best = null;
|
|
1209
|
+
for (const lane of lanes) {
|
|
1210
|
+
if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
|
|
1211
|
+
const laneWidth = Math.min(0.9, lane.width - 0.1);
|
|
1212
|
+
const h = laneWidth / 2;
|
|
1213
|
+
const rearStrip = Math.min(0.8, backyard);
|
|
1214
|
+
const rearY = door.v + rearStrip + laneWidth / 2;
|
|
1215
|
+
const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
|
|
1216
|
+
const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
|
|
1217
|
+
let frontStub = null;
|
|
1218
|
+
if (!overlapsParking && setback > 0) {
|
|
1219
|
+
const dir = social && social.u < lane.x ? -1 : 1;
|
|
1220
|
+
const band = [-0.6 - h, -0.6 + h];
|
|
1221
|
+
let nearestX = social ? social.u : lane.x;
|
|
1222
|
+
let nearestGap = Infinity;
|
|
1223
|
+
for (const r of targets) {
|
|
1224
|
+
if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
|
|
1225
|
+
const edge = dir < 0 ? r[2] : r[0];
|
|
1226
|
+
const gap = Math.abs(edge - lane.x);
|
|
1227
|
+
if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
|
|
1228
|
+
nearestGap = gap;
|
|
1229
|
+
nearestX = edge;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
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];
|
|
1233
|
+
}
|
|
1234
|
+
const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
|
|
1235
|
+
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;
|
|
1236
|
+
const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
|
|
1237
|
+
if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
|
|
1238
|
+
}
|
|
1239
|
+
if (best) {
|
|
1240
|
+
const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
|
|
1241
|
+
width = laneWidth;
|
|
1242
|
+
if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
|
|
1243
|
+
if (door.side === "back") {
|
|
1244
|
+
rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
|
|
1245
|
+
points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
|
|
1246
|
+
} else {
|
|
1247
|
+
const x0 = door.side === "left" ? lane.x - h : w;
|
|
1248
|
+
const x1 = door.side === "left" ? 0 : lane.x + h;
|
|
1249
|
+
rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
|
|
1250
|
+
points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
|
|
1251
|
+
}
|
|
1252
|
+
if (frontStub) {
|
|
1253
|
+
rects.push(frontStub);
|
|
1254
|
+
points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
|
|
1255
|
+
}
|
|
1256
|
+
} else {
|
|
1257
|
+
const stub = 1;
|
|
1258
|
+
if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
|
|
1259
|
+
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]);
|
|
1260
|
+
points.push([door.u, door.v]);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
const reached = rects.slice();
|
|
1264
|
+
const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1265
|
+
let grows = true;
|
|
1266
|
+
while (grows) {
|
|
1267
|
+
grows = false;
|
|
1268
|
+
for (const r of pool) {
|
|
1269
|
+
if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
|
|
1270
|
+
reached.push(r);
|
|
1271
|
+
grows = true;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
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);
|
|
1276
|
+
const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
|
|
1277
|
+
if (gate) fromId = gate.id;
|
|
1278
|
+
paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
942
1282
|
// src/zoning.ts
|
|
943
1283
|
var ZONES = {};
|
|
944
1284
|
var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
|
|
@@ -1070,14 +1410,34 @@ var GameFloorplan = (() => {
|
|
|
1070
1410
|
const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
|
|
1071
1411
|
const FLOOR_DEFS = floorsOf(tpl, op);
|
|
1072
1412
|
const style = styleOf(styleId);
|
|
1073
|
-
const plan = {
|
|
1413
|
+
const plan = {
|
|
1414
|
+
w,
|
|
1415
|
+
d,
|
|
1416
|
+
tpl,
|
|
1417
|
+
floorDefs: FLOOR_DEFS,
|
|
1418
|
+
style: styleId,
|
|
1419
|
+
floors: [],
|
|
1420
|
+
rooms: [],
|
|
1421
|
+
zones: [],
|
|
1422
|
+
doors: [],
|
|
1423
|
+
windows: [],
|
|
1424
|
+
walls: [],
|
|
1425
|
+
gates: [],
|
|
1426
|
+
paths: [],
|
|
1427
|
+
lot: null,
|
|
1428
|
+
openPairs: {},
|
|
1429
|
+
reservations: [],
|
|
1430
|
+
options: op,
|
|
1431
|
+
playerRadius: op.playerRadius ?? PLAYER_RADIUS,
|
|
1432
|
+
carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
|
|
1433
|
+
};
|
|
1074
1434
|
FLOOR_DEFS.forEach((A, floorIndex) => {
|
|
1075
1435
|
const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
|
|
1076
1436
|
plan.floors.push({
|
|
1077
1437
|
index: floorIndex,
|
|
1078
1438
|
name: tpl.singleFloor ? "floor_n" : A.label,
|
|
1079
1439
|
number: tpl.singleFloor ? floorNumber : void 0,
|
|
1080
|
-
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : floorIndex * A.ceiling,
|
|
1440
|
+
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
|
|
1081
1441
|
ceiling: A.ceiling
|
|
1082
1442
|
});
|
|
1083
1443
|
plan.openPairs[floorIndex] = A.openPairs || [];
|
|
@@ -1093,7 +1453,8 @@ var GameFloorplan = (() => {
|
|
|
1093
1453
|
const a = byId[sp.a];
|
|
1094
1454
|
if (!a) throw new Error("Door references a missing room: " + sp.a);
|
|
1095
1455
|
let edge;
|
|
1096
|
-
if (sp.
|
|
1456
|
+
if (sp.edge) edge = sp.edge;
|
|
1457
|
+
else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
|
|
1097
1458
|
else {
|
|
1098
1459
|
const b = byId[sp.b];
|
|
1099
1460
|
if (!b) throw new Error("Door references a missing room: " + sp.b);
|
|
@@ -1101,7 +1462,7 @@ var GameFloorplan = (() => {
|
|
|
1101
1462
|
}
|
|
1102
1463
|
if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
|
|
1103
1464
|
const width = sp.width;
|
|
1104
|
-
const margin = width / 2 +
|
|
1465
|
+
const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
|
|
1105
1466
|
if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
|
|
1106
1467
|
const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
|
|
1107
1468
|
const exterior = sp.b === "outside";
|
|
@@ -1123,51 +1484,101 @@ var GameFloorplan = (() => {
|
|
|
1123
1484
|
exterior,
|
|
1124
1485
|
side: sp.side || null,
|
|
1125
1486
|
leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
|
|
1126
|
-
model: sp.steel ? "steel" : width > 1.1 ? "double" : "single",
|
|
1487
|
+
model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
|
|
1488
|
+
type: sp.type || "door",
|
|
1489
|
+
steps: sp.steps || 0,
|
|
1127
1490
|
hinge: "start",
|
|
1128
1491
|
zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
|
|
1129
1492
|
});
|
|
1130
1493
|
});
|
|
1131
1494
|
plan.doors.push(...floorDoors);
|
|
1132
|
-
const
|
|
1133
|
-
(A.windows(w, d, op) || []
|
|
1134
|
-
const a = byId[windowDef.room];
|
|
1135
|
-
if (!a) throw new Error("Window references a missing room: " + windowDef.room);
|
|
1136
|
-
const edge = exteriorEdge(a, windowDef.side, w, d, windowDef.at);
|
|
1137
|
-
if (!edge) return;
|
|
1138
|
-
const width = windowDef.width;
|
|
1139
|
-
const m0 = edge.a0 + width / 2 + 0.3;
|
|
1140
|
-
const m1 = edge.a1 - width / 2 - 0.3;
|
|
1141
|
-
if (m1 < m0 - TOL) return;
|
|
1142
|
-
const centers = windowDef.n ? Array.from({ length: windowDef.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (windowDef.n + 1)) : [windowDef.at];
|
|
1143
|
-
for (const c0 of centers) {
|
|
1144
|
-
const c = clamp(c0, m0, m1);
|
|
1145
|
-
const u = edge.axis === "u" ? c : edge.pos;
|
|
1146
|
-
const v = edge.axis === "u" ? edge.pos : c;
|
|
1147
|
-
if (floorDoors.some((p) => p.axis === edge.axis && eq(p.pos, edge.pos) && Math.abs(p.at - c) < (p.width + width) / 2 + 0.15)) continue;
|
|
1148
|
-
if (windowsOut.some((j) => j.axis === edge.axis && eq(j.pos, edge.pos) && Math.abs(j.at - c) < (j.width + width) / 2 + 0.2)) continue;
|
|
1149
|
-
windowsOut.push({
|
|
1150
|
-
id: "w" + floorIndex + "_" + windowsOut.length,
|
|
1151
|
-
floor: floorIndex,
|
|
1152
|
-
u: r2(u),
|
|
1153
|
-
v: r2(v),
|
|
1154
|
-
axis: edge.axis,
|
|
1155
|
-
pos: edge.pos,
|
|
1156
|
-
at: r2(c),
|
|
1157
|
-
width,
|
|
1158
|
-
y0: windowDef.y0 || 1,
|
|
1159
|
-
y1: windowDef.y1 || 2.15,
|
|
1160
|
-
room: windowDef.room,
|
|
1161
|
-
side: windowDef.side
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
});
|
|
1495
|
+
const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
|
|
1496
|
+
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 });
|
|
1165
1497
|
plan.windows.push(...windowsOut);
|
|
1166
|
-
plan.walls.push(...
|
|
1498
|
+
plan.walls.push(...floorWalls);
|
|
1167
1499
|
});
|
|
1500
|
+
if (tpl.config) plan.config = tpl.config(op, w, d);
|
|
1501
|
+
if (tpl.reservations) tpl.reservations(plan);
|
|
1168
1502
|
buildLot(plan, tpl, w, d, op, style, styleId, bc);
|
|
1169
1503
|
return plan;
|
|
1170
1504
|
}
|
|
1505
|
+
function placeWindows(params) {
|
|
1506
|
+
const { list, byId, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
|
|
1507
|
+
const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
|
|
1508
|
+
const found = [];
|
|
1509
|
+
const groups = [];
|
|
1510
|
+
let order = 0;
|
|
1511
|
+
for (const def of windowDefs) {
|
|
1512
|
+
const room = byId[def.room];
|
|
1513
|
+
if (!room) throw new Error("Window references a missing room: " + def.room);
|
|
1514
|
+
const edge = exteriorEdge(room, def.side, w, d, def.at);
|
|
1515
|
+
if (!edge) continue;
|
|
1516
|
+
const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
|
|
1517
|
+
for (const centre of centres) {
|
|
1518
|
+
const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
|
|
1519
|
+
const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
|
|
1520
|
+
const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
|
|
1521
|
+
const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
|
|
1522
|
+
const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
|
|
1523
|
+
if (!target) continue;
|
|
1524
|
+
let group = groups.find((x) => x.wall === target && x.def.room === def.room);
|
|
1525
|
+
if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
|
|
1526
|
+
group.entries.push({ def, centre, order: order++ });
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
for (const g of groups) {
|
|
1530
|
+
const room = byId[g.def.room];
|
|
1531
|
+
const bath = room.type === "bathroom";
|
|
1532
|
+
const length = g.end - g.start;
|
|
1533
|
+
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 }));
|
|
1534
|
+
const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
|
|
1535
|
+
while (its.length && !fits(its)) {
|
|
1536
|
+
const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
|
|
1537
|
+
if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
|
|
1538
|
+
else its.pop();
|
|
1539
|
+
}
|
|
1540
|
+
if (!its.length) continue;
|
|
1541
|
+
const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
|
|
1542
|
+
let pos = g.start + space;
|
|
1543
|
+
for (const x of its) {
|
|
1544
|
+
const c = pos + x.width / 2;
|
|
1545
|
+
pos += x.width + space;
|
|
1546
|
+
const u = g.edge.axis === "u" ? c : g.edge.pos;
|
|
1547
|
+
const v = g.edge.axis === "u" ? g.edge.pos : c;
|
|
1548
|
+
let y0 = x.def.y0 || 1;
|
|
1549
|
+
let y1 = x.def.y1 || 2.15;
|
|
1550
|
+
if (bath) {
|
|
1551
|
+
y0 = Math.max(y0, 2.1);
|
|
1552
|
+
y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
|
|
1553
|
+
}
|
|
1554
|
+
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 });
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
for (const room of list) {
|
|
1558
|
+
if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
|
|
1559
|
+
let best = null;
|
|
1560
|
+
for (const t of stretches) {
|
|
1561
|
+
if (!t.sides.includes(room.id)) continue;
|
|
1562
|
+
const i0 = t.axis === "u" ? t.u0 : t.v0;
|
|
1563
|
+
const i1 = t.axis === "u" ? t.u1 : t.v1;
|
|
1564
|
+
const length = i1 - i0;
|
|
1565
|
+
const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
|
|
1566
|
+
if (width < 0.6 - TOL || best && best.length >= length) continue;
|
|
1567
|
+
const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
|
|
1568
|
+
const c = (i0 + i1) / 2;
|
|
1569
|
+
const pos = t.axis === "u" ? t.v0 : t.u0;
|
|
1570
|
+
const bath = room.type === "bathroom";
|
|
1571
|
+
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 } };
|
|
1572
|
+
}
|
|
1573
|
+
if (best) found.push(best.window);
|
|
1574
|
+
}
|
|
1575
|
+
found.sort((x, y) => x._order - y._order);
|
|
1576
|
+
found.forEach((j, i) => {
|
|
1577
|
+
delete j._order;
|
|
1578
|
+
j.id = "w" + floorIndex + "_" + i;
|
|
1579
|
+
});
|
|
1580
|
+
return found;
|
|
1581
|
+
}
|
|
1171
1582
|
function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
|
|
1172
1583
|
const seed = op.seed == null ? 1 : op.seed;
|
|
1173
1584
|
const rFacade = createRng(seed, STREAM.facade);
|
|
@@ -1227,9 +1638,11 @@ var GameFloorplan = (() => {
|
|
|
1227
1638
|
parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
|
|
1228
1639
|
addZone("garden", "garden", "garden", parts, libs.garden);
|
|
1229
1640
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1641
|
+
const yard = cfg.yardZones;
|
|
1642
|
+
if (backDepth > 0 && yard) {
|
|
1643
|
+
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);
|
|
1232
1644
|
} else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
|
|
1645
|
+
for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
|
|
1233
1646
|
if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
|
|
1234
1647
|
if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
|
|
1235
1648
|
const gates = [];
|
|
@@ -1287,12 +1700,14 @@ var GameFloorplan = (() => {
|
|
|
1287
1700
|
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]] });
|
|
1288
1701
|
}
|
|
1289
1702
|
}
|
|
1703
|
+
if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
|
|
1704
|
+
else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES2, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
|
|
1290
1705
|
plan.paths = paths;
|
|
1291
1706
|
lot.paths = paths;
|
|
1292
1707
|
const boundary = (rule) => {
|
|
1293
1708
|
const type = pickWeighted(rLot, rule.type);
|
|
1294
1709
|
const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
|
|
1295
|
-
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? 2 : style.height;
|
|
1710
|
+
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
|
|
1296
1711
|
return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
|
|
1297
1712
|
};
|
|
1298
1713
|
const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
|
|
@@ -1562,7 +1977,7 @@ var GameFloorplan = (() => {
|
|
|
1562
1977
|
}
|
|
1563
1978
|
return roomCenter;
|
|
1564
1979
|
};
|
|
1565
|
-
const free = (rect) => !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
1980
|
+
const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
1566
1981
|
const rule = def.facing || "center";
|
|
1567
1982
|
let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
|
|
1568
1983
|
if (def.anchor === "wall" || def.anchor === "window") {
|
|
@@ -1808,6 +2223,8 @@ var GameFloorplan = (() => {
|
|
|
1808
2223
|
};
|
|
1809
2224
|
if (tpl.singleFloor) result.floorNumber = op.floor || 3;
|
|
1810
2225
|
result.partyWalls = tpl.partyWalls || [];
|
|
2226
|
+
if (plan.config) result.config = plan.config;
|
|
2227
|
+
runResultExtensions(plan, result, seed, op);
|
|
1811
2228
|
if (zone) {
|
|
1812
2229
|
const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
|
|
1813
2230
|
result.zoning = report;
|
|
@@ -2077,6 +2494,214 @@ var GameFloorplan = (() => {
|
|
|
2077
2494
|
}
|
|
2078
2495
|
};
|
|
2079
2496
|
|
|
2497
|
+
// src/silhouettes/random.ts
|
|
2498
|
+
function createSilhouetteRandom(seed) {
|
|
2499
|
+
let state = seed;
|
|
2500
|
+
return () => {
|
|
2501
|
+
state |= 0;
|
|
2502
|
+
state = state + 1831565813 | 0;
|
|
2503
|
+
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
2504
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2505
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/silhouettes/builder.ts
|
|
2510
|
+
var ROOT = -1;
|
|
2511
|
+
var MAX_BOX_RADIUS = 0.15;
|
|
2512
|
+
var BOX_RADIUS_RATIO = 0.22;
|
|
2513
|
+
var PLAIN_BOX_RADIUS = 0.012;
|
|
2514
|
+
function boxRadius(width, height, depth) {
|
|
2515
|
+
const radius = Math.min(Math.min(width, height, depth) * BOX_RADIUS_RATIO, MAX_BOX_RADIUS);
|
|
2516
|
+
return radius < PLAIN_BOX_RADIUS ? 0 : radius;
|
|
2517
|
+
}
|
|
2518
|
+
function createSilhouetteBuilder(random) {
|
|
2519
|
+
const parts = [];
|
|
2520
|
+
const windows = [];
|
|
2521
|
+
const pick = (list) => list[Math.floor(random() * list.length)];
|
|
2522
|
+
const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
2523
|
+
const box = (x, y, z, width, height, depth, color, o = {}) => {
|
|
2524
|
+
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) });
|
|
2525
|
+
};
|
|
2526
|
+
const prism = (x, y, z, width, height, depth, color, parent = ROOT) => {
|
|
2527
|
+
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
|
|
2528
|
+
};
|
|
2529
|
+
const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = 14) => {
|
|
2530
|
+
parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
|
|
2531
|
+
};
|
|
2532
|
+
const line = (points, color, parent = ROOT) => {
|
|
2533
|
+
parts.push({ shape: "line", parent, points, color });
|
|
2534
|
+
};
|
|
2535
|
+
const sign = (text, x, y, z, width, height, ry, colors, parent = ROOT) => {
|
|
2536
|
+
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors });
|
|
2537
|
+
};
|
|
2538
|
+
const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
|
|
2539
|
+
const length = Math.hypot(x1 - x0, z1 - z0);
|
|
2540
|
+
const count = Math.ceil(length / 3);
|
|
2541
|
+
for (let index = 0; index <= count; index++) {
|
|
2542
|
+
const t = index / count;
|
|
2543
|
+
cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, parent, 5);
|
|
2544
|
+
}
|
|
2545
|
+
for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
|
|
2546
|
+
};
|
|
2547
|
+
const finish = (footprint) => ({ parts, windows, footprint });
|
|
2548
|
+
return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/silhouettes/building.ts
|
|
2552
|
+
var STOREY_HEIGHT = 3;
|
|
2553
|
+
var SHOP_AWNING_HEIGHT = 4.5;
|
|
2554
|
+
var WINDOW_COLUMN_WIDTH = 2.6;
|
|
2555
|
+
var SHOP_SIGNS = ["FARM\xC1CIA", "MAGAZINE", "LOT\xC9RICA", "PADARIA", "\xD3TICA", "CAL\xC7ADOS", "BAR"];
|
|
2556
|
+
var SIGN_COLORS = ["#ffd27a", "#e8dfcf", "#c4483b"];
|
|
2557
|
+
var DEFAULT_LIT_CHANCE = 0.07;
|
|
2558
|
+
function createCityBuilding(p) {
|
|
2559
|
+
const random = p.random ?? createSilhouetteRandom(p.seed ?? 7);
|
|
2560
|
+
const b = createSilhouetteBuilder(random);
|
|
2561
|
+
const { x, z, width: w, depth: d, height: h } = p;
|
|
2562
|
+
b.box(x, 0, z, w, h, d, p.color);
|
|
2563
|
+
b.box(x, h, z, w + 0.3, 0.35, d + 0.3, 2763308);
|
|
2564
|
+
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);
|
|
2565
|
+
const hasShop = p.shopChance !== void 0 && p.shopChance > 0 && random() < p.shopChance;
|
|
2566
|
+
if (hasShop) {
|
|
2567
|
+
for (const face of [1, -1]) {
|
|
2568
|
+
const zFace = z + face * (d / 2);
|
|
2569
|
+
b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
|
|
2570
|
+
b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
|
|
2571
|
+
const text = b.pick(SHOP_SIGNS);
|
|
2572
|
+
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) });
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
|
|
2576
|
+
return b.finish({ x, z, width: w, depth: d, height: h + 0.35 });
|
|
2577
|
+
}
|
|
2578
|
+
function addWindows(b, p, base) {
|
|
2579
|
+
const { x, z, width: w, depth: d, height: h } = p;
|
|
2580
|
+
const floors = Math.max(1, Math.floor((h - 1) / STOREY_HEIGHT));
|
|
2581
|
+
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]];
|
|
2582
|
+
for (const [ox, oz, faceWidth, ry] of faces) {
|
|
2583
|
+
const columns = Math.max(1, Math.floor(faceWidth / WINDOW_COLUMN_WIDTH));
|
|
2584
|
+
for (let floor = 0; floor < floors; floor++) {
|
|
2585
|
+
const y = base + 1.8 + floor * STOREY_HEIGHT;
|
|
2586
|
+
for (let column = 0; column < columns; column++) {
|
|
2587
|
+
const u = (column - (columns - 1) / 2) * WINDOW_COLUMN_WIDTH;
|
|
2588
|
+
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) });
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/silhouettes/houses.ts
|
|
2595
|
+
var LIT_WINDOW = 16757854;
|
|
2596
|
+
var DARK_WINDOW = 1053979;
|
|
2597
|
+
var DEFAULT_ROOF = 5913130;
|
|
2598
|
+
var randomOf = (o) => o.random ?? createSilhouetteRandom(o.seed ?? 7);
|
|
2599
|
+
var pane = (isLit) => ({ color: isLit ? LIT_WINDOW : DARK_WINDOW, emissive: isLit });
|
|
2600
|
+
function createTwoStoreyHouse(p) {
|
|
2601
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2602
|
+
const { width: w, depth: d } = p;
|
|
2603
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2604
|
+
b.box(0, 0, 0, w, 6, d, p.color, { parent: g });
|
|
2605
|
+
b.prism(0, 6, 0, w + 0.4, 1.4, d + 0.4, p.roofColor ?? DEFAULT_ROOF, g);
|
|
2606
|
+
b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
|
|
2607
|
+
for (const floor of [0, 1]) {
|
|
2608
|
+
for (const side of [-1, 1]) {
|
|
2609
|
+
const { color, emissive } = pane(b.random() < 0.12);
|
|
2610
|
+
b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color, { parent: g, emissive });
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
2614
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: 7.4 });
|
|
2615
|
+
}
|
|
2616
|
+
function createSlabHouse(p) {
|
|
2617
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2618
|
+
const { width: w, depth: d, floors } = p;
|
|
2619
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2620
|
+
for (let floor = 0; floor < floors; floor++) {
|
|
2621
|
+
const isTop = floor === floors - 1;
|
|
2622
|
+
b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
|
|
2623
|
+
const { color, emissive } = pane(b.random() < 0.1);
|
|
2624
|
+
b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color, { parent: g, emissive });
|
|
2625
|
+
}
|
|
2626
|
+
b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
|
|
2627
|
+
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 });
|
|
2628
|
+
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 });
|
|
2629
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: floors * 2.9 + 1 });
|
|
2630
|
+
}
|
|
2631
|
+
function createColonialHouse(p) {
|
|
2632
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2633
|
+
const { width: w } = p;
|
|
2634
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2635
|
+
b.box(0, 0, 0, w, 4.2, 10, p.color, { parent: g });
|
|
2636
|
+
b.box(0, 4.2, 4.6, w, 0.8, 0.25, p.color, { parent: g });
|
|
2637
|
+
b.prism(0, 4.2, -0.4, w, 1.4, 9.6, 8014386, g);
|
|
2638
|
+
const count = Math.max(1, Math.floor(w / 2.2));
|
|
2639
|
+
for (let index = 0; index < count; index++) {
|
|
2640
|
+
const u = (index - (count - 1) / 2) * 2.2;
|
|
2641
|
+
const isDoor = index === 0 && p.hasDoor;
|
|
2642
|
+
const lit = isDoor ? false : b.random() < 0.15;
|
|
2643
|
+
const color = isDoor ? 3810326 : lit ? LIT_WINDOW : 2767434;
|
|
2644
|
+
b.box(u, 1, 5.02, 0.9, 2, 0.05, color, { parent: g, emissive: lit });
|
|
2645
|
+
}
|
|
2646
|
+
return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
|
|
2647
|
+
}
|
|
2648
|
+
function createChalet(p) {
|
|
2649
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2650
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2651
|
+
b.box(0, 0, 0, 7, 0.8, 9, 5920336, { parent: g });
|
|
2652
|
+
b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
|
|
2653
|
+
b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
|
|
2654
|
+
b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
|
|
2655
|
+
const { color, emissive } = pane(b.random() < 0.3);
|
|
2656
|
+
b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color, { parent: g, emissive });
|
|
2657
|
+
b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW, { parent: g });
|
|
2658
|
+
return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
|
|
2659
|
+
}
|
|
2660
|
+
function createCompanyHouse(p) {
|
|
2661
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2662
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2663
|
+
b.box(0, 0, 0, 6, 3, 9, p.color ?? 14209216, { parent: g });
|
|
2664
|
+
b.prism(0, 3, 0, 6.4, 1.6, 9.4, 9062962, g);
|
|
2665
|
+
b.box(0, 0, 4.52, 0.9, 2.1, 0.05, 3811868, { parent: g });
|
|
2666
|
+
b.box(1.8, 1.1, 4.52, 1, 1, 0.04, DARK_WINDOW, { parent: g });
|
|
2667
|
+
b.fence(-3.2, 6.5, 3.2, 6.5, 0.9, 14735560, g);
|
|
2668
|
+
return b.finish({ x: p.x, z: p.z, width: 6.4, depth: 13, height: 4.6 });
|
|
2669
|
+
}
|
|
2670
|
+
function createStiltHouse(p) {
|
|
2671
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2672
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2673
|
+
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);
|
|
2674
|
+
b.box(0, 1.5, 0, 5, 0.2, 7, 5916208, { parent: g });
|
|
2675
|
+
b.box(0, 1.7, 0, 4.6, 2.6, 6.4, p.color ?? 6969926, { parent: g });
|
|
2676
|
+
b.prism(0, 4.3, 0, 5.2, 1.1, 7, 8026744, g);
|
|
2677
|
+
b.box(0, 1.5, 5, 1.2, 0.15, 4, 5916208, { parent: g });
|
|
2678
|
+
return b.finish({ x: p.x, z: p.z, width: 5.2, depth: 9, height: 5.4 });
|
|
2679
|
+
}
|
|
2680
|
+
function createFarmhouse(p) {
|
|
2681
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2682
|
+
const g = b.group(p.x, 0, p.z, 0);
|
|
2683
|
+
b.box(0, 0, 0, 15, 3.2, 11, 14208952, { parent: g });
|
|
2684
|
+
b.prism(0, 3.2, 0, 17, 2.2, 13, 9062962, g);
|
|
2685
|
+
b.box(0, 0, 6.8, 15, 0.25, 2.6, 6969926, { parent: g });
|
|
2686
|
+
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);
|
|
2687
|
+
b.box(-3, 1, 5.52, 1.2, 1.2, 0.04, LIT_WINDOW, { parent: g, emissive: true });
|
|
2688
|
+
return b.finish({ x: p.x, z: p.z, width: 17, depth: 13, height: 5.4 });
|
|
2689
|
+
}
|
|
2690
|
+
function createDetachedHouse(p) {
|
|
2691
|
+
const b = createSilhouetteBuilder(randomOf(p));
|
|
2692
|
+
const { width: w, depth: d } = p;
|
|
2693
|
+
const g = b.group(p.x, 0, p.z, p.ry);
|
|
2694
|
+
b.box(0, 0, 0, w, 3, d, p.color, { parent: g });
|
|
2695
|
+
b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
|
|
2696
|
+
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
2697
|
+
for (const side of [-1, 1]) {
|
|
2698
|
+
const { color, emissive } = pane(b.random() < 0.15);
|
|
2699
|
+
b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color, { parent: g, emissive });
|
|
2700
|
+
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 });
|
|
2701
|
+
}
|
|
2702
|
+
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2080
2705
|
// src/index.ts
|
|
2081
2706
|
registerGroundItems();
|
|
2082
2707
|
registerPermeableGround("lawn", "dirt");
|
|
@@ -2109,6 +2734,13 @@ var GameFloorplan = (() => {
|
|
|
2109
2734
|
registerState,
|
|
2110
2735
|
setStateDistribution,
|
|
2111
2736
|
registerLocale,
|
|
2737
|
+
registerResultExtension,
|
|
2738
|
+
registerFindsProvider,
|
|
2739
|
+
findsFor,
|
|
2740
|
+
findsAnswer,
|
|
2741
|
+
registerViewerLayer,
|
|
2742
|
+
viewerLayers: VIEWER_LAYERS,
|
|
2743
|
+
blocksWindow,
|
|
2112
2744
|
// zoning
|
|
2113
2745
|
defineZone,
|
|
2114
2746
|
getZone,
|
|
@@ -2123,6 +2755,7 @@ var GameFloorplan = (() => {
|
|
|
2123
2755
|
catalog: CATALOG,
|
|
2124
2756
|
libraries: LIBRARIES,
|
|
2125
2757
|
styles: STYLES,
|
|
2758
|
+
paletteStyles: PALETTE_STYLES,
|
|
2126
2759
|
createRng,
|
|
2127
2760
|
mulberry32,
|
|
2128
2761
|
hashString,
|
|
@@ -2130,7 +2763,7 @@ var GameFloorplan = (() => {
|
|
|
2130
2763
|
windowZone,
|
|
2131
2764
|
openingZones,
|
|
2132
2765
|
color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
|
|
2133
|
-
constants: { WALL_THICKNESS, DOOR_CLEARANCE }
|
|
2766
|
+
constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
|
|
2134
2767
|
};
|
|
2135
2768
|
return __toCommonJS(index_exports);
|
|
2136
2769
|
})();
|