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